離開深度學(xué)習(xí)瞎折騰了一段時間后,我終于又回來了。
于是趕緊回顧了下18年之后NLP的發(fā)展,基本就是將遷移學(xué)習(xí)更廣泛的用于NLP領(lǐng)域,以及把17年年底的《Attention is all you need》里的思想給發(fā)揚(yáng)光大了,ELMO彌補(bǔ)了傳統(tǒng)word2vec多義詞表示的不足,GPT使用更強(qiáng)大的特征提取器Transformer取代LSTM,Bert使用雙向Transformer進(jìn)一步改進(jìn)了GPT成為這兩年發(fā)展的集大成者。
從Bert模型所帶來的NLP界里程碑式的影響和所取得的成就來看,無疑Bert將會是未來兩三年NLP應(yīng)用發(fā)展的基石,于是有必要仔細(xì)的看看其模型的結(jié)構(gòu),數(shù)據(jù)是如何流動的,訓(xùn)練的和測試的。
不得不說現(xiàn)在的學(xué)習(xí)環(huán)境相對幾年前好太多了,本文主要參考了以下幾篇文章,然后加了點(diǎn)自己的理解:
Dissecting BERT Part 1: The Encoder
Dissecting BERT Appendix: The Decoder
它的總體框架同lstm時代的MNT或者是attention is all you need中的transformer一樣的encoder-decoder結(jié)構(gòu):
我們先來介紹一下Encoder部分。
Encoder
為了理解這個架構(gòu),我們使用一個簡單的具體的例子,來看一下輸入的數(shù)據(jù)是怎么通過encoder一步一步變化讓后到輸出的。
從詞到向量
bert的詞嵌入由三個嵌入token embedding、segment embedding,和position embedding疊加而成。
Token embedding
這個過程跟以往的RNNs沒什么區(qū)別,比如給定一個句子:
Hello, how are you?
第一步是先將其標(biāo)記化:
“ Hello, how are you?” → [“Hello”, “,” , “how”, “are”, “you”, “?”]
然后是數(shù)字化,將每個標(biāo)記映射到語料詞匯表中的唯一整數(shù)編號:
[“Hello”, “, “, “how”, “are”, “you”, “?”] → [34, 90, 15, 684, 55, 193]
接下來就是得到序列中每個詞的詞嵌入,也就是將整數(shù)映射到一個 維的向量,這個向量是模型在訓(xùn)練時學(xué)習(xí)的,你可以將其視為一個查表的過程,這些向量的元素作為模型的參數(shù),像其他權(quán)重一樣通過反向傳播進(jìn)行了優(yōu)化。
在論文中是使用WordPiece tokenization 來將英文單詞轉(zhuǎn)換成768()維的向量,轉(zhuǎn)化的過程類似這樣:
把每個詞的向量放到一起,就得到了一個句子長度x向量維度() 尺寸的矩陣Z:
說明一點(diǎn),我們通常使用填充的方式來讓輸入序列具有相同的長度,比如通過添加"<pad>" 標(biāo)記來增加某些序列的長度,還是前面的例子,填充后可能變?yōu)椋?/p>
[“<pad>”, “<pad>”, “<pad>”, “Hello”, “, “, “how”, “are”, “you”, “?”] →
[5, 5, 5, 34, 90, 15, 684, 55, 193]
如果設(shè)定設(shè)定為9,那我們就把句子從5填充到了9。
Positional Encoding
但是,上面的embedding并沒有包含詞的位置信息。于是,我們的目標(biāo)是能夠根據(jù)詞在句子中的位置適當(dāng)調(diào)整這個向量,使它帶上位置信息。
作者選擇的方法是使用預(yù)定的(非學(xué)習(xí)的)正余弦函數(shù)將 之間的數(shù)字加到前面的embedding中,即通過正余弦函數(shù)將位置表示為彼此的線性組合,從而實現(xiàn)網(wǎng)絡(luò)學(xué)習(xí)中標(biāo)記位置之間的相對關(guān)系。在Token embedding 獲得的矩陣
的基礎(chǔ)上加上位置矩陣
。
數(shù)學(xué)上,用 表示序列中標(biāo)記的位置,用
表示token embedding特征向量中的位置:
具體來說,對于給定的句子 ,其位置嵌入矩陣為:
作者解釋說,使用這種確定性方法的結(jié)果和學(xué)習(xí)位置表示(就像我們對詞嵌入那樣)的結(jié)果差不多,因此這樣反而會有一些優(yōu)勢:
- input_length 可以無限長,因為函數(shù)可以計算任意位置
- 需要學(xué)習(xí)的參數(shù)更好啊,訓(xùn)練更快
因此,添加了位置信息之后的矩陣是:
它是第一個encoder塊的輸入,尺寸是
Encoder block
共有N個編碼器塊連接在一起直到生成編碼器的輸出,特定的塊負(fù)責(zé)查找輸入表示之間的關(guān)系并將編碼在其輸出中。
直觀地,通過這些塊的迭代過程將幫助神經(jīng)網(wǎng)絡(luò)捕獲輸入序列中的詞之間的更加復(fù)雜的關(guān)系,你可以把它理解成一個整體用來捕捉輸入序列的語義。
Multi-Head Attention
encoder中使用Transformer的多頭注意力機(jī)制,這意味著它將計算 份不同權(quán)重矩陣的自注意力,然后將結(jié)果連接在一起。
這些并行注意力計算的結(jié)果稱之為Head,我們用下標(biāo) 來表示一個特定的head和相關(guān)的權(quán)重矩陣。
如上圖所示,一旦計算了所有head,它們將被連接起來,得到一個尺寸為 的矩陣,然后將它乘以一個尺寸為
的權(quán)重矩陣
進(jìn)行線性變換,就得到了一個尺寸為
的最終結(jié)果,用數(shù)學(xué)公式表示就是:
其中的 通過
乘以相應(yīng)權(quán)重矩陣
獲得,我們通過一個簡單的例子來可視化的看一下這個過程。
這圖描繪了輸入標(biāo)記通過 token embedding 和 positional encoding ,再輸入到Encoder:
接下來,我們再來看下Encoder中的操作過程,先看一下單頭的self-attention:
上圖描繪了一個Head的 是怎么來的,其中的
的尺寸是
, 因為Q和K需要計算相似性,所以維度應(yīng)當(dāng)是相同的,
的尺寸是
,
的維度可以相同也可以不同,在論文中
.
所謂的自注意力,就是 與
的點(diǎn)積進(jìn)行
的縮放之后通過softmax獲得一個概率權(quán)重,然后用這些權(quán)重分別乘以各自的
即可:
為了加深理解,我們選擇其中一個頭,通過圖形繼續(xù)可視化的看一下這個變化過程:
然后計算self-attention,
多頭的話就是同時有多個上述計算過程在進(jìn)行:
假設(shè)我們有8個Head,那么我們就獲得8個 :
但是,顯然前饋層只需要一個矩陣 ,怎么處理呢?類似多卷積核的處理,把這8個矩陣連起來,乘以一個權(quán)重矩陣
壓縮到一個矩陣。
為了有一個更加全面直觀的認(rèn)識,我們把上面整個過程放到一個圖里,
顯然,第二個encoder塊是不需要embedding過程的,只要把第一個encoder塊的輸出作為輸入即可。
經(jīng)過上面的介紹,你應(yīng)該對這個過程已經(jīng)有了足夠的了解,但是,為什么可以利用向量點(diǎn)積來計算注意力概率呢?
于是讓我們進(jìn)一步深入來了解其中的原理。
這個結(jié)構(gòu)體系的關(guān)鍵在于:
也就是每個詞的q向量與每個詞的k向量的點(diǎn)積,套用點(diǎn)積公式:
這意味著 和
的方向越相似,長度越大,點(diǎn)積就越大。詞與此之間關(guān)聯(lián)越大,對于理解這個詞時得到的關(guān)注越大,跟我們的本意是相同的。
Add & Norm
我們再看一下最開頭的結(jié)構(gòu)示意圖,每個encoder塊在Multi-Head Attention之后經(jīng)過一個 Add & Norm層才進(jìn)入下一個塊。于是我們來看一下這一層做了些什么。
Add實際就是一個殘差連接,將輸出加上輸入,這個在每一塊的self-attenton以及FFN之后都會有,然后跟隨一個Layer Norm 。
Norm 是一個Layer Normlization,將 正則化,就是把它縮放到一個均值為0方差為1的域里。因為
不過一般在這一層之前,就會有一個dropout層。
Position-wise Feed-Forward Network
每個encoder塊都由 mulit-head atteion add & Norm
feed forword network
add & Norm 這樣一個過程,下面來介紹一下這個Feed-Forward Network。
這是一個全連接層,包含兩個線性變化和一個非線性函數(shù)(實際一般就是ReLu),
對于輸入的 (尺寸為
) ,通過權(quán)重矩陣
(尺寸為
)和偏置
線性變換到隱藏層 (尺寸為
) ,然后**ReLu **激活 ,記下來再用權(quán)重矩陣
(尺寸為
) 和偏置
的線性變換到輸出層(尺寸為
) ,表示成數(shù)學(xué)公式就是:
在最后一個encoder塊輸出之后連接到decoder。
Decoder
Decoder和Encoder的結(jié)構(gòu)是類似的,但是因為可視信息的不同,又有所差別。
Transformer解決的是翻譯的問題,將一個句子翻譯成另一種語言,我們希望模型能夠捕捉到輸入句子中詞之間的關(guān)系,并且將輸入句子中包含的信息與每一步已翻譯的內(nèi)容結(jié)合起來。繼續(xù)上面的例子,我們的目標(biāo)是把一個句子從英文翻譯為西班牙文,這是我們獲得的序列標(biāo)記:
X = [‘Hello’, ‘,’, ‘how’, ‘a(chǎn)re’, ‘you’, ‘?’] (Input sequence)
Y = [‘Hola’, ‘,’, ‘como’, ‘estas’, ‘?’] (Target sequence)
我們同之前一樣來看看輸入到輸出數(shù)據(jù)是如何流動的。
這是我們的解碼器的輸入標(biāo)記:
[‘<SS>’, ’Hola’, ‘,’, ‘ como’, ‘estas’, ‘?’]
然后這是解碼器的期望輸出:
[’Hola’, ‘,’, ‘ como’, ‘estas’, ‘?’,’<EOS>’]
但是,這里存在一個問題,比如輸入這邊我們已經(jīng)看到了'como' 的后面是'estas', 然后再用它來預(yù)測'estas' ,這顯然是不合理的,因為模型在測試的時候是看不到后面的詞的。
因此,我們需要修改注意力層,防止模型可以看到預(yù)測詞右邊的信息,與此同時,它能利用已經(jīng)預(yù)測的詞,即左邊的信息。
繼續(xù)上面的例子,我們將輸入標(biāo)記轉(zhuǎn)換成矩陣的形式,并添加位置信息:
和encoder一樣,decoder塊的輸出也將是大小為 的矩陣,在逐行線性變換+softmax激活后,將生成一個舉證,其中每行的最大元素表示下一個單詞。也就是說,分配"<SS>" 的行負(fù)責(zé)預(yù)測“Hola”, 分配"Hola"的行負(fù)責(zé)預(yù)測"," ...以此類推。比如,為了預(yù)測"estas", 我們將允許該行直接和下圖中綠色區(qū)域互動,而不能和紅色區(qū)域互動:
但是,在我們使用多頭注意力機(jī)制的時候,所有的行都會產(chǎn)生交互,因此需要在輸入的時候添加遮罩,這個遮罩會在注意力計算之后進(jìn)行:
這是self-attention的計算結(jié)果:
然后我們在此基礎(chǔ)上添加遮掩,就是把矩陣上三角的位置全部設(shè)置為 :
于是,在進(jìn)行softmax激活之后,矩陣就變成了:
恰好達(dá)到了我們的要求,那些需要在訓(xùn)練時忽略的右側(cè)的詞的注意力全部變成了0。
當(dāng)將這個注意力矩陣與 相乘時,預(yù)測的詞就是模型可以訪問元素右邊的元素。注意,這里的多頭注意力輸出將是
維的,因為它的序列長度是
。
這個就是Decoder從target序列的輸入,并經(jīng)過Masked Multi-Head Attention 的一個變化得到了,decoder的還有一部分輸入來自于源語句經(jīng)過Encoder的最終輸出
(尺寸是
)。
接下來,就是與encoder一樣的 Multi-Head Attention Add and Layer Norm -> FFN 的過程。
只不過,現(xiàn)在的 來自于
,而
來自于
:
計算每個query相對于key的注意力之后,得到的是一個 的矩陣, 繼續(xù)咱們的例子,比如注意力矩陣為:
如上圖所見,這個注意力是當(dāng)前Decoder輸入與Encoder輸出的每個詞之間的注意力,咱們用這個矩陣再乘以 ,就得到了一個
的矩陣,每一行代表了源語句相對于當(dāng)前輸入詞匯的特征:
h個Head連接起來,尺寸變?yōu)? ,它通過
的權(quán)重矩陣
線性變換到一個
的輸出。
這在多個Decoder之后,最后輸出的矩陣通過乘以權(quán)重矩陣 (
) 進(jìn)行線性變換,變換之后再對每一行的向量softmax, 其中選擇值最大位置對應(yīng)詞表索引的詞就是預(yù)測的詞。
損失的話只需要用預(yù)測的每個詞向量與真實的詞的one-hot詞表示計算交叉熵即可。