在2017年之前,語言模型都是通過RNN,LSTM來建模,這樣雖然可以學習上下文之間的關系,但是無法並行化,給模型的訓練和推理帶來了困難,因此有人提出了一種完全基於attention來對語言建模的模型,叫做transformer。transformer擺脫了NLP任務對於RNN,LSTM的依賴,使用了self-attention的方式對上下文進行建模,提高了訓練和推理的速度。
1、Transformer
<1> Inputs是經過padding的輸入數據,大小是[batch size, max seq length]。
<2> 初始化embedding matrix,通過embedding lookup將Inputs映射成token embedding,大小是[batch size, max seq length, embedding size],然后乘以embedding size的開方。
<3> 通過sin和cos函數創建positional encoding,表示一個token的絕對位置信息,並加入到token embedding中,然后dropout。
<4> multi-head attention
<4.1> 輸入token embedding,通過Dense生成Q,K,V,大小是[batch size, max seq length, embedding size],然后按第2維split成num heads份並按第0維concat,生成新的Q,K,V,大小是[num heads*batch size, max seq length, embedding size/num heads],完成multi-head的操作。
<4.2> 將K的第1維和第2維進行轉置,然后Q和轉置后的K的進行點積,結果的大小是[num heads*batch size, max seq length, max seq length]。
<4.3> 將<4.2>的結果除以hidden size的開方(在transformer中,hidden size=embedding size),完成scale的操作。
<4.4> 將<4.3>中padding的點積結果置成一個很小的數(-2^32+1),完成mask操作,后續softmax對padding的結果就可以忽略不計了。
<4.5> 將經過mask的結果進行softmax操作。
<4.6> 將softmax的結果和V進行點積,得到attention的結果,大小是[num heads*batch size, max seq length, hidden size/num heads]。
<4.7> 將attention的結果按第0維split成num heads份並按第2維concat,生成multi-head attention的結果,大小是[batch size, max seq length, hidden size]。Figure 2上concat之后還有一個linear的操作,但是代碼里並沒有。
<5> 將token embedding和multi-head attention的結果相加,並進行Layer Normalization。
<6> 將<5>的結果經過2層Dense,其中第1層的activation=relu,第2層activation=None。
<7> 功能和<5>一樣。
<8> Outputs是經過padding的輸出數據,與Inputs不同的是,Outputs的需要在序列前面加上一個起始符號”<s>”,用來表示序列生成的開始,而Inputs不需要。
<9> 功能和<2>一樣。
<10> 功能和<3>一樣。
<11> 功能和<4>類似,唯一不同的一點在於mask,<11>中的mask不僅將padding的點積結果置成一個很小的數,而且將當前token與之后的token的點積結果也置成一個很小的數。
<12> 功能和<5>一樣。
<13> 功能和<4>類似,唯一不同的一點在於Q,K,V的輸入,<13>的Q的輸入來自於Outputs 的token embedding,<13>的K,V來自於<7>的結果。
<14> 功能和<5>一樣。
<15> 功能和<6>一樣。
<16> 功能和<7>一樣,結果的大小是[batch size, max seq length, hidden size]。
<17> 將<16>的結果的后2維和embedding matrix的轉置進行點積,生成的結果的大小是[batch size, max seq length, vocab size]。
<18> 將<17>的結果進行softmax操作,生成的結果就表示當前時刻預測的下一個token在vocab上的概率分布。
<19> 計算<18>得到的下一個token在vocab上的概率分布和真實的下一個token的one-hot形式的cross entropy,然后sum非padding的token的cross entropy當作loss,利用adam進行訓練。
more : multi-head相當於把一個大空間划分成多個互斥的小空間,然后在小空間內分別計算attention,雖然單個小空間的attention計算結果沒有大空間計算得精確,但是多個小空間並行然后concat有助於網絡捕捉到更豐富的信息。
當模型變得越來越大,樣本數越來越多的時候,self-attention無論是並行化帶來的訓練提速,還是在長距離上的建模,都是要比傳統的RNN,LSTM好很多。transformer現在已經各種具有代表性的nlp預訓練模型的基礎,bert系列使用了transformer的encoder,gpt系列transformer的decoder。在推薦領域,transformer的multi-head attention也應用得很廣泛。
2、BERT
在bert之前,將預訓練的embedding應用到下游任務的方式大致可以分為2種,一種是feature-based,例如ELMo這種將經過預訓練的embedding作為特征引入到下游任務的網絡中;一種是fine-tuning,例如GPT這種將下游任務接到預訓練模型上,然后一起訓練。然而這2種方式都會面臨同一個問題,就是無法直接學習到上下文信息,像ELMo只是分別學習上文和下文信息,然后concat起來表示上下文信息,抑或是GPT只能學習上文信息。因此,作者提出一種基於transformer encoder的預訓練模型,可以直接學習到上下文信息,叫做bert。bert使用了12個transformer encoder block,在13G的數據上進行了預訓練,可謂是nlp領域大力出奇跡的代表。在整個流程上與transformer encoder沒有大的差別,只是在embedding,multi-head attention,loss上有所差別。
bert和transformer在embedding上的差異主要有3點:
<1> transformer的embedding由2部分構成,一個是token embedding,通過embedding matrix lookup到token_ids上生成表示token的向量;一個是position embedding,是通過sin和cos函數創建的定值向量。而bert的embedding由3部分構成,第一個同樣是token embedding,通過embedding matrix lookup到token_ids上生成表示token的向量;第二個是segment embedding,用來表達當前token是來自於第一個segment,還是第二個segment,因此segment vocab size是2;第三個是position embedding,與transformer不同的是,bert創建了一個position embedding matrix,通過position embedding matrix lookup到token_ids的位置上生成表示token位置的位置向量。
<2> transformer在embedding之后跟了一個dropout,但是bert在embedding之后先跟了一個layer normalization,再跟了一個dropout。
<3> bert在token序列之前加了一個特定的token“[cls]”,這個token對應的向量后續會用在分類任務上;如果是句子對的任務,那么兩個句子間使用特定的token“[seq]”來分割。
bert和transformer在multi-head attention上的差異:
<1> transformer在<4.7>會concat<4.6>的attention的結果。而bert不僅會concat<4.6>的attention的結果,還會把前N-1個encoder block中attention的結果都concat進來。
<2> transformer在<4.7>之后沒有linear的操作,而bert在transformer的<4.7>之后有一個linear的操作。
bert和transformer在loss上的差異:bert預訓練的loss由2部分構成,一部分是NSP的loss,就是token“[cls]”經過1層Dense,然后接一個二分類的loss,其中0表示segment B是segment A的下一句,1表示segment A和segment B來自2篇不同的文本;另一部分是MLM的loss,segment中每個token都有15%的概率被mask,而被mask的token有80%的概率用“<mask>”表示,有10%的概率隨機替換成某一個token,有10%的概率保留原來的token,被mask的token經過encoder后乘以embedding matrix的轉置會生成在vocab上的分布,然后計算分布和真實的token的one-hot形式的cross entropy,最后sum起來當作loss。這兩部分loss相加起來當作total loss,利用adam進行訓練。bert fine-tune的loss會根據任務性質來設計,例如分類任務中就是token“[cls]”經過1層Dense,然后接了一個二分類的loss;例如問題回答任務中會在paragraph上的token中預測一個起始位置,一個終止位置,然后以起始位置和終止位置的預測分布和真實分布為基礎設計loss;例如序列標注,預測每一個token的詞性,然后以每一個token在詞性的預測分布和真實分布為基礎設計loss。 bert在encoder之后,在計算NSP和MLM的loss之前,分別對NSP和MLM的輸入加了一個Dense操作,這部分參數只對預訓練有用,對fine-tune沒用。而transformer在decoder之后就直接計算loss了,中間沒有Dense操作。
bert的框架決定了這個模型適合解決自然語言理解的問題,因為沒有解碼的過程,所以bert不適合解決自然語言生成的問題。
增大預訓練模型的大小通常能夠提高預訓練模型的推理能力,但是當預訓練模型增大到一定程度之后,會碰到GPU/TPU memory的限制。因此,albert作者在bert中加入了2項減少參數的技術,能夠縮小bert的大小,並且修改了bert NSP的loss,在和bert有相同參數量的前提之下,有更強的推理能力。
3、albert
在bert以及諸多bert的改進版中,embedding size都是等於hidden size的,這不一定是最優的。因為bert的token embedding是上下文無關的,而經過multi-head attention+ffn后的hidden embedding是上下文相關的,bert預訓練的目的是提供更准確的hidden embedding,而不是token embedding,因此token embedding沒有必要和hidden embedding一樣大。albert將token embedding進行了分解,首先降低embedding size的大小,然后用一個Dense操作將低維的token embedding映射回hidden size的大小。bert的embedding size=hidden size,因此詞向量的參數量是vocab size * hidden size,進行分解后的參數量是vocab size * embedding size + embedding size * hidden size,只要embedding size << hidden size,就能起到減少參數的效果。
bert的12層transformer encoder block是串行在一起的,每個block雖然長得一模一樣,但是參數是不共享的。albert將transformer encoder block進行了參數共享,這樣可以極大地減少整個模型的參數量。
在auto-encoder的loss之外,bert使用了NSP的loss,用來提高bert在句對關系推理任務上的推理能力。而albert放棄了NSP的loss,使用了SOP的loss。NSP的loss是判斷segment A和segment B之間的關系,其中0表示segment B是segment A的下一句,1表示segment A和segment B來自2篇不同的文本。SOP的loss是判斷segment A和segment B的的順序關系,0表示segment B是segment A的下一句,1表示segment A是segment B的下一句。
albert使用了2項參數減少的技術,但是2項技術對於參數減少的貢獻是不一樣的,第1項是詞向量矩陣的分解,當embedding size從768降到64時,可以節省21M的參數量,但是模型的推理能力也會隨之下降。第2項是multi-head attention+ffn的參數共享,在embedding size=128時,可以節省77M的參數量,模型的推理能力同樣會隨之下降。雖然參數減少會導致了模型推理能力的下降,但是可以通過增大模型使得參數量變回和bert一個量級,這時模型的推理能力就超過了bert。
在albert之前,很多bert的改進版都對NSP的loss提出了質疑。structbert在NSP的loss上進行了修改,有1/3的概率是segment B是segment A的下一句,有1/3的概率是segment A是segment B的下一句,有1/3的概率是segment A和segment B來自2篇不同的文本。roberta則是直接放棄了NSP的loss,修改了樣本的構造方式,將輸入2個segment修改為從一個文本中連續sample句子直到塞滿512的長度。當到達文本的末尾且未塞滿512的長度時,先增加一個“[sep]”,再從另一個文本接着sample,直到塞滿512的長度。
albert在structbert的基礎之上又拋棄了segment A和segment B來自2篇不同的文本的做法,只剩下1/2的概率是segment B是segment A的下一句,1/2的概率是segment A是segment B的下一句。論文中給出了這么做的解釋,NSP的loss包含了2部分功能:topic prediction和coherence prediction,其中topic prediction要比coherence prediction更容易學習,而MLM的loss也包含了topic prediction的功能,因此bert難以學到coherence prediction的能力。albert的SOP loss拋棄了segment A和segment B來自2篇不同的文本的做法,讓loss更關注於coherence prediction,這樣就能提高模型在句對關系推理上的能力。
albert雖然減少參數量,但是並不會減少推理時間,推理的過程只不過是從串行計算12個transformer encoder block變成了循環計算transformer encoder block 12次。albert最大的貢獻在於使模型具備了比原始的bert更強的成長性,在模型變向更大的時候,推理能力還能夠得到提高。
4、GPT、structbert、xlnet
gpt在bert之前就發表了,使用了transformer decoder作為預訓練的框架。在看到了decoder只能get上文信息,不能get下文信息的缺點之后,bert改用了transformer encoder作為預訓練的框架,能夠同時get上下文信息,獲得成功。
structbert的創新點主要在loss上,除了MLM的loss外,還有一個重構token順序的loss和一個判斷2個segment關系的loss。重構token順序的loss是以一定的概率挑選segment中的token三元組,然后隨機打亂順序,最后經過encoder之后能夠糾正被打亂順序的token三元組的順序。判斷2個segment關系的loss是1/3的概率是segment B是segment A的下一句,有1/3的概率是segment A是segment B的下一句,有1/3的概率是segment A和segment B來自2篇不同的文本,通過“[cls]”預測樣本屬於這3種的某一種。
在xlnet使用126G的數據登頂GLUE之后不久,roberta使用160G的數據又打敗了xlnet。roberta的創新點主要有4點:第1點是動態mask,之前bert使用的是靜態mask,就是數據預處理的時候完成mask操作,之后訓練的時候同一個樣本都是相同的mask結果,動態mask就是在訓練的時候每輸入一個樣本都要重新mask,動態mask相比靜態mask有更多不同mask結果的數據用於訓練,效果很好。第2點是樣本的構造方式,roberta放棄了NSP的loss,修改了樣本的構造方式,將輸入2個segment修改為從一個文本中連續sample句子直到塞滿512的長度。當到達文本的末尾且未塞滿512的長度時,先增加一個“[sep]”,再從另一個文本接着sample,直到塞滿512的長度。第3點是增大了batch size,在訓練相同數據量的前提之下,增大batch size能夠提高模型的推理能力。第4點是使用了subword的分詞方法,類比於中文的字,相比於full word的分詞方法,subword的分詞方法使得詞表的大小從30k變成了50k,雖然實驗效果上subword的分詞方法比full word差,但是作者堅信subword具備了理論優越性,今后肯定會比full word好。
本文來自某大神的文章,僅個人學習理解用,侵刪~