前言
還記得不久之前的機器閱讀理解領域,微軟和阿里在SQuAD上分別以R-Net+和SLQA超過人類,百度在MS MARCO上憑藉V-Net霸榜並在BLEU上超過人類。這些網路可以說一個比一個複雜,似乎“如何設計出一個更work的task-specific的網路"變成了NLP領域政治正確的研究方向。而在這種風向下,不管word2vec也好,glove也好,fasttext也好,都只能充當一個錦上添花的作用。說好的遷移學習、預訓練呢?在NLP似乎始終沒成主角。
小夕寫這篇文章時也有點慚愧,搞了好一段時間的表示與遷移,雖然早在直覺上感覺這應該是NLP的核心問題,但是也沒做出一些令自己滿意的實驗結果,直到幾天前的BERT出來,才感覺是貧窮限制了我的想象力╮( ̄▽ ̄””)╭(劃掉),才感覺自己著眼的點還是太窄了。
每個人對於BERT的理解都不一樣,本文就試著從word2vec和ELMo的角度說說BERT。下面先簡單回顧一下word2vec和ELMo中的精華,已經理解很透徹的小夥伴可以快速下拉到BERT章節啦。
word2vec
說來也都是些俗套而樂此不疲一遍遍寫的句子,2013年Google的word2vec一出,讓NLP各個領域遍地開花,一時間好像不用上預訓練的詞向量都不好意思寫論文了。而word2vec是什麼呢?
模型
顯然就是一個“線性”語言模型。既然我們的目標是學習詞向量,而且詞向量在語義上要支援一些”線性的語義運算“,如”皇帝-皇后=男-女“(忽略武則天),那麼使用一個線性模型自然足夠了,跑的又快又能完成任務,非常優雅。
另外word2vec的一個精髓是把語言模型的那一套softmax加速方法也給順便優化了,用一個看似開腦洞的“負取樣”方法來代替傳統的層級softmax和NCE做法。而這個名字高大上的“負取樣”到底是什麼呢?
負取樣
我們知道對於訓練語言模型來說,softmax層非常難算,畢竟你要預測的是當前位置是哪個詞,那麼這個類別數就等同於詞典規模,因此動輒幾萬幾十萬的類別數,算softmax函式當然很費力啦。但是,如果我們的目標不在於訓練一個精準的語言模型,而只是為了訓練得到語言模型的副產物-詞向量,那麼其實只需要用這裡隱含的一個計算代價更小的“子任務”就好啦。
想一想,給你10000張寫有數字的卡片,讓你找出其中的最大值,是不是特別費力?但是如果把裡面的最大值事先抽出來,跟五張隨機抽取的卡片混到一起,讓你選出其中的最大值,是不是就容易多啦?
負取樣就是這個思想,即不直接讓模型從整個詞表找最可能的詞了,而是直接給定這個詞(即正例)和幾個隨機取樣的噪聲詞(即取樣出來的負例),只要模型能從這裡面找出正確的詞就認為完成目標啦。所以這個想法對應的目標函式即:
這裡是正例,是隨機取樣出來的負例(取樣k個),是sigmoid函式。然後即最大化正例的似然,最小化負例的似然。
這種負取樣的思想被成功的應用在了BERT模型中,只不過粒度從詞變成了句子。不要急,慢慢往後看~
char-level與上下文
雖然2015年到2017年也有不少工作試圖從char-level入手,另闢蹊徑,擺脫預訓練詞向量的遊戲規則,然而實測只是曇花一現,很快被懟了[8][9]。不過,人們同時也意識到了char-level的文字中也蘊含了一些word-level的文字所難以描述的模式,因此一方面出現了可以學習到char-level特徵的詞向量FastText[5],另一方面在有監督任務中開始通過淺層CNN、HIghwayNet、RNN等網路引入char-level文字的表示。
不過,至此為止,詞向量都是上下文無關的。也就是說,同一個詞在不同的語境中總是相同的詞向量,很明顯這就導致詞向量模型缺乏詞義消歧(WSD)的能力。於是,人們為了讓詞向量變得上下文相關,開始在具體的下游任務中基於詞向量sequence來做encoding。
最常見的encoding方法當然就是用RNN系的網路,除此之外還有成功的用深層CNN來encoding的工作(如文字分類[6],機器翻譯[7],機器閱讀理解[4]),然!而!Google說了,CNN也太俗了,我們要用全連線網路!(劃掉)self-attention!於是就有了為NLP深度定製的Transformer模型[11],Transformer的提出是在機器翻譯任務上,但是其在其他領域如檢索式對話[3]上也發揮了巨大的威力。
不過,既然發現在各個NLP任務中基本都有encoding的需要,那麼為啥不在最開始就讓詞向量擁有上下文相關的能力呢?於是有了ELMo[2]。
ELMo
當然,實際上ELMo不是第一個試圖產生上下文相關的詞向量的模型,不過確是一個讓你有充分理由放棄word2vec的模型(手動微笑),畢竟犧牲點推理速度換來辣麼多的效能提升,大部分情況下超值呀~ELMo在模型層上就是一個stacked bi-lstm(嚴格來說是訓練了兩個單向的stacked lstm),所以當然有不錯的encoding能力。同時其原始碼實現上也支援用Highway Net或者CNN來額外引入char-level encoding。訓練它的話自然也是語言模型標準的最大化似然函式,即
不過這個ELMo的亮點當然不在於模型層,而是其通過實驗間接說明了在多層的RNN中,不同層學到的特徵其實是有差異的,因此ELMo提出在預訓練完成並遷移到下游NLP任務中時,要為原始詞向量層和每一層RNN的隱層都設定一個可訓練引數,這些引數通過softmax層歸一化後乘到其相應的層上並求和便起到了weighting的作用,然後對“加權和”得到的詞向量再通過一個引數來進行詞向量整體的scaling以更好的適應下游任務。
ps:其實最後這個引數還是非常重要的,比如word2vec中,一般來說cbow和sg學出來的詞向量方差差異比較大,這時那個方差跟適合下游任務後續層方差匹配的詞向量就收斂更快,更容易有更好的表現
數學表示式如下
其中L=2就是ELMo論文中的設定,j=0時代表原始詞向量層,j=1是lstm的第一隱層,j=2是第二隱層。是引數被softmax歸一化之後的結果(也就是說)。
通過這樣的遷移策略,那些對詞義消歧有需求的任務就更容易通過訓練給第二隱層一個很大的權重,而對詞性、句法有明顯需求的任務則可能對第一隱層的引數學習到比較大的值(實驗結論)。總之,這樣便得到了一份”可以被下游任務定製“的特徵更為豐富的詞向量,效果比word2vec好得多也就不足為奇了。
不過話說回來,ELMo的目標也僅僅是學習到上下文相關的、更強大的詞向量,其目的依然是為下游任務提供一個紮實的根基,還沒有想要弒君稱王的意思。
而我們知道,僅僅是對文字進行充分而強大的encoding(即得到每個詞位非常精準豐富的特徵)是遠不夠覆蓋所有NLP任務的。在QA、機器閱讀理解(MRC)、自然語言推理(NLI)、對話等任務中,還有很多更復雜的模式需要捕捉,比如句間關係。為此,下游任務中的網路會加入各種花式attention(參考NLI、MRC、Chatbot中的SOTA們)。
而隨著捕捉更多神奇模式的需要,研究者們為每個下游任務定製出各種各樣的網路結構,導致同一個模型,稍微一換任務就掛掉了,甚至在同一個任務的情況下換另一種分佈的資料集都會出現顯著的效能損失,這顯然不符合人類的語言行為呀~要知道人類的generalization能力是非常強的,這就說明,或許現在整個NLP的發展軌跡就是錯的,尤其是在SQuAD的帶領下,窮盡各種trick和花式結構去刷榜,真正之於NLP的意義多大呢?
好像扯遠了,不過所幸,這條越走越偏的道路終於被一個模型shutdown了,那就是幾天前Google釋出的Bidirectional Encoder Representations from Transformers (BERT)[1].
BERT
這篇paper的最重要意義不在於用了什麼模型,也不在於怎麼訓練的,而是它提出一種全新的遊戲規則。
開始遊戲之前,先幫小夕點一下小廣告好不好呀\(//∇//)\
像之前說的,為每個NLP任務去深度定製泛化能力極差的複雜模型結構其實是非常不明智的,走偏了方向的。既然ELMo相比word2vec會有這麼大的提升,這就說明預訓練模型的潛力遠不止為下游任務提供一份精準的詞向量,所以我們可不可以直接預訓練一個龍骨級的模型呢?如果它裡面已經充分的描述了字元級、詞級、句子級甚至句間關係的特徵,那麼在不同的NLP任務中,只需要去為任務定製一個非常輕量級的輸出層(比如一個單層MLP)就好了,畢竟模型骨架都已經做好了嘛。
而BERT正是做了這件事情,或者說,它真的把這件事情做成了,它作為一個general的龍骨級模型輕鬆的挑戰了11個任務上的深度定製的模型。。。
所以它怎麼完成的呢?
深層雙向的encoding
首先,它指出,對上下文相關的詞向量的學習上,先前的預訓練模型還不夠!雖然在下游有監督任務中,encoding的方式已經是花裡胡哨非常充分了,深度雙向encoding基本成了許多複雜下游任務的標配(比如MRC, dialogue)。但是在預訓練模型上,先前的最先進模型也只是基於傳統的語言模型來做,而傳統的語言模型是單向的(數學上已經定義了),即
而且往往都很淺(想象一下LSTM堆三層就train不動了,就要上各種trick了),比如ELMo。
另外,雖然ELMo有用雙向RNN來做encoding,但是這兩個方向的RNN其實是分開訓練的,只是在最後在loss層做了個簡單相加。這樣就導致對於每個方向上的單詞來說,在被encoding的時候始終是看不到它另一側的單詞的。而顯然句子中有的單詞的語義會同時依賴於它左右兩側的某些詞,僅僅從單方向做encoding是不能描述清楚的。
那麼為什麼不像下游監督任務中那樣做真正的雙向encoding呢?
原因一想就很清楚了,畢竟傳統的語言模型是以預測下一個詞為訓練目標的,然而如果做了雙向encoding的話,那不就表示要預測的詞已經看到了嘛╮( ̄▽ ̄””)╭這樣的預測當然沒有意義了。所以,在BERT中,提出了使用一種新的任務來訓練監督任務中的那種真正可以雙向encoding的模型,這個任務稱為Masked Language Model (Masked LM)。
Masked LM
顧名思義,Masked LM就是說,我們不是像傳統LM那樣給定已經出現過的詞,去預測下一個詞,而是直接把整個句子的一部分詞(隨機選擇)蓋住(make it masked),這樣模型不就可以放心的去做雙向encoding了嘛,然後就可以放心的讓模型去預測這些蓋住的詞是啥。這個任務其實最開始叫做cloze test(大概翻譯成“完形填空測試”)。
這樣顯然會導致一些小問題。這樣雖然可以放心的雙向encoding了,但是這樣在encoding時把這些蓋住的標記也給encoding進去了╮( ̄▽ ̄””)╭而這些mask標記在下游任務中是不存在的呀。。。那怎麼辦呢?對此,為了儘可能的把模型調教的忽略這些標記的影響,作者通過如下方式來告訴模型“這些是噪聲是噪聲!靠不住的!忽略它們吧!”,對於一個被蓋住的單詞:
有80%的概率用“[mask]”標記來替換
有10%的概率用隨機取樣的一個單詞來替換
有10%的概率不做替換(雖然不做替換,但是還是要預測哈)
Encoder
在encoder的選擇上,作者並沒有用爛大街的bi-lstm,而是使用了可以做的更深、具有更好並行性的Transformer encoder來做。這樣每個詞位的詞都可以無視方向和距離的直接把句子中的每個詞都有機會encoding進來。另一方面我主觀的感覺Transformer相比lstm更容易免受mask標記的影響,畢竟self-attention的過程完全可以把mask標記針對性的削弱匹配權重,但是lstm中的輸入門是如何看待mask標記的那就不得而知了。
等下,小夕在之前的文章中也說過了,直接用Transformer encoder顯然不就丟失位置資訊了嘛?難道作者這裡也像Transformer原論文中那樣搞了個讓人怕怕的sin、cos函式編碼位置?並木有,作者這裡很簡單粗暴的直接去訓練了一個position embedding ╮( ̄▽ ̄””)╭ 這裡就是說,比如我把句子截斷到50的長度,那麼我們就有50個位置嘛,所以就有50個表徵位置的單詞,即從位置0一直到位置49。。。然後給每個位置詞一個隨機初始化的詞向量,再隨他們訓練去吧(很想說這特喵的也能work?太簡單粗暴了吧。。。)。另外,position embedding和word embedding的結合方式上,BERT裡選擇了直接相加。
最後,在深度方面,最終BERT完全版的encoder喪心病狂的疊加了24層的multi-head attention block(要知道對話裡的SOTA模型DAM也才用了5層…)。。。而且每個block包含16抽頭、1024隱單元╮( ̄▽ ̄””)╭此處打出標語:money is all you need (劃掉)
學習句子與句對關係表示
像之前說的,在很多工中,僅僅靠encoding是不足以完成任務的(這個只是學到了一堆token級的特徵),還需要捕捉一些句子級的模式,來完成SLI、QA、dialogue等需要句子表示、句間互動與匹配的任務。對此,BERT又引入了另一個極其重要卻又極其輕量級的任務,來試圖把這種模式也學習到。
句子級負取樣
還記得小夕在前面word2vec章節說過的,word2vec的一個精髓是引入了一個優雅的負取樣任務來學習詞向量(word-level representation)嘛。那麼如果我們把這個負取樣的過程給generalize到sentence-level呢?這便是BERT學習sentence-level representation的關鍵啦。
BERT這裡跟word2vec做法類似,不過構造的是一個句子級的分類任務。即首先給定的一個句子(相當於word2vec中給定context),它下一個句子即為正例(相當於word2vec中的正確詞),隨機取樣一個句子作為負例(相當於word2vec中隨機取樣的詞),然後在該sentence-level上來做二分類(即判斷句子是當前句子的下一句還是噪聲)。通過這個簡單的句子級負取樣任務,BERT就可以像word2vec學習詞表示那樣輕鬆學到句子表示啦。
句子級表示
等等,前面說了這麼半天,還沒有說句子該怎麼表示呢。。。
BERT這裡並沒有像下游監督任務中的普遍做法一樣,在encoding的基礎上再搞個全域性池化之類的,它首先在每個sequence(對於句子對任務來說是兩個拼起來的句子,對於其他任務來說是一個句子)前面加了一個特殊的token,記為[CLS],如圖
ps:這裡的[sep]是句子之間的分隔符,BERT同時支援學習句對的表示,這裡是[SEP]便是為了區分句對的切割點。
然後讓encoder對[CLS]進行深度encoding,深度encoding的最高隱層即為整個句子/句對的表示啦。這個做法乍一看有點費解,不過別忘了,Transformer是可以無視空間和距離的把全域性資訊encoding進每個位置的,而[CLS]作為句子/句對的表示是直接跟分類器的輸出層連線的,因此其作為梯度反傳路徑上的“關卡”,當然會想辦法學習到分類相關的上層特徵啦。
另外,為了讓模型能夠區分裡面的每個詞是屬於“左句子”還是“右句子”,作者這裡引入了“segment embedding”的概念來區分句子。對於句對來說,就用embedding A和embedding B來分別代表左句子和右句子;而對於句子來說,就只有embedding A啦。這個embedding A和B也是隨模型訓練出來的。
ps: 這做法跟position embedding一樣感覺簡單粗暴,實在很費解為什麼BERT用在“quora question pairs”這種理論上需要網路保持對稱的任務上依然能work,心情複雜
所以最終BERT每個token的表示由token原始的詞向量token embedding、前文提到的position embedding和這裡的segment embedding三部分相加而成,如圖:
簡潔到過分的下游任務介面
真正體現出BERT這個模型是龍骨級模型而不再是詞向量的,就是其到各個下游任務的介面設計了,或者換個更洋氣的詞叫遷移策略。
首先,既然句子和句子對的上層表示都得到了,那麼當然對於文字分類任務和文字匹配任務(文字匹配其實也是一種文字分類任務,只不過輸入是文字對)來說,只需要用得到的表示(即encoder在[CLS]詞位的頂層輸出)加上一層MLP就好了呀~
既然文字都被深度雙向encoding了,那麼做序列標註任務就只需要加softmax輸出層就好了呀,連CRF都不用了呀~
讓小夕更木有想到的是,在span抽取式任務如SQuAD上,把深度encoding和深度attention這倆大禮包省掉就算了,甚至都敢直接把輸出層的pointer net給丟掉了?直接像DrQA那樣傲嬌的用兩個線性分類器分別輸出span的起點和終點?不多說了,已跪m(_ _)m
最後來看一下實驗效果
嗯,這很Google。
此論文一出,小夕非常開心,因為很多之前的想法都不用去做實驗驗證了,因為已經被BERT摁死了(。 ́︿ ̀。)分類、標註和遷移任務都可以從頭開始了,SQuAD的造樓計劃也可以停了,感謝BERT沒有跑生成任務,這給人帶來了一點想象空間。嗯,手動微笑流淚。
最後,喜歡小夕的小哥哥小姐姐們歡迎通過下方打賞按鈕或者點選下方小廣告鼓勵小夕哦,愛你們??~
參考文獻
[1] 2018 | BERT- Pre-training of Deep Bidirectional Transformers for Language Understanding
[2] 2018NAACL | Deep contextualized word representations
[3] 2018 ACL | Multi-Turn Response Selection for Chatbots with Deep Attention Matching Network
[4] 2018ICLR | Fast and Accurate Reading Comprehension by Combining Self-Attention and Convolution
[5] 2017TACL | Enriching Word Vectors with Subword Information
[6] 2017ACL | Deep Pyramid Convolutional Neural Networks for Text Categorization
[7] 2017 | Convolutional Sequence to Sequence Learning
[8] 2017 | Do Convolutional Networks need to be Deep for Text Classification ?
[9] 2016 | Convolutional Neural Networks for Text Categorization/ Shallow Word-level vs. Deep Character-level
[10] 2013NIPS | Distributed-representations-of-words-and-phrases-and-their-compositionality