前言
在18年末時,NLP各大公眾號、新聞媒體都被BERT(《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》)刷屏了,刷新了自然語言處理11項紀錄,也被稱為了2018年最強自然語言處理模型。
筆者很早便完整看了這篇論文,遲遲沒有動手,一方面是因為自己懶,各種事情耽擱了,另一方面還是想通過在具體的任務中進行了確切的實踐后再與大家分享交流。廢話不多說,下面我們進入正題,2018最強自然語言處理模型BERT(注意修飾語2018,因為最近冒出來的OpenAI的研究人員訓練的超大規模15億參數模型已經進一步打破了記錄,並開源了一個117M的小模型和代碼:https://github.com/openai/gpt-2,感興趣的讀者可以看看)
BERT模型概覽
了解BERT模型我們需要先回顧谷歌在早前發表的一些觀點和paper,我們首先來簡單回顧下seq2seq,之后說說attention引出的transformer模型,最后看看BERT模型的細節以及創新點。
Seq2Seq
關於Seq2Seq的模型抽象,筆者之前在淺談分詞算法系列博文中也有反復提及(淺談分詞算法(5)基於字的分詞方法(bi-LSTM)),在分詞或者詞性標注的NLP任務中,我們將文本序列映射到另一個結果序列,如詞性tag,分詞的BEMS標記tag等。
而在另一類NLP任務,即機器翻譯(MT)中,也可以抽象成一種序列到序列的模型,在谷歌2014年的論文Sequence to Sequence Learning with Neural Networks中,提出了端到端的序列到序列的映射模型,利用了LSTM分別做編碼和解碼(encoder-decoder)操作,並在英文與法文的互相翻譯上取得了很棒的效果。比較經典的模型了,網上資料一大把,我們來簡單說說這個模型,詳細的讀者可以翻論文和各種資料。
Seq2Seq即序列到序列的模型,將輸入序列\((x_1,x_2,...,x_T)\)映射到輸出序列\((y_1,y_2,...,y_{T'})\),其中每個\(y_t\)依賴於之前的輸出值\(y_1,y_2,...,y_{t-1}\),另外值得注意的是,在大多數應用場景中輸入序列和輸出序列的長度是不相等的\((T\neq T')\)。
NMT是典型的Seq2Seq應用場景,將輸入序列的一種語言翻譯為輸出序列的另一種語言,基於條件概率:
其中\(c_t\)表示通過注意力機制(Attention)計算的上下文信息:
條件概率可以通過以下公式進行計算:
其中
\(f(\cdot)\)是一個全連接層,對於機器翻譯任務而言,softmax函數用於計算詞典中每個詞的概率。
encoder-decoder的這種框架在深度模型中是非常常用的,很多實際的NLP問題可以抽象到這個框架上來進行建模,比如NMT(機器翻譯)、TTS(語音合成)、ASR(語音識別)等。
Attention
我們了解了Seq2Seq模型后,再來一起看看對其增加Attention后的模型。在Neural Machine Translation by Jointly Learning to Align and Translate這篇論文中,Bahdanau等人在原有Seq2Seq框架的基礎上新增了attention(注意力機制),其基於的主要先驗感知是:基本的encoder-decoder框架在達到瓶頸后,作者希望通過將源句中與目標句對應部分的關聯程度,由模型來自動查找(soft search),而不是硬性的切分。換句好理解的,源句中的每個詞在翻譯到目標詞匯時起到的作用是不一樣的,作者希望模型能夠學出這種關聯強度。舉個例子:
我/想/買/蘋果/手機。
I want to buy iPhone.
比如在翻譯蘋果這個詞的step時,這時候會有一個明顯的歧義,是吃的蘋果呢,還是商標品牌呢,那么源句中蘋果的上下文對翻譯時產生的影響是不一樣的,我/想/買,僅憑這三個詞我們並不能解決這個蘋果的歧義問題,但是看到蘋果后面跟着手機這個詞,歧義馬上消除,蘋果指品牌。那么在這個過程中,很明顯手機這個詞所起到的作用是遠遠大於我/想/買三個詞的。
這只是一個非常直觀上的感知,為什么Attention會起作用,當然模型內部的邏輯不會這么具化。下面我們一起看下具體的模型定義:
圖中我們可以看到大的還是一個encoder-decoder的框架,但是在decoder的過程中,不是將final-state直接作為輸入給到,而是在每個step解碼時,都考慮encoder過程中對每個詞的編碼,並以一個權重的形式加權后作為當前step預測的輸入,我們來具體看下模型定義:
- 模型輸入為:\(X=(x_0,x_1,x_2,...,x_T)\)
- 模型輸出為:\(Y=(y_0,y_1,y_2,....,y_T)\)
- encoder層隱層狀態:\(H=(h_0,h_1,h_2,...,h_T)\)
- decoder層上一時刻隱層狀態:\(s_{t-1}\)
- decoder層上一時刻的輸出:\(y_{t-1}\)
- 當前decoder step的上下文:\(c_t\)
圖中整個attention的流程是:
- 根據輸入X,利用雙向LSTM得到兩組隱向量,直接concat后得到最終的H;
- 根據encoder層得到的H,對於第t時刻的步驟,當前時刻的上下文\(c_t\)由以下方法計算:
其中\(\alpha_{ij}\)是權重,又稱為alignment;h是當前時刻step的encoder所有隱變量,又叫做value或者memory; i代表decoder層的時刻step, j代表encoder層的時刻step。
而\(\alpha_{ij}\)的計算方式如下:
其中\(e_{ij}=a(s_{i-1},h_j)\),我們來解釋下這波公式:本質上\(\alpha\)就是一個softmax,其作用就是用一個概率和為1的分布,來刻畫encoder不同時刻得到的隱變量的權重,從而衡量其對decoder的重要性;而\(e_{ij}\)其實是通過a,一種線性表達式來度量\(s_{i-1},h_j\)之間的相關程度。細化下a(在論文中稱為 alignment mode)的計算方式:
- 對\(s_{i-1}\)做線性映射,得到的向量起名為query,記為\(q_i\);
- 同樣對\(h_j\)做線性映射,得到的向量起名為key,記為\(k_j\);
- 最終\(e_{ij}=a(s_{i-1},h_j)=v^Ttanh(q_i+k_j)\),其中\(q_i k_j\)維度一致記為d,v的維度為d*1。
上面在做alignment時采用了query和key相加的方式,也即Bahdanau attention(以作者名字命名);還有另一種常見的Luong attention,核心就是\(e_{ij}\)的計算方式不同,換成相乘即乘性attention,具體計算公式如下:
乘性attention常用的即general一項,dot兩者直接相乘相當於是general的特例情況,即以進行過維度統一。還有一些其他變種,大家可以找找相關論文根據具體任務和x、y的向量空間具體分析。
總結下,attention在原有的encoder-decoder框架的基礎上,通過query和key的相關程度,之后通過類似softmax的歸一化操作得到權重,利用權重、隱狀態進行加權和得到上下文context向量,作為decoder層的輸入。直觀上attention使得與decoder層的狀態s相關度越大的h,權重會越高,從而在context中占比更多,對y的輸出影響更大,這也是直觀的先驗解釋,為什么attention會這么有效。
注意:這里記住query、key和value的由來,沒啥為什么,就起了個別名,在transfomer中它們還將發揮更多的作用。
Transformer
在聊完Attention機制后,我們來聊一聊更加激進的一個模型,Transformer,該框架是谷歌於2017年發表的一篇paper:Attention is all you need[3]。為什么說其激進呢,因為傳統意義上的attention框架是構建在RNN或者CNN的基礎上,而在該篇論文中,整個框架完全是基於attention的思路建立的,所以也就起名為Attetion is all you need(名字有點囂張...),下面我們一起來看看transformer模型,先上圖:
這張圖是論文中模型架構圖,從主體上看,模型還是遵照了encoder-decoder的框架,左半部分為編碼部分,右半部分為解碼部分,我們一步步拆解開說下。
圖中在Encoder和Decoder兩邊有Nx的標志,原文中也解釋了Encoder部分疊加了6個完全一樣的層,同樣Decoder層也包含6個相同的層,不過值得注意的是模型中每一層之間都用到了殘差連接。
對於每層Encoder的內部,首先會通過一個self-attention層,之后跟一層全連接前饋神經網絡;而對於每層Decoder的內部,首先通過一個self-attention,之后會多跟一個attention層用於單詞聚焦(類似之前RNN中加入的attention作用),之后同樣會跟一層全連接前饋神經網絡。
encoder部分
(1)self-attention
上文中Transformer的整體結構我們明白了,我們首先來闡述下self-attention。關於Attention我們在前文中已有詳細闡述,只不過之前的attention層依托於RNN之上,而在transformer中使用的self-attention其核心思想來源於attention,不過是獨立的一層模型,我們首先直觀的理解下self-attention的原理:
例句:因為小狗太累了,所以它沒有穿過街區。
我們在理解這句話中的“它”時,很容易關聯到“小狗”,那計算機在用向量表征“它”這個詞時,如何做到兼顧上下文呢?並且更多的關注到“小狗”這個詞,而不是“因為”或者“街區”這些詞呢?
我們先回顧一下RNN中計算步驟t的詞\(w_t\)的hidden-state時是怎么做的?輸入當前詞的embedding,並且接收來自t-1的狀態向量,根據遺忘門控制接收程度,這樣在計算當前詞的hidden-state向量時,就考慮了之前的序列(上文),那么如果也想考慮當前詞之后的序列(下文)怎么辦呢?那就正反各來一遍RNN,即bi-RNN。
現在回到self-attention模型,如果要計算當前詞的向量表征,那么我們需要關注上下文其它詞,並且每個詞對當前詞的影響不同,即注意力不同,下面我們進一步細剖析原理。
簡單回顧下上文attention時提到的query、key和value的概念(筆者專門讓大家注意),通過對s做映射得到query,對h做映射得到key,而h也即是value,之后通過query和key計算關聯度之后,利用加權和的方式獲得最終的編碼向量。
那么在self-attention中,所謂self就都是它自己,沒有所謂encoder和decoder的區分,對於經過embedding后的文本向量,通過三個不同的權重向量,分別映射成為query、key以及value,之后通過下圖的步驟得到最終的編碼向量:
我們可以看到除了映射成為query、key、value時的過程與attention不同外,后續的計算流程self-attention與attention是基本一致的,計算過程如下:
在閱讀原論文時,我們會發現論文中在self-attention的基礎上,有一個Multi-Head Attention,即多頭注意力機制,就是字面意思,將文本embedding通過多個self-attention得到不同的編碼結果,然后拼接起來進入下一層,如下圖:
這樣做的主要目的是從不同的語義空間投射原文本,能夠從更多的角度表征,並且能夠拓展模型對不同位置的關注能力。
(2)輸入編碼
包括CNN、LSTM,我們在進入模型前,需要先將自然語言通過embedding的方式進行編碼,從而將高維語義空間的自然語言轉化到低維的向量空間,這基本上成了目前NLP的通用模式。那么在Transformer中為什么我們要單獨再來說說embedding的問題呢?
熟悉LSTM的可以回憶下,我們將每個詞或者字符編碼成embedding后,會按照正序或者逆序進入LSTM,在這個過程中,序列中詞的位置信息是得到保存的;其實Text-CNN也可以通過不同大小的filter,捕捉到句子中詞序的信息。
但是在Transformer中,我們發現序列編碼好的矩陣,進入模型,分別通過三個不同的權重矩陣變成了Q、K、V,這個過程當中詞序的信息是丟失了的,為了解決這個問題,Transformer在Embedding的基礎上,加上了位置編碼(Position Encoding),沒錯就是兩者直接相加。原文中在對位置進行編碼時,用了一種神奇的規則,利用sine和cosine,正余弦函數來編碼,論文中解釋了也使用了可學習的位置編碼方式,但發現效果差不多,最后選擇了正余弦編碼方式,因為這種編碼方式能夠將位置編碼擴展到看不見長度的序列優點(比如推斷過程中出現的序列比訓練樣本中任何文本都長)。也比較玄學,感覺作者也是做了很多實驗對比選擇了一個較優方案吧,編碼方式如下:
其中pos是位置,i是維度。也就是說,位置編碼的每個維度波長形成從2π到10000·2π的幾何級數。而作者提到選擇此功能是因為其假設它可以讓模型輕松學會理解相對位置,因為對於任何固定偏移k,PEpos + k可以表示為一個線性的PEpos。
(3)殘差和FFN
還有兩個細節我們沒有提到,就是Transformer中的殘差和FFN部分。在FFN的部分,模型使用了ReLU作為激活函數,公式如下:
每一層會使用不同的參數,可以將其理解為兩個Kernel size為1的卷積。
另外一點是在模型結構中,每層都會間隔一層Add & Norm即layer-normalization,這里深度殘差網絡的特性,將前一層和前前一層的輸出進行求和正則后,作為下一層的輸入,該思想成名於圖像領域的ResNet,當時在imageNet的結果超過了之前的VGG等模型,感興趣的讀者可以瞅瞅。正則化時,文中提到了dropout設置為0.1。
Decoder部分
(1)結構
逐層理清模型中Encoder的部分,我們來看看Decoder部分。Decoder部分整體與Encoder是一致的,不過從模型圖的右邊我們可以看出其多了一個子層Multi-Head Attention,在Decoder時,會先進入第一個子層self-attention,得到Q,之后中間層的atention並不是self-attention,其K、V來自Encoder層,結合前一個Q,進入該子層,有這么幾點需要注意下:
- 第i個位置的Encoder輸出簡單變化作為K、V與i-1位置的Decoder第一個子層的輸出作為Q;
- Encoder在編碼時可以將輸入序列作為一個整體,以矩陣的形式一次性編碼,但Decoder時當前位置的輸出需要依賴上一個位置的輸出,所以是按順序解碼的;
- 在Decoder的第一個子層self attention做了mask,保證當前位置i的預測只依賴小於i位置的輸出,避免利用未來的信息;
- Decoder層在預測到類似
的結束符時便會停止預測。
(2)Linear和Softmax
Decoder經過兩個attention子層和一個FFN子層后的輸出,其解碼向量經過一個線性映射WX+b,將其映射到整個字典空間,然后經過softmax規整成字典詞匯的概率分布,即每個詞的分數,之后選取分數最高的詞作為輸出即可。
BERT
BERT全稱是Bidirectional Encoder Representations from Transformers,取了核心單詞的首字母而得名,從名字我們能看出該模型兩個核心特質:依賴於Transformer以及雙向,下面來看論文中的一結構對比圖:
論文在最一開始就與另外兩個pretrain模型:ELMo和OpenAI GPT做了對比,從結構上我們可以看出ELMo的基礎是使用了LSTM,而OpenAI GPT和BERT使用了Transformer作為基本模型。注意BERT一些核心的創新點:
- 相較於OpenAI GPT模型而言,其為雙向Transformer;
- 而同是雙向,ELMo由於是基於LSTM,BERT基於Transformer,並且核心的是兩者的目標函數是不一致的:
OpenAI GPT:$$P(w_i|w_1,...,w_{i-1})$$
ELMo:$$P(w_i|w_1,...,w_{i-1})和P(w_i|w_{i+1},...,w_n)$$
BERT:$$P(w_i|w_1,...,w_{i-1},w_{i+1},...,w_n)$$
即傳統意義雙向類似ELMo模型,根據上下文,每個詞會得到left-to-right和right-to-left兩種表示,我們可以將兩者concat在一起作為該詞的表示,再進行下游的任務操作。而直覺上,如果我們能有一個更加深入的雙向模型,直接能夠給出詞的上下文表示。遺憾的是,不可能訓練像普通LM一樣的深度雙向模型,因為這會產生一些循環,在這些循環中,單詞可以間接地“看到自己”,並且預測變得微不足道(其實這點有待進一步商榷)。
所以BERT采用了一些非常簡單的trick來實現,
利用自編碼器,從輸入中掩蓋了一部分的單詞並且必須從上下文重構這些單詞。即所謂Masked LM,其實就是通常意義上的“完形填空”。
關於雙向的設計思路,BERT作者在https://www.reddit.com/r/MachineLearning/comments/9nfqxz/r_bert_pretraining_of_deep_bidirectional/進行過詳細論述,感興趣讀者可以移步看看。
Embedding
在前文Transformer我們已經詳細闡述過了一句話進入模型的Embedding過程,BERT除了token embedding和position embedding,由於還需要以兩個句子作為輸入,還添加了segment embedding,如下圖:
- Token Embeddings是詞向量(中文進入就是字符向量),第一個單詞是CLS標志,可以用於之后的分類任務
- Segment Embeddings用來區別兩種句子,因為預訓練不光做LM還要做以兩個句子為輸入的分類任務
- Position Embeddings和之前文章中的Transformer不一樣,不是三角函數而是學習出來的
預訓練
BERT為了能夠在大規模語料上進行無監督學習,非常巧妙的設計了兩個預訓練任務:一個是隨機遮蔽(mask)掉一個句子中的詞,利用上下文進行預測;另一個是預測下一個句子(類似QA場景)。
(1) Task #1: Masked LM
Input:
the man [MASK1] to [MASK2] store
Label:
[MASK1] = went; [MASK2] = store
該任務就是BERT為了做到雙向深度上下文表示設計的預訓練trick任務,而在mask單詞的時候,作者也采用了一些技巧,隨機mask掉15%的token,最終的損失函數只計算mask掉的token。而對於被mask掉的詞也並非簡單粗暴的將全部替換成[MASK]標簽完事,會遵循如下步驟:
- 80%即大部分情況下,被mask掉的詞會被[MASK]標簽代替;
- 10%的情況下,將該詞用一個隨機的詞替換掉;
- 10%的情況下,保留該詞在原位置。
這樣做的目的是偏向代表實際觀察到的詞。另外模型在預訓練時,Transformer編碼器並不知道哪些詞被mask掉了,所以模型對每個詞都會關注。同時,因為隨機替換僅發生在所有詞的1.5%(即15%*10%),對模型的語言理解能力影響很小。
(2) Task #2: Next Sentence Prediction
Input:
the man went to the store [SEP] he bought a gallon of milk
Label:
IsNext
Input:
the man went to the store [SEP] penguins are flightless birds
Label:
NotNext
由於在LM的下游任務還會涉及到問答(Question Answering (QA) )和推理( Natural Language Inference (NLI))的任務,這需要LM有理解句子間關系的能力,所以作者新增了一個預訓練任務,輸入句子A和B,預測B是否為A的下一個句子,以50%的概率配對A和B,即50%B是真的,50%B是隨機選取的一個句子。
所以作者提示在選取預訓練語料時,要盡可能選取document-level的語料而非segment-level混合在一起的語料。
文本分類試驗
利用BERT我在文本分類任務上進行了嘗試,語料集是用戶評論內容,目標是預測用戶評論內容的情感極性,分為正中負三類。
BERT源碼拉下來后需要進行一些簡單調整,比如將TPUEstimator換成普通的estimator,改變一些模型指標計算方式等。
筆者首先利用1000W左右的評論語料對BERT的中文預訓練模型進行了遷移學習,之后通過500W語料分別在text-cnn、lstm concat cnn以及lstm concat cnn with bert上進行了訓練對比。(注:這里lstm concat cnn是筆者在該任務上試驗后選取的效果較好的模型結構)
指標情況如下:
Text_cnn
Avg acc: 0.8802
sentence num:13530, tags all num:13530, neg num:5539, neu num:1398, pos num:6593
precision recall f1-score support
0 0.88 0.96 0.92 5539
1 0.54 0.22 0.31 1398
2 0.91 0.95 0.93 6593
avg / total 0.86 0.88 0.86 13530
LSTM concat CNN
precision recall f1-score support
0 0.92 0.96 0.94 5539
1 0.60 0.43 0.50 1398
2 0.93 0.95 0.94 6593
avg / total 0.89 0.90 0.89 13530
0.8997782705099778
3.9ms/條
LSTM concat CNN with BERT
precision recall f1-score support
0 0.92 0.96 0.94 5539
1 0.67 0.35 0.46 1398
2 0.90 0.97 0.94 6593
avg / total 0.89 0.90 0.89 13530
0.9000739098300073
29.365628ms/條
筆者在實驗過程中發現的幾點需要注意:
- 直接用BERT進行fine-tune的效果,在中文語料上效果一般,因為中文是以字作為embedding來考慮的,丟失了太多的詞語信息;
- 將BERT作為輔助上下文,添加在別的模型中,效果會有所提升,但效果有限;
- 添加BERT后,CPU做inference速度較慢,需要考慮計算成本
鑒於第3點,筆者嘗試了將BERT中的字向量完全抽出來,作為輔助輸入到模型中,但是這種方式的效果不是很好,直覺上BERT需要依賴上下文來求得當前token的embedding,單獨抽出來失去了其雙向深度編碼的優勢。
后續筆者准備嘗試下百度的https://github.com/PaddlePaddle/LARK/tree/develop/ERNIE,其考慮了中文的詞語信息,更適合中文場景,BERT只能進行純字的embedding在中文場景效果提升有限。
另外還有更好的想法歡迎大家留言一起討論~
參考文獻
- Sutskever I, Vinyals O, Le Q V. Sequence to sequence learning with neural networks[C]//Advances in neural information processing systems. 2014: 3104-3112.
- Bahdanau D, Cho K, Bengio Y. Neural machine translation by jointly learning to align and translate[J]. arXiv preprint arXiv:1409.0473, 2014.
- Vaswani A, Shazeer N, Parmar N, et al. Attention is all you need[J]. Advances in Neural Information Processing Systems, 2017: 5998-6008.
- Devlin J, Chang M W, Lee K, et al. Bert: Pre-training of deep bidirectional transformers for language understanding[J]. arXiv preprint arXiv:1810.04805, 2018.
- https://github.com/google-research/bert