對Transformer的一些理解

[X_O]發表於2024-07-02

在學習Transformer這個模型前對seq2seq架構有個瞭解時很有必要的

先上圖

輸入和輸出

首先理解模型時第一眼應該理解輸入和輸出最開始我就非常糾結

有一個Inputs,一個Outputs(shift right)和一個Output Probabilities,首先需要藉助這三個輸入/輸出來初步瞭解該模型的執行方式。這裡以一個英譯漢的任務來舉例,在訓練時Input端的輸入就是英語,Outputs(shift right)端輸入的就是對應的漢語譯文,Output Probabilities輸出的就是模型預測的下一個詞語(漢語),首先要確定一點,輸出的詞是一個一個出來的,而不是一起出來的,第n個詞的預測需要依賴前n-1個詞,如果之前沒有接觸過seq2seq,這裡就會有個疑問,Outputs(shift right)已經將答案給模型了,那這個訓練有什麼意義呢?這裡就涉及到Masked Multi-Head Attention,這個Masked讓模型在預測第n個詞語的時候,會將第n個詞語及之後的詞語給蓋住,讓模型接觸不到後面的內容,這樣保證模型不去“抄答案”。

那對於Outputs(shift right)為什麼論文中要加一個shift right,是因為模型在輸出第n個詞時候需要前n-1個,那要輸出第一個詞怎麼辦呢,這裡人為定義了一個<start>,解碼器中輸入的所有句子都是以<start>開頭的。

Input Embedding

可以看到,所有直接的資料在輸入編/解碼器之前都會經過一次Embedding和一次Positional Encoding,對於Embedding,計算機無法直接理解中文或者英文,需要將其編碼為向量方便操作,one-hot就是用來做這個的,但是對於大模型來說,動輒幾萬個詞,如果使用one-hot編碼,詞向量將是幾萬維的,這是不可接受的,因此有了更加高效的方法,例如Word2Vec、GloVe、FastText等,就拿Word2Vec來說,他能夠捕捉單詞語義的相似性,例如大樹這個詞語的詞向量為v1樹木這個詞的詞向量為v2,藍色這個詞語的詞向量為v3,那麼v1v2的點積就比v1v3的點積要大,兩個詞的詞向量點積越大,那這兩個詞的語義越相近,這是由Word2Vec這個演算法所決定的。

Positional Encoding

位置編碼,在seq2seq模型中,我們主要運用的是RNN模型作為主幹,加上注意力機制來改善效果,RNN模型的輸入不論是中文還是英文,不論是編碼器還是解碼器,都是一個詞一個詞給進去的,在處理序列資料時,每個時間步的計算依賴於前一個時間步的輸出。這種順序依賴性使得RNN很難利用平行計算來加速訓練和推理過程,這就造成了效能瓶頸,同時過長的輸入會導致梯度爆炸或者梯度消失。

而Transformer中,完全拋棄了RNN的基本結構,使用自注意力機制來處理輸入序列,可以對輸入序列中的所有元素同時進行處理,從而大大提高了計算速度和效率。而由於資料是一起被放入模型中,一起被處理,其位置資訊就丟失了,seq2seq中的詞是一個一個輸入進去的,輸入的先後順序就隱藏了位置資訊,因此為了儲存資料的位置資訊,我們就需要Positional Encoding(位置編碼)。

在論文中的解決方案是這樣的,透過一定的方法(後面會介紹)為句子中的每個詞生成一個位置資訊,然後將這個位置資訊直接加到對應的詞向量上面去,過程如下

Positional Encoding是如何生成的呢,論文中的方法為Sine and Cosine Positional Encodings,其思想是,為輸入序列中的每個位置生成一個固定的向量,這個向量的構造方式是透過不同頻率的正弦和餘弦函式來實現的。具體的公式如下:

\[PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \]

\[PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) \]

其中,pos 是位置,i 是向量的維度索引,\(d_{\text{model}}\) 是模型的embedding維度。這種方法確保了每個位置的編碼向量是唯一的,並且不同位置之間的距離可以透過這些編碼向量進行區分。這個公式看起來很頭大,其實不必過於糾結,知道它是幹什麼用的就行,這個方法也不是完美方法,在後來的bert中就沒用使用這個方法了,說明還是存在一些問題的。

編碼器

左邊的一塊叫做編碼器,右邊的一塊叫做解碼器,這個取名很形象,借用之前的例子,編碼器將英語編碼成一種只有計算機才能理解的語言(不是計算機直接理解的計算機語言),再透過解碼器解碼成目標語言。

Multi-Head Attention

首先,我們需要搞清楚什麼是自注意力機制,也就是 self-Attention。

所謂注意力機制,我的理解就是將重點放在重要的地方。舉個簡單的例子,當我們翻譯句子時,例如原句是 "A black man",當我們翻譯完了 "A black" 時,到 "man" 時,我們腦子裡還是有 "A black man" 這個句子,但是此時我們的注意力肯定是放在 "man" 這個單詞上,而會忽略 "A black",就是對這三個單詞的注意力的重視程度(權重)不一樣,"man" 這個單詞的權重很高,而其餘的很低。

這裡可能有人會質疑,翻譯不就是一個詞一個詞的翻譯嗎?其實翻譯也是要看上下文的。你翻譯 "A black" 時,就是 "一個黑色的",但是你看到 "man" 時,你不能直接翻譯成 "一個黑色的人",而要翻譯成 "一個黑人"。所以翻譯任務不是簡單的逐詞翻譯,而要聯絡上下文。在翻譯很長的資訊時,如果我們對所有的上下文都同樣對待,將會陷入資訊的海洋中迷失自己。因此,在翻譯過程中為每個詞新增權重時是很有必要的。

我們在翻譯過程中,經驗和大腦與生俱來的抽象能力自動幫我們實現了這一注意力的過程,但是如何把這個思想傳遞給模型呢?這裡就是注意力機制要做的事情。

首先我之前提到過使用現代詞嵌入技術是可以在向量中反映出詞之間的相似程度的,我把每個詞向量和同一句話中的其它詞向量求內積,就可以得到每個詞向量和其它詞向量的相似度(可以理解為關聯的強弱,因為這個詞向量也是模型從海量文字中學習來的,比如大量文字中都出現了紅蘋果,幾乎沒有黃蘋果,那麼顯然紅和蘋果的關聯性就遠大於黃和蘋果的關聯性。)暫不考慮其細節,我們似乎可以用這種點積的大小來反映各個詞之間的關聯程度,既然關聯程度不一樣,我們可以量化這種不同從而得到一個權重矩陣也就是注意力矩陣。

先看這張圖

這裡的Q、K、V就是經過Encoding之後的詞向量,將其記作X如果按照之前的思路,我們現在應該\(X \cdot X^T\),然而如果是這樣的話顯然就限定了資料的分佈,而且如果詞嵌入向量沒有訓練好的的話這裡會十分影響模型效能,而且好像沒有什麼可以訓練的引數,我們應該在這裡加入一點東西讓網路複雜一點,因此我們引入了三個獨立的線性層,分別記作W1,W2和W3,我們將\(X \cdot W_1\)記作Q,\(X \cdot W_2\)記作K,\(X \cdot W_3\)記作V,這就是自注意力機制中Q、K、V的來歷。

那在我們使用\(Q \cdot K^T\)就能得到輸入句子中每個詞與其他詞之間的相似度的矩陣,為保證梯度穩定性,我們進行一個常規的歸一化,透過數學推導,\(Q \cdot K^T\)的均值為0,方差為\(d\)(詞嵌入向量的維度),歸一化之後的式子如下

\[\frac{Q \cdot K^T}{\sqrt{d}} \]

我們已經得到了處理後的內積,離我們想要的權重矩陣就只有一步之遙了,將其對映到0-1之間的機率分佈即可,這裡我們可以選用Sigmoid函式或者Softmax函式,論文中作者使用了Softmax函式,那我們就可以得到注意力矩陣了

\[Softmax(\frac{Q \cdot K^T}{\sqrt{d}}) \]

得到了注意力矩陣(權重矩陣),我們將其乘進V矩陣(原始矩陣)中得到加權後的矩陣,也就是加了注意力之後的矩陣

\[Softmax(\frac{Q \cdot K^T}{\sqrt{d}}) \cdot V \]

這就是論文裡的公式,用流程圖展示如下

以上我們解釋了Attention,那麼Multi-Head又該如何解釋呢?

在向量X(經過Encoding之後的詞向量)進入自注意力機制模組前將他”斷開“,不同的維度進入不同的自注意力機制模組進行相同的的運算,例如”你“這個詞,假設它的詞嵌入向量Y是512維的[a0, a1, a2, ···, a510, a511],我們只使用兩個頭,也就是h=2,那麼就將Y截斷,[a0, a1, ···, a254, a255]進入第一個自注意力機制模組進行計算,[a256, a257, ···, a510, a511]進入第二個模組經行同樣的計算,在各自計算完成後拼接(concat)起來,再透過一個全連線層增加模型的複雜度。事實上,這樣做是很有必要的,這樣可以訓練多個注意力矩陣提取不同維度的資訊,增加了模型的複雜度,同時透過拆分維度把計算量分成一小塊一小塊的了,提高了並行性。

至此,我們走完了Multi-Head Attention這個模組。

Add & Norm

對於Add,借鑑了殘差結構,就是將Multi-Head Attention輸出的結果與向量X加一下,這樣可以保證梯度穩定,這又是如何實現的呢?

編碼器旁邊有個Nx,說明肯定不止一層,這裡如果加一下的話輸出的結果就從f(x)變成了f(x)+x,這樣在求導時就由f'(x)變成了f'(x)+1,可以一定程度上緩解梯度消失的問題。

Multi-Head Attention輸出的結果與向量X加完的結果進行一個Layer Normalizaiton

\[\text{LayerNorm}(x) = \gamma \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta \]

其中,\(\gamma\)\(\beta\)是可訓練的引數,其中為什麼要用LN而不是BN主要是由於每個句子的含有詞的長度不同,會導致數值不太穩定和一些其他考慮。注意,BN是對所有的batch的某個feature做歸一化,LN是對某個batch的所有feature做歸一化。

Feed Forward

基於位置的前饋網路(FFN)

過程:

\[FFN(x) = max(0, xW_1 + b_1)W_2 + b_2 \]

兩個全連線層中間夾著一個ReLU,為什麼需要這個東西呢?首先分析一下輸入到FFN的資料的形狀應該是(b, n, d)的,那它跟CNN中的有什麼區別呢?

首先對於CNN來說我們解釋b、n、d的含義:

b: 批處理大小,即一次處理的影像數量。例如,如果一次輸入處理 32 張圖片,則 b = 32

n: 特徵數量,這通常對應於卷積層和池化層之後的特徵圖數量。例如,如果最後一層的卷積層有 512 個輸出通道,那麼 n = 512

d: 特徵維度,指每個特徵向量的長度。這可能是卷積層輸出特徵圖的寬度和高度的乘積。例如,如果最後一層的特徵圖的大小為 7x7,那麼 d = 7 * 7 = 49

那對於Transformer來說,b、n、d的含義又有所區別

b: Batch size,即一次輸入網路的資料樣本數。這個維度表示在一次前向傳遞中處理的序列數量。也就是指的句子數量。

n: Sequence length,序列的長度。這個維度表示每個輸入序列中包含的詞數量。也就是指句子裡面詞的數量。

d: Feature dimension,特徵的維度。這個維度表示每個標記的嵌入向量的長度。也就是之前提到的d。

對於一個三維向量後面接上一個全連線層,CNN是如何處理的呢?

全連線層會將這個三維資料展平成二維資料 (b, n*d),然後再輸入到全連線層中進行分類。例如,假設你有一個批次大小為 32 的輸入,經過卷積和池化操作後得到 512 個 7x7 的特徵圖,那麼輸入到全連線層的資料形狀將會是 (32, 512, 49),在展平後變成 (32, 512 * 49),即 (32, 25088)

那Transformer可不可以效仿這種做法呢?

顯然是不行的因為每個句子的長度不可能都是相同的,那麼n就是變化的,n*d的值就是變化的,由於這種變化的特性,我們無法確定一個(n*d, out_dim)的矩陣,做預測的時候,由於每個句子的n不同,也很難完成任務,因此不能完全效仿CNN的做法,只得另謀它路。那文中是如何實現這個全連線層的呢?

作者使用了一個1*1的卷積,透過改變通道數的方式來實現全連線層的效果。

先將輸入資料的形狀從 (b, n, d) 轉換為 (b, d, n)(這一步為了適應conv1函式的輸入引數),然後對其使用一個 1*1 的卷積層,假設隱藏層大小為 512,將輸出通道數設為 512,資料形狀將變成 (b, 512, n)。然後經過一次 ReLU 啟用函式,再用同樣的操作,將輸出通道數設定為 d,將資料形狀變回 (b, d, n),最後調整回原始形狀 (b, n, d)

實現方法可能有點出入,但是思想就是利用1*1的卷積來實現全連線層的作用,在只需要大致理解模型時候不用過多關注這些細節,只需知道這裡就是兩個全連線層夾著一個ReLU即可

解碼器

學完編碼器各個元件後會發現編碼器這邊其實沒有什麼新東西了,只有唯一一個Masked Multi-Head Attention特別一點了。

Masked Multi-Head Attention

對於一個訓練好的模型來說,假如我們要做英譯漢,最理想的情況是這樣的:

  1. 我們在 Inputs 端輸入 "A red apple"。
  2. Outputs(shift right) 端會自動輸入一個 <start> 作為起始標記。
  3. 解碼器依據輸入在經過一系列的變化,Output Probabilities 輸出 "一個"。
  4. Outputs(shift right) 端會自動輸入 <start> 一個
  5. 解碼器依據輸入在經過一系列的變化,Output Probabilities 輸出 "紅色的"。
  6. Outputs(shift right) 端會自動輸入 <start> 一個 紅色的
  7. 解碼器依據輸入在經過一系列的變化,Output Probabilities 輸出 "蘋果"。
  8. Outputs(shift right) 端會自動輸入 <start> 一個 紅色的 蘋果
  9. 解碼器依據輸入在經過一系列的變化,Output Probabilities 輸出 <end>
  10. 翻譯完成。

可以看到,結果是一個一個輸出的,第n個詞的輸出需要依賴前n-1個詞的輸入,訓練過程也是一樣

  1. 我們在 Inputs 端輸入 "A red apple"。
  2. Outputs(shift right) 端會自動輸入一個 <start> 作為起始標記。
  3. 解碼器依據輸入在經過一系列的變化,但是實際情況下,如果訓練的不夠,Output Probabilities 輸出結果很可能不是 "一個",而是其他的,我們就用交叉熵損失函式來計算損失值(量化它的輸出與標準答案“一個”的差異),根據這個來調整網路的引數。
  4. Outputs(shift right) 端會自動輸入 <start> 一個,注意,不是<start>加上Output Probabilities 輸出的不標準的答案,而是標準答案,這個方法叫Teacher forcing,試想如果第一個輸出就不對,用錯的結果繼續生成的也只能是錯誤的結果,最後隨著訓練的繼續只能越錯越多,十分不利於模型的收斂,因此我們的輸入端是要求輸入標準答案的。也正是因為有了這種機制,我們讓模型去預測一個的同時,也能讓模型去預測紅色的,因為訓練過程中的輸入不依賴上一步的輸出,這也就為平行計算提供了可能。
  5. 一直重複3,4步驟直至句子結束

但是有個問題,假如我們現在需要<start> 一個,來看模型預測的結果與紅色的的差距,我們該怎麼從標準答案裡把一個選出來呢,畢竟我們給模型的資料是整個句子一個 紅色的 蘋果

我們先將整個句子經過Embedding之後傳入Masked Multi-Head Attention塊,再計算 $ Q \cdot K^{T} $後得到的矩陣做一個遮蓋的處理

<start> 一個 紅色的 蘋果
<start> 0.36 -inf -inf -inf
一個 -0.28 0.13 -inf -inf
紅色的 -0.9 0.42 1.17 -inf
蘋果 -0.3 0.17 0.5 0.25

這樣在生成注意力矩陣時,經過softmax時權重幾乎會變為0,就不會考慮後面的內容了。

其餘的內容與Multi-Head Attention一模一樣。

接下來後面的內容在瞭解玩編碼器後就很好理解了。

資料經過Masked Multi-Head Attention後經過一個Add & Norm,之後的結構可以看到是和編碼器一模一樣的,唯一的區別就是輸入

它需要三個輸入,分別是Q、K、V,其中K、V來自編碼器最終的輸出,Q來自剛剛處理完成的資料,經過編碼器一模一樣的操作之後得到最終輸出。

輸出

將最後得到的資料首先經過一次線性變換,然後Softmax得到輸出的機率分佈,然後透過詞典,輸出機率最大的對應的單詞作為我們的預測輸出。

相關文章