最近看了Attention Is All You Need這篇經典論文。論文裏有不少地方描述都很模糊,後來是看了參考文獻裏其餘人的源碼分析文章纔算是打通整個流程。記錄一下。git
N: batch size
T: 一個句子的長度
E: embedding size
C: attention_size(num_units)
h: 多頭header的數量github
翻譯前文本,翻譯後文本,作長度截斷或填充處理,使得全部語句長度都固定爲T。
獲取翻譯先後語言的詞庫,對少出現詞作剔除處理,詞庫添加< PAD >, < UNK >, < Start >, < End >四個特殊字符。
翻譯先後文本根據詞庫,將文本轉爲id。
設batch_size=N, 則轉換後翻譯先後數據的size爲:X=(N, T), Y=(N, T)算法
前面結構圖中Encoder的輸入Inputs就是1.1中轉換好的X。函數
1.2.1 Input Embedding
設輸入詞庫大小爲vocab_in_size, embedding的維度爲E,則先隨機初始化一個(vocab_in_size, E)大小的矩陣,根據embedding矩陣將X轉換爲(N, T, E)大小的矩陣。源碼分析
1.2.2 Positional encoding
Position embedding矩陣維度也是(N,T,E),不一樣batch上,在T維度上相同位置的值同樣。論文裏用了三角函數sin和cos。
將Position embedding直接疊加到1.2.1的X上就是送入multi-head attention的輸入了。測試
1.2.3 Multi-Head Attention優化
線性變換
將輸入X=(N,T,E)經過線性變換,將特徵維度轉換爲C。通過轉換維度爲X=(N,T,C)。spa
轉爲多頭
沿特徵方向平分爲h份,在batch維度上拼接,方便後面計算。轉換後維度X=(h*N, T, C/h).net
計算\(QK^T\)
這裏query(Q)和key(K)都是前面的X,計算後維度out=(h*N,T,T)翻譯
Mask Key
將out矩陣中key方向上原始key信息爲0的部分mask掉,另其爲一個極大的負數。所謂信息爲0是指文本中PAD的部分。最開始會將PAD的embedding設爲全0矩陣。
Softmax
把上一步的輸入作softmax操做,變爲歸一化權值。維度(h*N,T,T)
Mask Query
把query部分信息量爲0對應的維度置0。即這一部分的權重爲0。信息量爲0一樣指PAD。
乘以value
self attention的value也就是上面的X(h*N, T, C/h),相乘後維度=(h*N, T, C/h)
reshape
將多頭的部分恢復原來的維度,處理後維度out=(N, T, C)
1.2.4 Add & Norm
殘差操做,out = out+X 維度(N, T, C)
layer norm歸一化,維度(N, T, C)
多個block
上面1.2.3和1.2.4操做重複屢次,最後一層的輸出就是Encoder的最終輸出。記爲Enc。
這裏大部分跟前面Encoder是同樣的。前面結構圖中Decoder的輸入Outputs就是1.1中轉換好的Y。
1.3.1 output Embedding
設輸入詞庫大小爲vocab_out_size, embedding的維度爲E,則先隨機初始化一個(vocab_out_size, E)大小的矩陣,根據embedding矩陣將Y轉換爲(N, T, E)大小的矩陣。
1.3.2 Positional encoding
見1.2.2
1.3.3 Masked Multi-Head Attention
跟1.2.3基本相同,只是多了一個Mask步驟
線性變換
將輸入Y=(N,T,E)經過線性變換,將特徵維度轉換爲C。通過轉換維度爲Y=(N,T,C)。
轉爲多頭
沿特徵方向平分爲h份,在batch維度上拼接,方便後面計算。轉換後維度Y=(h*N, T, C/h)
計算\(QK^T\)
這裏query(Q)和key(K)都是前面的Y,計算後維度out=(h*N,T,T)
Mask Key
將out矩陣中key方向上原始key信息爲0的部分mask掉,另其爲一個極大的負數。所謂信息爲0是指文本中PAD的部分。最開始會將PAD的embedding設爲全0矩陣。
Mask當前詞以後的詞
作這一步的緣由是在解碼位置i的詞時,咱們只知道位置0到i-1的信息,並不知道後面的信息。處理方式是將T_k>T_q部分置爲一個極大的負數。T_k表示key方向維度,T_q表示query方向維度。
Softmax
把上一步的輸入作softmax操做,變爲歸一化權值。維度(h*N,T,T)
Mask Query
把query部分信息量爲0對應的維度置0。即這一部分的權重爲0。信息量爲0一樣指PAD。
乘以value
self attention的value也就是上面的X(h*N, T, C/h),相乘後維度=(h*N, T, C/h)
reshape
將多頭的部分恢復原來的維度,處理後維度out=(N, T, C)
1.3.4 Add & Norm
殘差操做,out = out+X 維度(N, T, C)
layer norm歸一化,維度(N, T, C)
1.3.5 Multi-Head Attention
跟以前的區別在於,之前是self attention,這裏query是上面decode的輸出dec, key是encoder的輸出enc
轉爲多頭
將dec沿特徵方向平分爲h份,在batch維度上拼接,方便後面計算。轉換後維度dec=(h*N, T, C/h)
計算\(QK^T\)
這裏query(Q)=dec和key(K)=enc,計算後維度out=(h*N,T_q,T_k)
Mask Key
將out矩陣中key方向上原始key信息爲0的部分mask掉,另其爲一個極大的負數。所謂信息爲0是指文本中PAD的部分。最開始會將PAD的embedding設爲全0矩陣。
Softmax
把上一步的輸入作softmax操做,變爲歸一化權值。維度(h*N,T_q,T_k)
Mask Query
把query部分信息量爲0對應的維度置0。即這一部分的權重爲0。信息量爲0一樣指PAD。
乘以value
self attention的value也就是上面的enc(h*N, T, C/h),相乘後維度=(h*N, T_q, C/h)
reshape
將多頭的部分恢復原來的維度,處理後維度out=(N, T_q, C)
1.3.6 Add & Norm
殘差操做,out = out+dec 維度(N, T_q, C)
layer norm歸一化,維度(N, T_q, C)
多個block
上面1.3.3-1.3.6重複屢次
全鏈接變換
將上面輸出結果(N, T_q, C)轉換爲(N, T_q, vocab_out_size)維,softmax獲取每一個位置輸出各個詞的機率。經過優化算法迭代更新參數。
測試時的Encoder部分比較好理解,跟訓練時處理同樣。只不過參數都是訓練好的,好比embedding矩陣直接使用前面訓練好的矩陣。
主要問題是在decoder的輸入上。
對於一個語句,decoder一開始輸入全0序列。表示什麼信息也不知道(或者一個Start標籤,表示開始)。通過一次decoder後輸出一個長度爲T的預測序列out1
第二次,輸入out1預測的第一個字符,後面是全0,表示知道一個詞了。通過decoder處理後,得到長度爲T的輸出預測序列out2
第三次,輸入out2預測的前兩個字符,後面是全0,表示知道2個詞了。
依次類推。
注意,訓練時decode結果是一次性獲取的。可是測試的時候一次只獲取一個詞。須要相似RNN同樣循環屢次。
有些詞顛倒一下順序,含義是會變化的。
好比:奶牛 -> dairy cattle
若是沒有添加位置信息,顛倒後會翻譯成 牛奶 -> cattle dairy。
但這顯然是不對的,在顛倒順序後詞的含義改變了, 應該翻譯爲 milk。
爲了處理這種問題,因此須要加入位置信息。