天天看點

谷歌終于開源BERT代碼:3 億參數量,機器之心全面解讀

今日,谷歌終于放出官方代碼和預訓練模型,包括 BERT 模型的 TensorFlow 實作、BERT-Base 和 BERT-Large 預訓練模型和論文中重要實驗的 TensorFlow 代碼。在本文中,機器之心首先會介紹 BERT 的直覺概念、業界大牛對它的看法以及官方預訓練模型的特點,并在後面一部分具體解讀 BERT 的研究論文與實作,整篇文章的主要結構如下所示:

1 簡介

  • 預訓練 NLP 模型
  • 計算力
  • 研究團隊
  • 官方預訓練模型

2 Transformer 概覽

3 BERT 論文解讀

  • 輸入表征
  • 預訓練過程
  • 微調過程

4 官方模型詳情

  • 微調預訓練 BERT
  • 使用預訓練 BERT 抽取語義特征

BERT 的核心過程非常簡潔,它會先從資料集抽取兩個句子,其中第二句是第一句的下一句的機率是 50%,這樣就能學習句子之間的關系。其次随機去除兩個句子中的一些詞,并要求模型預測這些詞是什麼,這樣就能學習句子内部的關系。最後再将經過處理的句子傳入大型 Transformer 模型,并通過兩個損失函數同時學習上面兩個目标就能完成訓練。

業界廣泛認為谷歌新提出來的 BERT 預訓練模型主要在三方面會啟發今後的研究,即對預訓練 NLP 模型的貢獻、計算力對研究的重要性、以及研究團隊和工程能力。

其實預訓練模型或遷移學習很早就有人研究,但真正廣受關注還是在近幾年。清華大學劉知遠表示:「大概在前幾年,可能很多人都認為預訓練的意義不是特别大,當時感覺直接在特定任務上做訓練可能效果會更好。我認為 BERT 相當于在改變大家的觀念,即在極大資料集上進行預訓練對于不同的 NLP 任務都會有幫助。」

雖然 CV 領域的預訓練模型展現出強大的能力,但 NLP 領域也一直探讨實作無監督預訓練的方法,複旦大學邱錫鵬說:「其實早在 16 年的時候,我們在知乎上讨論過 NLP 的發展方向。我記得當初回答 NLP 有兩個問題,其中第一個就是怎麼充分挖掘無标注資料,而 BERT 這篇論文提供了兩個很好的方向來挖掘無标注資料的潛力。雖然這兩個方法本身并不新穎,但它相當于做得非常極緻。另外一個問題是 Transformer,它當時在機器翻譯中已經展示出非常強的能力,其實越大的資料量就越能顯示出這個結構的優點,因為它可以疊加非常深的層級。」

深度好奇創始人兼 CTO 呂正東博士最後總結道:「通用的 composition architecture + 大量資料 + 好的 unsupervised 損失函數,帶來的好的 sentence model, 可以走很遠。它的架構以及它作為 pre-trained model 的使用方式,都非常類似視覺領域的好的深度分類模型,如 AlexNet 和 Residual Net。」

盡管 BERT 效果驚人,但它需要的計算量非常大,原作者在論文中也表示每次隻能預測 15% 的詞,是以模型收斂得非常慢。BERT 的作者在 Reddit 上也表示預訓練的計算量非常大,Jacob 說:「OpenAI 的 Transformer 有 12 層、768 個隐藏單元,他們使用 8 塊 P100 在 8 億詞量的資料集上訓練 40 個 Epoch 需要一個月,而 BERT-Large 模型有 24 層、2014 個隐藏單元,它們在有 33 億詞量的資料集上需要訓練 40 個 Epoch,是以在 8 塊 P100 上可能需要 1 年?16 Cloud TPU 已經是非常大的計算力了。」

呂正東表示:「BERT 是一個 google 風格的暴力模型,暴力模型的好處是驗證概念上簡單模型的有效性,進而粉碎大家對于奇技淫巧的迷戀; 但暴力模型通常出現的一個壞處是'there is no new physics',我相信不少人對 BERT 都有那種『我也曾經多多少少想過類似的事情』的感覺,雖然也僅僅是想過而已。」

最後對于 BERT 的研究團隊,微軟全球技術院士黃學東說:「包括一作 Jacob 在内,BERT 四個作者有三個是微軟前員工。這個研究其實改變了自然語言處理今後的方向,他們的貢獻應該和當年微軟在計算機視覺中用 ResNet 所造就的貢獻是一樣的。可惜 Jacob 不是在我們團隊做的,我們本來可以多一項光榮的任務。我非常喜歡 Jacob 的東西,他以前也是微軟的優秀員工。」

BERT 官方預訓練模型

在衆多研究者的關注下,谷歌釋出了 BERT 的實作代碼與預訓練模型。其中代碼比較簡單,基本上是标準的 Transformer 實作,但是釋出的預訓練模型非常重要,因為它需要的計算力太多。總體而言,谷歌開放了預訓練的 BERT-Base 和 BERT-Large 模型,且每一種模型都有 Uncased 和 Cased 兩種版本。

其中 Uncased 在使用 WordPiece 分詞之前都轉換為小寫格式,并剔除所有 Accent Marker,而 Cased 會保留它們。項目作者表示一般使用 Uncased 模型就可以了,除非大小寫對于任務很重要才會使用 Cased 版本。所有預訓練模型及其位址如下:

  • BERT-Large, Cased:24-layer, 1024-hidden, 16-heads, 340M parameters(目前無法使用,需要重新生成)。

每一個 ZIP 檔案都包含了三部分,即儲存預訓練模型與權重的 ckpt 檔案、将 WordPiece 映射到單詞 id 的 vocab 檔案,以及指定模型超參數的 json 檔案。除此之外,谷歌還釋出了原論文中将預訓練模型應用于各種 NLP 任務的源代碼,感興趣的讀者可以檢視 GitHub 項目複現論文結果。

BERT 官方項目位址:

https://github.com/google-research/bert

最後,這個項目可以在 CPU、GPU 和 TPU 上運作,但是在有 12GB 到 16GB 顯存的 GPU 上,很可能模型會發生顯存不足的問題。是以讀者也可以在 Colab 先試着使用 BERT,如下展示了在 Colab 上使用免費 TPU 微調 BERT 的 Notebook:

BERT Colab 位址:

https://colab.sandbox.google.com/github/tensorflow/tpu/blob/master/tools/colab/bert_finetuning_with_cloud_tpus.ipynb

在整個 Transformer 架構中,它隻使用了注意力機制和全連接配接層來處理文本,是以 Transformer 确實沒使用循環神經網絡或卷積神經網絡實作「特征抽取」這一功能。此外,Transformer 中最重要的就是自注意力機制,這種在序列内部執行 Attention 的方法可以視為搜尋序列内部的隐藏關系,這種内部關系對于翻譯以及序列任務的性能有顯著提升。

如 Seq2Seq 一樣,原版 Transformer 也采用了編碼器-解碼器架構,但它們會使用多個 Multi-Head Attention、前饋網絡、層級歸一化和殘差連接配接等。下圖從左到右展示了原論文所提出的 Transformer 架構、Multi-Head Attention 和點乘注意力。本文隻簡要介紹這三部分的基本概念與結構,更詳細的 Transformer 解釋與實作請檢視機器之心的 GitHub 項目:

基于注意力機制,機器之心帶你了解與訓練神經機器翻譯系統 。

其中點乘注意力是注意力機制的一般表達形式,将多個點乘注意力疊加在一起可以組成 Transformer 中最重要的 Multi-Head Attention 子產品,多個 Multi-Head Attention 子產品堆疊在一起就組成了 Transformer 的主體結構,并借此抽取文本中的資訊。

谷歌終于開源BERT代碼:3 億參數量,機器之心全面解讀

改編自論文《Attention is all your need》。

上圖右邊的點乘注意力其實就是标準 Seq2Seq 模型中的注意力機制。其中 Query 向量與 Value 向量在 NMT 中相當于目智語輸入序列與源語輸入序列,Query 與 Key 向量的點乘相當于餘弦相似性,經過 SoftMax 函數後可得出一組歸一化的機率。這些機率相當于給源語輸入序列做權重平均,即表示在生成一個目智語單詞時源語序列中哪些詞是重要的。

上圖中間的 Multi-head Attention 其實就是多個點乘注意力并行處理并将最後的結果拼接在一起。這種注意力允許模型聯合關注不同位置的不同表征子空間資訊,我們可以了解為在參數不共享的情況下,多次執行點乘注意力。

最後上圖左側為 Transformer 的整體架構。輸入序列首先會轉換為詞嵌入向量,在與位置編碼向量相加後可作為 Multi-Head 自注意力子產品的輸入,自注意力子產品表示 Q、V、K 三個矩陣都是相同的。該子產品的輸出再經過一個全連接配接層就可以作為編碼器子產品的輸出。

原版 Transformer 的解碼器與編碼器結構基本一緻,隻不過在根據前面譯文預測目前譯文時會用到編碼器輸出的原語資訊。在 BERT 論文中,研究者表示他們隻需要使用編碼器抽取文本資訊,是以相對于原版架構隻需要使用編碼器子產品。

在模型架構上,BERT 使用了非常深的網絡,原版 Transformer 隻堆疊了 6 個編碼器解碼器子產品,即上圖的 N=6。而 BERT 基礎模型使用了 12 個編碼器子產品(N=12),BERT 大模型堆疊了 24 個編碼器子產品(N=24)。其中堆疊了 6 個子產品的 BERT 基礎模型主要是為了和 OpenAI GPT 進行對比。

BERT 的全稱是基于 Transformer 的雙向編碼器表征,其中「雙向」表示模型在處理某一個詞時,它能同時利用前面的詞和後面的詞兩部分資訊。這種「雙向」的來源在于 BERT 與傳統語言模型不同,它不是在給定所有前面詞的條件下預測最可能的目前詞,而是随機遮掩一些詞,并利用所有沒被遮掩的詞進行預測。下圖展示了三種預訓練模型,其中 BERT 和 ELMo 都使用雙向資訊,OpenAI GPT 使用單向資訊。

谷歌終于開源BERT代碼:3 億參數量,機器之心全面解讀

圖:選自《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》。

如上所示為不同預訓練模型的架構,BERT 可以視為結合了 OpenAI GPT 和 ELMo 優勢的新模型。其中 ELMo 使用兩條獨立訓練的 LSTM 擷取雙向資訊,而 OpenAI GPT 使用新型的 Transformer 和經典語言模型隻能擷取單向資訊。BERT 的主要目标是在 OpenAI GPT 的基礎上對預訓練任務做一些改進,以同時利用 Transformer 深度模型與雙向資訊的優勢。

前面已經了解過 BERT 最核心的過程就是同時預測加了 MASK 的缺失詞與 A/B 句之間的二進制關系,而這些首先都需要展現在模型的輸入中,在 Jacob 等研究者的原論文中,有一張圖很好地展示了模型輸入的結構。

谷歌終于開源BERT代碼:3 億參數量,機器之心全面解讀

如上所示,輸入有 A 句「my dog is cute」和 B 句「he likes playing」這兩個自然句,我們首先需要将每個單詞及特殊符号都轉化為詞嵌入向量,因為神經網絡隻能進行數值計算。其中特殊符 [SEP] 是用于分割兩個句子的符号,前面半句會加上分割編碼 A,後半句會加上分割編碼 B。

因為要模組化句子之間的關系,BERT 有一個任務是預測 B 句是不是 A 句後面的一句話,而這個分類任務會借助 A/B 句最前面的特殊符 [CLS] 實作,該特殊符可以視為彙集了整個輸入序列的表征。

最後的位置編碼是 Transformer 架構本身決定的,因為基于完全注意力的方法并不能像 CNN 或 RNN 那樣編碼詞與詞之間的位置關系,但是正因為這種屬性才能無視距離長短模組化兩個詞之間的關系。是以為了令 Transformer 感覺詞與詞之間的位置關系,我們需要使用位置編碼給每個詞加上位置資訊。

BERT 最核心的就是預訓練過程,這也是該論文的亮點所在。簡單而言,模型會從資料集抽取兩句話,其中 B 句有 50% 的機率是 A 句的下一句,然後将這兩句話轉化前面所示的輸入表征。現在我們随機遮掩(Mask 掉)輸入序列中 15% 的詞,并要求 Transformer 預測這些被遮掩的詞,以及 B 句是 A 句下一句的機率這兩個任務。

谷歌終于開源BERT代碼:3 億參數量,機器之心全面解讀

首先谷歌使用了 BooksCorpus(8 億詞量)和他們自己抽取的 Wikipedia(25 億詞量)資料集,每次疊代會抽取 256 個序列(A+B),一個序列的長度為小于等于 512 個「詞」。是以 A 句加 B 句大概是 512 個詞,每一個「句子」都是非常長的一段話,這和一般我們所說的句子是不一樣的。這樣算來,每次疊代模型都會處理 12.8 萬詞。

對于二分類任務,在抽取一個序列(A+B)中,B 有 50% 的機率是 A 的下一句。如果是的話就會生成标注「IsNext」,不是的話就會生成标注「NotNext」,這些标注可以作為二進制分類任務判斷模型預測的憑證。

對于 Mask 預測任務,首先整個序列會随機 Mask 掉 15% 的詞,這裡的 Mask 不隻是簡單地用「[MASK]」符号代替某些詞,因為這會引起預訓練與微調兩階段不是太比對。是以谷歌在确定需要 Mask 掉的詞後,80% 的情況下會直接替代為「[MASK]」,10% 的情況會替代為其它任意的詞,最後 10% 的情況會保留原詞。

  • 原句:my dog is hairy
  • 80%:my dog is [MASK]
  • 10%:my dog is apple
  • 10%:my dog is hairy

注意最後 10% 保留原句是為了将表征偏向真實觀察值,而另外 10% 用其它詞替代原詞并不會影響模型對語言的了解能力,因為它隻占所有詞的 1.5%(0.1 × 0.15)。此外,作者在論文中還表示因為每次隻能預測 15% 的詞,是以模型收斂比較慢。

最後預訓練完模型,就要嘗試把它們應用到各種 NLP 任務中,并進行簡單的微調。不同的任務在微調上有一些差别,但 BERT 已經強大到能為大多數 NLP 任務提供高效的資訊抽取功能。對于分類問題而言,例如預測 A/B 句是不是問答對、預測單句是不是文法正确等,它們可以直接利用特殊符 [CLS] 所輸出的向量 C,即 P = softmax(C * W),新任務隻需要微調權重矩陣 W 就可以了。

對于其它序列标注或生成任務,我們也可以使用 BERT 對應的輸出資訊作出預測,例如每一個時間步輸出一個标注或詞等。下圖展示了 BERT 在 11 種任務中的微調方法,它們都隻添加了一個額外的輸出層。在下圖中,Tok 表示不同的詞、E 表示輸入的嵌入向量、T_i 表示第 i 個詞在經過 BERT 處理後輸出的上下文向量。

谷歌終于開源BERT代碼:3 億參數量,機器之心全面解讀

如上圖所示,句子級的分類問題隻需要使用對應 [CLS] 的 C 向量,例如(a)中判斷問答對是不是包含正确回答的 QNLI、判斷兩句話有多少相似性的 STS-B 等,它們都用于處理句子之間的關系。句子級的分類還包含(b)中判語句中斷情感趨向的 SST-2 和判斷文法正确性的 CoLA 任務,它們都是處理句子内部的關系。

在 SQuAD v1.1 問答資料集中,研究者将問題和包含回答的段落分别作為 A 句與 B 句,并輸入到 BERT 中。通過 B 句的輸出向量,模型能預測出正确答案的位置與長度。最後在命名實體識别資料集 CoNLL 中,每一個 Tok 對應的輸出向量 T 都會預測它的标注是什麼,例如人物或地點等。

前面我們已經介紹過谷歌官方釋出的 BERT 項目,這一部分主要會讨論如何在不同的 NLP 任務中微調預訓練模型,以及怎樣使用預訓練 BERT 抽取文本的語義特征。此外,原項目還展示了 BERT 的預訓練過程,但由于它需要的計算力太大,是以這裡并不做介紹,讀者可詳細閱讀原項目的說明檔案。

項目位址:

該項目表示原論文中 11 項 NLP 任務的微調都是在單塊 Cloud TPU(64GB RAM)上進行的,目前無法使用 12GB - 16GB 記憶體的 GPU 複現論文中 BERT-Large 模型的大部分結果,因為記憶體比對的最大批大小仍然太小。但是基于給定的超參數,BERT-Base 模型在不同任務上的微調應該能夠在一塊 GPU(顯存至少 12GB)上運作。

這裡主要介紹如何在句子級的分類任務以及标準問答資料集(SQuAD)微調 BERT-Base 模型,其中微調過程主要使用一塊 GPU。而 BERT-Large 模型的微調讀者可以參考原項目。

以下為原項目中展示的句子級分類任務的微調,在運作該示例之前,你必須運作一個腳本下載下傳GLUE data,并将它放置到目錄$GLUE_DIR。然後,下載下傳預訓練BERT-Base模型,解壓縮後存儲到目錄$BERT_BASE_DIR。

GLUE data 腳本位址:

https://gist.github.com/W4ngatang/60c2bdb54d156a41194446737ce03e2e

該示例代碼在Microsoft Research Paraphrase Corpus(MRPC)上對BERT-Base進行微調,該語料庫僅包含3600個樣本,在大多數GPU上該微調過程僅需幾分鐘。

export BERT_BASE_DIR=/path/to/bert/uncased_L-12_H-768_A-12export GLUE_DIR=/path/to/glue
python run_classifier.py \
  --task_name=MRPC \
  --do_train=true \
  --do_eval=true \
  --data_dir=$GLUE_DIR/MRPC \
  --vocab_file=$BERT_BASE_DIR/vocab.txt \
  --bert_config_file=$BERT_BASE_DIR/bert_config.json \
  --init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \
  --max_seq_length=128 \
  --train_batch_size=32 \
  --learning_rate=2e-5 \
  --num_train_epochs=3.0 \
  --output_dir=/tmp/mrpc_output/      

輸出如下:

***** Eval results *****
  eval_accuracy = 0.845588
  eval_loss = 0.505248
  global_step = 343
  loss = 0.505248      

可以看到,開發集準确率是84.55%。類似MRPC這樣的較小資料集在開發集準确率上方差較高,即使是從同樣的預訓練檢查點開始運作。如果你重新運作多次(確定使用不同的output_dir),結果将在84%和88%之間。注意:你或許會看到資訊“Running train on CPU.”這隻是表示模型不是運作在Cloud TPU上而已。

通過預訓練BERT抽取語義特征

對于原論文11項任務之外的試驗,我們也可以通過預訓練BERT抽取定長的語義特征向量。因為在特定案例中,與其端到端微調整個預訓練模型,直接擷取預訓練上下文嵌入向量會更有效果,并且也可以緩解大多數記憶體不足問題。在這個過程中,每個輸入token的上下文嵌入向量指預訓練模型隐藏層生成的定長上下文表征。

例如,我們可以使用腳本extract_features.py 抽取語義特征:

# Sentence A and Sentence B are separated by the ||| delimiter.# For single sentence inputs, don't use the delimiter.echo 'Who was Jim Henson ? ||| Jim Henson was a puppeteer' > /tmp/input.txt
python extract_features.py \
  --input_file=/tmp/input.txt \
  --output_file=/tmp/output.jsonl \
  --vocab_file=$BERT_BASE_DIR/vocab.txt \
  --bert_config_file=$BERT_BASE_DIR/bert_config.json \
  --init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \
  --layers=-1,-2,-3,-4 \
  --max_seq_length=128 \
  --batch_size=8      

上面的腳本會建立一個JSON檔案(每行輸入占一行),JSON檔案包含layers指定的每個Transformer層的BERT激活值(-1是Transformer的最後一個隐藏層)。注意這個腳本将生成非常大的輸出檔案,預設情況下每個輸入token 會占據 15kb 左右的空間。

最後,項目作者表示它們近期會解決GPU顯存占用太多的問題,并且會釋出多語言版的BERT預訓練模型。他們表示隻要在維基百科有比較大型的資料,那麼他們就能提供預訓練模型,是以我們還能期待下次谷歌釋出基于中文語料的BERT預訓練模型。

谷歌終于開源BERT代碼:3 億參數量,機器之心全面解讀

參考連結:

https://arxiv.org/pdf/1810.04805.pdf https://www.zhihu.com/question/298203515 https://www.reddit.com/r/MachineLearning/comments/9nfqxz/r_bert_pretraining_of_deep_bidirectional/

繼續閱讀