「變形金剛」Google Tensor2Tensor系統
來自專欄騰訊雲+社區20 人贊了文章
歡迎大家前往騰訊雲+社區,獲取更多騰訊海量技術實踐乾貨哦~
本文由張金超博士發表於雲+社區專欄
導語: 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模型的結構,從底向上是一個機器翻譯的過程。
圖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結構更好的表現,但是門控參數量一定的情況下,這種能力是一定的。隨著句子的增長,相對距離的增大,存在明顯的理論上限。
圖2 使用雙向RNN對序列進行建模
? 圖3展示了使用多層CNN對序列進行建模的方法。第一層的CNN單元覆蓋的語義環境範圍較小,第二層覆蓋的語義環境範圍會變大,依次類推,越深層的CNN單元,覆蓋的語義環境會越大。一個詞首先會在底層CNN單元上與其近距離的詞產生交互,然後在稍高層次的CNN單元上與其更遠一些詞產生交互。所以,多層的CNN結構體現的是一種從局部到全局的特徵抽取過程。詞之間的交互距離,與他們的相對距離成正比。距離較遠的詞只能在較高的CNN節點上相遇,才產生交互。這個過程可能會存在較多的信息丟失。
圖3 使用多層CNN對序列進行建模
? 圖4展示的是基於self-attention機制的序列建模方法。注意,為了使圖展示的更清晰,少畫了一些連接線,圖中「sentence」層中的每個詞和第一層self-attention layer中的節點都是全連接的關係,第一層self-attention layer和第二層self-attention layer之間的節點也都是全連接的關係。我們可以看到在這種建模方法中,任意兩個詞之間的交互距離都是1,與詞之間的相對距離不存在關係。這種方式下,每個詞的語義的確定,都考慮了與整個句子中所有的詞的關係。多層的self-attention機制,使得這種全局交互變的更加複雜,能夠捕獲到更多的信息。
圖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進行加權平均,得到最終的查詢結果。計算過程,形式化為:
? 常用的相關度計算函數有基於加法式(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函數是乘法式的,那麼這個查詢的過程就是矩陣的乘法,可以形式化為:
在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計算公式如下:
多頭機制是指,引入多組的參數矩陣來分別對Q、K、V進行線性變換求self-attention的結果,然後將所有的結果拼接起來作為最後的self-attention輸出。這樣描述可能不太好理解,一看公式和示意圖就會明白了,如下:
圖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文章中提出了一種非常新穎的時序信息建模方式,就是利用三角函數的周期性,來建模詞之間的相對位置關係。具體的方式是將絕對位置作為三角函數中的變數做計算,具體公式如下:
? 該公式的設計非常先驗,尤其是分母部分,不太好解釋。從筆者個人的觀點來看,一方面三角函數有很好的周期性,也就是隔一定的距離,因變數的值會重複出現,這種特性可以用來建模相對距離;另一方面,三角函數的值域是[-1,1],可以很好的提供embedding元素的值。
2.4.3 多層結構
? Transformer中的多層結構非常強大,使用了之前已經被驗證過的很多有效的方法,包括:residual connection、layer normalization,另外還有self-attention層與Feed Forward層的堆疊使用,也是非常值得參考的結構。圖6展示了Transformer的Encoder和Decoder一層的結構。
圖6 Transformer模型結構
? 圖6中,左側的Nx代表一層的Encoder,這一層中包含了兩個子層(sub-layer),第一個子層是多頭的self-attention layer,第二個子層是一個Feed Forward層。每個子層的輸入和輸出都存在著residual connection,這種方式理論上可以很好的回傳梯度。Layer Normalization的使用可以加快模型的收斂速度。self-attention子層的計算,我們前面用了不少的篇幅講過了,這裡就不再贅述了。Feed Forward子層實現中有兩次線性變換,一次Relu非線性激活,具體計算公式如下:
文章的附頁中將這種計算方式也看做是一種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的學習率調節方法,如公式所示:
公式比較先驗,看上去比較複雜,其實邏輯表達起來比較清楚,需要預先設置一個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系統的代碼邏輯劃分如下,共包括三個大的模塊:
- 問題定義和數據管理的模塊。該模塊用來定義問題和處理數據,比如定義一個翻譯的問題,裡面定義抽辭彙表和構造訓練樣本的方法。
- 模型定義和計算圖構建的模塊。該模塊用來定義模型屬性和計算圖結構。
- 實驗流程式控制制與並行化模塊。該模塊用於實驗流程式控制制,設置可用計算設備,提供模型並行化運行方法。
圖7 Tensor2Tensor主要邏輯模塊
這裡不會對代碼做追蹤式的分析,會分條的講解一些閱讀Tensor2Tensor系統代碼時可能遇到的問題,點出一些重要的功能所在的位置和實現邏輯。
- 工廠模式。系統使用工廠模式管理問題、模型、超參、模態等模塊的方法。前面在使用篇講到了registry.py這個比較關鍵的文件,是系統總體管理和調度模塊的一個核心文件。如果要在系統中增加新的問題、模型、超參、模態等,也都需要通過在類前加裝飾器的方式來註冊到registry中,否則系統找不到新加的模塊。
- 問題類(problem)。data_generators/problem.py中的class Problem是後面所有problem的基類。之前說到系統中的類之間的多層繼承關係導致代碼讀起來比較麻煩,舉個例子來說,一個翻譯問題繼承路線是這樣的:Problem>>Text2TextProblem>>TranslateProblem>>TranslateEndeWmtBpe32k>> TranslateEndeWmt32k,中間各種的方法和變數覆蓋,父類和子類之間方法的穿插調用,導致一些閱讀困難。總的來說,一個序列到序列的問題應該包括以下信息和方法:數據文件信息,辭彙表文件名、類型、大小,構造辭彙表的方法,序列化訓練數據和開發數據的方法,讀取數據文件為model(estimator)構造輸入流input_fn的方法,設定問題評估metric的方法。可以總結來說,問題的屬性定義、訓練和評價樣本的構造、數據的處理和讀取,都由problem這個體系裡面的類和方法來提供。
- 辭彙表對象(TextEncoder)。系統中有多種多樣的辭彙表(TextEncoder)對象,可以支持字母(character),子詞(subword/bpe),辭彙(token)等多重方式。TextEncoder主要功能就是構建辭彙表、實現符號到id的映射。T2T里有構造bpe辭彙表的方法,沒有word piece辭彙表的構造方法,也可以看出T2T研究團隊和GNMT研究團隊的區分。兩個團隊一直在交替的更新機器翻譯任務的最高成績。構建BPE辭彙表的具體實現在SubwordTextEncoder中的 build_to_target_size()方法,該方法不是之前Sennrich使用迭代次數來控制辭彙表大小的方式,而是使用二分查找的方式,通過搜索最優的minimum token count值來逼近預先設置的辭彙表的大小。
- T2TModel類。utils/t2t_model.py中的class T2TModel是模型功能的基類,該類繼承自layer,Transformer類便繼承於此類。T2TModel類中定義了模型的計算圖結構,即給定feature後,模型是怎麼根據feature進行圖計算,得到logit,loss,然後根據loss求梯度,調用optimizer進行梯度回傳,進行參數更新的。構建計算圖的目的是最終要構建tf.estimator.EstimatorSpec()對象。可以理解為,所有的模型圖計算過程都在該對象中被表達了。T2TModel可以返回三種EstimatorSpec對象,分別用於訓練、評價和解碼。訓練的過程可以支持數據並行,具體的實現是同時在多個數據片上激活計算圖,得到的loss做平均,這是一種同步並行訓練的方式。T2TModel中也提供了供解碼的方法。
- Transformer類。models/transformer.py中的class Transformer繼承自class T2TModel,為其父類構建圖的時候,提供各種支持的方法,encode方法可以使用Encoder結構對源端進行壓縮表示,decode方法使用Decoder結構對目標端進行生成。同時,transformer.py中有多套參數供選擇。模型中feed-forward子層的實現也在該文件中(transformer_ffn_layer)。
- 數據並行類。devices.py和expert_utils.py配合使用,主要功能是根據用戶給定的並行設備參數,列出可以使用的設備名,然後給定一個能夠調用這些設備,並行執行方法的方法。
- 實驗流程式控制制。實驗流程式控制制使用的是Tensorflow的高級API對象,主要包括Experiment對象、Estimator對象、Dataset對象。對這三個對象,我們可以這麼理解:a) Experiment是一次運行的實驗,用來控制實驗流程,輸送數據到模型。b) Estimator是具體的模型對象,可以包括訓練、評估、解碼三個功能。c) Dataset為運行的實驗過程讀數據文件提供數據流。
- Experiment對象。我們來看下圖中Experiment初始化所需的形參就能更好的理解「實驗」這個概念了。Experiment對象中需要迭代中的各種step參數,需要一個Estimator對象,兩個輸入流函數(input)。Experiment對象在運行中,將數據給到Estimator對象,然後控制訓練和迭代流程。
圖8 Experiment對象的部分形參
9.Estimator對象。可以理解為模型對象,可以通過Estimator執行模型的訓練、評估、解碼。Estimator對象最重要的一個形參是model_fn,也就是具體執行訓練、評估、解碼的函數入口。三個入口分別對應著三個EstimatorSpec對象,如圖9,10所示。
圖9 Estimator中最重要的形參是model_fn
圖10 Estimator中的三種model_fn,實現三種功能
? 從圖10可以看出,用於訓練的EstimatorSpec對象需要描述計算圖中feature和(loss,train_op)之間的關係;用於評估的EstimatorSpec對象需要描述計算圖中feature和(loss,eval_metrics_ops)之間的關係;用於評估的EstimatorSpec對象需要描述features和predictions之間的關係。
- Dataset對象。該對象是讀文件,構造訓練和評估的數據流。訓練和評估對應著兩種不同的數據輸入流,如圖11所示。
圖11 Dataset對象提供數據流
11. Positional encoding的實現。論文中的實現和代碼中的實現存在公式變形和不一致的情況,可能會導致困惑,故在此指出。論文中Positional encoding中三角函數的參數部分公式如下:
? 代碼中的實現需要對該公式做變形,以規避數值溢出的風險,公式變形過程如下:
? 還需要指出的是,論文中根據維度下標的奇偶性來交替使用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
推薦閱讀:
※為什麼羅永浩從來不跟Google比?
※為何 Google 幾乎完全收回 20% 自由項目時間,員工如何評價?
※2018年谷歌助手(duplex)是否可以通過圖靈測試?
※如何看待第三方ROM PixelExperience?
※去 Google 這樣的公司工作需要什麼樣的硬性指標么?