1. 程式人生 > >NLP大殺器BERT模型解讀

NLP大殺器BERT模型解讀

寫在前面

谷歌研究人員通過新的BERT模型在11項NLP任務中奪得STOA結果,這在自然語言處理學界以及工業界都引起了不小的熱議。作者通過在33億文字的語料上訓練語言模型,再分別在不同的下游任務上微調,這樣的模型在不同的任務均得到了目前為止最好的結果,並且有一些結果相比此前的最佳成績得到了幅度不小的提升。但是如此好的效果並不是隨便突然的一個憑空出現的想法導致的,而是作者結合NLP最近幾年表現優異的模型的結果。可以認為,bert的成名史為:

語言模型 ==> word embedding ==> ELMO ==> GPT ==> BERT

在BERT模型中的重點詞有幾個:pre-training, deep, bidirectional, Transformaer, Language understanding

哈哈哈哈,上面重點詞其實就是我抄了論文題目一本正經寫的。

另外,本文參考自張俊林 《從Word Embedding到Bert模型—自然語言處理中的預訓練技術發展史》一文及其PPT!

Pre-training in CV

預處理在bert模型中佔了很重要的部分,所以先從運用pre-training最成熟經典的影象領域開始入手。

上圖就展示了影象處理過程中的預訓練。

我們設計好網路結構以後,對於影象來說一般是CNN的多層疊加網路結構,可以先用某個訓練集合比如訓練集合A或者訓練集合B對這個網路進行預先訓練,在A任務上或者B任務上學會網路引數,然後存起來以備後用。假設我們面臨第三個任務C,網路結構採取相同的網路結構,在比較淺的幾層CNN結構,網路引數初始化的時候可以載入A任務或者B任務學習好的引數,其它CNN高層引數仍然隨機初始化。之後我們用C任務的訓練資料來訓練網路,此時有兩種做法,一種是淺層載入的引數在訓練C任務過程中不動,這種方法被稱為“Frozen”

;另外一種是底層網路引數儘管被初始化了,在C任務訓練過程中仍然隨著訓練的程序不斷改變,這種一般叫“Fine-Tuning”,顧名思義,就是更好地把引數進行調整使得更適應當前的C任務。一般影象或者視訊領域要做預訓練一般都這麼做。

這麼做有幾個好處,首先,如果手頭任務C的訓練集合資料量較少的話,現階段的好用的CNN比如Resnet/Densenet/Inception等網路結構層數很深,幾百萬上千萬引數量算起步價,上億引數的也很常見,訓練資料少很難很好地訓練這麼複雜的網路,但是如果其中大量引數通過大的訓練集合比如ImageNet預先訓練好直接拿來初始化大部分網路結構引數,然後再用C任務手頭比較可憐的資料量上Fine-tuning過程去調整引數讓它們更適合解決C任務,那事情就好辦多了。這樣原先訓練不了的任務就能解決了,即使手頭任務訓練資料也不少,加個預訓練過程也能極大加快任務訓練的收斂速度,所以這種預訓練方式是老少皆宜的解決方案,另外療效又好,所以在做影象處理領域很快就流行開來。

那麼新的問題來了,為什麼這種預訓練的思路是可行的?

目前我們已經知道,對於層級的CNN結構來說,不同層級的神經元學習到了不同型別的影象特徵,由底向上特徵形成層級結構,如上圖所示,如果我們手頭是個人臉識別任務,訓練好網路後,把每層神經元學習到的特徵視覺化肉眼看一看每層學到了啥特徵,你會看到最底層的神經元學到的是線段等特徵,圖示的第二個隱層學到的是人臉五官的輪廓,第三層學到的是人臉的輪廓,通過三步形成了特徵的層級結構,越是底層的特徵越是所有不論什麼領域的影象都會具備的比如邊角線弧線等底層基礎特徵,越往上抽取出的特徵越與手頭任務相關。正因為此,所以預訓練好的網路引數,尤其是底層的網路引數抽取出特徵跟具體任務越無關,越具備任務的通用性,所以這是為何一般用底層預訓練好的引數初始化新任務網路引數的原因。而高層特徵跟任務關聯較大,實際可以不用使用,或者採用Fine-tuning用新資料集合清洗掉高層無關的特徵抽取器。

Pre-training in NLP --- Word Embedding

說到pre-training在NLP領域的使用,其實早在2003年就有提出了word embedding技術,並且一直到如今都還在被廣泛使用。

Word Embedding其實一開始並不是主要目的,而是在訓練語言模型任務是得到的副產品。

比如上圖著名的出自Bengio大師的“神經網路語言模型”,在訓練任務時得到的引數矩陣C(需要學習)其實就是每一個單詞對應的word embedding值。

後來又出現了專門用於訓練word embedding的模型word2vec和glove等。關於word2vec模型可以參考深度學習word2vec筆記之基礎篇

那麼word embedding到底和pre-training有什麼關係呢?其實這就是標準的預訓練過程。被廣泛應用於NLP的下游任務。

假設如上圖所示,我們有個NLP的下游任務,比如QA,就是問答問題,所謂問答問題,指的是給定一個問題X,給定另外一個句子Y,要判斷句子Y是否是問題X的正確答案。問答問題假設設計的網路結構如上圖所示。它的使用方法其實和前面講的NNLM是一樣的,句子中每個單詞以Onehot形式作為輸入,然後乘以學好的Word Embedding矩陣Q,就直接取出單詞對應的Word Embedding了。這乍看上去好像是個查表操作,不像是預訓練的做法是吧?其實不然,那個Word Embedding矩陣Q其實就是網路Onehot層到embedding層對映的網路引數矩陣。所以你看到了,使用Word Embedding等價於什麼?等價於把Onehot層到embedding層的網路用預訓練好的引數矩陣Q初始化了。這跟前面講的影象領域的低層預訓練過程其實是一樣的,區別無非Word Embedding只能初始化第一層網路引數,再高層的引數就無能為力了。下游NLP任務在使用Word Embedding的時候也類似影象有兩種做法,一種是Frozen,就是Word Embedding那層網路引數固定不動;另外一種是Fine-Tuning,就是Word Embedding這層引數使用新的訓練集合訓練也需要跟著訓練過程更新掉。

那麼,word embedding 存在什麼問題呢?就是多義詞問題!我們知道,多義詞是自然語言中經常出現的現象,也是語言靈活性和高效性的一種體現。多義詞對Word Embedding來說有什麼負面影響?比如多義詞Bank,有兩個常用含義,但是Word Embedding在對bank這個單詞進行編碼的時候,是區分不開這兩個含義的,因為它們儘管上下文環境中出現的單詞不同,但是在用語言模型訓練的時候,不論什麼上下文的句子經過word2vec,都是預測相同的單詞bank,而同一個單詞佔的是同一行的引數空間,這導致兩種不同的上下文資訊都會編碼到相同的word embedding空間裡去。所以word embedding無法區分多義詞的不同語義,這就是它的一個比較嚴重的問題。

於是就有了ELMO。

from Word Embedding to EMLO

ELMO是“Embedding from Language Models”的簡稱,其實這個名字並沒有反應它的本質思想,提出ELMO的論文題目:“Deep contextualized word representation”更能體現其精髓,而精髓在哪裡?在deep contextualized這個短語,一個是deep,一個是context,其中context更關鍵。在此之前的Word Embedding本質上是個靜態的方式,所謂靜態指的是訓練好之後每個單詞的表達就固定住了,以後使用的時候,不論新句子上下文單詞是什麼,這個單詞的Word Embedding不會跟著上下文場景的變化而改變,所以對於比如Bank這個詞,它事先學好的Word Embedding中混合了幾種語義 ,在應用中來了個新句子,即使從上下文中(比如句子包含money等詞)明顯可以看出它代表的是“銀行”的含義,但是對應的Word Embedding內容也不會變,它還是混合了多種語義。這是為何說它是靜態的,這也是問題所在。ELMO的本質思想是:我事先用語言模型學好一個單詞的Word Embedding,此時多義詞無法區分,不過這沒關係。在我實際使用Word Embedding的時候,單詞已經具備了特定的上下文了,這個時候我可以根據上下文單詞的語義去調整單詞的Word Embedding表示,這樣經過調整後的Word Embedding更能表達在這個上下文中的具體含義,自然也就解決了多義詞的問題了。所以ELMO本身是個根據當前上下文對Word Embedding動態調整的思路。

ELMO採用了典型的兩階段過程,第一個階段是利用語言模型進行預訓練;第二個階段是在做下游任務時,從預訓練網路中提取對應單詞的網路各層的Word Embedding作為新特徵補充到下游任務中。上圖展示的是其預訓練過程,它的網路結構採用了雙層雙向LSTM,目前語言模型訓練的任務目標是根據單詞 Wi 的上下文去正確預測單詞 Wi ,Wi 之前的單詞序列Context-before稱為上文,之後的單詞序列Context-after稱為下文。圖中左端的前向雙層LSTM代表正方向編碼器,輸入的是從左到右順序的除了預測單詞外 Wi 的上文Context-before和下文Context-after;右端的逆向雙層LSTM代表反方向編碼器,輸入的是從右到左的逆序的句子上文和下文;每個編碼器的深度都是兩層LSTM疊加,而每一層的正向和逆向單詞編碼會拼接到一起。這個網路結構其實在NLP中是很常用的。使用這個網路結構利用大量語料做語言模型任務就能預先訓練好這個網路,如果訓練好這個網路後,輸入一個新句子 Snew ,句子中每個單詞都能得到對應的三個Embedding:最底層是單詞的Word Embedding,往上走是第一層雙向LSTM中對應單詞位置的Embedding,這層編碼單詞的句法資訊更多一些;再往上走是第二層LSTM中對應單詞位置的Embedding,這層編碼單詞的語義資訊更多一些。也就是說,ELMO的預訓練過程不僅僅學會單詞的Word Embedding,還學會了一個雙層雙向的LSTM網路結構,而這兩者後面都有用。

上面介紹的是ELMO的第一階段:預訓練階段。那麼預訓練好網路結構後,如何給下游任務使用呢?上圖展示了下游任務的使用過程,比如我們的下游任務仍然是QA問題,此時對於問句X,我們可以先將句子X作為預訓練好的ELMO網路的輸入,這樣句子X中每個單詞在ELMO網路中都能獲得對應的三個Embedding(單詞的Embedding,句法層面的Embedding,語義層面的Embedding),之後給予這三個Embedding中的每一個Embedding一個權重a,這個權重可以學習得來,根據各自權重累加求和,將三個Embedding整合成一個。然後將整合後的這個Embedding作為X句在自己任務的那個網路結構中對應單詞的輸入,以此作為補充的新特徵給下游任務使用。對於上圖所示下游任務QA中的回答句子Y來說也是如此處理。因為ELMO給下游提供的是每個單詞的特徵形式,所以這一類預訓練的方法被稱為“Feature-based Pre-Training”。至於為何這麼做能夠達到區分多義詞的效果,你可以想一想,其實比較容易想明白原因。

前面我們提到靜態Word Embedding無法解決多義詞的問題,那麼ELMO引入上下文動態調整單詞的embedding後多義詞問題解決了嗎?解決了,而且比我們期待的解決得還要好。對於Glove訓練出的Word Embedding來說,多義詞比如play,根據它的embedding找出的最接近的其它單詞大多數集中在體育領域,這很明顯是因為訓練資料中包含play的句子中體育領域的數量明顯佔優導致;而使用ELMO,根據上下文動態調整後的embedding不僅能夠找出對應的“演出”的相同語義的句子,而且還可以保證找出的句子中的play對應的詞性也是相同的,這是超出期待之處。之所以會這樣,是因為我們上面提到過,第一層LSTM編碼了很多句法資訊,這在這裡起到了重要作用。

ELMO經過這般操作,效果如何呢?實驗效果見上圖,6個NLP任務中效能都有幅度不同的提升,最高的提升達到25%左右,而且這6個任務的覆蓋範圍比較廣,包含句子語義關係判斷,分類任務,閱讀理解等多個領域,這說明其適用範圍是非常廣的,普適性強,這是一個非常好的優點。

那麼站在現在這個時間節點看,ELMO有什麼值得改進的缺點呢?首先,一個非常明顯的缺點在特徵抽取器選擇方面,ELMO使用了LSTM而不是新貴Transformer,Transformer是谷歌在17年做機器翻譯任務的“Attention is all you need”的論文中提出的,引起了相當大的反響,很多研究已經證明了Transformer提取特徵的能力是要遠強於LSTM的。如果ELMO採取Transformer作為特徵提取器,那麼估計Bert的反響遠不如現在的這種火爆場面。另外一點,ELMO採取雙向拼接這種融合特徵的能力可能比Bert一體化的融合特徵方式弱,但是,這只是一種從道理推斷產生的懷疑,目前並沒有具體實驗說明這一點。

我們如果把ELMO這種預訓練方法和影象領域的預訓練方法對比,發現兩者模式看上去還是有很大差異的。除了以ELMO為代表的這種基於特徵融合的預訓練方法外,NLP裡還有一種典型做法,這種做法和影象領域的方式就是看上去一致的了,一般將這種方法稱為“基於Fine-tuning的模式”,而GPT就是這一模式的典型開創者。

from Word Embedding to GPT

GPT是“Generative Pre-Training”的簡稱,從名字看其含義是指的通用的預訓練,核心在通用上。GPT也採用兩階段過程,第一個階段是利用語言模型進行預訓練第二階段通過Fine-tuning的模式解決下游任務。上圖展示了GPT的預訓練過程,其實和ELMO是類似的,主要不同在於兩點:首先,特徵抽取器不是用的RNN,而是用的Transformer,上面提到過它的特徵抽取能力要強於RNN,這個選擇很明顯是很明智的(一般可以認為Transformer > RNN > CNN);其次,GPT的預訓練雖然仍然是以語言模型作為目標任務,但是採用的是單向的語言模型,所謂“單向”的含義是指:語言模型訓練的任務目標是根據 Wi 單詞的上下文去正確預測單詞 Wi ,Wi 之前的單詞序列Context-before稱為上文,之後的單詞序列Context-after稱為下文。ELMO在做語言模型預訓練的時候,預測單詞 Wi 同時使用了上文和下文,而GPT則只採用Context-before這個單詞的上文來進行預測,而拋開了下文。這個選擇現在看不是個太好的選擇,原因很簡單,它沒有把單詞的下文融合進來,這限制了其在更多應用場景的效果,比如閱讀理解這種任務,在做任務的時候是可以允許同時看到上文和下文一起做決策的。如果預訓練時候不把單詞的下文嵌入到Word Embedding中,是很吃虧的,白白丟掉了很多資訊。

上面講的是GPT如何進行第一階段的預訓練,那麼假設預訓練好了網路模型,後面下游任務怎麼用?它有自己的個性,和ELMO的方式大有不同。

上圖展示了GPT在第二階段如何使用。首先,對於不同的下游任務來說,本來你可以任意設計自己的網路結構,現在不行了,你要向GPT的網路結構看齊,把任務的網路結構改造成和GPT的網路結構是一樣的。然後,在做下游任務的時候,利用第一步預訓練好的引數初始化GPT的網路結構,這樣通過預訓練學到的語言學知識就被引入到你手頭的任務裡來了,這是個非常好的事情。再次,你可以用手頭的任務去訓練這個網路,對網路引數進行Fine-tuning,使得這個網路更適合解決手頭的問題。

這裡引入了一個新問題:對於NLP各種花樣的不同任務,怎麼改造才能靠近GPT的網路結構呢?

GPT論文給了一個改造施工圖如上,其實也很簡單:對於分類問題,不用怎麼動,加上一個起始和終結符號即可;對於句子關係判斷問題,比如Entailment,兩個句子中間再加個分隔符即可;對文字相似性判斷問題,把兩個句子順序顛倒下做出兩個輸入即可,這是為了告訴模型句子順序不重要;對於多項選擇問題,則多路輸入,每一路把文章和答案選項拼接作為輸入即可。從上圖可看出,這種改造還是很方便的,不同任務只需要在輸入部分施工即可。

那麼站在現在的時間節點看,GPT有什麼值得改進的地方呢?其實最主要的就是那個單向語言模型,如果改造成雙向的語言模型任務估計也沒有Bert太多事了。當然,即使如此GPT也是非常非常好的一個工作,跟Bert比,其作者炒作能力亟待提升。

Here comes BERT

我們經過跋山涉水,終於到了目的地Bert模型了。Bert採用和GPT完全相同的兩階段模型,首先是語言模型預訓練;其次是使用Fine-Tuning模式解決下游任務。和GPT的最主要不同在於在預訓練階段採用了類似ELMO的雙向語言模型,當然另外一點是語言模型的資料規模要比GPT大。所以這裡Bert的預訓練過程不必多講了。

第二階段,Fine-Tuning階段,這個階段的做法和GPT是一樣的。當然,它也面臨著下游任務網路結構改造的問題,在改造任務方面Bert和GPT有些不同,下面簡單介紹一下。

上圖給出示例,對於句子關係類任務,很簡單,和GPT類似,加上一個起始和終結符號,句子之間加個分隔符即可。對於輸出來說,把第一個起始符號對應的Transformer最後一層位置上面串接一個softmax分類層即可。對於分類問題,與GPT一樣,只需要增加起始和終結符號,輸出部分和句子關係判斷任務類似改造;對於序列標註問題,輸入部分和單句分類是一樣的,只需要輸出部分Transformer最後一層每個單詞對應位置都進行分類即可。儘管Bert論文沒有提,但是稍微動動腦子就可以想到,其實對於機器翻譯或者文字摘要,聊天機器人這種生成式任務,同樣可以稍作改造即可引入Bert的預訓練成果。只需要附著在S2S結構上,encoder部分是個深度Transformer結構,decoder部分也是個深度Transformer結構。根據任務選擇不同的預訓練資料初始化encoder和decoder即可。這是相當直觀的一種改造方法。當然,也可以更簡單一點,比如直接在單個Transformer結構上加裝隱層產生輸出也是可以的。不論如何,從這裡可以看出,NLP四大類任務都可以比較方便地改造成Bert能夠接受的方式。這其實是Bert的非常大的優點,這意味著它幾乎可以做任何NLP的下游任務,具備普適性,這是很強的。

到這裡我們可以再梳理下幾個模型之間的演進關係。從上圖可見,Bert其實和ELMO及GPT存在千絲萬縷的關係,比如如果我們把GPT預訓練階段換成雙向語言模型,那麼就得到了Bert;而如果我們把ELMO的特徵抽取器換成Transformer,那麼我們也會得到Bert。所以你可以看出:Bert最關鍵兩點,一點是特徵抽取器採用Transformer;第二點是預訓練的時候採用雙向語言模型。

那麼新問題來了:對於Transformer來說,怎麼才能在這個結構上做雙向語言模型任務呢?乍一看上去好像不太好搞。作者們提出了一個叫Masked語言模型來解決這個問題,其實這跟word2vec中的CBOW模型思想很類似,它的核心思想是:在做語言模型任務的時候,我把要預測的單詞摳掉,然後根據它的上文Context-Before和下文Context-after去預測單詞。

Masked雙向語言模型向上圖展示這麼做:隨機選擇語料中15%的單詞,把它摳掉,也就是用[Mask]掩碼代替原始單詞,然後要求模型去正確預測被摳掉的單詞。但是這裡有個問題:訓練過程大量看到[mask]標記,但是真正後面用的時候是不會有這個標記的,這會引導模型認為輸出是針對[mask]這個標記的,但是實際使用又見不到這個標記,這自然會有問題。為了避免這個問題,Bert改造了一下,15%的被上天選中要執行[mask]替身這項光榮任務的單詞中,只有80%真正被替換成[mask]標記,10%被狸貓換太子隨機替換成另外一個單詞,10%情況這個單詞還待在原地不做改動。這就是Masked雙向語音模型的具體做法。

Bert還有一個創新點叫做 Next Sentence Prediction。指的是做語言模型預訓練的時候,分兩種情況選擇兩個句子,一種是選擇語料中真正順序相連的兩個句子;另外一種是第二個句子從語料庫中拋色子,隨機選擇一個拼到第一個句子後面。我們要求模型除了做上述的Masked語言模型任務外,附帶再做個句子關係預測,判斷第二個句子是不是真的是第一個句子的後續句子。之所以這麼做,是考慮到很多NLP任務是句子關係判斷任務,單詞預測粒度的訓練到不了句子關係這個層級,增加這個任務有助於下游句子關係判斷任務。所以可以看到,它的預訓練是個多工過程。這也是Bert的一個創新。

順帶講解下Bert的輸入部分,也算是有些特色。它的輸入部分是個線性序列,兩個句子通過分隔符分割,最前面和最後增加兩個識別符號號。每個單詞有三個embedding:位置資訊embedding,這是因為NLP中單詞順序是很重要的特徵,需要在這裡對位置資訊進行編碼;單詞embedding,這個就是我們之前一直提到的單詞embedding;第三個是句子embedding,因為前面提到訓練資料都是由兩個句子構成的,那麼每個句子有個句子整體的embedding項對應給每個單詞。把單詞對應的三個embedding疊加,就形成了Bert的輸入。

至於Bert在預訓練的輸出部分如何組織,可以參考下圖的註釋。

Summary

最後,我講講我對Bert的評價和看法,我覺得Bert是NLP裡裡程碑式的工作,對於後面NLP的研究和工業應用會產生長久的影響,這點毫無疑問。但是從上文介紹也可以看出,從模型或者方法角度看,Bert借鑑了ELMO,GPT及CBOW,主要提出了Masked 語言模型及Next Sentence Prediction,但是這裡Next Sentence Prediction基本不影響大局,而Masked LM明顯借鑑了CBOW的思想。所以說Bert的模型沒什麼大的創新,更像最近幾年NLP重要進展的集大成者。如果歸納一下這些進展就是:首先是兩階段模型,第一階段雙向語言模型預訓練,這裡注意要用雙向而不是單向,第二階段採用具體任務Fine-tuning或者做特徵整合;第二是特徵抽取要用Transformer作為特徵提取器而不是RNN或者CNN;第三,雙向語言模型可以採取CBOW的方法去做(當然我覺得這個是個細節問題,不算太關鍵,前兩個因素比較關鍵)。Bert最大的亮點在於效果好及普適性強,幾乎所有NLP任務都可以套用Bert這種兩階段解決思路,而且效果應該會有明顯提升。可以預見的是,未來一段時間在NLP應用領域,Transformer將佔據主導地位,而且這種兩階段預訓練方法也會主導各種應用。

另外,我們應該弄清楚預訓練這個過程本質上是在做什麼事情,本質上預訓練是通過設計好一個網路結構來做語言模型任務,然後把大量甚至是無窮盡的無標註的自然語言文字利用起來,預訓練任務把大量語言學知識抽取出來編碼到網路結構中,當手頭任務帶有標註資訊的資料有限時,這些先驗的語言學特徵當然會對手頭任務有極大的特徵補充作用,因為當資料有限的時候,很多語言學現象是覆蓋不到的,泛化能力就弱,整合儘量通用的語言學知識自然會加強模型的泛化能力。如何引入先驗的語言學知識其實一直是NLP尤其是深度學習場景下的NLP的主要目標之一,不過一直沒有太好的解決辦法,而ELMO/GPT/Bert的這種兩階段模式看起來無疑是解決這個問題自然又簡潔的方法,這也是這些方法的主要價值所在。