1. 程式人生 > 實用技巧 >【Transformer】圖解 Transformer

【Transformer】圖解 Transformer

原題:The Illustrated Transformer
原文:HTML
作者:Jay Alammar


文章目錄


前一篇文章中,我們研究了注意力——現代深度學習模型中一種普遍存在的方法。注意力是一個幫助提高神經機器翻譯應用程式效能的概念。在這篇文章中,我們將看看Transformer——一個利用注意力來提高這些模型訓練速度的模型。Transformer 在特定任務中的表現優於谷歌神經機器翻譯模型。然而,最大的好處來自於Transformer如何進行並行化。事實上,谷歌雲推薦使用Transformer作為參考模型來使用他們的Cloud TPU產品。所以讓我們試著把這個模型分解開來,看看它是如何工作的。

Transformer 是在論文 Attention is All You Need 中提出的。它的張量流實現是Tensor2Tensor 的一部分。哈佛的NLP小組用PyTorch實現建立了一個註釋論文的指南。在這篇文章中,我們將試圖把事情簡單,並逐一地介紹相關概念,希望能讓沒有深入瞭解相關知識的人更容易理解。


A High-Level Look

讓我們從將模型視為單個黑盒開始。在機器翻譯應用程式中,它會使用一種語言的句子,並輸出另一種語言的翻譯。

在這裡插入圖片描述
彈出Optimus Prime優勢,我們看到了編碼元件,解碼元件以及它們之間的連線。

在這裡插入圖片描述
編碼元件是一堆編碼器(論文中將6個編碼器堆疊在一起,6並非固定的,可以嘗試其它數量)。解碼元件是相同數量的解碼器的堆疊。

在這裡插入圖片描述
編碼器的結構都是相同的,但它們不共享權重。每一層都分為兩個子層:
在這裡插入圖片描述
編碼器的輸入首先流經一個自我關注層(self-attention layer)——該層幫助編碼器在編碼特定單詞時檢視輸入句子中的其他單詞。我們將在後面的文章中詳細介紹。

自關注層的輸出被輸入到前饋神經網路。完全相同的前饋網路獨立地應用於每個位置。

解碼器具有這兩個層,但是在它們之間有一個注意力層,幫助解碼器聚焦於輸入句子的相關部分(類似於seq2seq模型中的注意力)

在這裡插入圖片描述

Bringing The Tensors Into The Picture

現在我們已經看到了模型的主要元件,讓我們開始看看各種向量/張量,以及它們如何在這些元件之間流動,以將訓練好的模型的輸入轉化為輸出。

正如在一般的自然語言處理應用中的情況一樣,我們首先使用嵌入演算法(embedding algorithm)將每個輸入單詞轉換成一個向量。
在這裡插入圖片描述
Each word is embedded into a vector of size 512. We’ll represent those vectors with these simple boxes.

嵌入(embedding)只應用在最底層的編碼器中。所有編碼器共有的抽象(abstraction)是,它們接收每個大小為512的向量列表(list)——在底部編碼器中的輸入是詞嵌入(word embeddings),但在其他編碼器的輸入中,是前一個編碼器輸出。這個列表的大小是我們可以設定的超引數——基本上是訓練資料集中最長句子的長度。

在將單詞嵌入到我們的輸入序列中之後,每個單詞都流經編碼器的兩層。

在這裡,我們開始看到Transformer的一個關鍵屬性,即每個位置的單詞都流經編碼器中自己的路徑。自我注意層中的這些路徑之間存在依賴性。但是,前饋層不具有這些依賴性,因此可以在流過前饋層的同時並行執行各種路徑。

接下來,我們將示例切換到較短的句子,然後看一下編碼器每個子層中發生的情況。

Now We’re Encoding!

正如我們已經提到的,編碼器接收向量列表作為輸入。它通過將這些向量傳遞到自我注意層(self-attention layer),然後傳遞到前饋神經網路,然後將輸出到下一個編碼器來處理此列表。
在這裡插入圖片描述
The word at each position passes through a self-attention process. Then, they each pass through a feed-forward neural network – the exact same network with each vector flowing through it separately.

Self-Attention at a High Level

說下面的句子是我們要翻譯的輸入句子:

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

這句話中的 “it” 指的是什麼?是指街道還是動物?對人類來說,這是一個簡單的問題,但對演算法而言卻不那麼簡單。

當模型處理 “it” 一詞時,自我關注使它可以將 “it”“animal” 相關聯。

在模型處理每個單詞(輸入序列中的每個位置)時,自我關注使其能夠檢視輸入序列中的其他位置以尋找線索,從而有助於更好地對該單詞進行編碼。

如果你熟悉RNN,考慮一下如何通過保持隱藏狀態(hidden state)來使RNN將其已處理的先前單詞/向量的表示形式與當前正在處理的單詞/向量進行合併(incorporate)。自我注意是Transformer用來將其他相關單詞的 “understanding” 融入當前正在處理的單詞的方法。
在這裡插入圖片描述
As we are encoding the word “it” in encoder #5 (the top encoder in the stack), part of the attention mechanism was focusing on “The Animal”, and baked a part of its representation into the encoding of “it”.

你可以使用 Tensor2Tensor notebook,在其中載入Transformer模型,並使用此互動式視覺化檔案對其進行檢查。

Self-Attention in Detail

首先,讓我們看一下如何使用向量計算自我注意力,然後再著眼於如何實際實現-使用矩陣。

計算自我注意(self-attention)的第一步(first step)是從每個編碼器的輸入向量中建立三個向量(在這種情況下,是每個單詞的嵌入(embedding))。因此,對於每個單詞,我們建立一個查詢向量(Query vector)、一個關鍵字向量(Key vector)和一個值向量(Value vector)。這些向量是通過將嵌入乘以在訓練過程中訓練的三個矩陣而建立的。

請注意,這些新向量的維數小於嵌入向量的維數。它們的維數是64,而嵌入和編碼器輸入/輸出向量的維數是512。它們不必更小,這是一個架構選擇,以使多頭注意力(multiheaded attention)的計算大部分恆定。

在這裡插入圖片描述
X 1 X_1 X1 乘以 W Q W^Q WQ 權重矩陣產生 q 1 q_1 q1,即與該單詞相關聯的 “query” vector。我們最終建立了輸入句子中每個單詞的 “query”,“key”,“value” projection。

What are the “query”, “key”, and “value” vectors?

它們是對計算和思考注意力有用的抽象概念。一旦你開始閱讀下面的注意力是如何計算的,你就會知道這些向量中的每一個所扮演的角色。

計算自我關注(self-attention)的第二步是計算分數。假設我們在計算這個例子中第一個詞 “Thinking” 的自我關注度。我們需要對照這個單詞給輸入句子的每個單詞打分。分數決定了當我們在某個位置編碼一個單詞時,應該把多少注意力放在輸入句子的其他部分。

分數是通過將查詢向量(query vector)與我們正在評分的相應單詞的關鍵向量( key vector)的點積來計算的。因此,如果我們在位置#1處理單詞的自我注意,第一個分數將是q1和k1的點積。第二個分數是q1和k2的點積。

在這裡插入圖片描述
第三步和第四步是將分數除以8(論文中使用的關鍵向量(key vectors)維數的平方根——64)。這導致更穩定的梯度。這裡可能有其他可能的值,但這是預設值,然後通過softmax操作傳遞結果。Softmax將分數歸一化,使它們都為正,加起來為1。

在這裡插入圖片描述
這個softmax分數決定了每個單詞在這個位置上的表達量(expressed)。顯然,這個位置的單詞將具有最高的softmax分數,但有時關注與當前單詞相關的另一個單詞是有用的。

第五步是將每個值向量(value vector)乘以softmax分數,為求和做準備。這裡的直覺是保持我們想要關注的單詞(words)的值不變,並弱化(drown-out)不相關的單詞(例如,將它們乘以像0.001這樣的小數字)。

第六步是對加權值向量求和。這將在這個位置產生自我關注層(self-attention layer)的輸出(對於第一個單詞)。

在這裡插入圖片描述
自我注意計算到此結束。得到的向量是我們可以傳送到前饋神經網路的向量。然而,在實際實現中,這種計算是以矩陣形式進行的,以便更快地處理。現在讓我們來看一下,我們已經在單詞層面上看到了計算的直覺。

Matrix Calculation of Self-Attention

第一步是計算 Query, Key, Value 矩陣。為此,我們將嵌入內容打包到矩陣 X X X 中,然後將其乘以訓練過的權重矩陣 ( W Q , W K , W V ) (W^Q,W^K,W^V) (WQWKWV)

在這裡插入圖片描述
Every row in the X matrix corresponds to a word in the input sentence. We again see the difference in size of the embedding vector (512, or 4 boxes in the figure), and the q/k/v vectors (64, or 3 boxes in the figure)

最後,由於我們要處理矩陣,因此我們可以將步驟2至6壓縮為一個公式,以計算自我注意層的輸出。

在這裡插入圖片描述
The self-attention calculation in matrix form

The Beast With Many Heads

論文中通過新增一種稱為“multi-headed”注意力的機制,進一步完善了自我注意力層。這樣可以通過兩種方式提高關注層(attention layer)的效能:

它擴充套件了模型專注於不同位置的能力。是的,在上面的示例中, z 1 z_1 z1 包含所有其他編碼的一點點,但是它可能由實際單詞本身決定。如果我們要翻譯這樣的句子 “The animal didn’t cross the street because it was too tired”,那麼我們會想知道 “it” 指的是什麼。

在這裡插入圖片描述
With multi-headed attention, we maintain separate Q/K/V weight matrices for each head resulting in different Q/K/V matrices. As we did before, we multiply X by the WQ/WK/WV matrices to produce Q/K/V matrices.

如果執行上述的相同的自注意力計算,則僅在8個不同的時間使用不同的權重矩陣,最終將得到8個不同的 Z Z Z 矩陣。

在這裡插入圖片描述
這給我們帶來了一些挑戰。前饋層不希望有8個矩陣,而是需要單個矩陣(每個單詞一個向量)。因此,我們需要一種將這8個壓縮為單個矩陣的方法。

該怎麼做?合併(concat)矩陣,然後將它們乘以權重矩陣 W O W^O WO

這就是多頭自我注意力(multi-headed self-attention)。這包括很多矩陣,將它們全部放在一個圖中,以便對比檢視它們。

在這裡插入圖片描述
現在,我們已經涉及到 attention heads,讓我們從前面重新回顧一下示例,看看在示例句中對“it”一詞進行編碼時,不同的 attention heads 所關注的位置:

在這裡插入圖片描述
As we encode the word “it”, one attention head is focusing most on “the animal”, while another is focusing on “tired” – in a sense, the model’s representation of the word “it” bakes in some of the representation of both “animal” and “tired”.

但是,如果將所有 attention head 新增到圖片中,則可能難以解釋:
在這裡插入圖片描述

Representing The Order of The Sequence Using Positional Encoding

到目前為止,我們所描述的模型中缺少的一件事是一種解決輸入序列中單詞順序的方法。

為了解決這個問題,transformer 為每個輸入嵌入(input embedding)新增一個向量。這些向量遵循模型學習的特定模式,這有助於它確定每個單詞的位置,或者序列中不同單詞之間的距離。這裡的直覺是,一旦嵌入向量(embedding vectors)被投影到Q/K/V向量中,並且在點積注意力期間,將這些值新增到嵌入中可以在嵌入向量之間提供有意義的距離。

在這裡插入圖片描述
To give the model a sense of the order of the words, we add positional encoding vectors – the values of which follow a specific pattern.

如果我們假設嵌入的維數為4,則實際的位置編碼應如下所示:
在這裡插入圖片描述
A real example of positional encoding with a toy embedding size of 4

這種模式是什麼樣的?

在下圖中,每行對應一個向量的位置編碼(positional encoding)。所以第一行將是我們新增到輸入序列中第一個單詞嵌入的向量。每行包含512個值,每個值介於1和-1之間。我們對它們進行了顏色編碼,所以圖案是可見的。

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

本文(第3.5節)描述了位置編碼的公式。你可以在get_timing_signal_1d()中看到生成位置編碼的程式碼。這不是唯一可能的位置編碼方法。然而,它的優點是能夠縮放到看不見的序列長度(例如,如果訓練的模型被要求翻譯比我們訓練集中的任何一個句子都長的句子)。

July 2020 Update:上面顯示的位置編碼來自 Transformer 的 Tranformer2Transformer 實現。論文中顯示的方法略有不同,它沒有直接連線(concatenate),而是將兩個訊號交織( interweaves)在一起,下圖顯示了這種情況。 這裡是生成它的程式碼。

在這裡插入圖片描述

The Residuals

需要注意編碼器架構的一個細節,每個編碼器中的每個子層(self-attention, ffnn)周圍都有一個殘差連線(residual connection),後面是 layer-normalization 步驟。

在這裡插入圖片描述
如果我們將向量和與自我關注相關的 layer-norm 操作視覺化,它看起來像這樣:
在這裡插入圖片描述
解碼器的子層也是如此。如果我們考慮一個由兩個堆疊的編碼器和解碼器組成的 Transformer,它看起來像這樣:

在這裡插入圖片描述

The Decoder Side

既然我們已經涵蓋了編碼器方面的大多數概念,我們基本上也知道解碼器的元件是如何工作的。讓我們來看看他們是如何合作的。

編碼器從處理輸入序列開始。頂部編碼器的輸出隨後被轉換成一組關注向量(attention vectors) K K K V V V。這些被每個解碼器在其“編碼器-解碼器關注”層中使用,這有助於解碼器關注輸入序列中的適當位置:
在這裡插入圖片描述
完成編碼階段後,開始解碼階段。解碼階段的每一步都從輸出序列中輸出一個元素(在這種情況下是英語翻譯句子)。

以下步驟重複該過程,直到達到一個特殊符號,表明變壓器解碼器已完成其輸出。每個步驟的輸出在下一個時間步驟中被饋送到底部解碼器,解碼器像編碼器一樣彈出它們的解碼結果。就像我們對編碼器輸入所做的那樣,我們將位置編碼嵌入並新增到解碼器輸入中,以指示每個單詞的位置。

在這裡插入圖片描述
解碼器中的自我關注層(self attention layers)的工作方式與編碼器中的略有不同:

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

Encoder-Decoder Attention Layer 就像多頭自我注意(multiheaded self-attention)一樣工作,只是它從它下面的層建立它的查詢矩陣(Queries matrix),並從編碼器堆疊的輸出中獲取鍵矩陣( Keys matrix)和值矩陣(Values matrix)。

The Final Linear and Softmax Layer

解碼器堆疊輸出一個浮點向量,如何把它變成一個單詞?這是最後的線性層和Softmax層的工作。

線性層(Linear layer)是一個簡單的全連線神經網路,它將解碼器堆疊產生的向量投影到一個更大的向量中,稱為對數向量(logits vector)。

假設我們的模型知道從其訓練資料集中學習的10,000個不同的英語單詞(我們模型的“output vocabulary”)。這將使logits向量的寬度變為10,000個單元——每個單元對應一個單詞得分。這就是解釋模型的輸出以及線性層的方式。

然後,softmax層將那些分數轉換為概率(全部為正,全部相加為1.0)。選擇具有最高概率的單元,並且與此時間相關的單詞將作為該時間步的輸出。

在這裡插入圖片描述
This figure starts from the bottom with the vector produced as the output of the decoder stack. It is then turned into an output word.

Recap Of Training

現在,我們已經通過訓練有素的Transformer涵蓋了整個前向傳播過程,這對於瞭解訓練模型的直覺很有用。

在訓練過程中,未經訓練的模型將經過完全相同的前向傳播路徑。但是由於是在有標記的訓練資料集上進行訓練,因此可以將其輸出與實際的輸出進行比較。

為了直觀地說明這一點,我們假設輸出詞彙表僅包含六個單詞:“a”,“am”,“i”,“thanks”,“student”,“<eos>”(“end of sentence”的縮寫) 。

在這裡插入圖片描述
The output vocabulary of our model is created in the preprocessing phase before we even begin training.

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

在這裡插入圖片描述
回顧之後,讓我們討論模型的損失函式——訓練階段優化的指標,可以得出經過訓練的,希望是驚人的準確模型。

The Loss Function

假設正在訓練模型。這是訓練階段的第一步,我們將以一個簡單的示例對其進行訓練——將“merci”轉換為“thanks”。

這意味著我們希望輸出是一個表示單詞“thanks”的概率分佈。但是,由於尚未對該模型進行訓練,因此目前不太可能發生。
在這裡插入圖片描述
由於模型的引數(權重)都是隨機初始化的,因此(未經訓練的)模型會針對每個單元格/單詞生成具有任意值的概率分佈。我們可以將其與實際輸出進行比較,然後使用反向傳播調整所有模型的權重,以使輸出更接近所需的輸出。

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

但是請注意,這是一個過於簡化的示例。實際上,我們將使用一個單詞多於一個單詞的句子。例如,輸入:“je suis étudiant”,預期輸出:“i am a student”。這實際上意味著我們希望我們的模型連續輸出概率分佈,其中:

  • 每個概率分佈都由一個寬度 vocab_size 的向量表示(在我們的示例中為6,但是實際上可能為30,000或50,000);
  • 第一概率分佈在與單詞“i”相關聯的單元中具有最高概率;
  • 第二概率分佈在與單詞“am”相關聯的單元格處具有最高概率;
  • 依此類推,直到第五個輸出分佈指示“<end of sentence>”符號,該符號還具有10,000個元素詞彙表中與之相關的單元格。

在這裡插入圖片描述
我們將在訓練示例中針對一個樣本句子針對目標概率分佈進行訓練。

在足夠大的資料集上訓練模型足夠的時間後,我們希望產生的概率分佈如下所示:
在這裡插入圖片描述
希望經過訓練,該模型將輸出我們期望的正確翻譯。當然,這並不是該短語是否屬於訓練資料集的真正跡象(請參閱:交叉驗證)。請注意,即使不太可能是該時間步長的輸出,每個位置也會有概率得分——這是softmax的一個非常有用的屬性,可以幫助訓練過程。

現在,由於該模型一次生成一個輸出,因此我們可以假設該模型正在從該概率分佈中選擇具有最高概率的單詞,然後丟棄其餘單詞。這是做到這一點的一種方法(稱為貪婪解碼(greedy decoding))。做到這一點的另一種方法是,堅持前兩個單詞(例如,“I” 和 “a”),然後在下一步中執行模型兩次:一次假設第一個輸出位置為單詞 “I”,另一個時間假設第一個輸出位置是單詞 “a”,考慮到位置#1和#2,保留產生的錯誤更少的情況。對位置#2和#3也重複這一過程。此方法稱為波束搜尋(beam search),在我們的示例中,beam_size=2(意味著始終保留兩部分假設(未完成的翻譯)),並且 top_beams=2(意味著將返回兩個翻譯)。這些都是超引數。


Go Forth And Transform

我希望你已經找到了一個有用的地方來開始打破與Transformer的主要概念的僵局。如果你想深入瞭解,我建議你採取以下步驟:

Follow-up works【Paper】: