圖解Transformer

前言

      Attention這種機制最開始應用於機器翻譯的任務中,並且取得了巨大的成就,因而在最近的深度學習模型中受到了大量的關注。在在這個基礎上,我們提出一種完全基於Attention機制來加速深度學習訓練過程的算法模型-Transformer。事實證明Transformer結構在特定任務上已經優於了谷歌的神經網絡機器翻譯模型。但是,Transformer最大的優勢在於其在並行化處理上做出的貢獻。谷歌也在利用Transformer的並行化方式來營銷自己的雲TPU。所以,現在讓我們一步一步剖析Transformer的神祕面紗,讓我看看他是怎麼一步一步訓練的。

     Transformer在Goole的一篇論文Attention is All You Need被提出,爲了方便實現調用Transformer Google還開源了一個第三方庫,基於TensorFlow的Tensor2Tensor,一個NLP的社區研究者貢獻了一個Torch版本的支持:guide annotating the paper with PyTorch implementation。這裏,我想用一些方便理解的方式來一步一步解釋Transformer的訓練過程,這樣即便你沒有很深的深度學習知識你也能大概明白其中的原理。

A High-Level Look

       我們先把Transformer想象成一個黑匣子,在機器翻譯的領域中,這個黑匣子的功能就是輸入一種語言然後將它翻譯成其他語言。如下圖:

掀起The Transformer的蓋頭,我們看到在這個黑匣子由2個部分組成,一個Encoders和一個Decoders。

我們再對這個黑匣子進一步的剖析,發現每個Encoders中分別由6個Encoder組成(論文中是這樣配置的)。而每個Decoders中同樣也是由6個Decoder組成。

對於Encoders中的每一個Encoder,他們結構都是相同的,但是並不會共享權值。每層Encoder有2個部分組成,如下圖: 

每個Encoder的輸入首先會通過一個self-attention層,通過self-attention層幫助Endcoder在編碼單詞的過程中查看輸入序列中的其他單詞。如果你不清楚這裏在說什麼,不用着急,之後我們會詳細介紹self-attention的。

Self-attention的輸出會被傳入一個全連接的前饋神經網絡,每個encoder的前饋神經網絡參數個數都是相同的,但是他們的作用是獨立的。

每個Decoder也同樣具有這樣的層級結構,但是在這之間有一個Attention層,幫助Decoder專注於與輸入句子中對應的那個單詞(類似與seq2seq models的結構)

Bringing The Tensors Into The Picture

      在上一節,我們介紹了Transformer的網絡結構。現在我們以圖示的方式來研究Transformer模型中各種張量/向量,觀察從輸入到輸出的過程中這些數據在各個網絡結構中的流動。

      首先還是NLP的常規做法,先做一個詞嵌入:什麼是文本的詞嵌入?

      我們將每個單詞編碼爲一個512維度的向量,我們用上面這張簡短的圖形來表示這些向量。詞嵌入的過程只發生在最底層的Encoder。但是對於所有的Encoder來說,你都可以按下圖來理解。輸入(一個向量的列表,每個向量的維度爲512維,在最底層Encoder作用是詞嵌入,其他層就是其前一層的output)。另外這個列表的大小和詞向量維度的大小都是可以設置的超參數。一般情況下,它是我們訓練數據集中最長的句子的長度。

     上圖其實介紹到了一個Transformer的關鍵點。你注意觀察,在每個單詞進入Self-Attention層後都會有一個對應的輸出。Self-Attention層中的輸入和輸出是存在依賴關係的,而前饋層則沒有依賴,所以在前饋層,我們可以用到並行化來提升速率。

    下面我用一個簡短的句子作爲例子,來一步一步推導transformer每個子層的數據流動過程。

Now We’re Encoding!

       正如之前所說,Transformer中的每個Encoder接收一個512維度的向量的列表作爲輸入,然後將這些向量傳遞到‘self-attention’層,self-attention層產生一個等量512維向量列表,然後進入前饋神經網絡,前饋神經網絡的輸出也爲一個512維度的列表,然後將輸出向上傳遞到下一個encoder。

     如上圖所示,每個位置的單詞首先會經過一個self attention層,然後每個單詞都通過一個獨立的前饋神經網絡(這些神經網絡結構完全相同)。

Self-Attention at a High Level

      Self attention這個單詞看起來好像每個人都知道是什麼意思,但實質上他是算法領域中新出的概念,你可以通過閱讀:Attention is All You Need 來理解self attention的原理。

      假設下面的句子就是我們需要翻譯的輸入句:

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

      這句話中的"it"指的是什麼?它指的是“animal”還是“street”?對於人來說,這其實是一個很簡單的問題,但是對於一個算法來說,處理這個問題其實並不容易。self attention的出現就是爲了解決這個問題,通過self attention,我們能將“it”與“animal”聯繫起來。

      當模型處理單詞的時候,self attention層可以通過當前單詞去查看其輸入序列中的其他單詞,以此來尋找編碼這個單詞更好的線索。

      如果你熟悉RNNs,那麼你可以回想一下,RNN是怎麼處理先前單詞(向量)與當前單詞(向量)的關係的?RNN是怎麼計算他的hidden state的。self-attention正是transformer中設計的一種通過其上下文來理解當前詞的一種辦法。你會很容易發現...相較於RNNs,transformer具有更好的並行性。

如上圖,是我們第五層Encoder針對單詞'it'的圖示,可以發現,我們的Encoder在編碼單詞‘it’時,部分注意力機制集中在了‘animl’上,這部分的注意力會通過權值傳遞的方式影響到'it'的編碼。

更多細節可以查看 Tensor2Tensor notebook 

Self-Attention in Detail

    這一節我們先介紹如何用向量的方式來計算self attention,然後再來看看它是如何使用矩陣來實現的。

     計算self attention的第一步是從每個Encoder的輸入向量上創建3個向量(在這個情況下,對每個單詞做詞嵌入)。所以,對於每個單詞,我們創建一個Query向量,一個Key向量和一個Value向量。這些向量是通過詞嵌入乘以我們訓練過程中創建的3個訓練矩陣而產生的。

     注意這些新向量的維度比嵌入向量小。我們知道嵌入向量的維度爲512,而這裏的新向量的維度只有64維。新向量並不是必須小一些,這是網絡架構上的選擇使得Multi-Headed Attention(大部分)的計算不變。

我們將X_{1}乘以W^{Q}的權重矩陣得到新向量q_{1}q_{1}既是“query”的向量。同理,最終我們可以對輸入句子的每個單詞創建“query”,
“key”,“value”的新向量表示形式。

對了..“query”,“key”,“value”是什麼向量呢?有什麼用呢?

這些向量的概念是很抽象,但是它確實有助於計算注意力。不過先不用糾結去理解它,後面的的內容,會幫助你理解的。

      計算self attention的第二步是計算得分。以上圖爲例,假設我們在計算第一個單詞“thinking”的self attention。我們需要根據這個單詞對輸入句子的每個單詞進行評分。當我們在某個位置編碼單詞時,分數決定了對輸入句子的其他單詞的關照程度。

       通過將query向量和key向量點擊來對相應的單詞打分。所以,如果我們處理開始位置的的self attention,則第一個分數爲q_{1}k_{1}的點積,第二個分數爲q_{2}k_{2}的點積。如下圖

第三步和第四步的計算,是將第二部的得分除以8\sqrt{d_{k}})(論文中使用key向量的維度是64維,其平方根=8,這樣可以使得訓練過程中具有更穩定的提取。這個\sqrt{d_{k}}並不是唯一值,經驗所得)。然後再將得到的輸出通過softmax函數標準化,使得最後的列表和爲1。

這個softmax的分數決定了當前單詞在每個句子中每個單詞位置的表示程度。很明顯,當前單詞對應句子中此單詞所在位置的softmax的分數最高,但是,有時候attention機制也能關注到此單詞外的其他單詞,這很有用。

第五步是將每個Value向量乘以softmax後的得分。這裏實際上的意義在於保存對當前詞的關注度不變的情況下,降低對不相關詞的關注。

第六步是 累加加權值的向量。 這會在此位置產生self-attention層的輸出(對於第一個單詞)。

總結self-attention的計算過程,(單詞級別)就是得到一個我們可以放到前饋神經網絡的矢量。 然而在實際的實現過程中,該計算會以矩陣的形式完成,以便更快地處理。下面我們來看看Self-Attention的矩陣計算方式。

Matrix Calculation of Self-Attention

第一步是去計算Query,Key和Value矩陣。我們將詞嵌入轉化成矩陣X中,並將其乘以我們訓練的權值矩陣(W^{Q},W^{K},W^{V}

X矩陣中的每一行對應於輸入句子中的一個單詞。 我們看到的X每一行的方框數實際上是詞嵌入的維度,圖中所示的和論文中是有差距的。X(圖中的4個方框論文中爲512個)和q / k / v向量(圖中的3個方框論文中爲64個)

最後,由於我們正在處理矩陣,我們可以在一個公式中濃縮前面步驟2到6來計算self attention層的輸出。

 

The Beast With Many Heads

本文通過使用“Multi-headed”的機制來進一步完善self attention層。“Multi-headed”主要通過下面2中方式改善了attention層的性能:

1. 它拓展了模型關注不同位置的能力。在上面例子中可以看出,The animal didn't cross the street because it was too tired,我們的attention機制計算出“it”指代的爲“animal”,這在對語言的理解過程中是很有用的。

2.它爲attention層提供了多個“representation subspaces”。由下圖可以看到,在self attention中,我們有多個個Query / Key / Value權重矩陣(Transformer使用8個attention heads)。這些集合中的每個矩陣都是隨機初始化生成的。然後通過訓練,用於將詞嵌入(或者來自較低Encoder/Decoder的矢量)投影到不同的“representation subspaces(表示子空間)”中。

    通過multi-headed attention,我們爲每個“header”都獨立維護一套Q/K/V的權值矩陣。然後我們還是如之前單詞級別的計算過程一樣處理這些數據。

    如果對上面的例子做同樣的self attention計算,而因爲我們有8頭attention,所以我們會在八個時間點去計算這些不同的權值矩陣,但最後結束時,我們會得到8個不同的Z矩陣。如下圖:

    瞧瞧,這會給我們後續工作造成什麼問題?

    我們知道在self-attention後面緊跟着的是前饋神經網絡,而前饋神經網絡接受的是單個矩陣向量,而不是8個矩陣。所以我們需要一種辦法,把這8個矩陣壓縮成一個矩陣

    我們怎麼做? 

   我們將這8個矩陣連接在一起然後再與一個矩陣W^{O}相乘。步驟如下圖所示:

這樣multi-headed self attention的全部內容就介紹完了。之前可能都是一些過程的圖解,現在我將這些過程連接在一起,用一個整體的框圖來表示一下計算的過程,希望可以加深理解。

      現在我們已經觸及了attentionheader,讓我們重新審視我們之前的例子,看看例句中的“it”這個單詞在不同的attention header情況下會有怎樣不同的關注點。

      如圖:當我們對“it”這個詞進行編碼時,一個注意力的焦點主要集中在“animal”上,而另一個注意力集中在“tired” 

但是,如果我們將所有注意力添加到圖片中,那麼事情可能更難理解:

 

Representing The Order of The Sequence Using Positional Encoding

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

 

媽的 不擼了...明天再擼

下面是谷歌翻譯的結果 將就看=.=

 

我們到目前爲止描述的模型中缺少的一件事是一種考慮輸入序列中單詞順序的方法。

爲了解決這個問題,變換器爲每個輸入嵌入添加了一個向量。這些向量遵循模型學習的特定模式,這有助於確定每個單詞的位置,或者序列中不同單詞之間的距離。這裏的直覺是,將這些值添加到嵌入中,一旦它們被投影到Q / K / V向量中並且在點積注意期間,就在嵌入向量之間提供有意義的距離。

 


爲了讓模型瞭解單詞的順序,我們添加位置編碼向量 - 其值遵循特定模式。

 

如果我們假設嵌入的維度爲4,那麼實際的位置編碼將如下所示:


玩具嵌入大小爲4的位置編碼的真實示例

 

這種模式可能是什麼樣的?

在下圖中,每行對應矢量的位置編碼。因此第一行將是我們添加到輸入序列中第一個字的嵌入的向量。每行包含512個值 - 每個值的值介於1和-1之間。我們對它們進行了顏色編碼,使圖案可見。


嵌入大小爲512(列)的20個字(行)的位置編碼的真實示例。您可以看到它在中心位置分成兩半。這是因爲左半部分的值由一個函數(使用正弦)生成,而右半部分由另一個函數(使用餘弦)生成。然後將它們連接起來以形成每個位置編碼矢量。

位置編碼的公式在論文(3.5節)中描述。您可以在中查看用於生成位置編碼的代碼get_timing_signal_1d()。這不是位置編碼的唯一可能方法。然而,它具有能夠擴展到看不見的序列長度的優點(例如,如果我們訓練的模型被要求翻譯的句子比我們訓練集中的任何句子更長)。

剩餘物

在繼續之前我們需要提到的編碼器架構中的一個細節是每個編碼器中的每個子層(自注意,ffnn)在其周圍具有殘餘連接,然後是層規範化步驟。

 

如果我們要將矢量和與自我關注相關的圖層規範操作可視化,它將如下所示:

 

這也適用於解碼器的子層。如果我們想到2個堆疊編碼器和解碼器的變壓器,它看起來像這樣:

解碼器方面

現在我們已經涵蓋了編碼器方面的大多數概念,我們基本上知道解碼器的組件如何工作。但是讓我們來看看它們如何協同工作。

編碼器通過處理輸入序列開始。然後將頂部編碼器的輸出變換成一組關注矢量K和V.這些將由每個解碼器在其“編碼器 - 解碼器關注”層中使用,這有助於解碼器關注輸入序列中的適當位置:


完成編碼階段後,我們開始解碼階段。解碼階段中的每個步驟輸出來自輸出序列的元素(在這種情況下爲英語翻譯句子)。

以下步驟重複此過程,直至特殊 到達符號表示變壓器解碼器已完成其輸出。每個步驟的輸出在下一個時間步驟中被饋送到底部解碼器,並且解碼器像編碼器一樣冒泡它們的解碼結果。就像我們對編碼器輸入所做的那樣,我們在這些解碼器輸入中嵌入並添加位置編碼,以指示每個字的位置。

解碼器中的自關注層以與編碼器中的自注意層略有不同的方式操作:

在解碼器中,僅允許自我關注層關注輸出序列中的較早位置。這是通過-inf在自我關注計算中的softmax步驟之前屏蔽未來位置(將它們設置爲)來完成的。

“編碼器 - 解碼器注意”層的工作方式與多頭自注意一樣,只是它從下面的層創建其查詢矩陣,並從編碼器堆棧的輸出中獲取鍵和值矩陣。

最終線性和Softmax層

解碼器堆棧輸出浮點數向量。我們如何將其變成一個單詞?這是最終線性層的工作,其後是Softmax層。

線性層是一個簡單的完全連接的神經網絡,它將由解碼器堆棧產生的向量投影到一個更大,更大的向量中,稱爲對數向量。

讓我們假設我們的模型知道從訓練數據集中學到的10,000個獨特的英語單詞(我們的模型的“輸出詞彙表”)。這將使logits矢量10,000個細胞寬 - 每個細胞對應於一個唯一單詞的得分。這就是我們如何解釋模型的輸出,然後是線性層。

然後softmax層將這些分數轉換爲概率(所有正數,都加起來爲1.0)。選擇具有最高概率的單元,並且將與其相關聯的單詞作爲該時間步的輸出。

 

 
該圖從底部開始,產生的矢量作爲解碼器堆棧的輸出。然後它變成輸出字。

 

回顧培訓

現在我們已經通過訓練有素的變壓器覆蓋了整個前進過程,看一下培訓模型的直覺是有用的。

在訓練期間,未經訓練的模型將通過完全相同的前進傳球。但由於我們正在對標記的訓練數據集進行訓練,因此我們可以將其輸出與實際正確的輸出進行比較。

爲了想象這一點,讓我們假設我們的輸出詞彙只包含六個單詞(“a”,“am”,“i”,“thanks”,“student”和“<eos>”(“句末”的縮寫)) 。


在我們開始訓練之前,我們模型的輸出詞彙是在預處理階段創建的。

一旦我們定義了輸出詞彙表,我們就可以使用相同寬度的向量來表示詞彙表中的每個單詞。這也稱爲單熱編碼。例如,我們可以使用以下向量指示單詞“am”:


示例:我們的輸出詞彙表的單熱編碼

在回顧一下之後,讓我們討論一下模型的損失函數 - 我們在訓練階段優化的指標,以引導一個訓練有素且令人驚訝的精確模型。

損失函數

假設我們正在訓練我們的模型。說這是我們在訓練階段的第一步,我們正在通過一個簡單的例子進行訓練 - 將“merci”翻譯成“謝謝”。

這意味着,我們希望輸出是指示“謝謝”一詞的概率分佈。但由於這種模式還沒有接受過訓練,所以這種情況不太可能發生。


由於模型的參數(權重)都是隨機初始化的,因此(未經訓練的)模型產生具有每個單元/單詞的任意值的概率分佈。我們可以將它與實際輸出進行比較,然後使用反向傳播調整所有模型的權重,使輸出更接近所需的輸出。

 

你如何比較兩個概率分佈?我們簡單地從另一箇中減去一個。有關更多詳細信息,請查看 交叉熵Kullback-Leibler散度

但請注意,這是一個過於簡單的例子。更現實的是,我們將使用長於一個單詞的句子。例如 - 輸入:“jesuisétudiant”和預期輸出:“我是學生”。這實際意味着,我們希望我們的模型能夠連續輸出概率分佈,其中:

  • 每個概率分佈由寬度爲vocab_size的向量表示(在我們的玩具示例中爲6,但更實際地是3,000或10,000的數字)
  • 第一概率分佈在與單詞“i”相關聯的單元處具有最高概率
  • 第二概率分佈在與單詞“am”相關聯的單元格中具有最高概率
  • 依此類推,直到第五個輸出分佈表示' <end of sentence>'符號,其中還有一個與10,000元素詞彙表相關聯的單元格。


我們將在一個樣本句子的訓練示例中訓練我們的模型的目標概率分佈。

 

在足夠大的數據集上訓練模型足夠的時間之後,我們希望產生的概率分佈看起來像這樣:


希望通過培訓,該模型將輸出我們期望的正確翻譯。當然,這個短語是否是訓練數據集的一部分並不是真正的指示(參見:交叉驗證)。請注意,即使不太可能是該時間步的輸出,每個位置都會獲得一點概率 - 這是softmax的一個非常有用的屬性,有助於訓練過程。

現在,因爲模型一次生成一個輸出,我們可以假設模型從該概率分佈中選擇具有最高概率的單詞並丟棄其餘的單詞。這是一種方法(稱爲貪婪解碼)。另一種方法是堅持,比如前兩個詞(例如,'我'和'a'),然後在下一步中,運行模型兩次:一旦假設第一個輸出位置是單詞'I',另一次假設第一個輸出位置是單詞'me',並且考慮到#1和#2位置保留的任何版本產生的錯誤都較少。我們重複這個位置#2和#3 ......等等。這種方法稱爲“波束搜索”,在我們的例子中,beam_size是兩個(因爲我們在計算位置#1和#2的波束後比較了結果),和top_beams也是兩個(因爲我們保留了兩個單詞)。這些都是您可以試驗的超參數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章