天天看點

Bert

2019-10-09 19:55:26

問題描述:談談對Bert的了解。

問題求解:

2018年深度學習在NLP領域取得了比較大的突破,最大的新聞當屬Google的BERT模型橫掃各大比賽的排行榜。

NLP領域到目前為止有三次重大的突破:Word Embedding、Self-Attention機制和Contextual Word Embedding。

1)Word Embedding,word embedding解決了傳統詞向量稀疏的問題,将詞映射到一個低維的稠密語義空間,進而使得相似的詞可以共享上下文資訊,進而提升泛化能力。這一領域最傑出的算法代表有word2vec,glove等。但是這樣方案有個問題,就是沒有考慮到詞的上下文資訊,比如bank即可能是“銀行”,也可能是“水邊”,通過這種方案是沒有辦法對這種情況進行區分的。

2)Self-Attension機制,2017年Google提出了Transformer模型,并且起了一個相當霸氣的标題“Attension is all you need”,引入了Self-Attention。Self-Attention的初衷是為了用Attention替代LSTM,進而可以更好的并行(因為LSTM的時序依賴特效很難并行),進而可以處理更大規模的語料。現在Transformer已經成為Encoder/Decoder的霸主。

3)Contextual Word Embedding,2018年的研究熱點就變成了怎麼利用無監督的資料學到一個詞在不同上下文的不同語義表示方法。在BERT之前比較大的進展是ELMo和OpenAI GPT。尤其是OpenAI GPT,它在BERT出現之前已經橫掃過各大排行榜一次了,當然Google的BERT又橫掃了一次。BERT的很多思路都是沿用OpenAI GPT的,要說BERT的學術貢獻,最多是利用了Mask LM(這個模型在上世紀就存在了)和Next Sentence Predicting這個Multi-task Learning而已。

Bert 整體架構

BERT是“Bidirectional Encoder Representations from Transformers”的首字母縮寫。 BERT仍然使用的是Transformer模型,那它是怎麼解決語言模型隻能利用一個方向的資訊的問題呢?答案是它的pretraining訓練的不是普通的語言模型,而是Mask語言模型。

Google在論文中給了兩個模型,一個base模型,還有一個大一點的large模型,它們的參數設定如下所示。

BERT-Base,12層,768個隐單元,12個head,110M參數

BERT-Large,24層,1024個隐單元,16個head,340M參數

Bert

Bert 輸入:

BERT的輸入表示如圖下圖所示。比如輸入的是兩個句子”my dog is cute”,”he likes playing”。後面會解釋為什麼需要兩個句子。這裡采用類似GPT的兩個句子的表示方法,首先會在第一個句子的開頭增加一個特殊的Token [CLS],在cute的後面增加一個[SEP]表示第一個句子結束。

接着對每個Token進行3個Embedding:詞的Embedding;位置的Embedding和Segment的Embedding。

BERT模型要求有一個固定的Sequence的長度,比如512。如果不夠就在後面padding,否則就截取掉多餘的Token,進而保證輸入是一個固定長度的Token序列,後面的代碼會詳細的介紹。第一個Token總是特殊的[CLS],它本身沒有任何語義,是以它會(必須)編碼整個句子(其它詞)的語義。

Bert

Bert 模型:

Bert模型的骨架就是Transformer的encoder子產品,即一個self-attension + feed forward。

在bert-base中有12個encoder子產品,在bert-large中有24個encoder子產品。

Bert

Bert 輸出:

Bert 會給每個token輸出一個embedding,在base中,次元為768,在large上,次元為1024。

Bert

Bert 預訓練機制 

任務一:Mask LM

為了解決隻能利用單向資訊的問題,BERT使用的是Mask語言模型而不是普通的語言模型。Mask語言模型有點類似與完形填空——給定一個句子,把其中某個詞遮擋起來,讓人猜測可能的詞。

這裡會随機的Mask掉15%的詞,然後讓BERT來預測這些Mask的詞,通過調整模型的參數使得模型預測正确的機率盡可能大,這等價于交叉熵的損失函數。這樣的Transformer在編碼一個詞的時候會(必須)參考上下文的資訊。

但是這有一個問題:在Pretraining Mask LM時會出現特殊的Token [MASK],但是在後面的fine-tuning時卻不會出現,這會出現Mismatch的問題。是以BERT中,如果某個Token在被選中的15%個Token裡,則按照下面的方式随機的執行:

80%的機率替換成[MASK],比如my dog is hairy → my dog is [MASK] 10%的機率替換成随機的一個詞,比如my dog is hairy → my dog is apple 10%的機率替換成它本身,比如my dog is hairy → my dog is hairy

這樣做的好處是,BERT并不知道[MASK]替換的是哪一個詞,而且任何一個詞都有可能是被替換掉的,比如它看到的apple可能是被替換的詞。這樣強迫模型在編碼目前時刻的時候不能太依賴于目前的詞,而要考慮它的上下文,甚至更加上下文進行”糾錯”。比如上面的例子模型在編碼apple是根據上下文my dog is應該把apple(部分)編碼成hairy的語義而不是apple的語義。 

任務二:NSP

在有些任務中,比如問答,前後兩個句子有一定的關聯關系,我們希望BERT Pretraining的模型能夠學習到這種關系。是以BERT還增加了一個新的任務——預測兩個句子是否有關聯關系。這是一種Multi-Task Learing。BERT要求的Pretraining的資料是一個一個的”文章”,比如它使用了BookCorpus和維基百科的資料,BookCorpus是很多本書,每本書的前後句子是有關聯關系的;而維基百科的文章的前後句子也是有關系的。對于這個任務,BERT會以50%的機率抽取有關聯的句子(注意這裡的句子實際隻是聯系的Token序列,不是語言學意義上的句子),另外以50%的機率随機抽取兩個無關的句子,然後讓BERT模型來判斷這兩個句子是否相關。比如下面的兩個相關的句子:

下面是兩個不相關的句子:

Elmo、GPT、 Bert三者之間有什麼差別

1)Elmo

ELMo是Embeddings from Language Models的縮寫,意思就是語言模型得到的(句子)Embedding。

這篇論文的想法其實非常非常簡單,但是取得了非常好的效果。它的思路是用深度的雙向RNN(LSTM)在大量未标注資料上訓練語言模型。

然後在實際的任務中,對于輸入的句子,我們使用這個語言模型來對它處理,得到輸出的向量,是以這可以看成是一種特征提取。

但是和普通的Word2Vec或者GloVe的pretraining不同,ELMo得到的Embedding是有上下文的。

為了能夠拿到雙向資訊,Elmo采用了雙向分層的LSTM,最後的結果是各層hidding layer的concat後的權重

Bert

2)GPT

GPT相對于Elmo改進的地方在于使用了Transformer架構。

之前我們介紹的Transformer模型是用來做機器翻譯的,它有一個Encoder和一個Decoder。這裡使用的是Encoder,隻不過Encoder的輸出不是給Decoder使用,而是直接用它來預測下一個詞,如下圖所示。

但是直接用Self-Attention來訓練語言模型是有問題的,因為在k時刻計算tk的時候隻能利用它之前的詞(或者逆向的語言模型隻能用它之後的詞)。但是Transformer的Self-Attention是可以利用整個句子的資訊的,這顯然不行,因為你讓它根據”it is a”來預測後面的詞,而且還告訴它整個句子是”it is a good day”,它就可能”作弊”,直接把下一個詞輸出了,這樣loss是零。

是以這裡要借鑒Decoder的Mask技巧,通過Mask讓它在編碼tk的時候隻能利用k之前(包括k本身)的資訊。

我們這裡使用多層的Transformer來實作語言模型,具體為:

Bert

這裡的We是詞的Embedding Matrix,Wp是位置Embedding Matrix。注意這裡的位置編碼沒有使用前面Transformer的固定編碼方式,而是采用類似詞的Embedding Matrix,讓它自己根據任務學習出合适的位置編碼。

前面講了,我們能夠處理的任務要求輸入是一個序列,而輸出是一個分類标簽。對于有些任務,比如情感分類,這是沒有問題的,但是對于相似度計算或者問答,輸入是兩個序列。為了能夠使用GPT,我們需要一些特殊的技巧把兩個輸入序列變成一個輸入序列。

Bert

3)Bert

Elmo和GPT最大的問題就是傳統的語言模型是單向的,BERT能夠同時利用前後兩個方向的資訊。

Bert

比如句子”The animal didn’t cross the street because it was too tired”。我們在編碼it的語義的時候需要同時利用前後的資訊,因為在這個句子中,it可能指代animal也可能指代street。根據tired,我們推斷它指代的是animal,因為street是不能tired。但是如果把tired改成wide,那麼it就是指代street了。傳統的語言模型,不管是RNN還是Transformer,它都隻能利用單方向的資訊。比如前向的RNN,在編碼it的時候它看到了animal和street,但是它還沒有看到tired,是以它不能确定it到底指代什麼。如果是後向的RNN,在編碼的時候它看到了tired,但是它還根本沒看到animal,是以它也不能知道指代的是animal。Transformer的Self-Attention理論上是可以同時attend to到這兩個詞的,但是根據前面的介紹,由于我們需要用Transformer來學習語言模型,是以必須用Mask來讓它看不到未來的資訊,是以它也不能解決這個問題的。

Bert采用MLM解決了雙向的問題,另外Bert中還引入了NSP來友善下遊任務的使用。

Bert和OpenAI GPT相比,還有一點很重要的差別就是訓練資料。OpenAI GPT使用的是BooksCorpus語料,總的詞數800M;而BERT還增加了wiki語料,其詞數是2,500M,是以BERT訓練資料的總詞數是3,300M。是以BERT的訓練資料是OpenAI GPT的4倍多,這是非常重要的一點。

Bert 核心源碼

根據下遊任務的不同,一般來說我們需要修改的地方有如下幾處。

1)tokenization.py

主要是分詞的算法,BERT裡英文分詞主要是由FullTokenizer類來實作的,中文分詞可以使用ChineseCharTokenizer類。

FullTokenizer的構造函數需要傳入參數詞典vocab_file和do_lower_case。如果我們自己從頭開始訓練模型(後面會介紹),那麼do_lower_case決定了我們的某些是否區分大小寫。如果我們隻是Fine-Tuning,那麼這個參數需要與模型一緻,比如模型是uncased_L-12_H-768_A-12,那麼do_lower_case就必須為True。

2)run_classifier.py / DataProcessor類

DataProcessor類負責資料是怎麼讀入的。這是一個抽象基類,定義了get_train_examples、get_dev_examples、get_test_examples和get_labels等4個需要子類實作的方法,另外提供了一個_read_tsv函數用于讀取tsv檔案。我們需要繼承這個類來完成我們的資料讀取操作。

3)run_classifier.py / create_model函數

在這個方法裡編寫實際需要處理的具體任務的代碼。

4)run_classifier.py / main函數

main函數的主要代碼如下,我們可以對main中模型的輸出結果自定義處理方式,如調用sklearn計算準召等。