目錄html
本文翻譯自Jay Alammar的博文The Illustrated Transformergit
注意力是一個有助於提升神經機器翻譯模型性能的機制。在這篇文章中,咱們將着眼於Transformer——一個利用注意力來提升模型訓練速度的模型。Transformer在特定任務中的性能優於Google以前的神經機器翻譯模型。然而,最大的好處來自於Transformer如何使本身適合於並行化計算。實際上,Google Cloud推薦使用Transformer做爲參考模型來使用他們的雲TPU產品。那麼,讓咱們試着把模型拆開看看它是如何工做的。github
Transformer是在論文Attention is All You Need中提出的。它的TensorFlow實現已經加入到Tensor2tTensor包中。哈佛大學的NLP小組用PyTorch實現了該模型(鏈接:The Annotated Transformer)。在這篇文章中,咱們將試圖把事情簡單化一點,並逐一介紹這些概念,但願沒有尚未深刻了解的人更容易理解這些概念。算法
讓咱們從將模型看做一個單獨的黑盒開始。在機器翻譯中,輸入是一種語言的句子,輸出是翻譯後的另外一種語言對應的句子。網絡
進一步深刻Transformer這個黑盒,咱們看到一個編碼組件,一個解碼組件,以及它們之間的聯繫。架構
編碼組件是一堆編碼器(論文中將6個編碼器疊在一塊兒——數字6沒有什麼特別之處,你能夠嘗試更多或更少的個數)。解碼組件是相同數量的解碼器組成的棧。函數
編碼器在結構上是相同的(可是它們不共享權重)。每個又由兩個子層組成:工具
編碼器的輸入首先經過一個自注意力層——這個層幫助編碼器在對特定單詞進行編碼時照顧到輸入的句子中的其餘單詞。咱們將在稍後的文章中更深刻地研究自注意力機制。post
自注意力層的輸出被輸入到前饋神經網絡。徹底相同的前饋網絡分別應用於各個編碼器中。性能
解碼器也有編碼器中的那兩層,但在這兩層之間還多了一層注意力層,它幫助解碼器將注意力集中在輸入句子的相關部分(相似於注意力在seq2seq模型中的做用)。
如今咱們已經瞭解了模型的主要組件,讓咱們開始研究各類向量/張量,以及它們如何在這些組件之間流動,從而將通過訓練的模型的輸入轉換爲輸出。
與通常NLP模型的狀況同樣,咱們首先使用嵌入算法將每一個輸入單詞轉換爲一個向量。
每個單詞被轉換爲一個512維的向量
詞嵌入僅用於最底部的編碼器。全部編碼器都會以一個向量列表爲輸入(每一個向量有512維),最底部的編碼器接收的是詞嵌入,但其餘編碼器中接收的是在其下面的編碼器的輸出。這個列表的大小是超參數——基本上它是咱們的訓練數據集中最長的句子的長度。
在這裏,咱們開始看到轉換器的一個關鍵特性,即每一個位置上的單詞在編碼器中沿着本身的路徑流動。在自注意力層中,這些路徑之間存在依賴關係。可是,前饋層不能捕捉這種依賴關係,所以各個詞在流經前饋層時能夠並行計算。
接下來,咱們將以一個較短的句子爲例,看看在編碼器中每一個子層中都發生了什麼。
正如咱們已經提到的,編碼器接收一個向量列表做爲輸入。它經過將這些向量傳遞到一個自注意力層,而後轉入前饋神經網絡,最後將輸出送入到下一個編碼器。
讓咱們提煉一下自注意力如何工做的。
假設下面的句子是咱們想要翻譯的句子:
「The animal didn't cross the street because it was too tired」
這句話中的「it」指代的是什麼?是街道仍是動物?這對人類來講是一個簡單的問題,但對算法來講就不那麼簡單了。
當模型處理「it」這個詞時,自注意力就可以將「it」與「動物」聯繫起來。
當模型處理每一個單詞(輸入序列中的每一個位置)時,自注意力可以捕捉該單詞與輸入序列中的其餘位置上的單詞的聯繫來尋找線索,以幫助更好地編碼該單詞。
若是你熟悉RNN,想一想一個隱藏狀態何以將RNN已處理的前一個單詞/向量的表示與它正在處理的當前單詞/向量結合起來。Transformer就是用自注意力來將其餘相關單詞的「理解」轉化爲咱們正在處理的單詞的。
在第五個編碼器在編碼it這個單詞時,注意力機制讓模型更多地關注到「The animal"
你能夠在Tensor2Tensor的notebook中載入Transformer,而後用可視化工具看看。
首先讓咱們先看看如何用向量計算自注意力,而後再看看它是如何用矩陣運算實現這一過程。
計算自注意力的第一步是從編碼器的每一個輸入向量中建立三個向量(在本例中,是每一個單詞的嵌入)。所以,對於每一個單詞,咱們建立一個Query向量、一個Key向量和一個Value向量。這些向量是經過將詞嵌入與3個訓練後的矩陣相乘獲得的。
注意這些新的向量在維數上比嵌入向量小。它們的維數爲64,而詞嵌入和編碼器的輸入/輸出向量的維數爲512。把向量維度下降僅僅是一種處於架構考慮的選擇,從而使多頭注意力(multi-headed attention)計算保持維度上的固定。
$X_ 1$與權重矩陣$W^Q$相乘獲得相應的query向量$q_1$, 一樣能夠獲得輸入序列中每一個單詞對應的$W^K$和$W^V$ 。
**什麼是「query向量」、「key向量」和「value向量」? 它們是用來計算注意力的。經過閱讀下面的內容,你就會知道這些向量的做用。
{x_1} 它們是對計算和思考注意力有用的抽象概念。一旦你繼續閱讀下面如何計算注意力,你就會知道全部你須要知道的關於這些向量所扮演的角色。
計算自注意力的第二步是計算注意力得分。假設咱們要計算本例中第一個單詞「Thinking」的自注意力得分。咱們須要對輸入句子中的每一個單詞進行打分。這個分數決定了咱們在編碼某個位置上的單詞時,對其餘單詞的關注程度。
這個得分是經過計算query向量與各個單詞的key向量的點積獲得的。因此若是咱們要計算位置1上的單詞的自注意力得分,那麼第一個分數就是$q_1$和$k_1$的點積。第二個分數是$q_1$和$k_2$的點積。
第三步和第四步是將得分除以8(key向量的維數(64)的平方根,是默認值。這能讓梯度更新的過程更加穩定),而後將結果進行softmax操做。Softmax將分數標準化,使它們都是正數,加起來等於1。
softmax的結果決定了每一個單詞在這個位置(1)上的相關程度(譯者注:至關於權重)。顯然,1這個位置的單詞會有最高的softmax得分。
第五步是將每一個value向量乘以softmax的結果(爲求和作準備)。這裏的思想是儘可能保持咱們想要關注的單詞的value值不變,而掩蓋掉那些不相關的單詞(例如將它們乘以很小的數字)。
第六步是將帶權重的各個value向量加起來。就此,產生在這個位置上(第一個單詞)的self-attention層的輸出。
這就是自注意力的計算過程。獲得的輸出向量能夠送入前饋神經網絡。可是在實際的實現中,爲了更快的處理,這種計算是以矩陣形式進行的。
第一步是計算Query矩陣、Key矩陣和Value矩陣。咱們把詞嵌入矩陣X乘以訓練獲得的的權重矩陣($W^Q$, $W^K$, $W^V$)
X的每一行表明一個單詞
最後,因爲咱們處理的是矩陣,咱們能夠將第二部到第六步合併爲一個公式中計算自注意力層的輸出。
論文進一步細化了自注意力層,增長了「多頭」注意力機制。這從兩個方面提升了自注意力層的性能:
用不一樣的權重矩陣作8次不一樣的計算,咱們最終獲得8個不一樣的Z矩陣。
這給咱們留下了一點挑戰。前饋層不須要八個矩陣——它只須要一個矩陣(每一個單詞對應一個向量)。因此咱們須要一種方法把這8個壓縮成一個矩陣。
咱們怎麼作呢?咱們把這些矩陣拼接起來而後用一個額外的權重矩陣與之相乘。
這就是多頭自注意力的所有內容。讓咱們試着把這麼多矩陣放在一張圖上看看。
讓咱們回顧一下以前的例子,看看當咱們在例句中編碼單詞「it」時,不一樣的注意力頭把焦點放到了哪裏:
當咱們編碼「it"這個詞時,有的「頭」(橘黃色部分)計算的結果認爲其與「the animal「關係比較密切,而有的(綠色部分)認爲和「tired」關係更近 ——那麼便把「The animal」 和「tire」的信息更多地編碼到了it中。
然而,若是咱們把全部的注意力都集中在這幅圖上,事情就會變得難以理解:
還有一件事沒有講到,就是模型如何編碼輸入序列中單詞順序。
爲了解決這個問題,Transformer在每一個詞嵌入向量上加了一個向量。這些向量遵循模型學習到的特定模式,這有助於模型肯定每一個單詞的位置,或序列中不一樣單詞之間的距離。其中的思想是,將這些值與詞嵌入向量相加可以獲得通過Q/K/V投射後的詞嵌入向量和計算自注意力得分時的向量間的距離。
爲了在模型中加入詞序,論文增長了位置編碼向量,其值是由特定的式子計算獲得的。
假設詞嵌入的維數爲4,那麼實際的位置編碼應該是這樣的:
這種模式多是什麼樣的?
在下面的圖中,每一行對應一個向量的位置編碼。因此第一行就是與一個單詞的詞嵌入矩陣相加的位置編碼向量。每行包含512個值——每一個值在1到-1之間。咱們用顏色標記了它們。
一個實際的例子:20個詞的序列(行數),每一個詞的嵌入向量有512維(列數)。從圖中能夠看到出現了左右各半兩個部分。這是由於向量左邊的數值和右邊的數值使用的是不一樣的函數獲得的,左邊使用的是正弦sin,右邊使用的是餘弦cos。經過拼接兩種函數獲得的向量獲得每個位置編碼向量。
位置編碼向量的計算公式在論文中有給出,你也能夠參考生成位置編碼的代碼 get_timing_signal_1d()
. 固然,這不是惟一的編碼位置信息的方法。可是這種方法的好處之一就是對於不定長度的序列,模型也可以處理(例如模型要翻譯一句長度比訓練數據都要長的句子時,模型也能完成)。
在繼續以前,咱們須要說說編碼器架構中的一個細節,即每一個編碼器中的每一個子層(self-attention, ffnn)在其周圍都有一個殘差鏈接(編者注:shortcut),同時還伴隨着一個規範化步驟。
更形象的表示以下:
這也適用於解碼器的子層。若是咱們僅考慮一個由兩個編碼器和兩個解碼器組成的Transformer,它看起來是這樣的:
如今咱們已經介紹了編碼器端的大多數概念,咱們基本上也知道解碼器的組件是如何工做的。但讓咱們看看它們是如何協同工做的。
編碼器從處理輸入序列開始,而後將上面編碼器的輸出轉換成一組注意向量K和V,這些將由每一個解碼器在其「編碼器-譯碼器注意力」層(encoder-decoder attention)中使用,幫助解碼器將注意力集中在輸入序列的適當位置:
在完成編碼過程以後,開始解碼過程。每個時間步,解碼器會輸出翻譯後的一個單詞。
重複這樣的解碼過程知道出現表明結束的特殊符號。每一時間步的輸出都會在下一個時間步解碼的時候的時候反饋給底層解碼器,解碼器就會像編碼器同樣,將該層的解碼結果想更高層傳遞。就像咱們對編碼器輸入所作的那樣,咱們將位置編碼也加入到解碼器輸入中以指示每一個單詞的位置。
解碼器中的自注意力層與編碼器中的自注意力層的工做方式略有不一樣:
在解碼器中,自注意力層只容許關注到輸出序列中較前的位置。這是經過在自注意力計算的softmax步驟以前用掩碼mask遮罩序列中後面的位置(將它們設置爲爲負無窮)來實現的。
編碼器-解碼器注意力層的工做原理與多頭自注意力層相似,只是它從下面的網絡層建立Query矩陣,並從編碼器棧的輸出中獲取Key矩陣和Value矩陣。
解碼器棧輸出一個浮點型向量。咱們怎麼把它變成一個詞呢?這是最後一個線性層的工做,其後接上一個Softmax層。
線性層是一個簡單的全鏈接神經網絡,它將解碼器棧產生的向量投影到一個更高維向量(logits)上。
假設咱們的模型知道10,000個從它的訓練數據集中學習的唯一英語單詞(咱們的模型的「輸出詞彙」)。那麼logits 就有10,000個維度,每一個維度對應一個唯一的單詞的得分。
以後的softmax層將這些分數轉換爲機率。選擇機率最大的維度,並對應地生成與之關聯的單詞做爲此時間步的輸出。
編碼器棧的輸出向量通過線性層和softmax獲得機率分佈
如今,咱們介紹了Transformer的整個正向傳遞過程。
假設咱們的輸出詞彙表只包含六個單詞(「a」、「am」、「i」、「thanks」、「student」和「
一旦定義了輸出詞彙表,就可使用相同維度的向量來表示詞彙表中的每一個單詞。這也稱爲one-hot編碼。例如,咱們能夠用下面的向量來表示「am」這個詞:
咱們用一個簡單的例子進行訓練——將「merci」翻譯成「thanks」。
咱們但願輸出是一個表示「thanks」的機率分佈。可是因爲這個模型尚未通過充分的訓練,因此如今還不太可能實現。
因爲模型參數是隨機初始化的,在剛開始訓練的時候,輸出的機率分佈也是沒有意義的。經過與正確的翻譯結果進行比較,用反向傳播更新模型的權重,讓模型逼近正確的翻譯結果。
如何比較兩個機率分佈?咱們只要從另外一箇中減去一個。要了解更多細節,請查看交叉熵 and KL散度.。
但請注意,這是一個過於簡化的示例。更實際一點,咱們將使用一個句子,而不是一個單詞。例如,輸入:「je suis etudiant」和指望輸出:「i am a student」。這真正的意思是,咱們想要咱們的模型連續輸出機率分佈,其中:
每個機率分佈由一個維度等於詞表大小的向量所表示
一直重複直到輸出的機率分佈對應的是 ‘<end of sentence>
’ 符號
目標機率分佈做爲監督信號來訓練模型
在對模型進行足夠長時間的大數據集訓練以後,咱們但願獲得的機率分佈是這樣的:
如今,由於這個模型每次產生一個輸出,咱們能夠假設這個模型從機率分佈中選擇機率最大的單詞,而後扔掉其他的。這是一種方法(稱爲貪婪解碼)。另外一個方法是前兩個單詞(說,好比「I」和「a」),而後在下一個時間步中,運行模型兩次:一次假設第一個輸出位置是「I」這個詞,而另外一個假設第一個輸出位置是‘me’這個詞,和哪一個版本產生更少的錯誤考慮# 1和# 2保存位置。咱們對2號和3號位置重複這個。這種方法稱爲「beam search」,在咱們的例子中,beam_size是2(由於咱們在計算位置#1和#2的beam以後比較告終果),top_beam也是2(由於咱們保留了兩個單詞)。這兩個超參數均可以進行實驗。
我但願你能夠從這裏開始瞭解Transformer的主要概念。若是你想深刻了解,我建議參考如下步驟: