谷歌終於開源BERT程式碼:3 億引數量,機器之心全面解讀

機器之心發表於2018-11-01

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

1 簡介

  • 預訓練 NLP 模型

  • 計算力

  • 研究團隊

  • 官方預訓練模型

2 Transformer 概覽

3 BERT 論文解讀

  • 輸入表徵

  • 預訓練過程

  • 微調過程

4 官方模型詳情

  • 微調預訓練 BERT

  • 使用預訓練 BERT 抽取語義特徵

1 簡介

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

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

預訓練 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-Base, Uncased:12-layer, 768-hidden, 12-heads, 110M parameters

  • 地址:https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-12_H-768_A-12.zip

  • BERT-Large, Uncased:24-layer, 1024-hidden, 16-heads, 340M parameters

  • 地址:https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-24_H-1024_A-16.zip

  • BERT-Base, Cased:12-layer, 768-hidden, 12-heads , 110M parameters

  • 地址:https://storage.googleapis.com/bert_models/2018_10_18/cased_L-12_H-768_A-12.zip

  • 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

2 Transformer 概覽

在整個 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 進行對比。

3 BERT 論文解讀

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 億引數量,機器之心全面解讀

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

如上所示,輸入有 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 億引數量,機器之心全面解讀

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

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

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

4 官方模型詳情

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

專案地址:https://github.com/google-research/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預訓練模型。

參考連結:

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/

相關文章