1. Embedding的使用
pytorch中實現了Embedding,下面是關於Embedding的使用。
torch.nn包下的Embedding,作為訓練的一層,隨模型訓練得到適合的詞向量。
建立詞向量層
embed = torch.nn.Embedding(n_vocabulary,embedding_size)
找到對應的詞向量放進網絡:詞向量的輸入應該是什么樣子
實際上,上面通過隨機初始化建立了詞向量層后,建立了一個“二維表”,存儲了詞典中每個詞的詞向量。每個mini-batch的訓練,都要從詞向量表找到mini-batch對應的單詞的詞向量作為RNN的輸入放進網絡。那么怎么把mini-batch中的每個句子的所有單詞的詞向量找出來放進網絡呢,輸入是什么樣子,輸出是什么樣子?
首先我們知道肯定先要建立一個詞典,建立詞典的時候都會建立一個dict:word2id:存儲單詞到詞典序號的映射。假設一個mini-batch如下所示:
['I am a boy.','How are you?','I am very lucky.']
顯然,這個mini-batch有3個句子,即batch_size=3
第一步首先要做的是:將句子標准化,所謂標准化,指的是:大寫轉小寫,標點分離,這部分很簡單就略過。經處理后,mini-batch變為:
[['i','am','a','boy','.'],['how','are','you','?'],['i','am','very','lucky','.']]
可見,這個list的元素成了一個個list。還要做一步:將上面的三個list按單詞數從多到少排列。標點也算單詞。至於為什么,后面會說到。
那就變成了:
batch = [['i','am','a','boy','.'],['i','am','very','lucky','.'],['how','are','you','?']]
可見,每個句子的長度,即每個內層list的元素數為:5,5,4。這個長度也要記錄。
lens = [5,5,4]
之后,為了能夠處理,將batch的單詞表示轉為在詞典中的index序號,這就是word2id的作用。轉換過程很簡單,假設轉換之后的結果如下所示,當然這些序號是我編的。
batch = [[3,6,5,6,7],[6,4,7,9,5],[4,5,8,7]]
同時,每個句子結尾要加EOS,假設EOS在詞典中的index是1。
batch = [[3,6,5,6,7,1],[6,4,7,9,5,1],[4,5,8,7,1]]
那么長度要更新:
lens = [6,6,5]
很顯然,這個mini-batch中的句子長度不一致!所以為了規整的處理,對長度不足的句子,進行填充。填充PAD假設序號是2,填充之后為:
batch = [[3,6,5,6,7,1],[6,4,7,9,5,1],[4,5,8,7,1,2]]
這樣就可以直接取詞向量訓練了嗎?
不能!上面batch有3個樣例,RNN的每一步要輸入每個樣例的一個單詞,一次輸入batch_size個樣例,所以batch要按list外層是時間步數(即序列長度),list內層是batch_size排列。即batch的維度應該是:
[seq_len,batch_size]
[seq_len,batch_size]
[seq_len,batch_size]
重要的問題說3遍。
怎么變換呢?變換方法可以是:使用itertools模塊的zip_longest函數。而且,使用這個函數,連填充這一步都可以省略,因為這個函數可以實現填充!
batch = list(itertools.zip_longest(batch,fillvalue=PAD))
# fillvalue就是要填充的值,強制轉成list
經變換,結果應該是:
batch = [[3,6,4],[6,4,5],[5,7,8],[6,9,7],[7,5,1],[1,1,2]]
記得我們還記錄了一個lens:
lens = [6,6,5]
batch還要轉成LongTensor:
batch=torch.LongTensor(batch)
這里的batch就是詞向量層的輸入。
詞向量層的輸出是什么樣的?
好了,現在使用建立了的embedding直接通過batch取詞向量了,如:
embed_batch = embed (batch)
假設詞向量維度是6,結果是:
tensor([[[-0.2699, 0.7401, -0.8000, 0.0472, 0.9032, -0.0902],
[-0.2675, 1.8021, 1.4966, 0.6988, 1.4770, 1.1235],
[ 0.1146, -0.8077, -1.4957, -1.5407, 0.3755, -0.6805]],
[[-0.2675, 1.8021, 1.4966, 0.6988, 1.4770, 1.1235],
[ 0.1146, -0.8077, -1.4957, -1.5407, 0.3755, -0.6805],
[-0.0387, 0.8401, 1.6871, 0.3057, -0.8248, -0.1326]],
[[-0.0387, 0.8401, 1.6871, 0.3057, -0.8248, -0.1326],
[-0.3745, -1.9178, -0.2928, 0.6510, 0.9621, -1.3871],
[-0.6739, 0.3931, 0.1464, 1.4965, -0.9210, -0.0995]],
[[-0.2675, 1.8021, 1.4966, 0.6988, 1.4770, 1.1235],
[-0.7411, 0.7948, -1.5864, 0.1176, 0.0789, -0.3376],
[-0.3745, -1.9178, -0.2928, 0.6510, 0.9621, -1.3871]],
[[-0.3745, -1.9178, -0.2928, 0.6510, 0.9621, -1.3871],
[-0.0387, 0.8401, 1.6871, 0.3057, -0.8248, -0.1326],
[ 0.2837, 0.5629, 1.0398, 2.0679, -1.0122, -0.2714]],
[[ 0.2837, 0.5629, 1.0398, 2.0679, -1.0122, -0.2714],
[ 0.2837, 0.5629, 1.0398, 2.0679, -1.0122, -0.2714],
[ 0.2242, -1.2474, 0.3882, 0.2814, -0.4796, 0.3732]]],
grad_fn=<EmbeddingBackward>)
維度的前兩維和前面講的是一致的。可見多了一個第三維,這就是詞向量維度。所以,Embedding層的輸出是:
[seq_len,batch_size,embedding_size]
2 關於pytorch中的GRU
取詞向量,放進GRU。
建立GRU
gru = torch.nn.GRU(input_size,hidden_size,n_layers)
# 這里的input_size就是詞向量的維度,hidden_size就是RNN隱藏層的維度,這兩個一般相同就可以
# n_layers是GRU的層數
可見,並不需要指定時間步數,也即seq_len,這是因為,GRU和LSTM都實現了自身的迭代。
GRU的輸入應該是什么樣子的?
上面的embed_batch作為Embedding層的輸出,可以直接放進GRU中嗎?
理論上可以,但這樣不對!因為GRU並不知道哪些是填充的,並不是每一個句子都滿足最大序列長度!所以我們事先用lens記錄了長度。
將輸出embed_batch轉成pack_padded_sequence,使用torch.nn.utils.rnn. 下的pack_padded_sequence方法。
batch_packed = torch.nn.utils.rnn.pack_padded_sequence(embed_batch, lens)
# 注意這里的輸入lens就是前面的長度list
這個 batch_packed 就是GRU的輸入。
batch_packed 長啥樣?
不妨看一下:
PackedSequence(data=tensor([[-0.2699, 0.7401, -0.8000, 0.0472, 0.9032, -0.0902],
[-0.2675, 1.8021, 1.4966, 0.6988, 1.4770, 1.1235],
[ 0.1146, -0.8077, -1.4957, -1.5407, 0.3755, -0.6805],
[-0.2675, 1.8021, 1.4966, 0.6988, 1.4770, 1.1235],
[ 0.1146, -0.8077, -1.4957, -1.5407, 0.3755, -0.6805],
[-0.0387, 0.8401, 1.6871, 0.3057, -0.8248, -0.1326],
[-0.0387, 0.8401, 1.6871, 0.3057, -0.8248, -0.1326],
[-0.3745, -1.9178, -0.2928, 0.6510, 0.9621, -1.3871],
[-0.6739, 0.3931, 0.1464, 1.4965, -0.9210, -0.0995],
[-0.2675, 1.8021, 1.4966, 0.6988, 1.4770, 1.1235],
[-0.7411, 0.7948, -1.5864, 0.1176, 0.0789, -0.3376],
[-0.3745, -1.9178, -0.2928, 0.6510, 0.9621, -1.3871],
[-0.3745, -1.9178, -0.2928, 0.6510, 0.9621, -1.3871],
[-0.0387, 0.8401, 1.6871, 0.3057, -0.8248, -0.1326],
[ 0.2837, 0.5629, 1.0398, 2.0679, -1.0122, -0.2714],
[ 0.2837, 0.5629, 1.0398, 2.0679, -1.0122, -0.2714],
[ 0.2837, 0.5629, 1.0398, 2.0679, -1.0122, -0.2714]],
grad_fn=<PackPaddedBackward>), batch_sizes=tensor([3, 3, 3, 3, 3, 2], grad_fn=<PackPaddedBackward>))
可以看到,屬性batch_sizes清楚的記錄了每個時間步上batch輸出是多少,而且去除了PAD。
此外,GRU還需要一個初始隱藏向量(注意層數和方向),嫌麻煩直接傳None也無妨。
所以輸入應該是( batch_packed , None )
GRU的輸出?
output,hidden = gru(batch_packed,None)
output:PackedSequence對象
PackedSequence(data=tensor([[ 0.0432, -0.0149, -0.0884, -0.0194, -0.0740, 0.1278],
[-0.0436, -0.0726, 0.0568, -0.0995, -0.1992, 0.1594],
[ 0.0582, 0.0625, -0.1639, 0.1474, 0.0077, 0.0542],
[-0.0052, -0.0732, 0.0031, -0.1367, -0.2336, 0.2307],
[ 0.0131, 0.0234, -0.0681, 0.0535, -0.1651, 0.1864],
[ 0.0324, 0.1441, -0.1788, 0.1800, -0.0816, 0.1684],
[-0.0788, -0.0148, -0.0292, -0.1348, -0.3352, 0.3045],
[ 0.0502, 0.0436, -0.1509, 0.1481, -0.1284, 0.1523],
[ 0.0627, 0.1626, -0.1888, 0.1341, -0.0984, 0.2627],
[-0.1391, -0.0149, 0.0473, -0.2069, -0.4410, 0.3690],
[ 0.1378, 0.0578, -0.2008, 0.1265, -0.0149, 0.2053],
[ 0.0780, 0.1199, -0.2107, 0.1460, -0.0906, 0.2291],
[-0.1019, 0.0055, -0.0304, -0.1277, -0.4149, 0.3582],
[ 0.0906, 0.1025, -0.1646, 0.0933, -0.0953, 0.2905],
[ 0.1004, 0.1175, -0.1911, 0.0979, -0.0877, 0.2771],
[-0.0607, 0.0469, -0.0935, -0.1002, -0.3568, 0.3707],
[ 0.0737, 0.1213, -0.1516, 0.0365, -0.1417, 0.3591]],
grad_fn=<CatBackward>), batch_sizes=tensor([3, 3, 3, 3, 3, 2], grad_fn=<PackPaddedBackward>))
前三個list對應於第一時間步,mini-batch的三個樣例的輸出。依次類推。最后只有兩個,因為最后是有缺省的。
hidden:是個張量。維度[n_layers,batch_size,hidden_size]
tensor([[[-0.1057, 0.2273, 0.0964, 0.2777, 0.1391, -0.1769],
[-0.1792, 0.1942, 0.1248, 0.0800, -0.0082, 0.0778],
[-0.2631, 0.1654, 0.1455, -0.1428, 0.1888, -0.2379]],
[[-0.0607, 0.0469, -0.0935, -0.1002, -0.3568, 0.3707],
[ 0.0737, 0.1213, -0.1516, 0.0365, -0.1417, 0.3591],
[ 0.1004, 0.1175, -0.1911, 0.0979, -0.0877, 0.2771]]],
grad_fn=<ViewBackward>)
所以到這,為什么逆序,為什么記錄長度也就清楚了。
3 關於pytroch中的LSTM
有點累了,過會寫。差不對,就LSTM有兩個隱藏層向量。
