Transformer模型詳解

凌逆戰發表於2022-01-29

2013年----word Embedding

2017年----Transformer

2018年----ELMo、Transformer-decoder、GPT-1BERT

2019年----Transformer-XL、XLNet、GPT-2

2020年----GPT-3

Transformer

  谷歌提出的Transformer模型,用全Attention的結構代替的LSTM,在翻譯上取得了更好的成績。這裡基於Attention Is All You Need,對 Transformer 做一個介紹。模型結構如下圖所示,模型包含 encoder 和 decoder 兩部分,其中 encoder 包含 Nx 個(6個)當前單元,decoder 部分包含 Nx 個框中單元。下面我們分塊對其進行描述。

Position Embedding

  Encoder 的輸入包含詞向量位置向量(Position Embedding)詞向量 部分和正常的網路一樣,通過學習獲得,維度為$d_{model}$。位置向量 將每個位置編號,然後每個編號對應一個向量,通過結合位置向量和詞向量,就給每個詞都引入了一定的位置資訊,這樣Attention就可以分辨出不同位置的詞了。而位置向量則和以往不同,以往的位置向量是通過學習獲得的,而這裡谷歌提出了一種位置向量嵌入的方法:

$$\left\{\begin{array}{l}
P E_{2 i}(p)=\sin \left(p / 10000^{2 i / d_{p o s}}\right) \\
P E_{2 i+1}(p)=\cos \left(p / 10000^{2 i / d_{p o s}}\right)
\end{array}\right.$$

  這裡的意思是將id為$p$的位置對映為一個$d_{pos}$維的位置向量,這個向量的第$i$個元素的數值就是$PE_i(p)$。Google在論文中說到他們比較過直接訓練出來的位置向量和上述公式計算出來的位置向量,效果是接近的。因此顯然我們更樂意使用公式構造的Position Embedding了,我們稱之為Sinusoidal形式的Position Embedding。

  Position Embedding本身是一個絕對位置的資訊,但在語言中,相對位置也很重要,Google選擇前述的位置向量公式的一個重要原因是:由於我們有$sin(\alpha +\beta )=sin\alpha cos\beta +cos\alpha sin\beta $以及$cos(\alpha +\beta )=cos\alpha cos\beta −sin\alpha sin\beta $,這表明位置$p+k$的向量可以表示成位置$p$的向量的線性變換,這提供了表達相對位置資訊的可能性。

Encoder部分

  Encoder 部分由6個相同的子模組組成,每個子模組就是上面圖中左側那個方塊了。包含幾個子部分:

  • Multi-Head Attention
  • Residual connection
  • Normalisation
  • Position-wise Feed-Forward Networks

  在Encoder 內部又可以看做包含兩個子層,一個是 Multi-Head Self-Attention為主,另一個是 Position-wise Feed-Forward Networks,每個子層內的運算可以總結為:

$$sub\_layer\_output = LayerNorm(x + (SubLayer(x)))$$

接下來著重介紹 兩個子層。Multi-Head Self-Attention由 Scaled Dot-product Attention 和 Multi-Head Attention 以及 Self Attention 組成,下面依次進行講解。

Scaled Dot-Product Attention

$$Attention(Q,\ K,\ V)=softmax(\frac{QK^T}{\sqrt{d_k}})V$$

其中$Q,K,V$分別是query、key、value,他們的shape分別為$(n×d_k)、(m×d_k)、(m×d_v)$。這個公式相比於正常的Dot Attention多了一個縮放因子$\frac{1}{\sqrt{d_k}}$,這個縮放因子可以防止內積過大,防止它經過 softmax 後落入飽和區間,因為飽和區的梯度幾乎為0,容易發生梯度消失。如果忽略啟用函式softmax的話,那麼事實上它就是$Q,K,V$三個矩陣相乘,最後的結果是一個$n×d_v$的矩陣。於是我們可以認為:這是一個Attention層,將序列$Q$編碼成了一個新的$n×d_v$的序列。我們可以理解為:Q通過與K內積並sofrmax的方式,來得到Q與V的相似度

  在這個 scaled dot-product attention 中,還有一個 mask部分,在訓練時它將被關閉,在測試或者實際使用中,它將被開啟去遮蔽當前預測詞後面的序列。

事實上這種Attention的定義並不新鮮,但由於Google的影響力,我們可以認為現在是更加正式地提出了這個定義,並將其視為一個層來看待;此外這個定義只是注意力的一種形式,還有一些其他選擇,比如$query$跟$key$的運算方式不一定是點乘(還可以是拼接後再內積一個引數向量),甚至權重都不一定要歸一化,等等。

Multi-Head Attention

  Multi-Head Attention就是把$Q,K,V$通過引數矩陣對映一下,然後再做Attention,把這個過程重複做$h$次,結果拼接起來。具體來說

$$\begin{equation}head_i = Attention(\boldsymbol{Q}\boldsymbol{W}_i^Q,\boldsymbol{K}\boldsymbol{W}_i^K,\boldsymbol{V}\boldsymbol{W}_i^V)\end{equation}$$

其中$W_i^Q、W_i^K、W_i^V$的shape分別為$(d_k\times \tilde{d}_k)、(d_k\times \tilde{d}_k)、(d_v\times \tilde{d}_v)$,然後

$$\begin{equation}MultiHead(\boldsymbol{Q},\boldsymbol{K},\boldsymbol{V}) = Concat(head_1,...,head_h)\end{equation}$$

  最後得到一個$n\times (h\tilde{d}_v)$的序列,所謂Multi-Head(多頭),就是隻多做幾次同樣的事情(引數不共享),然後把結果拼接。其中需要注意的是,一方面不同的head的矩陣是不同的,另一方面multi-head-Attention可以平行計算,論文裡$h=8,d_k=d_v=d_{model}/4=64$

  一般來說一個多頭 attention 的效果要優於單個 attention。按照谷歌官方的說法,這麼做形成多個子空間,可以讓模型關注不同方面的資訊,體現出差異性。但有實驗表明,頭之間的差距隨著所在層數變大而減小,因此這種差異性是否是模型追求的還不一定。

  至於頭數 h 的設定,並不是越大越好,到了某一個數就沒效果了。

Self Attention

  在Google的論文中,大部分的Attention都是Self Attention,即“自注意力”,或者叫內部注意力。也就是說,在序列內部做Attention,尋找序列內部的聯絡。體現在公式上就是$Attention(X,X,X)$,$X$是輸入序列。內部注意力在機器翻譯(甚至是一般的Seq2Seq任務)序列編碼上是相當重要的,而之前關於Seq2Seq的研究基本都只是把注意力機制用在解碼端。

  當然,更準確來說,Google所用的是Self Multi-Head Attention:

$$\begin{equation}\boldsymbol{Y}=MultiHead(\boldsymbol{X},\boldsymbol{X},\boldsymbol{X})\end{equation}$$

Position-wise Feed-Forward Networks

  論文裡說,它是一個前饋全連線網路,它被等同的應用到每一個位置上(Pplied to each position separately and identically),它由兩個線性變換和ReLU啟用函式組成:

$$FFN(x)=max(0,xW_1+b_1)W_2+b_2$$

  這個線性變換被應用到各個位置上,並且它們的引數是相同的。不過不同層之間的引數就不同了。這相當於一個核大小為1 的卷積。

  一個注意的地方是,$W_1$的維度是(2048,512),$W_2$是 (512,2048)。 即先升維,後降維,這是為了擴充中間層的表示能力,從而抵抗 ReLU 帶來的模型表達能力的下降。

Decoder部分

  Decoder 部分相比於 Encoder ,結構上多了一個 Masked Multi-Head Attention 子層,它對decoder 端的序列做 attention。相比於正常的 Scaled Dot-Product Attention,它在 Scale 後加了一個Mask 操作。這是因為在解碼時並不是一下子出來的,它還是像傳統 decoder 那樣,一個時間步一個時間步的生成,因此在生成當前時間步的時候,我們看不到後面的東西,因此用 MASK 給後面的 遮住。

因此在解碼時的流程為:

  • 假設當前已經解碼的序列為$s1,s2,…,st−1$,把該序列做詞向量和位置向量嵌入
  • 對上述向量做 Masked Multi-Head Attention,把得到的結果作為 Q
  • Encoder 端的輸出向量看做 K,V
  • 結合 Q,K,V 做 Multi-Head Attention 和 FFN等操作
  • 重複 decoder 部分 的子結構得到輸出,而後解碼得到輸出詞的概率

Attention的原理

我羅列了兩種理解方式,個人還是喜歡第一種,直接了當的,

直接解釋:Source裡面有很多value,為了方便查詢,我們給每個value都標記一個key,當我們想要了解query的時候,就可以通過key去查詢相關的value了。

案例解釋:圖書館(source)裡面有很多書(value),為了方便查詢,我們給書做了編號(key),當我們想要了解漫威(query)的時候,我們就可以看看那些動漫、電影、甚至二戰相關的書籍。

如果做閱讀理解的話,Q可以是篇章的向量序列,取K=V為問題的向量序列,那麼輸出就是所謂的Aligned Question Embedding。

Attention原理的3步分解

第一步:query和key進行相似度計算,得到權值

第二步:將權值進行歸一化,得到直接可用的權值

第三步:將權值和value進行加權求和

從上面的建模,我們可以大致感受到Attention的思路簡單,四個字“帶權求和”就可以高度概括,大道至簡。

Attention的N中型別

  下面從模型結構、計算區域、所用資訊、使用模型等方面對Attention的形式進行歸類,如下圖所示

計算區域

  根據Attention的計算區域,可以分成以下幾種:

Soft Attention:這是比較常見的Attention方式,對所有key求權重概率,每個key都有對應的權重,是一種全域性的計算方式(也可以叫做Global Attention)。這種方式比較理想,參考了所有key的內容,再進行加權。但是計算量可能會比較大一些。

Hard Attention:這種方式是直接精準定位到某個key,其餘key就都不管了,相當於這個key的概率是1,其餘key的概率全部都是0。因此這種對齊方式要求很高,要求一步到位,如果沒有正確對齊,會帶來很大的影響。另一方面,因為不可導,一般需要用強化學習的方法進行訓練(或者使用gumbel softmax子類的)。

Local Attention:這種方式其實是以上兩種方式的一個折中,對一個視窗區域進行計算。先用Hard方式定位到某個地方,以這個點為中心可以得到一個視窗區域,在這個小區域內用Soft方式來算Attention。

結構層次

單層Attention:用一個query對一段原文進行一次attention

多層Attention:一般用於文字具有層次關係的模型,假設我們把一個document劃分成多個句子,在第一層,我們分別對每個句子使用attention計算出一個句向量(也就是單層attention);在第二層,我們對所有句向量再做attention計算出一個文件向量(也是一個單層attention),最後再用這個文件向量去做任務。

多頭Attention:這是Attention is All You Need中提到的multi-head attention,用到了多個query對一段原文進行了多次attention,每個query都關注到原文的不同部分,相當於重複做多次單層attention

使用模型

  從模型上看,Attention一般用在CNN和LSTM上,也可以直接進行純Attention計算。

CNN+Attention:CNN的卷積操作可以提取重要特徵,我覺得這也算是Attention的思想,但是CNN的卷積感受視野是區域性的,需要通過疊加多層卷積區去擴大視野。另外,Max Pooling直接提取數值最大的特徵,也像是hard attention的思想,直接選中某個特徵。

  1. 在卷積操作前做Attention,比如Attention-Based BCNN-1,這個任務是文字蘊含任務需要處理兩段文字,同時對兩段輸入的序列向量進行attention,計算出特徵向量,再拼接到原始向量中,作為卷積層的輸入。
  2. 在卷積操作後做attention,比如Attention-Based BCNN-2,對兩段文字的卷積層的輸出做attention,作為pooling層的輸入。
  3. 在pooling層做attention,代替max pooling。比如Attention pooling,首先我們用LSTM學到一個比較好的句向量,作為query,然後用CNN先學習到一個特徵矩陣作為key,再用query對key產生權重,進行attention,得到最後的句向量。

LSTM+Attention:LSTM內部有Gate機制,其中input gate選擇哪些當前資訊進行輸入,forget gate選擇遺忘哪些過去資訊,我覺得這算是一定程度的Attention了,而且號稱可以解決長期依賴問題,實際上LSTM需要一步一步去捕捉序列資訊,在長文字上的表現是會隨著step增加而慢慢衰減,難以保留全部的有用資訊。LSTM通常需要得到一個向量,再去做任務,常用方式有:

  1. 直接使用最後的hidden state(可能會損失一定的前文資訊,難以表達全文)
  2. 對所有step下的hidden state進行等權平均(對所有step一視同仁)。
  3. Attention機制,對所有step的hidden state進行加權,把注意力集中到整段文字中比較重要的hidden state資訊。效能比前面兩種要好一點,而方便視覺化觀察哪些step是重要的,但是要小心過擬合,而且也增加了計算量。

純Attention:Attention is all you need,沒有用到CNN/RNN,乍一聽也是一股清流了,但是仔細一看,本質上還是一堆向量去計算attention。

Self-Attention優缺點

優點

引數少:self-attention的引數為$O(n^{2}d)$,而迴圈網路的引數為$O(nd^{2})$,卷積的引數為$O(knd^{2})$,當n遠小於d時,self-attention更快

可並行化:RNN需要一步步遞推才能捕捉到,而CNN則需要通過層疊來擴大感受野。Attention機制每一步計算不依賴於上一步的計算結果,因此可以和CNN一樣並行處理。

捕捉全域性資訊:更好的解決了長時依賴問題,同時只需一步計算即可獲取全域性資訊,在 Attention 機制引入之前,有一個問題大家一直很苦惱:長距離的資訊會被弱化,就好像記憶能力弱的人,記不住過去的事情是一樣的。Attention 是挑重點,就算文字比較長,也能從中間抓住重點,不丟失重要的資訊。

缺點

計算量:可以看到,事實上Attention的計算量並不低。比如Self Attention中,首先要對X做三次線性對映,這計算量已經相當於卷積核大小為3的一維卷積了,不過這部分計算量還只是O(n)的;然後還包含了兩次序列自身的矩陣乘法,這兩次矩陣乘法的計算量都是$O(n^2)$的,要是序列足夠長,這個計算量其實是很難接受的。

沒法捕捉位置資訊,即沒法學習序列中的順序關係。這點可以通過加入位置資訊,如通過位置向量來改善,具體可以參考最近大火的BERT模型。

實踐中 RNN 可以輕易解決的事,Transformer 沒做到,如 複製 string,尤其是碰到比訓練時的 序列更長時

Transformer vs CNN vs RNN

  假設輸入序列長度為$n$,每個元素的維度為$d$,輸出序列長度也為$n$,每個元素的維度也是$d$。

可以從每層的計算複雜度、並行的運算元量、學習距離長度三個方面比較 Transformer、CNN、RNN 三個特徵提取器。

計算複雜度

Self-Attention:考慮到 n 個 key 和 n 個 query 兩兩點乘,每層計算複雜度為 $O(n^2*d)$

RNN:考慮到矩陣(維度為$n*n$)和輸入向量相乘,每層計算複雜度為$O(n*d^2)$

CNN:對於$k$個卷積核經過$n$次一維卷積,每層計算複雜度為$O(k*n*d^2)$。

深度可分離卷積:每層計算複雜度為 $O(k*n*d+n*d^2)$。

因此:

當$n\leq d$時,self attention 要比 RNN 和 CNN 快,這也是大多數常見滿足的條件。

當$n>d$時,可以使用受限 self attention,即:計算 attention時僅考慮每個輸出位置附近視窗的$r$個輸入。這帶來兩個效果:

  • 每層計算複雜度降為$O(r*n*d)$
  • 最長學習距離降低為$r$,因此需要執行$O(n/r)$次才能覆蓋到所有輸入。

並行運算元量

可以通過必須序列的運算元量來描述

  • 對於 self-attention,CNN,其序列運算元量為$O(1)$,並行度最大。
  • 對於 RNN,其序列運算元量為$O(n)$,較難並行化。

最長計算路徑

覆蓋所有輸入的操作的數量

  • 對於self-attention,最長計算路徑為$O(1)$;對於 self-attention stricted,最長計算路徑為$O(n/r)$ 。
  • 對於常規卷積,則需要$O(n/k)$個卷積才能覆蓋所有的輸入;對於空洞卷積,則需要$O(log_kn)$才能覆蓋所有的輸入。
  • 對於 RNN,最長計算路徑為$O(n)$

作為額外收益,self-attention 可以產生可解釋性的模型:通過檢查模型中的注意力分佈,可以展示與句子語法和語義結構相關的資訊。

程式碼

(不做Mask)

pytorch程式碼:https://github.com/jadore801120/attention-is-all-you-need-pytorch

tensorflow程式碼:https://github.com/bojone/attention/blob/master/attention_tf.py

keras程式碼:https://github.com/bojone/attention/blob/master/attention_keras.py

參考

【論文】《Attention Is All You Need

【科學空間】《Attention is All You Need》淺讀(簡介+程式碼)

【華校專個人網站】Transformer的計算複雜度

【知乎問答】有了Transformer框架後是不是RNN完全可以廢棄了?

【程式碼】Attention Is All You Need的程式碼

【easyaitech】一文看懂 Attention(本質原理+3大優點+5大型別)

【easyaitech】一文看懂 NLP 裡的模型框架 Encoder-Decoder 和 Seq2Seq

「視訊」李宏毅 — transformer

「視訊」李宏毅 — ELMO、BERT、GPT 講解

相關文章