“變形金剛”為何強大:從模型到程式碼全面解析Google Tensor2Tensor系統

騰訊雲加社群發表於2018-07-09

歡迎大家前往騰訊雲+社群,獲取更多騰訊海量技術實踐乾貨哦~

本文由張金超博士發表於雲+社群專欄

導語: Google Tensor2Tensor系統是一套十分強大的深度學習系統,在多個任務上的表現非常搶眼。尤其在機器翻譯問題上,單模型的表現就可以超過之前方法的整合模型。這一套系統的模型結構、訓練和優化技巧等,可以被利用到公司的產品線上,直接轉化成生產力。本文對Tensor2Tensor系統從模型到程式碼進行了全面的解析,期望能夠給大家提供有用的資訊。

第一章:概述

​ Tensor2Tensor(T2T)是Google Brain Team在Github上開源出來的一套基於TensorFlow的深度學習系統。該系統最初是希望完全使用Attention方法來建模序列到序列(Sequence-to-Sequence,Seq2Seq)的問題,對應於《Attention Is All You Need》這篇論文。該項工作有一個有意思的名字叫“Transformer”。隨著系統的不斷擴充套件,T2T支援的功能變得越來越多,目前可以建模的問題包括:影象分類,語言模型、情感分析、語音識別、文字摘要,機器翻譯。T2T在很多工上的表現很好,並且模型收斂比較快,在TF平臺上的工程化程式碼實現的也非常好,是一個十分值得使用和學習的系統。

​ 如果是從工程應用的角度出發,想快速的上手使用T2T系統,只需要對模型有一些初步的瞭解,閱讀一下workthrough文件,很快就能做模型訓練和資料解碼了。這就是該系統想要達到的目的,即降低深度學習模型的使用門檻。系統對資料處理、模型、超參、計算裝置都進行了較高的封裝,在使用的時候只需要給到資料路徑、指定要使用的模型和超參、說明計算裝置就可以將系統執行起來了。

​ 如果想深入瞭解系統的實現細節,在該系統上做二次開發或是實現一些研究性的想法,那就需要花費一定的時間和精力來對模型和程式碼進行研究。T2T是一個較複雜的系統,筆者近期對模型和程式碼實現進行了全面的學習,同時對涉及到序列到序列功能的程式碼進行了剝離和重構,投入了較多的時間成本。因筆者是做自然語言處理研究的,這篇文章裡主要關注的是Transformer模型。寫這篇文章一方面是總結和記錄一下這個過程中的一些收穫,另一方面是把自己對T2T的理解分享出來,希望能夠提供一些有用的資訊給同學們。

第二章:序列到序列任務與Transformer模型

2.1 序列到序列任務與Encoder-Decoder框架

​ 序列到序列(Sequence-to-Sequence)是自然語言處理中的一個常見任務,主要用來做泛文字生成的任務,像機器翻譯、文字摘要、歌詞/故事生成、對話機器人等。最具有代表性的一個任務就是機器翻譯(Machine Translation),將一種語言的序列對映到另一個語言的序列。例如,在漢-英機器翻譯任務中,模型要將一個漢語句子(詞序列)轉化成一個英語句子(詞序列)。

​ 目前Encoder-Decoder框架是解決序列到序列問題的一個主流模型。模型使用Encoder對source sequence進行壓縮表示,使用Decoder基於源端的壓縮表示生成target sequence。該結構的好處是可以實現兩個sequence之間end-to-end方式的建模,模型中所有的引數變數統一到一個目標函式下進行訓練,模型表現較好。圖1展示了Encoder-Decoder模型的結構,從底向上是一個機器翻譯的過程。

img
圖1: 使用Encoder-Decoder模型建模序列到序列的問題

​ Encoder和Decoder可以選用不同結構的Neural Network,比如RNN、CNN。RNN的工作方式是對序列根據時間步,依次進行壓縮表示。使用RNN的時候,一般會使用雙向的RNN結構。具體方式是使用一個RNN對序列中的元素進行從左往右的壓縮表示,另一個RNN對序列進行從右向左的壓縮表示。兩種表示被聯合起來使用,作為最終序列的分散式表示。使用CNN結構的時候,一般使用多層的結構,來實現序列區域性表示到全域性表示的過程。使用RNN建模句子可以看做是一種時間序列的觀點,使用CNN建模句子可以看做一種結構化的觀點。使用RNN結構的序列到序列模型主要包括RNNSearch、GNMT等,使用CNN結構的序列到序列模型主要有ConvS2S等。

2.2 神經網路模型與語言距離依賴現象

​ Transformer是一種建模序列的新方法,序列到序列的模型依然是沿用了上述經典的Encoder-Decoder結構,不同的是不再使用RNN或是CNN作為序列建模機制了,而是使用了self-attention機制。這種機制理論上的優勢就是更容易捕獲“長距離依賴資訊(long distance dependency)”。所謂的“長距離依賴資訊”可以這麼來理解:1)一個詞其實是一個可以表達多樣性語義資訊的符號(歧義問題)。2)一個詞的語義確定,要依賴其所在的上下文環境。(根據上下文消岐)3)有的詞可能需要一個範圍較小的上下文環境就能確定其語義(短距離依賴現象),有的詞可能需要一個範圍較大的上下文環境才能確定其語義(長距離依賴現象)。

​ 舉個例子,看下面兩句話:“山上有很多杜鵑,春天到了的時候,會漫山遍野的開放,非常美麗。” “山上有很多杜鵑,春天到了的時候,會漫山遍野的啼鳴,非常婉轉。”在這兩句話中,“杜鵑”分別指花(azalea)和鳥(cuckoo)。在機器翻譯問題中,如果不看距其比較遠的距離的詞,很難將“杜鵑”這個詞翻譯正確。該例子是比較明顯的一個例子,可以明顯的看到詞之間的遠距離依賴關係。當然,絕大多數的詞義在一個較小範圍的上下文語義環境中就可以確定,像上述的例子在語言中佔的比例會相對較小。我們期望的是模型既能夠很好的學習到短距離的依賴知識,也能夠學習到長距離依賴的知識。

​ 那麼,為什麼Transformer中的self-attention理論上能夠更好的捕獲這種長短距離的依賴知識呢?我們直觀的來看一下,基於RNN、CNN、self-attention的三種序列建模方法,任意兩個詞之間的互動距離上的區別。圖2是一個使用雙向RNN來對序列進行建模的方法。由於是對序列中的元素按順序處理的,兩個詞之間的互動距離可以認為是他們之間的相對距離。W1和Wn之間的互動距離是n-1。帶有門控(Gate)機制的RNN模型理論上可以對歷史資訊進行有選擇的儲存和遺忘,具有比純RNN結構更好的表現,但是門控引數量一定的情況下,這種能力是一定的。隨著句子的增長,相對距離的增大,存在明顯的理論上限。

img
圖2 使用雙向RNN對序列進行建模

​ 圖3展示了使用多層CNN對序列進行建模的方法。第一層的CNN單元覆蓋的語義環境範圍較小,第二層覆蓋的語義環境範圍會變大,依次類推,越深層的CNN單元,覆蓋的語義環境會越大。一個詞首先會在底層CNN單元上與其近距離的詞產生互動,然後在稍高層次的CNN單元上與其更遠一些詞產生互動。所以,多層的CNN結構體現的是一種從區域性到全域性的特徵抽取過程。詞之間的互動距離,與他們的相對距離成正比。距離較遠的詞只能在較高的CNN節點上相遇,才產生互動。這個過程可能會存在較多的資訊丟失。

img
圖3 使用多層CNN對序列進行建模

​ 圖4展示的是基於self-attention機制的序列建模方法。注意,為了使圖展示的更清晰,少畫了一些連線線,圖中“sentence”層中的每個詞和第一層self-attention layer中的節點都是全連線的關係,第一層self-attention layer和第二層self-attention layer之間的節點也都是全連線的關係。我們可以看到在這種建模方法中,任意兩個詞之間的互動距離都是1,與詞之間的相對距離不存在關係。這種方式下,每個詞的語義的確定,都考慮了與整個句子中所有的詞的關係。多層的self-attention機制,使得這種全域性互動變的更加複雜,能夠捕獲到更多的資訊。

img
圖4 使用self-attention對序列進行建模

​ 綜上,self-attention機制在建模序列問題時,能夠捕獲長距離依賴知識,具有更好的理論基礎。

2.3 self-attention機制的形式化表達

​ 上面小節介紹了self-attention機制的好處,本小結來介紹一下self-attention機制的的數學形式化表達。首先,從attention機制講起。可以將attention機制看做一種query機制,即用一個query來檢索一個memory區域。我們將query表示為key_q,memory是一個鍵值對集合(a set of key-value pairs),共有M項,其中的第i項我們表示為<key_m[i], value_m[i]>。通過計算query和key_m[i]的相關度,來決定查詢結果中,value_m[i]所佔的權重比例。注意,這裡的key_q,key_m,value_m都是vector。

​ Attention的計算概括起來分三步:1)計算query和memory中每個key_m的相關度。2)對所有的相關度結果使用softmax函式進行概率歸一化處理。3)根據概率歸一化結果對memory中的所有value_m進行加權平均,得到最終的查詢結果。計算過程,形式化為:

img

​ 常用的相關度計算函式有基於加法式(additive)的和乘法式(dot-product)的兩種。加法式的函式,先要經過一個前向神經網路單元,再經過一個線性變換,得到一個實數值。乘法式的函式則是兩個向量的直接點乘,得到一個實數值。

​ 在Encoder-Decoder框架中,attention機制一般用於連線Encoder和Decoder,即以Decoder的狀態作為key,以源語言句子的分散式表示作為memory,從中查詢出相關的源語言資訊,生成目標語言的詞語。在該機制中,memory中的key_m和value_m是相同的。在self-attention機制中,每個詞彙以自己的embedding為query,查詢由所有詞彙的embedding構成的memory空間,得到查詢結果作為本詞的表示。假如句子長度為n,所有的詞分別查詢一遍memory得到的結果長度依然會是n。這些詞的查詢過程是可以並行的。如果relation函式是乘法式的,那麼這個查詢的過程就是矩陣的乘法,可以形式化為:

img

在self-attention中,Q=K=V,是一個由所有詞的詞向量構成的一個矩陣。

綜上,self-attention是一種序列建模的方式,在對句子進行分散式表示的時候,句子中的所有的詞都會發生直接的互動關係。

2.4 “Attention is All You Need”

​ 《Attention Is All You Need》這篇文章,描述了一個基於self-attention的序列到序列的模型,即“Transformer”。該模型將WMT2014英-德翻譯任務的BLEU值推到了新高,在英-法翻譯任務上,接近於之前報出的最好成績,而這僅僅是Transformer單模型的表現。之前報出的最好成績都是基於整合方法的,需要訓練多個模型,最後做整合。同時該模型也被用在英語的成分句法分析任務上,表現也基本接近於之前報出的最好模型成績。該模型的收斂速度也非常的快,在英-法3600萬句對的訓練集上,只需要8卡並行3.5天就可以收斂。

​ 該模型的表現的如此好的原因,其實不僅僅是一個self-attention機制導致的,實際上Transformer模型中使用了非常多有效的策略來使得模型對資料的擬合能力更強,收斂速度更快。整個Transformer的模型是一套解決方案,而不僅僅是對序列建模機制的改進。下面我們對其進行一一講解。

2.4.1 Self-attention機制的變種

​ 首先,還是來講一下Transformer中的self-attention機制。上面講到了self-attention的基本形式,但是Transformer裡面的self-attention機制是一種新的變種,體現在兩點,一方面是加了一個縮放因子(scaling factor),另一方面是引入了多頭機制(multi-head attention)。

​ 縮放因子體現在Attention的計算公式中多了一個向量的維度作為分母,目的是想避免維度過大導致的點乘結果過大,進入softmax函式的飽和域,引起梯度過小。Transformer中的self-attention計算公式如下:

img

多頭機制是指,引入多組的引數矩陣來分別對Q、K、V進行線性變換求self-attention的結果,然後將所有的結果拼接起來作為最後的self-attention輸出。這樣描述可能不太好理解,一看公式和示意圖就會明白了,如下:

img

img
圖5 單頭和多頭的Attention結構

​ 這種方式使得模型具有多套比較獨立的attention引數,理論上可以增強模型的能力。

2.4.2 位置編碼(Positional Encoding)

​ self-attention機制建模序列的方式,既不是RNN的時序觀點,也不是CNN的結構化觀點,而是一種詞袋(bag of words)的觀點。進一步闡述的話,應該說該機制視一個序列為扁平的結構,因為不論看上去距離多遠的詞,在self-attention機制中都為1。這樣的建模方式,實際上會丟失詞之間的相對距離關係。舉個例子就是,“牛 吃了 草”、“草 吃了 牛”,“吃了 牛 草”三個句子建模出來的每個詞對應的表示,會是一致的。

​ 為了緩解這個問題,Transformer中將詞在句子中所處的位置對映成vector,補充到其embedding中去。該思路並不是第一次被提出,CNN模型其實也存在同樣的難以建模相對位置(時序資訊)的缺陷,Facebook提出了位置編碼的方法。一種直接的方式是,直接對絕對位置資訊建模到embedding裡面,即將詞Wi的i對映成一個向量,加到其embedding中去。這種方式的缺點是隻能建模有限長度的序列。Transformer文章中提出了一種非常新穎的時序資訊建模方式,就是利用三角函式的週期性,來建模詞之間的相對位置關係。具體的方式是將絕對位置作為三角函式中的變數做計算,具體公式如下:

img

​ 該公式的設計非常先驗,尤其是分母部分,不太好解釋。從筆者個人的觀點來看,一方面三角函式有很好的週期性,也就是隔一定的距離,因變數的值會重複出現,這種特性可以用來建模相對距離;另一方面,三角函式的值域是[-1,1],可以很好的提供embedding元素的值。

2.4.3 多層結構

​ Transformer中的多層結構非常強大,使用了之前已經被驗證過的很多有效的方法,包括:residual connection、layer normalization,另外還有self-attention層與Feed Forward層的堆疊使用,也是非常值得參考的結構。圖6展示了Transformer的Encoder和Decoder一層的結構。

img
圖6 Transformer模型結構

​ 圖6中,左側的Nx代表一層的Encoder,這一層中包含了兩個子層(sub-layer),第一個子層是多頭的self-attention layer,第二個子層是一個Feed Forward層。每個子層的輸入和輸出都存在著residual connection,這種方式理論上可以很好的回傳梯度。Layer Normalization的使用可以加快模型的收斂速度。self-attention子層的計算,我們前面用了不少的篇幅講過了,這裡就不再贅述了。Feed Forward子層實現中有兩次線性變換,一次Relu非線性啟用,具體計算公式如下:

img

文章的附頁中將這種計算方式也看做是一種attention的變種形式。

圖6中,右側是Decoder中一層的結構,這一層中存在三個子層結構,第一層是self-attention layer用來建模已經生成的目標端句子。在訓練的過程中,需要一個mask矩陣來控制每次self-attention計算的時候,只計算到前t-1個詞,具體的實現方式,我們會在後面講程式碼實現的時候進行說明。第二個子層是Encoder和Decoder之間的attention機制,也就是去源語言中找相關的語義資訊,這部分的計算與其他序列到序列的注意力計算一致,在Transformer中使用了dot-product的方式。第三個子層是Feed Forward層,與Encoder中的子層完全一致。每個子層也都存在著residual connection和layer normalization操作,以加快模型收斂。

Transformer中的這種多層-多子層的機制,可以使得模型的複雜度和可訓練程度都變高,達到非常強的效果,值得我們借鑑。

2.4.4 優化方法與正則策略

​ 模型的訓練採用了Adam方法,文章提出了一種叫warm up的學習率調節方法,如公式所示:

img

公式比較先驗,看上去比較複雜,其實邏輯表達起來比較清楚,需要預先設定一個warmup_steps超參。當訓練步數step_num小於該值時,以括號中的第二項公式決定學習率,該公式實際是step_num變數的斜率為正的線性函式。當訓練步數step_num大於warm_steps時,以括號中的第一項決定學習率,該公式就成了一個指數為負數的冪函式。所以整體來看,學習率呈先上升後下降的趨勢,有利於模型的快速收斂。

模型中也採用了兩項比較重要的正則化方法,一個就是常用的dropout方法,用在每個子層的後面和attention的計算中。另一個就是label smoothing方法,也就是訓練的時候,計算交叉熵的時候,不再是one-hot的標準答案了,而是每個0值處也填充上一個非0的極小值。這樣可以增強模型的魯棒性,提升模型的BLEU值。這個思路其實也是一定程度在解決訓練和解碼過程中存在的exposure bias的問題。

2.4.5 本章小結

​ Transformer系統的強大表現,不僅僅是self-attention機制,還需要上述的一系列配合使用的策略。設計該系統的研究者對深度學習模型和優化演算法有著非常深刻的認識和敏銳的感覺,很多地方值得我們借鑑學習。Transformer的程式碼實現工程化比較好,但是也存在一些地方不方便閱讀和理解,後面的章節中會對Transformer的程式碼實現進行詳細講解,將整體結構講清楚,把其中的疑難模組點出來。

第三章:Tensor2Tensor系統實現深度解析

​ Tensor2Tensor的系統存在一些特點,導致使用和理解的時候可能會存在一些需要時間來思考和消化的地方,在此根據個人的理解,寫出一些自己曾經花費時間的地方。

3.1 使用篇

​ Tensor2Tensor的使用是比較方便的,對於系統中可以支援的問題,直接給系統設定好下面的資訊就可以執行了:資料,問題(problem),模型,超參集合,執行裝置。這裡的實現其實是採用了設計模型中的工廠模式,即給定一個問題名字,返回給相應的處理類;給定一個超參名,返回一套超參的物件。實現這種方式的一個重點檔案是utils/registry.py。在系統啟動的時候,所有的問題和超參都會在registry中註冊,儲存到_MODELS,_HPAPAMS,_RANGED_HPARAMS中等待呼叫。

​ 在此主要以序列到序列的系統使用和實現為主線進行講解。系統的執行分三個階段:資料處理,訓練,解碼。對應著三個入口:t2t-datagen,t2t-trainer,t2t-decoder。

資料處理的過程包括:

​ 1.(下載)讀取訓練和開發資料。如果需要使用自己的資料的話,可以在問題中指定。

​ 2.(讀取)構造詞彙表。可以使用自己預先構造好的詞彙表。系統也提供構建BPE詞彙表的方法。注意,這裡有個實現細節是系統在抽取BPE詞彙表的時候,有個引數,預設並非使用全量的資料。通過多次迭代嘗試,得到最接近預設詞彙表規模的一個詞彙表。在大資料量的時候,這個迭代過程會非常慢。

​ 3. 使用詞彙表將單詞對映成id,每個句子後會加EOS_ID,每個平行句對被構造成一個dict物件({‘inputs’:value,‘targets’:value}),將所有物件序列化,寫入到檔案中,供後面訓練和評價使用。

模型訓練的過程的過程主要通過高階的Tensorflow API來管理,只是需要指定資料、問題名、模型名、超參名、裝置資訊就可以執行了。比較關鍵的一個檔案是utils/trainer_lib.py檔案,在這個檔案中,構建Experiment、Estimator、Monitor等來控制訓練流程。使用者主要需要設定的就是訓練過程的一些引數,比如訓練最大迭代次數,模型評估的頻率,模型評估的指標等。超參可以直接使用系統已有的引數集,也可以通過字串的形式向內傳參。簡單的任務不太需要動超參,因為系統中的超參集合基本上都是經過實驗效果驗證的。需要注意的就是batch_size過大的時候,可能會導致視訊記憶體不足,導致程式錯誤。一般是使用continuous_train_and_eval模式,使模型的訓練和評估間隔進行,隨時可以監控模型的表現。

解碼的過程,可以提供整體檔案、也可以是基於Dataset的,同時系統也提供server的方式,可以提供線上的服務,並沒有什麼特別好講的。

3.2 深度掌握篇

3.2.1 Tensor2Tensor系統實現的特點

​ 下面列出了要深度掌握Tensor2Tensor系統時,可能因為其實現特點,會遇到的一些問題:

​ 1. 系統支援多工,任務混雜,導致程式碼結構比較複雜。在實現的時候,要考慮到整體的結構,所以會存在各種封裝、繼承、多型的實現。可能你只想用其中的一個功能,理解該功能對應的程式碼,但是卻需要排除掉大量的不相關的程式碼。

​ 2. 系統基於Tensorflow封裝較高的API。使用了Tensorflow中比較高的API來管理模型的訓練和預測,Experiment,Monitor,Estimator,Dataset物件的使用隱藏了比較多的控制流程,對於側重應用的使用者來說,可能是是好事情,設一設引數就能跑。但是對於想了解更多的開發人員來說,TF該部分的文件實在很少,說的也不清楚,很多時候需要去閱讀原始碼才能知道實驗到底是不是按照自己預期的進行的。這種方式也不太方便找bug和除錯。

​ 3. 某些方法呼叫比較深。原因應該還是出於整體結構和擴充套件性的考慮。這導致了實現一點很小功能的方法A,需要再調一個其他方法B,B再去呼叫方法C,實際上每個方法中就幾行程式碼,甚至有的方法就是空操作。

​ 4. 多層繼承和多型也降低了程式碼的可讀性。追溯一個類的某個方法的時候,需要看到其父類的父類的父類。。。這些父類和子類之間的方法又存在著調來調去的關係,同名方法又存在著覆蓋的關係,所以要花一些時間來確定當前的方法名到底是呼叫的的哪個類中的方法。

​ 5. 要求開發者有模型層面的理解和與程式碼實現的掛鉤。肯定是要提高對模型邏輯的理解,但在讀程式碼的過程中,會遇到兩種問題:第一個,程式碼實現的是論文中的功能,但不是論文中的原始公式,可能要做變形以規避溢位的問題,或是實現更高的效率;第二個,某些程式碼實現與其論文中的表述存在不一致的情況。

3.2.2 總體邏輯模組

總體來說,對T2T系統的程式碼邏輯劃分如下,共包括三個大的模組:

  1. **問題定義和資料管理的模組。**該模組用來定義問題和處理資料,比如定義一個翻譯的問題,裡面定義抽詞彙表和構造訓練樣本的方法。
  2. **模型定義和計算圖構建的模組。**該模組用來定義模型屬性和計算圖結構。
  3. **實驗流程控制與並行化模組。**該模組用於實驗流程控制,設定可用計算裝置,提供模型並行化執行方法。

img
圖7 Tensor2Tensor主要邏輯模組

這裡不會對程式碼做追蹤式的分析,會分條的講解一些閱讀Tensor2Tensor系統程式碼時可能遇到的問題,點出一些重要的功能所在的位置和實現邏輯。

  1. **工廠模式。**系統使用工廠模式管理問題、模型、超參、模態等模組的方法。前面在使用篇講到了registry.py這個比較關鍵的檔案,是系統總體管理和排程模組的一個核心檔案。如果要在系統中增加新的問題、模型、超參、模態等,也都需要通過在類前加裝飾器的方式來註冊到registry中,否則系統找不到新加的模組。
  2. **問題類(problem)。**data_generators/problem.py中的class Problem是後面所有problem的基類。之前說到系統中的類之間的多層繼承關係導致程式碼讀起來比較麻煩,舉個例子來說,一個翻譯問題繼承路線是這樣的:Problem>>Text2TextProblem>>TranslateProblem>>TranslateEndeWmtBpe32k>> TranslateEndeWmt32k,中間各種的方法和變數覆蓋,父類和子類之間方法的穿插呼叫,導致一些閱讀困難。總的來說,一個序列到序列的問題應該包括以下資訊和方法:資料檔案資訊,詞彙表檔名、型別、大小,構造詞彙表的方法,序列化訓練資料和開發資料的方法,讀取資料檔案為model(estimator)構造輸入流input_fn的方法,設定問題評估metric的方法。可以總結來說,問題的屬性定義、訓練和評價樣本的構造、資料的處理和讀取,都由problem這個體系裡面的類和方法來提供。
  3. **詞彙表物件(TextEncoder)。**系統中有多種多樣的詞彙表(TextEncoder)物件,可以支援字母(character),子詞(subword/bpe),詞彙(token)等多重方式。TextEncoder主要功能就是構建詞彙表、實現符號到id的對映。T2T裡有構造bpe詞彙表的方法,沒有word piece詞彙表的構造方法,也可以看出T2T研究團隊和GNMT研究團隊的區分。兩個團隊一直在交替的更新機器翻譯任務的最高成績。構建BPE詞彙表的具體實現在SubwordTextEncoder中的 build_to_target_size()方法,該方法不是之前Sennrich使用迭代次數來控制詞彙表大小的方式,而是使用二分查詢的方式,通過搜尋最優的minimum token count值來逼近預先設定的詞彙表的大小。
  4. **T2TModel類。**utils/t2t_model.py中的class T2TModel是模型功能的基類,該類繼承自layer,Transformer類便繼承於此類。T2TModel類中定義了模型的計算圖結構,即給定feature後,模型是怎麼根據feature進行圖計算,得到logit,loss,然後根據loss求梯度,呼叫optimizer進行梯度回傳,進行引數更新的。構建計算圖的目的是最終要構建tf.estimator.EstimatorSpec()物件。可以理解為,所有的模型圖計算過程都在該物件中被表達了。T2TModel可以返回三種EstimatorSpec物件,分別用於訓練、評價和解碼。訓練的過程可以支援資料並行,具體的實現是同時在多個資料片上啟用計算圖,得到的loss做平均,這是一種同步並行訓練的方式。T2TModel中也提供了供解碼的方法。
  5. **Transformer類。**models/transformer.py中的class Transformer繼承自class T2TModel,為其父類構建圖的時候,提供各種支援的方法,encode方法可以使用Encoder結構對源端進行壓縮表示,decode方法使用Decoder結構對目標端進行生成。同時,transformer.py中有多套引數供選擇。模型中feed-forward子層的實現也在該檔案中(transformer_ffn_layer)。
  6. 資料並行類。devices.py和expert_utils.py配合使用,主要功能是根據使用者給定的並行裝置引數,列出可以使用的裝置名,然後給定一個能夠呼叫這些裝置,並行執行方法的方法。
  7. **實驗流程控制。**實驗流程控制使用的是Tensorflow的高階API物件,主要包括Experiment物件、Estimator物件、Dataset物件。對這三個物件,我們可以這麼理解:a) Experiment是一次執行的實驗,用來控制實驗流程,輸送資料到模型。b) Estimator是具體的模型物件,可以包括訓練、評估、解碼三個功能。c) Dataset為執行的實驗過程讀資料檔案提供資料流。
  8. Experiment物件。我們來看下圖中Experiment初始化所需的形參就能更好的理解“實驗”這個概念了。Experiment物件中需要迭代中的各種step引數,需要一個Estimator物件,兩個輸入流函式(input)。Experiment物件在執行中,將資料給到Estimator物件,然後控制訓練和迭代流程。

img
圖8 Experiment物件的部分形參

9.Estimator物件。可以理解為模型物件,可以通過Estimator執行模型的訓練、評估、解碼。Estimator物件最重要的一個形參是model_fn,也就是具體執行訓練、評估、解碼的函式入口。三個入口分別對應著三個EstimatorSpec物件,如圖9,10所示。

img
圖9 Estimator中最重要的形參是model_fn

img
圖10 Estimator中的三種model_fn,實現三種功能

​ 從圖10可以看出,用於訓練的EstimatorSpec物件需要描述計算圖中feature和(loss,train_op)之間的關係;用於評估的EstimatorSpec物件需要描述計算圖中feature和(loss,eval_metrics_ops)之間的關係;用於評估的EstimatorSpec物件需要描述features和predictions之間的關係。

  1. Dataset物件。該物件是讀檔案,構造訓練和評估的資料流。訓練和評估對應著兩種不同的資料輸入流,如圖11所示。

img
圖11 Dataset物件提供資料流

\11. Positional encoding的實現。論文中的實現和程式碼中的實現存在公式變形和不一致的情況,可能會導致困惑,故在此指出。論文中Positional encoding中三角函式的引數部分公式如下:

img

​ 程式碼中的實現需要對該公式做變形,以規避數值溢位的風險,公式變形過程如下:

img

​ 還需要指出的是,論文中根據維度下標的奇偶性來交替使用sin和cos函式的說法,在程式碼中並不是這樣實現的,而是前一半的維度使用sin函式,後一半的維度使用cos函式,並沒有考慮奇偶性

​ 12. **以token數量作為batch size。**這種方式比起以句子個數作為batch size的方式來,能到batch佔視訊記憶體的空間更加平均,不會導致因為訓練資料導致的視訊記憶體佔用忽上忽下,造成視訊記憶體空間不夠用,導致程式崩潰。

\13. 如何做mask。由於模型是以batch為單位進行訓練的,batch的句長以其中最長的那個句子為準,其他句子要做padding。padding項在計算的過程中如果不處理的話,會引入噪音,所以就需要mask,來使padding項不對計算起作用。mask在attention機制中的實現非常簡單,就是在softmax之前,把padding位置元素加一個極大的負數,強制其softmax後的概率結果為0。舉個例子,[1,1,1]經過softmax計算後結果約為[0.33,0.33,0.33],[1,1,-1e9] softmax的計算結果約為[0.5, 0.5,0]。這樣就相當於mask掉了陣列中的第三項元素。在對target sequence進行建模的時候,需要保證每次只attention到前t-1個單詞,這個地方也需要mask,整體的mask是一個上三角矩陣,非0元素值為一個極大的負值。

\14. 基於batch的解碼。解碼的時候,如果是基於檔案的,那麼就會將句子組成batch來並行解碼。這裡有個小trick,就是先對句子進行排序,然後從長的句子開始組batch,翻譯,再把句子恢復成原先的順序返回。這種方式可以很好的檢測到視訊記憶體不足的錯誤,因為解句子最長的一個batch的時候,視訊記憶體都是夠得,那其他的batch也不存在問題。

總結

​ 本文對Google的Tensor2Tensor系統進行了深度的解讀,涉及到了比較多的方面,筆者也還需要對其進行更加深入的學習和研究,希望能夠與對該模型以及DL for NLP技術感興趣的同學們一起交流,共同進步!

問答

docker和docker-compose有什麼不同?

相關閱讀

深度學習之神經網路核心原理與演算法-歸一化與引數初始化

啟發式尋路演算法

深度學習(5)——RBF演算法簡介

此文已由作者授權騰訊雲+社群釋出,原文連結:https://cloud.tencent.com/developer/article/1116709?fromSource=waitui

歡迎大家前往騰訊雲+社群或關注雲加社群微信公眾號(QcloudCommunity),第一時間獲取更多海量技術實踐乾貨哦~

海量技術實踐經驗,盡在雲加社群! https://cloud.tencent.com/developer?fromSource=waitui

相關文章