1. BERT簡介

Transformer架構的出現,是NLP界的一個重要的里程碑。它激發了很多基於此架構的模型,其中一個非常重要的模型就是BERT。

BERT的全稱是Bidirectional Encoder Representation from Transformer,如名稱所示,BERT僅使用了Transformer架構的Encoder部分。BERT自2018年由谷歌釋出後,在多種NLP任務中(例如QA、文字生成、情感分析等等)都實現了更好的結果。

BERT的效果如此優異,其中一個主要原因是:它是一個基於上下文的詞嵌入(context-based embedding)模型。我們在之前的文章中(“NLP與深度學習(一)NLP任務流程”)

提到過:

“Word2vec與GloVe都有一個特點,就是它們是上下文無關(context-free)的詞嵌入。所以它們沒有解決:一個單詞在不同上下文中代表不同的含義的問題。例如,對於單詞bank,它在不同的上下文中,有銀行、河畔這種差別非常大的含義。“

BERT的出現,解決了這個問題。下面我們便從context-based與context-free開始,逐步介紹BERT模型。

2. Context-Based與Context-Free

首先我們介紹一下context-based與context-free這兩種詞嵌入(Embedding)的區別。

2.1. Context-Free Embedding

看看下面2條句子:

1. He got bit by Python

2. Python is my favorite programming language

作為人類,我們可以很容易分辨這2個句子中單詞Python的不同含義。在第1個句子中表示的是蟒蛇,第2個句子中表示的一種程式語言。而如果我們用context-free的詞嵌入(例如word2vec)來表示單詞Python,則單詞Python在上面2個句子中的表示方法都是同一個詞向量。也即是說,這樣會導致單詞Python在2個句子中的含義是一樣的。顯然,這個在我們實際場景中是不合適的。

這便是context-free embedding的缺點:無論某個單詞的上下文語境如何,它都僅有同一種對單詞的表示方法。

2.2. Context-Based Embedding

另外一種更好的方式便是Context-Based Embedding,也就是BERT使用的方式。它可以基於單詞的上下文,生成不同的詞嵌入表示。例如在上面的2個句子中,它就可以根據單詞Python所處的上下文,生成對它不同的詞向量表示。

以BERT為例,再次回顧上面提到的第1個句子:He got bit by Python。

BERT會將每個單詞與句子中所有其他單詞計算相關性,據此來理解每個單詞的上下文。也就是說,為了理解單詞“Python“的上下文含義,BERT會將單詞”Python“與其句子中所有其他單詞進行關聯,瞭解它們之間的相關性(也就是前文介紹過的multi-head self-attention機制)。所以在第1個句子中,BERT可以通過單詞”bit“來理解單詞”Python“的含義為”蟒蛇“。如下圖所示:

再看第2個句子“Python is my favorite programming language“。同樣,BERT會將每個單詞與句子中所有單詞進行相關性計算,並據此得到每個單詞的上下文資訊。所以對於單詞”Python“,BERT同樣會計算單詞”Python“與句子中每個單詞的相關性,並據此理解單詞“Python”的含義。例如,在這個句子中即根據單詞“programming“,BERT可以理解到單詞“Python”的含義為“程式語言”,如下圖所示:

像word2voc、GloVe這種context-free的詞嵌入,總是會對單詞輸出固定的詞向量,而忽略了上下文的資訊。而BERT這種context-based的方式,可以根據不同上下文,動態生成不同詞向量的方法,更符合我們人類對語言的理解(當然,這種動態的表示也是經過了大量樣本訓練後得出)。最後,BERT在各個NLP任務中的表現,也證實了這種方式是更優秀的。

3. BERT的基本原理

前面提到BERT基於的是Transformer模型,並且僅使用Transformer模型的Encoder部分。在Transformer模型中,Encoder的輸入是一串序列,輸出的是對序列中每個字元的表示。同樣,在BERT中,輸入的是一串序列,輸出的是也是對應序列中每個單詞的編碼。

仍以前面提到的“He got bit by Python”為例,BERT的輸入輸出如下圖所示:

Fig. 1 Sudharsan Ravichandiran. Understanding the BERT Model[1]

其中輸入為序列“He got bit by Python”,輸出的是對每個單詞的編碼Rword。這樣在經過了BERT處理後,即得到了對每個單詞包含的上下文表示Rword

這便是BERT的基本原理,下面我們介紹BERT的不同配置。

4. BERT的配置

BERT的研究人員提出了2個標準配置的BERT模型:

l  BERT-base

l  BERT-large

BERT-base用於與其他架構進行對比,並以此衡量其他架構的效能。BERT-large則是用在了論文中對各個NLP任務的結果展示。

下面分別介紹這2個配置的BERT模型。

4.1. BERT-base

BERT-base包含:

  1. 12個堆疊的encoder層,上一層的Encoder的輸出是下一層Encoder的輸入
  2. 每個Encoder使用12-head attention
  3. Encoder中的前饋網路包含768個隱藏單元(也就是說輸出的Rword維度為768維)

使用以下符號表示這幾個概念:

  1. Encoder堆疊的層數表示為L
  2. Attention的頭(multi-head)數表示為A
  3. 前饋網路的隱藏單元數表示為H

則在BERT-base模型中,L=12,A=12,H=768。模型的總引數個數為1億1千萬(100 million)。BERT-base模型如下圖所示:

4.2. BERT-large

BERT-large包含:

  1. 24個堆疊的Encoder層
  2. 每個Encoder使用16-head attention
  3. Encoder中的前饋網路包含1024個隱藏單元(也就是說輸出的Rword維度為1024維)

也就是說,在BERT-large模型中,L=24,A=16,H=1024。模型的總引數個數為3億4千萬(340 million)。

4.3. 其他BERT配置

除了上面2種標準配置外,我們也可以使用其他的配置來構建BERT模型。部分較小配置的BERT模型如:

  1. Bert-tiny:L=2,H=128
  2. Bert-mini:L=4,H=256
  3. Bert-small:L=4,H=512
  4. Bert-medium:L=8,H=512

這些小配置的BERT模型如下圖所示:

在計算資源有限的場景下,我們可以使用更小配置的BERT模型。不過,最常用的還是BERT-base和BERT-large,它們的準確率也相對小配置BERT模型更高。

在瞭解了BERT的配置後,下面介紹BERT模型的訓練。

5. BERT輸入資料的表示

首先需要明確的一個點是:BERT是一個預訓練模型。也就是說,它是在大量資料集上進行了預訓練後,才被應用到各類NLP任務中。在對BERT模型進行預訓練時,與前面介紹過的所有模型一樣,輸入的文字需要先進行處理後,才能送入到模型中。而在將文字資料輸入到BERT前,會使用到以下3個Embedding層:

  1. Token embedding
  2. Segment embedding
  3. Position embedding

下面逐個介紹這3個Embedding 層。

5.1. Token Embedding

Token Embedding就是將一個序列做分詞,把序列轉為一串單詞以及它們對應的詞向量。舉個例子,假設有2個句子,分別為:

  1. Beijing is a beautiful city.
  2. I love Beijing

首先,對2個句子進行分詞得到token,結果如下所示:

Tokens = [Beijing, is, a, beautiful, city, I, love, Beijing]

(需要特別注意的是:這裡為了方便解釋,我們使用了最普遍的分詞法為例,而BERT中使用的是WordPiece分詞。這種分詞方法可以顯著減少詞庫的大小,WordPiece基於的是BPE(Byte Pair Encoding),BPE屬於subword分詞法中的一種。

有關WordPiece的介紹我們前面已在“NLP與深度學習(一)NLP任務流程”中有過介紹,在此不再贅述。)

然後,我們在最前面加上1個[CLS] 的token,例如:

Tokens = [ [CLS], Beijing, is, a, beautiful, city, I, love, Beijing]

最後,在每個句子的後面分別加上[SEP] 的token,例如:

Tokens = [ [CLS], Beijing, is, a, beautiful, city, [SEP], I, love, Beijing, [SEP]]

這2個特殊的token我們之前(見“NLP與深度學習(一)NLP任務流程” )介紹過:“ [CLS] 、[SEP] 與[PAD] 是BERT Tokenizer中的保留詞,分別代表“分類任務”、“Sequences之間的間隔”,以及序列補全(序列補全與截斷是NLP任務中常用的方法,用於將不同長度的文字統一長度)。 ”

在做完以上步驟後,在輸入到BERT中之前,還需要將每個單詞轉換為與之對應的詞向量。如:

Input:[  [CLS], Beijing, is, a, beautiful, city, [SEP], I, love, Beijing, [SEP]  ]

Embedding: [  E[CLS], EBeijing, Eis, Ea, Ebeautiful, Ecity, E[SEP], EI, Elove, EBeijing, E[SEP]  ]

這樣即完成了Token Embedding。

5.2. Segment Embedding

接下來是一層Segment Embedding,用於區分2個給定的句子。仍以上面的2個句子為例。在對這2個句子做了分詞處理後,結果為:

Tokens = [ [CLS], Beijing, is, a, beautiful, city, [SEP], I, love, Beijing, [SEP]]

但是,除了[SEP] 外,我們還需要一種方式來告知模型哪個單詞屬於哪個句子。這也就是Segment Embedding做的事情。在將例子中的Tokens輸入到segment embedding後,它僅會輸出2種embedding,分別為EA或EB。也就是說,如果單詞屬於句子1,則輸出EA;如果屬於句子2,則輸出EB。如下所示:

Input:                        [  [CLS], Beijing, is, a, beautiful, city, [SEP], I, love, Beijing, [SEP]  ]

Segment Embedding:[  EA,   EA,     EA, EA, EA,      EA,  EA,  EB, EB,   EB,    EB,   ]

如果只有1個句子,則僅輸出EA

5.3. Position Embedding

BERT基於的是Transformer的Encoder,而由於Transformer中Encoder本身不包含Positional Encoding,所以BERT中還需要一層Position Embedding來指示單詞在句子中的位置。經過Position Embedding處理後,輸出如下所示:

Input:                        [  [CLS], Beijing, is, a, beautiful, city, [SEP], I, love, Beijing, [SEP]  ]

Position Embedding: [  E0,   E1,    E2,E3,E4,     E5,  E6,  E7,E8,  E9,    E10   ]

我們在上一章(見“NLP與深度學習(四)Transformer模型”)介紹Transformer時,提到在Transformer中也有一個Positional Embedding,使用的是正弦曲線的方法:

“在原論文中,作者還提到了另一種位置編碼的方法:直接通過訓練學習得到位置編碼。並且從測試結果來看,兩者幾乎沒什麼差別。”

但是在BERT中,作者使用的是“直接通過訓練學習”得到的位置編碼,但並沒有給出具體理由。但是實驗上(從Transformer論文中的試驗來看)效果應該差不多。

5.4. 輸入的最終表示

在將輸入序列經過上述3層embedding處理後,將每層embedding的結果進行相加,即得到了輸入資料的最終表示,也就是BERT模型的輸入。

如下所示:

BERT 論文[5]中的一個例子為:

6. BERT預訓練的方法

BERT模型在進行預訓練時,基於的是2種類型的任務:

  1. Masked language modeling(帶掩碼的語言模型)
  2. Next sentence prediction(預測下一條句子)

下面會分別介紹這2個任務。不過,在介紹Masked Language Modeling (帶掩碼的語言模型)任務前,有必要先介紹一下什麼是Language Modeling任務。

6.1. Language Modeling

在Language Modeling任務中,我們會給模型輸入一個單詞序列(句子),並令模型預測這個序列(句子)的下一個單詞。這種Language Modeling可以劃分為2類:

  1. Auto-regressive language modeling(自動迴歸的語言建模)
  2. Auto-encoding language modeling(自動編碼的語言建模)

Auto-regressive language modeling

自動迴歸的語言建模又可以分為以下2類:

  1. 前向預測(Forward prediction,從左到右)
  2. 後向預測(Backward prediction,從右到左)

以“Beijing is a beautiful city. I love Beijing”為例。我們將city單詞移除,替換為空,如下所示:

Beijing is a beautiful _. I love Beijing

接下來,我們讓模型預測空白位置的單詞:

  1. 如果使用Forward prediction,則模型從左到右讀入輸入,並做預測,例如:“Beijing is a beautiful _”。
  2. 而若是使用Backward prediction,則模型從右到左讀入輸入,並左預測,例如“_ I love Beijing”。

所以Auto-regressive models是單向的,僅從1個方向讀入句子。

Auto-encoding language modeling

與Auto-regressive models 不同的是,Auto-regressive models是雙向的。也就是說,它會從兩個方向均讀入句子,並對空白詞做預測。例如:

Beijing is a beautiful _. I love Beijing

雙向的模型的效果會更好,因為從2個方向讀入句子會更有助於理解上下文(例如我們之前介紹過的雙向RNN)。

以上便是language modeling的介紹,下面我們繼續來討論BERT中使用的預訓練方式——Masked Language Modeling。

6.2. Masked Language Modeling

BERT是一種Auto-encoding 語言模型,也就是說,它是從2個方向讀入句子並做預測。在masked language modeling 任務中(也稱為填空任務cloze task),對任何一個句子,隨機遮擋(mask)其中15%的單詞,並訓練網路預測這些單詞。在進行預測時,模型會從雙向讀入句子,並進行預測。

仍以“Beijing is a beautiful city. I love Beijing”為例。首先對句子做分詞,得到單個單詞:

Tokens = [Beijing, is, a, beautiful, city, I, love, Beijing]

然後在最前面加上[CLS],並在每個句子後加上[SEP] 的特殊標記:

Tokens = [ [CLS], Beijing, is, a, beautiful, city, [SEP], I, love, Beijing, [SEP]]

下面對15%的單詞做遮擋(mask),假設遮擋的是單詞“city”,則使用特殊標記[MASK]替換單詞“city”:

Tokens = [ [CLS], Beijing, is, a, beautiful, [MASK], [SEP], I, love, Beijing, [SEP]]

這樣即可提供給BERT去做訓練了。但是,這種方法會有一個小問題:導致pre-train與fine-tuning之間存在差異。假設我們使用了上述方式去對BERT進行訓練,而後再應用到遷移學習fine-tuning中。在預訓練的時候,語料庫裡是有[MASK] 這個標誌的,但是在實際的遷移學習中,輸入的文字是不包含[MASK]標誌的。所以這裡會導致pre-train與fine-tuning在使用時有一個gap。

為了解決這個問題,研究人員制定了80%,10%,10%的規則。具體地說,在隨機選擇了15%的單詞被遮擋後,對於這15%的單詞,做如下處理:

  1. 80%的概率將被遮擋的單詞替換為[MASK]。
  2. 10%的概率將被遮擋的單詞替換為1個隨機單詞,例如將上例中的“city”替換為“dog”
  3. 10%的概率不做任何改變,例如上例中“city”仍然是“city”

這樣做的好處是:

  1. 解決了與fine-tuning的gap問題。80%的遮擋率既應用了[MASK],也有20%的概率不使用[MASK]
  2. 存在10%的概率不做任何改變,保留了原有語義,讓模型可以有機會了解原始資料樣貌
  3. 存在10%的概率替換隨機單詞,可以使得模型不僅僅依賴於看到過的原始資料,而是還讓資料依賴於句子的上下文來預測目標詞,達到“糾錯”的目的

在對單詞序列進行了遮擋處理後,即可送入到BERT模型中進行訓練。此時,單詞序列會依次進入Token Embedding,Segment Embedding以及Position Embedding,並得到它們的最終表示。整個過程如下圖所示:

需要注意的是,這裡僅需要將E([MASK]) 輸入到前饋網路與softmax中即可。因為BERT基於的是multi-head self-attention,所以E([MASK]) 中已經包含了整個句子的資訊。

除了以上介紹的這種遮擋單詞的方法外,還有另一種稍微不同的方法,稱為whole word masking(WWM,全詞掩碼)。

Whole Word Masking

在介紹WWM之前,首先需要回顧的一個知識點是:BERT中使用的是WordPiece分詞法。以句子“let us start pretraining the model”為例,在對一個句子進行分詞後,生成的單詞為:

Tokens = [let, us, start, pre, ##train, ##ing, the, model]

加上 [CLS] 與 [SEP]:

Tokens = [ [CLS], let, us, start, pre, ##train, ##ing, the, model, [SEP] ]

隨機遮擋15%的單詞,假設遮擋的單詞為 let 與 ##train,則:

Tokens = [ [CLS], [MASK], us, start, pre, [MASK], ##ing, the, model, [SEP] ]

但是這裡尤為需要注意的一點是:單詞 ##train 實際上僅是單詞pretraining 的一部分。在 WWM 方法中,如果遮擋的單詞為一個subword,則會遮擋這個subword對應的所有單詞,例如:

Tokens = [ [CLS], [MASK], us, start, [MASK], [MASK], [MASK], the, model, [SEP] ]

但是,WWM仍需要遵守“1個句子中僅有15%單詞被遮擋”這一規則。所以若是在遮擋 所有subword後,已經達到了這一15%的閾值,則可以忽略其他詞(這個例子中就是單詞let)的遮擋,例如:

Tokens = [ [CLS], let, us, start, [MASK], [MASK], [MASK], the, model, [SEP] ]

在進行了WWM後,句子即可輸入到BERT中進行訓練,並讓模型預測被遮擋的單詞。

6.3. Next Sentence Prediction

Next Sentence Prediction(NSP)是另一個訓練BERT的方法,它是一個2分類任務。在此任務中,為BERT模型輸入2個句子,需要讓模型判斷第2個句子是否是第1個句子的下一個句子。例如,準備2個句子Sentence-A和Sentence-B(2個句子合起來為1條訓練資料),若Sentence-B是Sentence-A的下一個句子,則這條訓練資料的label為isNext。反之為notNext。

在BERT訓練中,他需要完成的便是判斷Sentence-B是否為Sentence-A的下一條句子。若是,則輸出isNext,反之輸出notNext。所以這是一個二分類任務。

NSP任務的用途在於:通過NSP任務,可以讓模型理解2條句子之間的關係。很多重要的下游任務(例如QA系統、自然語言推理等)都是基於句子之間的關係理解。而這種句子間的關係是無法通過language modeling 學習到的。

在準備訓練資料時,可以選取任何單一語言的語料庫。假設我們從語料庫獲取了大量文件。對於isNext類別,僅需要從同一個文件中選取2條連續的句子即可。而對於notNext類別,首先從一個文件選取一條句子,然後從另一個隨機文件中選取一條句子即可。一個重要的注意點是:需要確保isNext與notNext的資料條目分別佔50%。

在NSP任務中,輸入資料的處理方法與前面介紹的一致,在此不再贅述。不過需要提及的一點是:NSP使用的是[CLS] 作為輸入的2個句子的代表,輸入到前饋網路與softmax中,對結果進行預測。如下圖所示:

7. BERT預訓練的過程

BERT的預訓練語料庫使用的是Toronto BookCorpus和Wikipedia資料集。在準備訓練資料時,首先從語料庫中取樣2條句子,例如Sentence-A與Sentence-B。這裡需要注意的是:2條句子的單詞之和不能超過512個。對於採集的這些句子,50%為兩個句子是相鄰句子,另50%為兩個句子毫無關係。

假設採集了以下2條句子:

  1. Beijing is a beautiful city
  2. I love Beijing

對這2條句子先做分詞:

Tokens = [ [CLS], Beijing, is, a, beautiful, city, [SEP], I, love, Beijing, [SEP] ]

然後,以15%的概率遮擋單詞,並遵循80%-10%-10%的規則。假設遮擋的單詞為city,則:

Tokens = [ [CLS], Beijing, is, a, beautiful, [MASK], [SEP], I, love, Beijing, [SEP] ]

接下來將Tokens送入到BERT中,並訓練BERT預測被遮擋的單詞,同時也要預測這2條句子是否為相鄰(句子2是句子1的下一條句子)。也就是說,BERT是同時訓練Masked Language Modeling和NSP任務。

BERT的訓練引數是:1000000個step,每個batch包含256條序列(256 * 512個單詞 = 128000單詞/batch)。使用的是Adam,learning rate為1e-4、β1 = 0.9、β2 = 0.999。L2正則權重的衰減引數為0.01。對於learning rete,前10000個steps使用了rate warmup,之後開始線性衰減learning rate(簡單地說,就是前期訓練使用一個較大的learning rate,後期開始線性減少)。對所有layer使用0.1概率的dropout。使用的啟用函式為gelu,而非relu。

8. 總結

BERT的一個成功的關鍵在於:它提供了一個更深層的模型來學習到了一個更好的文字特徵,使得它在各類NLP任務上的表現也更精準。當前BERT+遷移學習已經應用在各個實際應用中,例如情感分析、QA系統等。另一方面,研究人員基於BERT也衍生出了很多變種,例如ALBERT、RoBERTa、ELECTRA等等。後續我們會繼續介紹BERT的應用以及它的變種。

References

[1] https://learning.oreilly.com/library/view/getting-started-with/9781838821593/4c8629af-d59b-4df9-a830-79a83433f118.xhtml

[2] https://www.geeksforgeeks.org/explanation-of-bert-model-nlp/

[3] https://zhuanlan.zhihu.com/p/95594311

[4] https://zhuanlan.zhihu.com/p/366396747

[5] https://arxiv.org/pdf/1810.04805.pdf