一文讀懂BERT(原理篇)2018年的10月11日,Google發布的論文《Pre-training of Deep Bidirectional Transformers for Language Understanding》,成功在 11 項 NLP 任務中取得 state of the art 的結果,贏得自然語言處理學界的一片贊譽之聲。 本文是對近期關于BERT論文、相關文章、代碼進行學習后的知識梳理,僅為自己學習交流之用。因筆者精力有限,如果文中因引用了某些文章觀點未標出處還望作者海涵,也希望各位一起學習的讀者對文中不恰當的地方進行批評指正。 1)資源梳理
2)NLP發展史關于NLP發展史,特別推薦weizier大佬的NLP的巨人肩膀。學術性和文學性都很棒,縱覽NLP近幾年的重要發展,對各算法如數家珍,深入淺出,思路清晰,文不加點,一氣呵成。
2001 - 神經語言模型第一個神經語言模型是Bengio等人在2001年提出的前饋神經網絡,如圖所示: 語言建模通常是應用RNN時的第一步,是一種非監督學習形式。盡管它很簡單,但卻是本文后面討論的許多技術發展的核心:
反過來講,這意味著近年來 NLP 的許多重要進展都可以歸結為某些形式的語言建模。為了“真正”理解自然語言,僅僅從文本的原始形式中學習是不夠的。我們需要新的方法和模型。 2008- 多任務學習多任務學習是在多個任務上訓練的模型之間共享參數的一種通用方法。在神經網絡中,可以通過給不同層施以不同的權重,來很容易地實現多任務學習。多任務學習的概念最初由Rich Caruana 在1993年提出,并被應用于道路跟蹤和肺炎預測(Caruana,1998)。直觀地說,多任務學習鼓勵模型學習對許多任務有用的表述。這對于學習一般的、低級的表述形式、集中模型的注意力或在訓練數據有限的環境中特別有用。詳情請看這篇文章 在2008年,Collobert 和 Weston 將多任務學習首次應用于 NLP 的神經網絡。在他們的模型中,查詢表(或單詞嵌入矩陣)在兩個接受不同任務訓練的模型之間共享,如下面的圖所示。 2013- 詞嵌入用稀疏向量表示文本,即所謂的詞袋模型在 NLP 有著悠久的歷史。正如上文中介紹的,早在 2001年就開始使用密集向量表示詞或詞嵌入。Mikolov等人在2013年提出的創新技術是通過去除隱藏層,逼近目標,進而使這些單詞嵌入的訓練更加高效。雖然這些技術變更本質上很簡單,但它們與高效的word2vec配合使用,便能使大規模的詞嵌入訓練成為可能。 Word2vec有兩種風格,如下面的圖所示:連續字袋 CBOW 和 skip-gram。不過他們的目標不同:一個是根據周圍的單詞預測中心單詞,而另一個則相反。 2013 - NLP 神經網絡2013 年和 2014 年是 NLP 問題開始引入神經網絡模型的時期。使用最廣泛的三種主要的神經網絡是:循環神經網絡、卷積神經網絡和遞歸神經網絡。 循環神經網絡(RNNs) 循環神經網絡是處理 NLP 中普遍存在的動態輸入序列的一個最佳的技術方案。Vanilla RNNs (Elman,1990)很快被經典的長-短期記憶網絡(Hochreiter & Schmidhuber,1997)所取代,它被證明對消失和爆炸梯度問題更有彈性。在 2013 年之前,RNN 仍被認為很難訓練;Ilya Sutskever 的博士論文為改變這種現狀提供了一個關鍵性的例子。下面的圖對 LSTM 單元進行了可視化顯示。雙向 LSTM(Graves等,2013)通常用于處理左右兩邊的上下文。 遞歸神經網絡 RNN 和 CNN 都將語言視為一個序列。然而,從語言學的角度來看,語言本質上是層次化的:單詞被組合成高階短語和從句,這些短語和從句本身可以根據一組生產規則遞歸地組合。將句子視為樹而不是序列的語言學啟發思想產生了遞歸神經網絡(Socher 等人, 2013),如下圖所示 RNN 和 LSTM 可以擴展到使用層次結構。單詞嵌入不僅可以在本地學習,還可以在語法語境中學習(Levy & Goldberg等,2014);語言模型可以基于句法堆棧生成單詞(Dyer等,2016);圖卷積神經網絡可以基于樹結構運行(Bastings等,2017)。 2014-sequence-to-sequence 模型2014 年,Sutskever 等人提出了 sequence-to-sequence 模型。這是一個使用神經網絡將一個序列映射到另一個序列的通用框架。在該框架中,編碼器神經網絡逐符號處理一個句子,并將其壓縮為一個向量表示;然后,一個解碼器神經網絡根據編碼器狀態逐符號輸出預測值,并將之前預測的符號作為每一步的輸入,如下圖所示。 由于其靈活性,這個框架現在是自然語言生成任務的首選框架,其中不同的模型承擔了編碼器和解碼器的角色。重要的是,解碼器模型不僅可以解碼一個序列,而且可以解碼任意表征。例如,可以基于圖像生成標題(Vinyals等,2015)(如下圖所示)、基于表生成文本(Lebret等,2016)和基于應用程序中源代碼更改描述(Loyola等,2017)。 2015- 注意力機制注意力機制(Bahdanau 等,2015)是神經網絡機器翻譯(NMT)的核心創新之一,也是使 NMT模型勝過經典的基于短語的MT系統的關鍵思想。sequence-to-sequence模型的主要瓶頸是需要將源序列的全部內容壓縮為一個固定大小的向量。注意力機制通過允許解碼器回頭查看源序列隱藏狀態來緩解這一問題,然后將其加權平均作為額外輸入提供給解碼器,如下面的圖所示 2015 - 基于記憶的網絡注意力機制可以看作是模糊記憶的一種形式。記憶由模型的隱藏狀態組成,模型選擇從記憶中檢索內容。研究者們提出了許多具有更明確記憶的模型。這些模型有不同的變體,如神經圖靈機(Graves等,2014)、記憶網絡(Weston等,2015)和端到端記憶網絡(Sukhbaatar等,2015)、動態記憶網絡(Kumar等,2015)、神經微分計算機(Graves等,2016)和循環實體網絡(Henaff等,2017)。 記憶的訪問通常基于與當前狀態的相似度,類似于注意力,通常可以寫入和讀取。模型在如何實現和利用內存方面有所不同。例如,端到端記憶網絡多次處理輸入,并更新記憶以實現多個推理步驟。神經圖靈機也有一個基于位置的尋址,這允許他們學習簡單的計算機程序,如排序。基于記憶的模型通常應用于一些特定任務中,如語言建模和閱讀理解。在這些任務中,長時間保存信息應該很有用。記憶的概念是非常通用的:知識庫或表可以充當記憶,而記憶也可以根據整個輸入或它的特定部分填充。 2018 - 預訓練語言模型預訓練的詞嵌入與上下文無關,僅用于初始化模型中的第一層。一系列監督型任務被用于神經網絡的預訓練。相反,語言模型只需要無標簽的文本;因此,訓練可以擴展到數十億個tokens, new domains, new languages。預訓練語言模型于 2015 年被首次提出(Dai & Le,2015);直到最近,它們才被證明在各種任務中效果還是不錯的。語言模型嵌入可以作為目標模型中的特征(Peters等,2018),或者使用語言模型對目標任務數據進行微調(Ramachandranden等,2017; Howard & Ruder,2018)。添加語言模型嵌入可以在許多不同的任務中提供比最先進的技術更大的改進,如下面的圖所示。
其他里程碑事件其他一些技術發展沒有上面提到的那樣流行,但仍然有廣泛的影響。
3)BERT:一切過往, 皆為序章Attention機制講解attention是一種能讓模型對重要信息重點關注并充分學習吸收的技術,它不算是一個完整的模型,應當是一種技術,能夠作用于任何序列模型中。本文較多引用了本篇文章思路,如感興趣可以跳轉學習。 Seq2Seq在開始講解Attention之前,我們先簡單回顧一下Seq2Seq模型,傳統的機器翻譯基本都是基于Seq2Seq模型來做的,該模型分為encoder層與decoder層,并均為RNN或RNN的變體構成,如下圖所示: AttentionAttention,正如其名,注意力,該模型在decode階段,會選擇最適合當前節點的context作為輸入。Attention與傳統的Seq2Seq模型主要有以下兩點不同。 1)encoder提供了更多的數據給到decoder,encoder會把所有的節點的hidden state提供給decoder,而不僅僅只是encoder最后一個節點的hidden state。
這里我們以一個具體的例子來看下其中的詳細計算步驟: 明白每一個節點是怎么獲取hidden state之后,接下來就是decoder層的工作原理了,其具體過程如下: 第一個decoder的節點初始化一個向量,并計算當前節點的hidden state,把該hidden state作為第一個節點的輸入,經過RNN節點后得到一個新的hidden state與輸出值。注意,這里和Seq2Seq有一個很大的區別,Seq2Seq是直接把輸出值作為當前節點的輸出,但是Attention會把該值與hidden state做一個連接,并把連接好的值作為context,并送入一個前饋神經網絡,最終當前節點的輸出內容由該網絡決定,重復以上步驟,直到所有decoder的節點都輸出相應內容。 Attention模型并不只是盲目地將輸出的第一個單詞與輸入的第一個詞對齊。實際上,它在訓練階段學習了如何在該語言對中對齊單詞(示例中是法語和英語)。Attention函數的本質可以被描述為一個查詢(query)到一系列(鍵key-值value)對的映射。 Transrofmer模型講解接下來我將介紹《Attention is all you need》這篇論文。這篇論文是google機器翻譯團隊在2017年6月放在arXiv上,最后發表在2017年nips上,到目前為止google學術顯示引用量為2203,可見也是受到了大家廣泛關注和應用。這篇論文主要亮點在于 《Attention Is All You Need》是一篇Google提出的將Attention思想發揮到極致的論文。這篇論文中提出一個全新的模型,叫 Transformer,拋棄了以往深度學習任務里面使用到的 CNN 和 RNN ,Bert就是基于Transformer構建的,這個模型廣泛應用于NLP領域,例如機器翻譯,問答系統,文本摘要和語音識別等等方向。關于Transrofmer模型的理解特別推薦一位國外博主文章《The Illustrated Transformer》。 Transformer總體結構和Attention模型一樣,Transformer模型中也采用了 encoer-decoder 架構。但其結構相比于Attention更加復雜,論文中encoder層由6個encoder堆疊在一起,decoder層也一樣。 現在我們知道了模型的主要組件,接下來我們看下模型的內部細節。首先,模型需要對輸入的數據進行一個embedding操作,(也可以理解為類似w2c的操作),enmbedding結束之后,輸入到encoder層,self-attention處理完數據后把數據送給前饋神經網絡,前饋神經網絡的計算可以并行,得到的輸出會輸入到下一個encoder。 Self-Attention接下來我們詳細看一下self-attention,其思想和attention類似,但是self-attention是Transformer用來將其他相關單詞的“理解”轉換成我們正常理解的單詞的一種思路,我們看個例子: 1、首先,self-attention會計算出三個新的向量,在論文中,向量的維度是512維,我們把這三個向量分別稱為Query、Key、Value,這三個向量是用embedding向量與一個矩陣相乘得到的結果,這個矩陣是隨機初始化的,維度為(64,512)注意第二個維度需要和embedding的維度一樣,其值在BP的過程中會一直進行更新,得到的這三個向量的維度是64低于embedding維度的。
2、計算self-attention的分數值,該分數值決定了當我們在某個位置encode一個詞時,對輸入句子的其他部分的關注程度。這個分數值的計算方法是Query與Key做點乘,以下圖為例,首先我們需要針對Thinking這個詞,計算出其他詞對于該詞的一個分數值,首先是針對于自己本身即q1·k1,然后是針對于第二個詞即q1·k2 Multi-Headed Attention這篇論文更厲害的地方是給self-attention加入了另外一個機制,被稱為“multi-headed” attention,該機制理解起來很簡單,就是說不僅僅只初始化一組Q、K、V的矩陣,而是初始化多組,tranformer是使用了8組,所以最后得到的結果是8個矩陣。
Positional Encoding到目前為止,transformer模型中還缺少一種解釋輸入序列中單詞順序的方法。為了處理這個問題,transformer給encoder層和decoder層的輸入添加了一個額外的向量Positional Encoding,維度和embedding的維度一樣,這個向量采用了一種很獨特的方法來讓模型學習到這個值,這個向量能決定當前詞的位置,或者說在一個句子中不同的詞之間的距離。這個位置向量的具體計算方法有很多種,論文中的計算方法如下 如果我們的嵌入維度為4,那么實際上的位置編碼就如下圖所示: 觀察下面的圖形,每一行都代表著對一個矢量的位置編碼。因此第一行就是我們輸入序列中第一個字的嵌入向量,每行都包含512個值,每個值介于1和-1之間。我們用顏色來表示1,-1之間的值,這樣方便可視化的方式表現出來: Layer normalization在transformer中,每一個子層(self-attetion,ffnn)之后都會接一個殘差模塊,并且有一個Layer normalization 說到 normalization,那就肯定得提到 Batch Normalization。BN的主要思想就是:在每一層的每一批數據上進行歸一化。我們可能會對輸入數據進行歸一化,但是經過該網絡層的作用后,我們的數據已經不再是歸一化的了。隨著這種情況的發展,數據的偏差越來越大,我的反向傳播需要考慮到這些大的偏差,這就迫使我們只能使用較小的學習率來防止梯度消失或者梯度爆炸。 BN的具體做法就是對每一小批數據,在批這個方向上做歸一化。如下圖所示: Decoder層
可以看到decoder部分其實和encoder部分大同小異,不過在最下面額外多了一個masked mutil-head attetion,這里的mask也是transformer一個很關鍵的技術,我們一起來看一下。 Maskmask 表示掩碼,它對某些值進行掩蓋,使其在參數更新時不產生效果。Transformer 模型里面涉及兩種 mask,分別是 padding mask 和 sequence mask。 其中,padding mask 在所有的 scaled dot-product attention 里面都需要用到,而 sequence mask 只有在 decoder 的 self-attention 里面用到。 Padding Mask什么是 padding mask 呢?因為每個批次輸入序列長度是不一樣的也就是說,我們要對輸入序列進行對齊。具體來說,就是給在較短的序列后面填充 0。但是如果輸入的序列太長,則是截取左邊的內容,把多余的直接舍棄。因為這些填充的位置,其實是沒什么意義的,所以我們的attention機制不應該把注意力放在這些位置上,所以我們需要進行一些處理。 具體的做法是,把這些位置的值加上一個非常大的負數(負無窮),這樣的話,經過 softmax,這些位置的概率就會接近0! 而我們的 padding mask 實際上是一個張量,每個值都是一個Boolean,值為 false 的地方就是我們要進行處理的地方。 Sequence mask文章前面也提到,sequence mask 是為了使得 decoder 不能看見未來的信息。也就是對于一個序列,在 time_step 為 t 的時刻,我們的解碼輸出應該只能依賴于 t 時刻之前的輸出,而不能依賴 t 之后的輸出。因此我們需要想一個辦法,把 t 之后的信息給隱藏起來。 那么具體怎么做呢?也很簡單:產生一個上三角矩陣,上三角的值全為0。把這個矩陣作用在每一個序列上,就可以達到我們的目的。
編碼器通過處理輸入序列啟動。然后將頂部編碼器的輸出轉換為一組注意向量k和v。每個解碼器將在其“encoder-decoder attention”層中使用這些注意向量,這有助于解碼器將注意力集中在輸入序列中的適當位置: 輸出層當decoder層全部執行完畢后,怎么把得到的向量映射為我們需要的詞呢,很簡單,只需要在結尾再添加一個全連接層和softmax層,假如我們的詞典是1w個詞,那最終softmax會輸入1w個詞的概率,概率值最大的對應的詞就是我們最終的結果。 BERT原理詳解從創新的角度來看,bert其實并沒有過多的結構方面的創新點,其和GPT一樣均是采用的transformer的結構,相對于GPT來說,其是雙向結構的,而GPT是單向的,如下圖所示 openai gpt就做了一個改進,也是通過transformer學習出來一個語言模型,不是固定的,通過任務 finetuning,用transfomer代替elmo的lstm。 openAI gpt雖然可以進行fine-tuning,但是有些特殊任務與pretraining輸入有出入,單個句子與兩個句子不一致的情況,很難解決,還有就是decoder只能看到前面的信息。 結構先看下bert的內部結構,官網最開始提供了兩個版本,L表示的是transformer的層數,H表示輸出的維度,A表示mutil-head attention的個數 預訓練模型首先我們要了解一下什么是預訓練模型,舉個例子,假設我們有大量的維基百科數據,那么我們可以用這部分巨大的數據來訓練一個泛化能力很強的模型,當我們需要在特定場景使用時,例如做文本相似度計算,那么,只需要簡單的修改一些輸出層,再用我們自己的數據進行一個增量訓練,對權重進行一個輕微的調整。 預訓練的好處在于在特定場景使用時不需要用大量的語料來進行訓練,節約時間效率高效,bert就是這樣的一個泛化能力較強的預訓練模型。 BERT的預訓練過程接下來我們看看BERT的預訓練過程,BERT的預訓練階段包括兩個任務,一個是Masked Language Model,還有一個是Next Sentence Prediction。 Masked Language ModelMLM可以理解為完形填空,作者會隨機mask每一個句子中15%的詞,用其上下文來做預測,例如: 此處將hairy進行了mask處理,然后采用非監督學習的方法預測mask位置的詞是什么,但是該方法有一個問題,因為是mask15%的詞,其數量已經很高了,這樣就會導致某些詞在fine-tuning階段從未見過,為了解決這個問題,作者做了如下的處理:
那么為啥要以一定的概率使用隨機詞呢?這是因為transformer要保持對每個輸入token分布式的表征,否則Transformer很可能會記住這個[MASK]就是"hairy"。至于使用隨機詞帶來的負面影響,文章中解釋說,所有其他的token(即非"hairy"的token)共享15%*10% = 1.5%的概率,其影響是可以忽略不計的。Transformer全局的可視,又增加了信息的獲取,但是不讓模型獲取全量信息。
Next Sentence Prediction選擇一些句子對A與B,其中50%的數據B是A的下一條句子,剩余50%的數據B是語料庫中隨機選擇的,學習其中的相關性,添加這樣的預訓練的目的是目前很多NLP的任務比如QA和NLI都需要理解兩個句子之間的關系,從而能讓預訓練的模型更好的適應這樣的任務。
輸入bert的輸入可以是單一的一個句子或者是句子對,實際的輸入值是segment embedding與position embedding相加,具體的操作流程可參考上面的transformer講解。 BERT的輸入詞向量是三個向量之和: Token Embedding:WordPiece tokenization subword詞向量。 總結
BERT是谷歌團隊糅合目前已有的NLP知識集大成者,刷新11條賽道彰顯了無與倫比的實力,且極容易被用于多種NLP任務。宛若一束煙花點亮在所有NLP從業者心中。更為可貴的是谷歌選擇了開源這些,讓所有從業者看到了在各行各業落地的更多可能性。 BERT優點
BERT缺點
關于BERT最新的各領域應用推薦張俊林的Bert時代的創新(應用篇) 思考
BERT適用場景 第一,如果NLP任務偏向在語言本身中就包含答案,而不特別依賴文本外的其它特征,往往應用Bert能夠極大提升應用效果。典型的任務比如QA和閱讀理解,正確答案更偏向對語言的理解程度,理解能力越強,解決得越好,不太依賴語言之外的一些判斷因素,所以效果提升就特別明顯。反過來說,對于某些任務,除了文本類特征外,其它特征也很關鍵,比如搜索的用戶行為/鏈接分析/內容質量等也非常重要,所以Bert的優勢可能就不太容易發揮出來。再比如,推薦系統也是類似的道理,Bert可能只能對于文本內容編碼有幫助,其它的用戶行為類特征,不太容易融入Bert中。 第二,Bert特別適合解決句子或者段落的匹配類任務。就是說,Bert特別適合用來解決判斷句子關系類問題,這是相對單文本分類任務和序列標注等其它典型NLP任務來說的,很多實驗結果表明了這一點。而其中的原因,我覺得很可能主要有兩個,一個原因是:很可能是因為Bert在預訓練階段增加了Next Sentence Prediction任務,所以能夠在預訓練階段學會一些句間關系的知識,而如果下游任務正好涉及到句間關系判斷,就特別吻合Bert本身的長處,于是效果就特別明顯。第二個可能的原因是:因為Self Attention機制自帶句子A中單詞和句子B中任意單詞的Attention效果,而這種細粒度的匹配對于句子匹配類的任務尤其重要,所以Transformer的本質特性也決定了它特別適合解決這類任務。 從上面這個Bert的擅長處理句間關系類任務的特性,我們可以繼續推理出以下觀點: 既然預訓練階段增加了Next Sentence Prediction任務,就能對下游類似性質任務有較好促進作用,那么是否可以繼續在預訓練階段加入其它的新的輔助任務?而這個輔助任務如果具備一定通用性,可能會對一類的下游任務效果有直接促進作用。這也是一個很有意思的探索方向,當然,這種方向因為要動Bert的第一個預訓練階段,所以屬于NLP屆土豪們的工作范疇,窮人們還是散退、旁觀、鼓掌、叫好為妙。 第三,Bert的適用場景,與NLP任務對深層語義特征的需求程度有關。感覺越是需要深層語義特征的任務,越適合利用Bert來解決;而對有些NLP任務來說,淺層的特征即可解決問題,典型的淺層特征性任務比如分詞,POS詞性標注,NER,文本分類等任務,這種類型的任務,只需要較短的上下文,以及淺層的非語義的特征,貌似就可以較好地解決問題,所以Bert能夠發揮作用的余地就不太大,有點殺雞用牛刀,有力使不出來的感覺。 這很可能是因為Transformer層深比較深,所以可以逐層捕獲不同層級不同深度的特征。于是,對于需要語義特征的問題和任務,Bert這種深度捕獲各種特征的能力越容易發揮出來,而淺層的任務,比如分詞/文本分類這種任務,也許傳統方法就能解決得比較好,因為任務特性決定了,要解決好它,不太需要深層特征。 第四,Bert比較適合解決輸入長度不太長的NLP任務,而輸入比較長的任務,典型的比如文檔級別的任務,Bert解決起來可能就不太好。主要原因在于:Transformer的self attention機制因為要對任意兩個單詞做attention計算,所以時間復雜度是n平方,n是輸入的長度。如果輸入長度比較長,Transformer的訓練和推理速度掉得比較厲害,于是,這點約束了Bert的輸入長度不能太長。所以對于輸入長一些的文檔級別的任務,Bert就不容易解決好。結論是:Bert更適合解決句子級別或者段落級別的NLP任務。 如果有小伙伴堅持看到這里的話深表感謝,本來要繼續寫源碼分析和具體的實踐了。時間關系,等下周再抽時間寫源碼分析與實踐部分吧。本文僅用于筆者自己總結自己BERT學習之路,期間引用很多專家學者的觀點思路,深表感謝。第一次駕馭這么長的技術文章,每個知識點都想寫點,感覺越寫越亂。若有讀者在閱讀本文期間有不好的閱讀體驗深表歉意。 |
|