理解Bert

離開深度學(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

The Illustrated Transformer

Dissecting BERT Appendix: The Decoder

它的總體框架同lstm時代的MNT或者是attention is all you need中的transformer一樣的encoder-decoder結(jié)構(gòu):

b1.png

我們先來介紹一下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ù)映射到一個emb\_dim? 維的向量,這個向量是模型在訓(xùn)練時學(xué)習(xí)的,你可以將其視為一個查表的過程,這些向量的元素作為模型的參數(shù),像其他權(quán)重一樣通過反向傳播進(jìn)行了優(yōu)化。

在論文中是使用WordPiece tokenization 來將英文單詞轉(zhuǎn)換成768(emb\_dim)維的向量,轉(zhuǎn)化的過程類似這樣:

b2.png

把每個詞的向量放到一起,就得到了一個句子長度x向量維度(input\_length * emb\_dim?) 尺寸的矩陣Z:

b3.png

說明一點(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è)定input\_length ?設(shè)定為9,那我們就把句子從5填充到了9。

Positional Encoding

但是,上面的embedding并沒有包含詞的位置信息。于是,我們的目標(biāo)是能夠根據(jù)詞在句子中的位置適當(dāng)調(diào)整這個向量,使它帶上位置信息。

作者選擇的方法是使用預(yù)定的(非學(xué)習(xí)的)正余弦函數(shù)將[-1,1] ? 之間的數(shù)字加到前面的embedding中,即通過正余弦函數(shù)將位置表示為彼此的線性組合,從而實現(xiàn)網(wǎng)絡(luò)學(xué)習(xí)中標(biāo)記位置之間的相對關(guān)系。在Token embedding 獲得的矩陣Z? 的基礎(chǔ)上加上位置矩陣P?

數(shù)學(xué)上,用i 表示序列中標(biāo)記的位置,用j 表示token embedding特征向量中的位置:

b4.png

具體來說,對于給定的句子P ,其位置嵌入矩陣為:

b5.png

作者解釋說,使用這種確定性方法的結(jié)果和學(xué)習(xí)位置表示(就像我們對詞嵌入那樣)的結(jié)果差不多,因此這樣反而會有一些優(yōu)勢:

  • input_length 可以無限長,因為函數(shù)可以計算任意位置
  • 需要學(xué)習(xí)的參數(shù)更好啊,訓(xùn)練更快

因此,添加了位置信息之后的矩陣是:

X = Z + P

它是第一個encoder塊的輸入,尺寸是input\_length * emb\_dim ?

Encoder block

共有N個編碼器塊連接在一起直到生成編碼器的輸出,特定的塊負(fù)責(zé)查找輸入表示之間的關(guān)系并將編碼在其輸出中。

直觀地,通過這些塊的迭代過程將幫助神經(jīng)網(wǎng)絡(luò)捕獲輸入序列中的詞之間的更加復(fù)雜的關(guān)系,你可以把它理解成一個整體用來捕捉輸入序列的語義。

Multi-Head Attention

encoder中使用Transformer的多頭注意力機(jī)制,這意味著它將計算h? 份不同權(quán)重矩陣的自注意力,然后將結(jié)果連接在一起。

這些并行注意力計算的結(jié)果稱之為Head,我們用下標(biāo)i? 來表示一個特定的head和相關(guān)的權(quán)重矩陣。

b6.png

如上圖所示,一旦計算了所有head,它們將被連接起來,得到一個尺寸為input\_length *(h *d_v) 的矩陣,然后將它乘以一個尺寸為(h * d_v) * emb\_dim 的權(quán)重矩陣W^0 進(jìn)行線性變換,就得到了一個尺寸為(input\_length) * emb\_dim? 的最終結(jié)果,用數(shù)學(xué)公式表示就是:

MultiHead(Q,K,V) = Concat(head_1,...,head_h)W^0 \\ where ~~head_i = Attention(XW_i^Q,XW_i^K,XW_i^V)

其中的Q, K, V 通過X? 乘以相應(yīng)權(quán)重矩陣W_i^Q獲得,我們通過一個簡單的例子來可視化的看一下這個過程。

這圖描繪了輸入標(biāo)記通過 token embedding 和 positional encoding ,再輸入到Encoder:

b7.png

接下來,我們再來看下Encoder中的操作過程,先看一下單頭的self-attention:

b8.png

上圖描繪了一個Head的Q (Querys), K(Keys) , V(Values)? 是怎么來的,其中的W_i^K , W^Q_i? 的尺寸是 d_{emb\_dim} * d_k? , 因為Q和K需要計算相似性,所以維度應(yīng)當(dāng)是相同的,W_i^V? 的尺寸是d_{emb\_dim} * d_v? ,V? 的維度可以相同也可以不同,在論文中d_k = d_v = emb\_dim /h? .

XW_i^K = K_i \\ XW_i^Q = Q_i \\ XW_i^V = V_i

所謂的自注意力,就是Q_iK_i^T 的點(diǎn)積進(jìn)行\sqrt {d_k} 的縮放之后通過softmax獲得一個概率權(quán)重,然后用這些權(quán)重分別乘以各自的V_i? 即可:

Attention(Q_i,K_i,V_i) = softmax(\frac{Q_iK_i^T}{\sqrt{d_k}}) V_i

為了加深理解,我們選擇其中一個頭,通過圖形繼續(xù)可視化的看一下這個變化過程:

b9.png

然后計算self-attention,

b10.png

多頭的話就是同時有多個上述計算過程在進(jìn)行:

b11.png

假設(shè)我們有8個Head,那么我們就獲得8個Z? :

b12.png

但是,顯然前饋層只需要一個矩陣Z ,怎么處理呢?類似多卷積核的處理,把這8個矩陣連起來,乘以一個權(quán)重矩陣W^0 ?壓縮到一個矩陣。

b13.png

為了有一個更加全面直觀的認(rèn)識,我們把上面整個過程放到一個圖里,

b14.png

顯然,第二個encoder塊是不需要embedding過程的,只要把第一個encoder塊的輸出作為輸入即可。

經(jīng)過上面的介紹,你應(yīng)該對這個過程已經(jīng)有了足夠的了解,但是,為什么可以利用向量點(diǎn)積來計算注意力概率呢?

于是讓我們進(jìn)一步深入來了解其中的原理。

這個結(jié)構(gòu)體系的關(guān)鍵在于:

Q_iK_i^T

也就是每個詞的q向量與每個詞的k向量的點(diǎn)積,套用點(diǎn)積公式:

qk = cos(q,k)\left \| q \right \|\left \| k \right \|

這意味著qk 的方向越相似,長度越大,點(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,將Z + X? 正則化,就是把它縮放到一個均值為0方差為1的域里。因為

不過一般在這一層之前,就會有一個dropout層。

Position-wise Feed-Forward Network

每個encoder塊都由 mulit-head atteion \to? add & Norm \to? feed forword network \to? add & Norm 這樣一個過程,下面來介紹一下這個Feed-Forward Network。

這是一個全連接層,包含兩個線性變化和一個非線性函數(shù)(實際一般就是ReLu),

b15.png

對于輸入的x? (尺寸為input\_length * emb\_dim?) ,通過權(quán)重矩陣W_1? (尺寸為emb\_dim * d_F ?)和偏置b_1? 線性變換到隱藏層 (尺寸為input\_length * d_F? ) ,然后**ReLu **激活 ,記下來再用權(quán)重矩陣W_2? (尺寸為d_F * emb\_dim?) 和偏置 b_2? 的線性變換到輸出層(尺寸為input\_length * emb\_dim ? ) ,表示成數(shù)學(xué)公式就是:

FFN(x) = max(0, xW_1+b_1)W_2 +b_2

在最后一個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)換成矩陣的形式,并添加位置信息:

b16.png

和encoder一樣,decoder塊的輸出也將是大小為target\_length * emb\_dim? 的矩陣,在逐行線性變換+softmax激活后,將生成一個舉證,其中每行的最大元素表示下一個單詞。也就是說,分配"<SS>" 的行負(fù)責(zé)預(yù)測“Hola”, 分配"Hola"的行負(fù)責(zé)預(yù)測"," ...以此類推。比如,為了預(yù)測"estas", 我們將允許該行直接和下圖中綠色區(qū)域互動,而不能和紅色區(qū)域互動:

b17.png

但是,在我們使用多頭注意力機(jī)制的時候,所有的行都會產(chǎn)生交互,因此需要在輸入的時候添加遮罩,這個遮罩會在注意力計算之后進(jìn)行:

\frac{Q_iK_i^T}{\sqrt{d_k}}

這是self-attention的計算結(jié)果:

b18.png

然后我們在此基礎(chǔ)上添加遮掩,就是把矩陣上三角的位置全部設(shè)置為- \infin

b19.png

于是,在進(jìn)行softmax激活之后,矩陣就變成了:

b20.png

恰好達(dá)到了我們的要求,那些需要在訓(xùn)練時忽略的右側(cè)的詞的注意力全部變成了0。

當(dāng)將這個注意力矩陣與V_i? 相乘時,預(yù)測的詞就是模型可以訪問元素右邊的元素。注意,這里的多頭注意力輸出將是target\_length * emb\_dim? 維的,因為它的序列長度是target\_length?

這個就是Decodertarget序列的輸入,并經(jīng)過Masked Multi-Head Attention 的一個變化得到了D,decoder的還有一部分輸入來自于源語句經(jīng)過Encoder的最終輸出E (尺寸是 input\_length * emb\_dim )。

接下來,就是與encoder一樣的 Multi-Head Attention \to Add and Layer Norm -> FFN 的過程。

只不過,現(xiàn)在的Q 來自于 D ,而K , V 來自于E:

DW_i^Q = Q_i ~~~~(size:target\_length * d_k) \\ EW_i^K = K_i ~~~~(size:input\_length * d_k) \\ EW_i^V = V_i ~~~~(size:input\_length * d_v)

計算每個query相對于key的注意力之后,得到的是一個target\_length * input\_length? 的矩陣, 繼續(xù)咱們的例子,比如注意力矩陣為:

b21.png

如上圖所見,這個注意力是當(dāng)前Decoder輸入與Encoder輸出的每個詞之間的注意力,咱們用這個矩陣再乘以V? ,就得到了一個target\_length * d_v? 的矩陣,每一行代表了源語句相對于當(dāng)前輸入詞匯的特征:

b22.png
b23.png

h個Head連接起來,尺寸變?yōu)?target\_length * d_v * h ? ,它通過d_v * h * emb\_dim ?的權(quán)重矩陣W_0? 線性變換到一個target\_length * emb\_dim ? 的輸出。

這在多個Decoder之后,最后輸出的矩陣通過乘以權(quán)重矩陣W_1 (emb\_dim * vocab\_size) 進(jìn)行線性變換,變換之后再對每一行的向量softmax, 其中選擇值最大位置對應(yīng)詞表索引的詞就是預(yù)測的詞。

損失的話只需要用預(yù)測的每個詞向量與真實的詞的one-hot詞表示計算交叉熵即可。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容