大資料文摘與百度NLP聯合出品

編譯:張馳、毅航、Conrad、龍心塵

https://

jalammar。github。io/illu

strated-transformer/

編者按:前一段時間谷歌推出的BERT模型在11項NLP任務中奪得STOA結果,引爆了整個NLP界。而BERT取得成功的一個關鍵因素是Transformer的強大作用。谷歌的Transformer模型最早是用於機器翻譯任務,當時達到了STOA效果。Transformer改進了RNN最被人詬病的訓練慢的缺點,利用self-attention機制實現快速並行。並且Transformer可以增加到非常深的深度,充分發掘DNN模型的特性,提升模型準確率。在本文中,我們將研究Transformer模型,把它掰開揉碎,理解它的工作原理。

以下是正文,請欣賞:

Transformer由論文《Attention is All You Need》提出,現在是谷歌雲TPU推薦的參考模型。論文相關的Tensorflow的程式碼可以從GitHub獲取,其作為Tensor2Tensor包的一部分。哈佛的NLP團隊也實現了一個基於PyTorch的版本,並註釋該論文。

在本文中,我們將試圖把模型簡化一點,並逐一介紹裡面的核心概念,希望讓普通讀者也能輕易理解。

Attention is All You Need:

https://

arxiv。org/abs/1706。0376

2

從宏觀的視角開始

首先將這個模型看成是一個黑箱操作。在機器翻譯中,就是輸入一種語言,輸出另一種語言。

BERT大火卻不懂Transformer?讀這一篇就夠了

那麼拆開這個黑箱,我們可以看到它是由編碼元件、解碼元件和它們之間的連線組成。

BERT大火卻不懂Transformer?讀這一篇就夠了

編碼元件部分由一堆編碼器(encoder)構成(論文中是將6個編碼器疊在一起——數字6沒有什麼神奇之處,你也可以嘗試其他數字)。解碼元件部分也是由相同數量(與編碼器對應)的解碼器(decoder)組成的。

BERT大火卻不懂Transformer?讀這一篇就夠了

所有的編碼器在結構上都是相同的,但它們沒有共享引數。每個解碼器都可以分解成兩個子層。

BERT大火卻不懂Transformer?讀這一篇就夠了

從編碼器輸入的句子首先會經過一個自注意力(self-attention)層,這層幫助編碼器在對每個單詞編碼時關注輸入句子的其他單詞。我們將在稍後的文章中更深入地研究自注意力。

自注意力層的輸出會傳遞到前饋(feed-forward)神經網路中。每個位置的單詞對應的前饋神經網路都完全一樣(譯註:另一種解讀就是一層視窗為一個單詞的一維卷積神經網路)。

解碼器中也有編碼器的自注意力(self-attention)層和前饋(feed-forward)層。除此之外,這兩個層之間還有一個注意力層,用來關注輸入句子的相關部分(和seq2seq模型的注意力作用相似)。

BERT大火卻不懂Transformer?讀這一篇就夠了

將張量引入圖景

我們已經瞭解了模型的主要部分,接下來我們看一下各種向量或張量(譯註:張量概念是向量概念的推廣,可以簡單理解向量是一階張量、矩陣是二階張量。)是怎樣在模型的不同部分中,將輸入轉化為輸出的。

像大部分NLP應用一樣,我們首先將每個輸入單詞透過詞嵌入演算法轉換為詞向量。

BERT大火卻不懂Transformer?讀這一篇就夠了

每個單詞都被嵌入為512維的向量,我們用這些簡單的方框來表示這些向量。

詞嵌入過程只發生在最底層的編碼器中。所有的編碼器都有一個相同的特點,即它們接收一個向量列表,列表中的每個向量大小為512維。在底層(最開始)編碼器中它就是詞向量,但是在其他編碼器中,它就是下一層編碼器的輸出(也是一個向量列表)。向量列表大小是我們可以設定的超引數——一般是我們訓練集中最長句子的長度。

將輸入序列進行詞嵌入之後,每個單詞都會流經編碼器中的兩個子層。

BERT大火卻不懂Transformer?讀這一篇就夠了

接下來我們看看Transformer的一個核心特性,在這裡輸入序列中每個位置的單詞都有自己獨特的路徑流入編碼器。在自注意力層中,這些路徑之間存在依賴關係。而前饋(feed-forward)層沒有這些依賴關係。因此在前饋(feed-forward)層時可以並行執行各種路徑。

然後我們將以一個更短的句子為例,看看編碼器的每個子層中發生了什麼。

現在我們開始“編碼”

如上述已經提到的,一個編碼器接收向量列表作為輸入,接著將向量列表中的向量傳遞到自注意力層進行處理,然後傳遞到前饋神經網路層中,將輸出結果傳遞到下一個編碼器中。

BERT大火卻不懂Transformer?讀這一篇就夠了

輸入序列的每個單詞都經過自編碼過程。然後,他們各自透過前向傳播神經網路——完全相同的網路,而每個向量都分別透過它。

從宏觀視角看自注意力機制

不要被我用自注意力這個詞弄迷糊了,好像每個人都應該熟悉這個概念。其實我之也沒有見過這個概念,直到讀到Attention is All You Need 這篇論文時才恍然大悟。讓我們精煉一下它的工作原理。

例如,下列句子是我們想要翻譯的輸入句子:

The animal didn‘t cross the street because it was too tired

這個“it”在這個句子是指什麼呢?它指的是street還是這個animal呢?這對於人類來說是一個簡單的問題,但是對於演算法則不是。

當模型處理這個單詞“it”的時候,自注意力機制會允許“it”與“animal”建立聯絡。

隨著模型處理輸入序列的每個單詞,自注意力會關注整個輸入序列的所有單詞,幫助模型對本單詞更好地進行編碼。

如果你熟悉RNN(迴圈神經網路),回憶一下它是如何維持隱藏層的。RNN會將它已經處理過的前面的所有單詞/向量的表示與它正在處理的當前單詞/向量結合起來。而自注意力機制會將所有相關單詞的理解融入到我們正在處理的單詞中。

BERT大火卻不懂Transformer?讀這一篇就夠了

當我們在編碼器#5(棧中最上層編碼器)中編碼“it”這個單詞的時,注意力機制的部分會去關注“The Animal”,將它的表示的一部分編入“it”的編碼中。

請務必檢查Tensor2Tensor notebook ,在裡面你可以下載一個Transformer模型,並用互動式視覺化的方式來檢驗。

從微觀視角看自注意力機制

首先我們瞭解一下如何使用向量來計算自注意力,然後來看它實怎樣用矩陣來實現。

計算自注意力的第一步就是從每個編碼器的輸入向量(每個單詞的詞向量)中生成三個向量。也就是說對於每個單詞,我們創造一個查詢向量、一個鍵向量和一個值向量。這三個向量是透過詞嵌入與三個權重矩陣後相乘建立的。

可以發現這些新向量在維度上比詞嵌入向量更低。他們的維度是64,而詞嵌入和編碼器的輸入/輸出向量的維度是512。 但實際上不強求維度更小,這只是一種基於架構上的選擇,它可以使多頭注意力(multiheaded attention)的大部分計算保持不變。

BERT大火卻不懂Transformer?讀這一篇就夠了

X1與WQ權重矩陣相乘得到q1, 就是與這個單詞相關的查詢向量。最終使得輸入序列的每個單詞的建立一個查詢向量、一個鍵向量和一個值向量。

什麼是查詢向量、鍵向量和值向量向量?

它們都是有助於計算和理解注意力機制的抽象概念。請繼續閱讀下文的內容,你就會知道每個向量在計算注意力機制中到底扮演什麼樣的角色。

計算自注意力的第二步是計算得分。假設我們在為這個例子中的第一個詞“Thinking”計算自注意力向量,我們需要拿輸入句子中的每個單詞對“Thinking”打分。這些分數決定了在編碼單詞“Thinking”的過程中有多重視句子的其它部分。

這些分數是透過打分單詞(所有輸入句子的單詞)的鍵向量與“Thinking”的查詢向量相點積來計算的。所以如果我們是處理位置最靠前的詞的自注意力的話,第一個分數是q1和k1的點積,第二個分數是q1和k2的點積。

BERT大火卻不懂Transformer?讀這一篇就夠了

第三步和第四步是將分數除以8(8是論文中使用的鍵向量的維數64的平方根,這會讓梯度更穩定。這裡也可以使用其它值,8只是預設值),然後透過softmax傳遞結果。softmax的作用是使所有單詞的分數歸一化,得到的分數都是正值且和為1。

BERT大火卻不懂Transformer?讀這一篇就夠了

這個softmax分數決定了每個單詞對編碼當下位置(“Thinking”)的貢獻。顯然,已經在這個位置上的單詞將獲得最高的softmax分數,但有時關注另一個與當前單詞相關的單詞也會有幫助。

第五步是將每個值向量乘以softmax分數(這是為了準備之後將它們求和)。這裡的直覺是希望關注語義上相關的單詞,並弱化不相關的單詞(例如,讓它們乘以0。001這樣的小數)。

第六步是對加權值向量求和(譯註:自注意力的另一種解釋就是在編碼某個單詞時,就是將所有單詞的表示(值向量)進行加權求和,而權重是透過該詞的表示(鍵向量)與被編碼詞表示(查詢向量)的點積並透過softmax得到。),然後即得到自注意力層在該位置的輸出(在我們的例子中是對於第一個單詞)。

BERT大火卻不懂Transformer?讀這一篇就夠了

這樣自自注意力的計算就完成了。得到的向量就可以傳給前饋神經網路。然而實際中,這些計算是以矩陣形式完成的,以便算得更快。那我們接下來就看看如何用矩陣實現的。

透過矩陣運算實現自注意力機制

第一步是計算查詢矩陣、鍵矩陣和值矩陣。為此,我們將將輸入句子的詞嵌入裝進矩陣X中,將其乘以我們訓練的權重矩陣(WQ,WK,WV)。

BERT大火卻不懂Transformer?讀這一篇就夠了

x矩陣中的每一行對應於輸入句子中的一個單詞。我們再次看到詞嵌入向量 (512,或圖中的4個格子)和q/k/v向量(64,或圖中的3個格子)的大小差異。

最後,由於我們處理的是矩陣,我們可以將步驟2到步驟6合併為一個公式來計算自注意力層的輸出。

BERT大火卻不懂Transformer?讀這一篇就夠了

自注意力的矩陣運算形式

“大戰多頭怪”

透過增加一種叫做“多頭”注意力(“multi-headed” attention)的機制,論文進一步完善了自注意力層,並在兩方面提高了注意力層的效能:

1。它擴充套件了模型專注於不同位置的能力。在上面的例子中,雖然每個編碼都在z1中有或多或少的體現,但是它可能被實際的單詞本身所支配。如果我們翻譯一個句子,比如“The animal didn’t cross the street because it was too tired”,我們會想知道“it”指的是哪個詞,這時模型的“多頭”注意機制會起到作用。

2。它給出了注意力層的多個“表示子空間”(representation subspaces)。接下來我們將看到,對於“多頭”注意機制,我們有多個查詢/鍵/值權重矩陣集(Transformer使用八個注意力頭,因此我們對於每個編碼器/解碼器有八個矩陣集合)。這些集合中的每一個都是隨機初始化的,在訓練之後,每個集合都被用來將輸入詞嵌入(或來自較低編碼器/解碼器的向量)投影到不同的表示子空間中。

BERT大火卻不懂Transformer?讀這一篇就夠了

在“多頭”注意機制下,我們為每個頭保持獨立的查詢/鍵/值權重矩陣,從而產生不同的查詢/鍵/值矩陣。和之前一樣,我們拿X乘以WQ/WK/WV矩陣來產生查詢/鍵/值矩陣。

如果我們做與上述相同的自注意力計算,只需八次不同的權重矩陣運算,我們就會得到八個不同的Z矩陣。

BERT大火卻不懂Transformer?讀這一篇就夠了

這給我們帶來了一點挑戰。前饋層不需要8個矩陣,它只需要一個矩陣(由每一個單詞的表示向量組成)。所以我們需要一種方法把這八個矩陣壓縮成一個矩陣。那該怎麼做?其實可以直接把這些矩陣拼接在一起,然後用一個附加的權重矩陣WO與它們相乘。

BERT大火卻不懂Transformer?讀這一篇就夠了

這幾乎就是多頭自注意力的全部。這確實有好多矩陣,我們試著把它們集中在一個圖片中,這樣可以一眼看清。

BERT大火卻不懂Transformer?讀這一篇就夠了

既然我們已經摸到了注意力機制的這麼多“頭”,那麼讓我們重溫之前的例子,看看我們在例句中編碼“it”一詞時,不同的注意力“頭”集中在哪裡:

BERT大火卻不懂Transformer?讀這一篇就夠了

當我們編碼“it”一詞時,一個注意力頭集中在“animal”上,而另一個則集中在“tired”上,從某種意義上說,模型對“it”一詞的表達在某種程度上是“animal”和“tired”的代表。

然而,如果我們把所有的attention都加到圖示裡,事情就更難解釋了:

BERT大火卻不懂Transformer?讀這一篇就夠了

使用位置編碼表示序列的順序

到目前為止,我們對模型的描述缺少了一種理解輸入單詞順序的方法。

為了解決這個問題,Transformer為每個輸入的詞嵌入添加了一個向量。這些向量遵循模型學習到的特定模式,這有助於確定每個單詞的位置,或序列中不同單詞之間的距離。這裡的直覺是,將位置向量新增到詞嵌入中使得它們在接下來的運算中,能夠更好地表達的詞與詞之間的距離。

BERT大火卻不懂Transformer?讀這一篇就夠了

為了讓模型理解單詞的順序,我們添加了位置編碼向量,這些向量的值遵循特定的模式。

如果我們假設詞嵌入的維數為4,則實際的位置編碼如下:

BERT大火卻不懂Transformer?讀這一篇就夠了

尺寸為4的迷你詞嵌入位置編碼例項

這個模式會是什麼樣子?

在下圖中,每一行對應一個詞向量的位置編碼,所以第一行對應著輸入序列的第一個詞。每行包含512個值,每個值介於1和-1之間。我們已經對它們進行了顏色編碼,所以圖案是可見的。

BERT大火卻不懂Transformer?讀這一篇就夠了

20字(行)的位置編碼例項,詞嵌入大小為512(列)。你可以看到它從中間分裂成兩半。這是因為左半部分的值由一個函式(使用正弦)生成,而右半部分由另一個函式(使用餘弦)生成。然後將它們拼在一起而得到每一個位置編碼向量。

原始論文裡描述了位置編碼的公式(第3。5節)。你可以在 get_timing_signal_1d()中看到生成位置編碼的程式碼。這不是唯一可能的位置編碼方法。然而,它的優點是能夠擴充套件到未知的序列長度(例如,當我們訓練出的模型需要翻譯遠比訓練集裡的句子更長的句子時)。

殘差模組

在繼續進行下去之前,我們需要提到一個編碼器架構中的細節:在每個編碼器中的每個子層(自注意力、前饋網路)的周圍都有一個殘差連線,並且都跟隨著一個“層-歸一化”步驟。

層-歸一化步驟:

https://

arxiv。org/abs/1607。0645

0

BERT大火卻不懂Transformer?讀這一篇就夠了

如果我們去視覺化這些向量以及這個和自注意力相關聯的層-歸一化操作,那麼看起來就像下面這張圖描述一樣:

BERT大火卻不懂Transformer?讀這一篇就夠了

解碼器的子層也是這樣樣的。如果我們想象一個2 層編碼-解碼結構的transformer,它看起來會像下面這張圖一樣:

BERT大火卻不懂Transformer?讀這一篇就夠了

解碼元件

既然我們已經談到了大部分編碼器的概念,那麼我們基本上也就知道解碼器是如何工作的了。但最好還是看看解碼器的細節。

編碼器透過處理輸入序列開啟工作。頂端編碼器的輸出之後會變轉化為一個包含向量K(鍵向量)和V(值向量)的注意力向量集 。這些向量將被每個解碼器用於自身的“編碼-解碼注意力層”,而這些層可以幫助解碼器關注輸入序列哪些位置合適:

BERT大火卻不懂Transformer?讀這一篇就夠了

在完成編碼階段後,則開始解碼階段。解碼階段的每個步驟都會輸出一個輸出序列(在這個例子裡,是英語翻譯的句子)的元素

接下來的步驟重複了這個過程,直到到達一個特殊的終止符號,它表示transformer的解碼器已經完成了它的輸出。每個步驟的輸出在下一個時間步被提供給底端解碼器,並且就像編碼器之前做的那樣,這些解碼器會輸出它們的解碼結果 。另外,就像我們對編碼器的輸入所做的那樣,我們會嵌入並新增位置編碼給那些解碼器,來表示每個單詞的位置。

而那些解碼器中的自注意力層表現的模式與編碼器不同:在解碼器中,自注意力層只被允許處理輸出序列中更靠前的那些位置。在softmax步驟前,它會把後面的位置給隱去(把它們設為-inf)。

這個“編碼-解碼注意力層”工作方式基本就像多頭自注意力層一樣,只不過它是透過在它下面的層來創造查詢矩陣,並且從編碼器的輸出中取得鍵/值矩陣。

最終的線性變換和Softmax層

解碼元件最後會輸出一個實數向量。我們如何把浮點數變成一個單詞?這便是線性變換層要做的工作,它之後就是Softmax層。

線性變換層是一個簡單的全連線神經網路,它可以把解碼元件產生的向量投射到一個比它大得多的、被稱作對數機率(logits)的向量裡。

不妨假設我們的模型從訓練集中學習一萬個不同的英語單詞(我們模型的“輸出詞表”)。因此對數機率向量為一萬個單元格長度的向量——每個單元格對應某一個單詞的分數。

接下來的Softmax 層便會把那些分數變成機率(都為正數、上限1。0)。機率最高的單元格被選中,並且它對應的單詞被作為這個時間步的輸出。

BERT大火卻不懂Transformer?讀這一篇就夠了

這張圖片從底部以解碼器元件產生的輸出向量開始。之後它會轉化出一個輸出單詞。

訓練部分總結

既然我們已經過了一遍完整的transformer的前向傳播過程,那我們就可以直觀感受一下它的訓練過程。

在訓練過程中,一個未經訓練的模型會透過一個完全一樣的前向傳播。但因為我們用有標記的訓練集來訓練它,所以我們可以用它的輸出去與真實的輸出做比較。

為了把這個流程視覺化,不妨假設我們的輸出詞彙僅僅包含六個單詞:“a”, “am”, “i”, “thanks”, “student”以及 “”(end of sentence的縮寫形式)。

BERT大火卻不懂Transformer?讀這一篇就夠了

我們模型的輸出詞表在我們訓練之前的預處理流程中就被設定好。

一旦我們定義了我們的輸出詞表,我們可以使用一個相同寬度的向量來表示我們詞彙表中的每一個單詞。這也被認為是一個one-hot 編碼。所以,我們可以用下面這個向量來表示單詞“am”:

BERT大火卻不懂Transformer?讀這一篇就夠了

例子:對我們輸出詞表的one-hot 編碼

接下來我們討論模型的損失函式——這是我們用來在訓練過程中最佳化的標準。透過它可以訓練得到一個結果儘量準確的模型。

損失函式

比如說我們正在訓練模型,現在是第一步,一個簡單的例子——把“merci”翻譯為“thanks”。

這意味著我們想要一個表示單詞“thanks”機率分佈的輸出。但是因為這個模型還沒被訓練好,所以不太可能現在就出現這個結果。

BERT大火卻不懂Transformer?讀這一篇就夠了

因為模型的引數(權重)都被隨機的生成,(未經訓練的)模型產生的機率分佈在每個單元格/單詞裡都賦予了隨機的數值。我們可以用真實的輸出來比較它,然後用反向傳播演算法來略微調整所有模型的權重,生成更接近結果的輸出。

你會如何比較兩個機率分佈呢?我們可以簡單地用其中一個減去另一個。更多細節請參考交叉熵和KL散度。

交叉熵:

https://

colah。github。io/posts/2

015-09-Visual-Information/

KL散度:

https://www。

countbayesie。com/blog/2

017/5/9/kullback-leibler-divergence-explained

但注意到這是一個過於簡化的例子。更現實的情況是處理一個句子。例如,輸入“je suis étudiant”並期望輸出是“i am a student”。那我們就希望我們的模型能夠成功地在這些情況下輸出機率分佈:

每個機率分佈被一個以詞表大小(我們的例子裡是6,但現實情況通常是3000或10000)為寬度的向量所代表。

第一個機率分佈在與“i”關聯的單元格有最高的機率

第二個機率分佈在與“am”關聯的單元格有最高的機率

以此類推,第五個輸出的分佈表示“”關聯的單元格有最高的機率

BERT大火卻不懂Transformer?讀這一篇就夠了

依據例子訓練模型得到的目標機率分佈。

在一個足夠大的資料集上充分訓練後,我們希望模型輸出的機率分佈看起來像這個樣子:

BERT大火卻不懂Transformer?讀這一篇就夠了

我們期望訓練過後,模型會輸出正確的翻譯。當然如果這段話完全來自訓練集,它並不是一個很好的評估指標(參考:交叉驗證,連結

https://www。

youtube。com/watch?

v=TIgfjmp-4BA

)。注意到每個位置(詞)都得到了一點機率,即使它不太可能成為那個時間步的輸出——這是softmax的一個很有用的性質,它可以幫助模型訓練。

因為這個模型一次只產生一個輸出,不妨假設這個模型只選擇機率最高的單詞,並把剩下的詞拋棄。這是其中一種方法(叫貪心解碼)。另一個完成這個任務的方法是留住機率最靠高的兩個單詞(例如I和a),那麼在下一步裡,跑模型兩次:其中一次假設第一個位置輸出是單詞“I”,而另一次假設第一個位置輸出是單詞“me”,並且無論哪個版本產生更少的誤差,都保留機率最高的兩個翻譯結果。然後我們為第二和第三個位置重複這一步驟。這個方法被稱作集束搜尋(beam search)。在我們的例子中,集束寬度是2(因為保留了2個集束的結果,如第一和第二個位置),並且最終也返回兩個集束的結果(top_beams也是2)。這些都是可以提前設定的引數。

再進一步

我希望透過上文已經讓你們瞭解到Transformer的主要概念了。如果你想在這個領域深入,我建議可以走以下幾步:閱讀Attention Is All You Need,Transformer部落格和Tensor2Tensor announcement,以及看看Łukasz Kaiser的介紹,瞭解模型和細節。

Attention Is All You Need:

https://

arxiv。org/abs/1706。0376

2

Transformer部落格:

https://

ai。googleblog。com/2017/

08/transformer-novel-neural-network。html

Tensor2Tensor announcement:

https://

ai。googleblog。com/2017/

06/accelerating-deep-learning-research。html

Łukasz Kaiser的介紹:

https://

colab。research。google。com

/github/tensorflow/tensor2tensor/blob/master/tensor2tensor/notebooks/hello_t2t。ipynb

接下來可以研究的工作:

Depthwise Separable Convolutions for Neural Machine Translation

https://

arxiv。org/abs/1706。0305

9

One Model To Learn Them All

https://

arxiv。org/abs/1706。0513

7

Discrete Autoencoders for Sequence Models

https://

arxiv。org/abs/1801。0979

7

Generating Wikipedia by Summarizing Long Sequences

https://

arxiv。org/abs/1801。1019

8

Image Transformer

https://

arxiv。org/abs/1802。0575

1

Training Tips for the Transformer Model

https://

arxiv。org/abs/1804。0024

7

Self-Attention with Relative Position Representations

https://

arxiv。org/abs/1803。0215

5

Fast Decoding in Sequence Models using Discrete Latent Variables

https://

arxiv。org/abs/1803。0338

2

Adafactor: Adaptive Learning Rates with Sublinear Memory Cost

https://

arxiv。org/abs/1804。0423

5

相關報道:

https://

jalammar。github。io/illu

strated-transformer/

本文系大資料文摘與百度NLP聯合出品系列文章第一篇。

百度NLP以“理解語言,擁有智慧,改變世界”為使命,開展包括自然語言處理、機器學習、資料探勘在內的技術研究和產品應用工作,引領著人工智慧技術的發展。