原博文:

谷歌終於開源BERT代碼:3 億參數量,機器之心全面解讀_模型?

www.sohu.com
圖標
谷歌開源BERT不費吹灰之力輕鬆訓練自然語言模型?

mp.weixin.qq.com
圖標
乾貨 | 谷歌BERT模型fine-tune終極實踐教程?

mp.weixin.qq.com
圖標

理論上,NLP 預訓練技術可以是上下文無關的,也可以是上下文互相檢索的。

  • 上下文無關的模型,像 word2vec 或者 GloVegenerate 是以單個辭彙嵌入辭彙表的表達方式。
  • 上下文檢索的模型並非由單個辭彙生成表達語句,而是根據這句話不同方向上的其他其他單詞來生成表達句。

BERT 最大的貢獻在於使用了兩個 奇異的非監督預測任務來解決之前提到的挑戰。使得讓單詞在文章上下文里「認出自己」變為可能。BERT解決這個挑戰使用了多種不同的預訓練任務:屏蔽和下一句預測。第一個預處理模型遮蔽約15%的輸入單詞,在深度雙向轉換解碼中運行整個語句,並且只預測被遮蔽的單詞,例如:

第二個預處理任務通過語料庫中隨機生成的簡單語句,學習語句之間的聯繫。

給出兩個語句A 和B, B是A之後的語句嗎?還是B 只是語料庫中隨機抽取的一句話?

這兩個預處理任務的結合使得 BERT在幾乎所有的NLP任務中 得到了更加豐富的,雙向的語義表達。

谷歌發布了 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
  • 地址:storage.googleapis.com/
  • BERT-Large, Uncased:24-layer, 1024-hidden, 16-heads, 340M parameters
  • 地址:storage.googleapis.com/
  • BERT-Base, Cased:12-layer, 768-hidden, 12-heads , 110M parameters
  • 地址:storage.googleapis.com/
  • BERT-Large, Cased:24-layer, 1024-hidden, 16-heads, 340M parameters(目前無法使用,需要重新生成)。

每一個 ZIP 文件都包含了三部分,即保存預訓練模型與權重的 ckpt 文件、將 WordPiece 映射到單詞 id 的 vocab 文件,以及指定模型超參數的 json 文件。除此之外,谷歌還發布了原論文中將預訓練模型應用於各種 NLP 任務的源代碼,感興趣的讀者可以查看 GitHub 項目復現論文結果。

BERT 官方項目地址:github.com/google-resea

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

BERT Colab地址?

colab.sandbox.google.com

在自己的數據集上運行 BERT

BERT的代碼同論文里描述的一致,主要分為兩個部分。一個是訓練語言模型(language model)的預訓練(pretrain)部分。另一個是訓練具體任務(task)的fine-tune部分。在開源的代碼中,預訓練的入口是在run_pretraining.py而fine-tune的入口針對不同的任務分別在run_classifier.py和run_squad.py。其中run_classifier.py適用的任務為分類任務。如CoLA、MRPC、MultiNLI這些數據集。而run_squad.py適用的是閱讀理解(MRC)任務,如squad2.0和squad1.1。

預訓練是BERT很重要的一個部分,與此同時,預訓練需要巨大的運算資源。按照論文里描述的參數,其Base的設定在消費級的顯卡Titan x 或Titan 1080ti(12GB RAM)上,甚至需要近幾個月的時間進行預訓練,同時還會面臨顯存不足的問題。

不過所幸的是谷歌滿足了Issues#2里各國開發者的請求,針對大部分語言都公布了BERT的預訓練模型。因此在我們可以比較方便地在自己的數據集上進行fine-tune。

下載預訓練模型

對於中文而言,google公布了一個參數較小的BERT預訓練模型。具體參數數值如下所示:

Chinese Simplified and Traditional, 12-layer, 768-hidden, 12-heads, 110M parameters

模型的下載鏈接:

Bert中文預訓練模型?

storage.googleapis.com

對下載的壓縮文件進行解壓,可以看到文件里有五個文件,其中bert_model.ckpt開頭的文件是負責模型變數載入的,而vocab.txt是訓練時中文文本採用的字典,最後bert_config.json是BERT在訓練時,可選調整的一些參數。

修改 processor

任何模型的訓練、預測都是需要有一個明確的輸入,而BERT代碼中processor就是負責對模型的輸入進行處理。我們以分類任務的為例,介紹如何修改processor來運行自己數據集上的fine-tune。在run_classsifier.py文件中我們可以看到,google對於一些公開數據集已經寫了一些processor,如XnliProcessor, MnliProcessor,MrpcProcessor和ColaProcessor。這給我們提供了一個很好的示例,指導我們如何針對自己的數據集來寫processor。

對於一個需要執行訓練交叉驗證測試完整過程的模型而言,自定義的processor里需要繼承DataProcessor,並重載獲取label的get_labels和獲取單個輸入的get_train_examples ,get_dev_examples和get_test_examples函數。其分別會在main函數的FLAGS.do_train、FLAGS.do_eval和FLAGS.do_predict階段被調用。

這三個函數的內容是相差無幾的,區別只在於需要指定各自讀入文件的地址。

以get_train_examples為例,函數需要返回一個由InputExample類組成的list。InputExample類是一個很簡單的類,只有初始化函數,需要傳入的參數中guid是用來區分每個example的,可以按照train-%d%(i)的方式進行定義。text_a是一串字元串,text_b則是另一串字元串。在進行後續輸入處理後(BERT代碼中已包含,不需要自己完成) text_a和text_b將組合成[CLS] text_a [SEP] text_b [SEP]的形式傳入模型。最後一個參數label也是字元串的形式,label的內容需要保證出現在get_labels函數返回的list里。

舉一個例子,假設我們想要處理一個能夠判斷句子相似度的模型,現在在data_dir的路徑下有一個名為train.csv的輸入文件,如果我們現在輸入文件的格式如下csv形式:

那麼我們可以寫一個如下的get_train_examples的函數。當然對於csv的處理,可以使用諸如csv.reader的形式進行讀入。

同時對應判斷句子相似度這個二分類任務,get_labels函數可以寫成如下的形式:

在對get_dev_examples和get_test_examples函數做類似get_train_examples的操作後,便完成了對processor的修改。其中get_test_examples可以傳入一個隨意的label數值,因為在模型的預測(prediction)中label將不會參與計算。

修改 processor 字典

修改完成processor後,需要在在原本main函數的processor字典里,加入修改後的processor類,即可在運行參數里指定調用該processor。

運行 fine-tune

之後就可以直接運行run_classsifier.py進行模型的訓練。在運行時需要制定一些參數,一個較為完整的運行參數如下所示:

export BERT_BASE_DIR=/path/to/bert/chinese_L-12_H-768_A-12 #全局變數 下載的預訓練bert地址
export MY_DATASET=/path/to/xnli #全局變數 數據集所在地址

python run_classifier.py
--task_name=selfsim #自己添加processor在processors字典里的key名
--do_train=true
--do_eval=true
--dopredict=true
--data_dir=$MY_DATASET
--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=5e-5
--num_train_epochs=2.0
--output_dir=/tmp/selfsim_output/ #模型輸出路徑

BERT 源代碼里還有什麼

在開始訓練我們自己fine-tune的BERT後,我們可以再來看看BERT代碼里除了processor之外的一些部分。

我們可以發現,process在得到字元串形式的輸入後,在file_based_convert_examples_ to_features里先是對字元串長度,加入[CLS]和[SEP]等一些處理後,將其寫入成TFrecord的形式。這是為了能在estimator里有一個更為高效和簡易的讀入。

我們還可以發現,在create_model的函數里,除了從modeling.py獲取模型主幹輸出之外,還有進行fine-tune時候的loss計算。因此,如果對於fine-tune的結構有自定義的要求,可以在這部分對代碼進行修改。如進行NER任務的時候,可以按照BERT論文里的方式,不只讀第一位的logits,而是將每一位logits進行讀取。

BERT這次開源的代碼,由於是考慮在google自己的TPU上高效地運行,因此採用的estimator是tf.contrib.tpu.TPUEstimator,雖然TPU的estimator同樣可以在gpu和cpu上運行,但若想在gpu上更高效地做一些提升,可以考慮將其換成tf.estimator.Estimator,於此同時model_fn里一些tf.contrib.tpu.TPUEstimatorSpec也需要修改成tf.estimator.EstimatorSpec的形式,以及相關調用參數也需要做一些調整。在轉換成較普通的estimator後便可以使用常用的方式對estimator進行處理,如生成用於部署的.pb文件等。

GitHub Issues 里一些有趣的內容

在GitHub Issues#95 (github.com/google-resea) 中大家討論了BERT模型在今年AI-Challenger比賽上的應用。我們也同樣嘗試了BERT在AI-Challenger的機器閱讀理解(mrc)賽道的表現。如果簡單得地將mrc的文本連接成一個長字元串的形式,可以在dev集上得到79.1%的準確率。

如果參考openAI的GPT論文(s3-us-west-2.amazonaws.com)里multi-choice的形式對BERT的輸入輸出代碼進行修改則可以將準確率提高到79.3%。採用的參數都是BERT默認的參數,而單一模型成績在賽道的test a排名中已經能超過榜單上的第一名。因此,在相關中文的任務中,bert能有很大的想像空間。


推薦閱讀:
相关文章