This post is all you need(基於Transformer的翻譯模型)

由於公眾號改版不再按照作者的釋出時間進行推送,為防止各位朋友錯過月來客棧推送的最新文章,大家可以手動將公眾號設定為“

星標

⭐”以第一時間獲得推送內容,感謝各位~

1 引言

各位朋友大家好,歡迎來到月來客棧。經過前面幾篇文章的介紹,相信各位讀者對於Transformer的基本原理以及實現過程已經有了一個較為清晰的認識。不過想要對一個網路模型有更加深刻的認識,那麼最好的辦法便是從資料預處理到模型訓練,自己完完全全的經歷一遍。因此,為了使得大家能夠更加透徹的理解Transformer的整個工作流程,在本篇文章中筆者將繼續帶著大家一起來還原論文中的文字翻譯模型。

This post is all you need(基於Transformer的翻譯模型)

圖 1。 Transformer網路結構圖

如圖1所示便是Transformer網路的整體結構圖,對於這部分內容在上一篇文章中總體上算是介紹完了,只是在資料預處理方面還未涉及。下面,筆者就以Multi30K[1]中的English-German平行語料為例進行介紹(注意這並不是論文中所用到資料集)。

2 資料預處理

2。1 語料介紹

在這裡,我們使用到的平行語料一共包含有6個檔案、、、、和,其分別為德語訓練語料、英語訓練語料、德語驗證語料、英語驗證語料、德語測試語料和英語測試語料。同時,這三部分的樣本量分別為29000、1014和1000條。

如下所示便是一條平行預料資料,其中第1行為德語,第2行為英語,後續我們需要完成的就是搭建一個翻譯模型將德語翻譯為英語。

2。2 資料集預覽

在正式介紹如何構建資料集之前,我們先通過幾張圖來了解一下整個構建的流程,以便做到心中有數,不會迷路。

This post is all you need(基於Transformer的翻譯模型)

圖 2。 翻譯模型資料集處理過程圖(一)

如圖2所示,左邊部分為原始輸入,右邊部分為目標輸入。從圖2可以看出,第1步需要完成的就是對原始語料進行tokenize操作。如果是對類似英文這樣的語料進行處理,那就是直接按空格切分即可。但是需要注意的是要把其中的逗號和句號也給分割出來。第2步需要做的就是根據tokenize後的結果對原始輸入和目標輸入分別建立一個字典。第3步需要做的則是將tokenize後結果根據字典中的索引將其轉換成token序列。第4步則是對同一個batch中的序列以最長的為標準其它樣本進行padding,並且同時需要在目標輸入序列的前後加上起止符(即‘< bos >’和‘ < eos >’) 。

This post is all you need(基於Transformer的翻譯模型)

圖 3。 翻譯模型資料集處理過程圖(二)

如圖3所示,在完成前面4個步驟後,對於目標序列來說第5步需要做的就是將第4步處理後的結果劃分成和。從圖3右側可以看出,和是相互對應起來的。例如對於第1個樣本來說,解碼第1個時刻的輸入應該是“2”,而此時刻對應的正確標籤就應該是中的‘8’;解碼第2個時刻的輸入應該是中的‘8’,而此時刻對應的正確標籤就應該是中的‘45’,以此類推下去。最後,第6步則是根據和各自的padding情況,得到一個padding mask向量(注意由於這裡中的兩個樣本長度一樣,所以並不需要padding),其中‘

T

’表示padding的位置。當然,這裡的並沒有畫出。

同時,圖3中各個部分的結果體現在Transformer網路中的情況如圖4所示。

This post is all you need(基於Transformer的翻譯模型)

圖 4。 基於Transformer翻譯模型的模型輸入情況

以上就是基於Transformer架構的翻譯模型資料預處理的整個大致流程,下面我們開始正式來透過編碼實現這一過程。

2。3 資料集構建

第1步:定義tokenize

如果是對類似英文這樣的語料進行處理,那就是直接按空格切分即可。但是需要注意的是要把其中的逗號和句號也給分割出來。因此,這部分程式碼可以根據如下方式進行實現:

可以看到,其實也非常簡單。例如對於如下文字來說

其tokenize後的結果為:

第2步:建立詞表

在介紹完tokenize的實現方法後,我們就可以正式透過中的方法來構建詞典了,程式碼如下:

在上述程式碼中,第3行程式碼用來指定特殊的字元;第5-7行程式碼用來遍歷檔案中的每一個樣本(每行一個)並進行tokenize和計數,其中對於進行介紹可以參考[2];第8行則是返回最後得到詞典。

在完成上述過程後,我們將得到兩個類的例項化物件。

一個為原始序列的字典:

一個為目標序列的字典:

此時,我們就需要定義一個類,並在類的初始化過程中根據訓練語料完成字典的構建,程式碼如下:

第3步:轉換為Token序列

在得到構建的字典後,便可以透過如下函式來將訓練集、驗證集和測試集轉換成Token序列:

在上述程式碼中,第11-4行分別用來將原始序列和目標序列轉換為對應詞表中的Token形式。在處理完成後,就會得到類似如下的結果:

其中左邊的一列就是原始序列的Token形式,右邊一列就是目標序列的Token形式,每一行構成一個樣本。

第4步:padding處理

從上面的輸出結果(以及圖2中第③步後的結果)可以看到,無論是對於原始序列來說還是目標序列來說,在不同的樣本中其對應長度都不盡相同。但是在將資料輸入到相應模型時卻需要保持同樣的長度,因此在這裡我們就需要對Token序列化後的樣本進行padding處理。同時需要注意的是,一般在這種生成模型中,

模型在訓練過程中只需要保證同一個batch中所有的原始序列等長,所有的目標序列等長即可

,也就是說不需要在整個資料集中所有樣本都保證等長。

因此,在實際處理過程中無論是原始序列還是目標序列都會以每個batch中最長的樣本為標準對其它樣本進行padding,具體程式碼如下:

在上述程式碼中,第6-7行用來在目標序列的首尾加上特定的起止符;第9-10行則是分別對一個batch中的原始序列和目標序列以各自當中最長的樣本為標準進行padding(這裡的匯入自)。

第5步:構造mask向量

在處理完成圖2中的第④步後,對於圖3中的第⑤步來說就是簡單的切片操作,因此就不作介紹。進一步需要根據和來構造相關的mask向量,具體程式碼如下:

在上述程式碼中,第1-4行是用來生成一個形狀為的注意力掩碼矩陣,用於在解碼過程中掩蓋當前position之後的position;第6-17行用來返回Transformer中各種情況下的mask矩陣,其中在這裡並沒有作用。

第6步:構造與使用示例

經過前面5步的操作,整個資料集的構建就算是已經基本完成了,只需要再構造一個迭代器即可,程式碼如下:

在上述程式碼中,第2-4行便是分別用來將訓練集、驗證集和測試集轉換為Token序列;第5-10行則是分別構造3個,其中將作為一個引數傳入來對每個batch的樣本進行處理。在完成類所有的編碼過程後,便可以透過如下形式進行使用:

各位讀者在閱讀這部分程式碼時最好是能夠結合圖2-4進行理解,這樣效果可能會更好。在介紹完資料集構建的整個過程後,下面就開始正式進入到翻譯模型的構建中。

3 基於Transformer的翻譯模型

3。1 網路結構

總體來說,基於Transformer的翻譯模型的網路結構其實就是圖4所展示的所有部分,只是在前面介紹Transformer網路結構時筆者並沒有把Embedding部分的實現給加進去。這是因為對於不同的文字生成模型,其Embedding部分會不一樣(例如在詩歌生成這一情景中編碼器和解碼器共用一個即可,而在翻譯模型中就需要兩個),所以將兩者進行了拆分。同時,待模型訓練完成後,在inference過程中

Encoder只需要執行一次

,所以在此過程中也需要單獨使用Transformer中的Encoder和Decoder。

首先,我們需要定義一個名為的類,其前向傳播過程程式碼如下所示:

在上述程式碼中,第7-12行便是用來定義一個Transformer結構;第13-16分別用來定義Positional Embedding、Token Embedding和最後的分類器;第29-39行便是用來執行整個前向傳播過程,其中Transformer的整個前向傳播過程在

前一篇文章

中已經介紹過,在這裡就不再贅述。

在定義完logits的前向傳播過後,便可以透過如下形式進行使用:

接著,我們需要再定義一個和在inference中使用,程式碼如下:

在上述程式碼中,第1-5行用於在inference時對輸入序列進行編碼並得到memory(只需要執行一次);第7-11行用於根據memory和當前解碼時刻的輸入對輸出進行預測,需要迴圈執行多次,這部分內容詳見模型預測部分。

3。2 模型訓練

在定義完成整個翻譯模型的網路結構後下面就可以開始訓練模型了。由於這部分程式碼較長,所以下面筆者依舊以分塊的形式進行介紹:

第1步:載入資料集

首先我們可以根據前面的介紹,透過類來載入資料集,其中中定義了模型所涉及到的所有配置引數。

第2步:定義模型並初始化權重

在載入資料後,便可以定義一個翻譯模型,並根據相關引數對其進行例項化;同時,可以對整個模型中的所有引數進行一個初始化操作。

第3步:定義損失學習率與最佳化器

在上述程式碼中,第1行是定義交叉熵損失函式,並同時指定需要忽略的索引。因為根據圖3的可知,有些位置上的標籤值其實是Padding後的結果,因此在計算損失的時候需要將這些位置給忽略掉。第2行程式碼則是論文中所提出來的動態學習率計算過程,其計算公式為:

具體實現程式碼為:

透過,就能夠在訓練過程中動態的調整學習率。學習率隨step增加而變換的結果如圖5所示:

This post is all you need(基於Transformer的翻譯模型)

圖 5。 動態學習率變化過程圖

從圖5可以看出,在前個step中,學習率是線性增長的,在這之後便是非線性下降,直至收斂於0。0004。

第4步:開始訓練

在上述程式碼中,第5-9行是用來得到模型各個部分的輸入;第10-18行是計算模型整個前向傳播的過程;第21-25行則是執行損失計算與反向傳播;第27-29則是將每個step更新後的學習率送入到模型中並更新引數;第31行是用來計算模型預測的準確率,具體過程將在後續文章中進行介紹。以下便是模型訓練過程中的輸出:

3。3 模型預測

在介紹完模型的訓練過程後接下來就來看模型的預測部分。生成模型的預測部分不像普通的分類任務只需要將網路最後的輸出做操作即可,生成模型在預測過程中往往需要按時刻一步步進行來進行。因此,下面我們這裡定義一個函式來執行這一過程,具體程式碼如下:

在上述程式碼中,第6行是將待翻譯的源序列進行序列化操作;第8-11行則是透過函式函式來對輸入進行解碼;第12行則是將最後解碼後的結果由Token序列在轉換成實際的目標語言。同時,函式的實現如下:

在上述程式碼中,第3行是將源序列輸入到Transformer的編碼器中進行編碼並得到Memory;第4-5行是初始化解碼階段輸入的第1個時刻的,在這裡也就是‘’;第6-18行則是整個迴圈解碼過程,在下一個時刻為或者達到最大長度後停止;第8-9行是根據當前解碼器輸入的長度生成注意力掩碼矩陣;第10行是根據以及當前時刻的輸入對當前時刻的輸出進行解碼;第12-14行則是分類得到當前時刻的解碼輸出結果;第15行則是將當前時刻的解碼輸出結果同當前時刻之前所有的輸入進行拼接,以此再對下一個時刻的輸出進行預測。

最後,我們只需要呼叫如下函式便可以完成對原始輸入語言的翻譯任務:

在上述程式碼中,第5-14行是定義網路結構,以及恢復本地儲存的網路權重;第15行則是開始執行翻譯任務;第19-28行為翻譯示例,其輸出結果為:

其中第一句德語為訓練集之外的資料。

以上完整程式碼可參見[3]。

4 總結

在這篇文章中,筆者首先介紹了翻譯模型的整個資料預處理過程,包括首先以圖示的方式對整個過程進行了說明,然後再一步步地透過編碼實現了整個資料集的構造過程;接著筆者介紹了基於Transformer結構的翻譯模型的整體構成,然後循序漸進地帶著各位讀者來實現了整個翻譯模型,包括基礎結構的搭建、模型訓練的詳細實現、動態學習率的調整實現等;最後介紹瞭如何來實現模型在實際預測過程中的處理流程等,包括源輸入序列的構建、解碼時刻輸入序列的構建等。在下一篇文章中,筆者將會介紹如何基於Transformer結構來搭建一個簡單的文字分類模型。

引用

[1] https://github。com/multi30k/dataset

[3] https://github。com/moon-hotel/TransformerTranslation