從 11 月初開始,google-research 就陸續開源了 BERT 的各個版本。google 此次開源的 BERT 是通過 tensorflow 高級 API—— tf.estimator 進行封裝 (wrapper) 的。因此對于不同數據集的适配,隻需要修改代碼中的 processor 部分,就能進行代碼的訓練、交叉驗證和測試。
在自己的數據集上運行 BERTBERT 的代碼同論文裡描述的一緻,主要分為兩個部分。一個是訓練語言模型(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模型的下載鍊接可以在 github 上 google 的開源代碼裡找到。對下載的壓縮文件進行解壓,可以看到文件裡有五個文件,其中 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 形式:
複制代碼
1, 你好, 您好 0, 你好, 你家住哪
那麼我們可以寫一個如下的 get_train_examples 的函數。當然對于 csv 的處理,可以使用諸如 csv.reader 的形式進行讀入。
複制代碼
def get_train_examples(self, data_dir): file_path = os.path.join(data_dir, 'train.csv') with open(file_path, 'r') as f: reader = f.readlines() examples = [] for index, line in enumerate(reader): guid = 'train-%d'%index split_line = line.strip().split(',') text_a = tokenization.convert_to_unicode(split_line[1]) text_b = tokenization.convert_to_unicode(split_line[2]) label = split_line[0] examples.append(InputExample(guid=guid, text_a=text_a, text_b=text_b, label=label)) return examples
同時對應判斷句子相似度這個二分類任務,get_labels 函數可以寫成如下的形式:
複制代碼
def get_labels(self): return ['0','1']
在對 get_dev_examples 和 get_test_examples 函數做類似 get_train_examples 的操作後,便完成了對 processor 的修改。其中 get_test_examples 可以傳入一個随意的 label 數值,因為在模型的預測(prediction)中 label 将不會參與計算。
修改 processor 字典修改完成 processor 後,需要在在原本 main 函數的 processor 字典裡,加入修改後的 processor 類,即可在運行參數裡指定調用該 processor。
複制代碼
processors = { "cola": ColaProcessor, "mnli": MnliProcessor, "mrpc": MrpcProcessor, "xnli": XnliProcessor, "selfsim": SelfProcessor # 添加自己的 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 裡一些有趣的内容從 google 對 BERT 進行開源開始,Issues 裡的讨論便異常活躍,BERT 論文第一作者 Jacob Devlin 也積極地在 Issues 裡進行回應,在交流讨論中,産生了一些很有趣的内容。
在 GitHub Issues#95 中大家讨論了 BERT 模型在今年 AI-Challenger 比賽上的應用。我們也同樣嘗試了 BERT 在 AI-Challenger 的機器閱讀理解(mrc)賽道的表現。如果簡單得地将 mrc 的文本連接成一個長字符串的形式,可以在 dev 集上得到 79.1% 的準确率。
如果參考 openAI 的 GPT 論文裡 multi-choice 的形式對 BERT 的輸入輸出代碼進行修改則可以将準确率提高到 79.3%。采用的參數都是 BERT 默認的參數,而單一模型成績在賽道的 test a 排名中已經能超過榜單上的第一名。因此,在相關中文的任務中,bert 能有很大的想象空間。
在 GitHub Issues#123 中,@hanxiao 給出了一個采用 ZeroMQ 便捷部署 BERT 的 service,可以直接調用訓練好的模型作為應用的接口。同時他将 BERT 改為一個大的 encode 模型,将文本通過 BERT 進行 encode,來實現句子級的 encode。此外,他對比了多 GPU 上的性能,發現 bert 在多 GPU 并行上的出色表現。
總結總的來說,google 此次開源的 BERT 和其預訓練模型是非常有價值的,可探索和改進的内容也很多。相關數據集上已經出現了對 BERT 進行修改後的複合模型,如 squad2.0 上哈工大 (HIT) 的 AoA DA BERT 以及西湖大學(DAMO)的 SLQA BERT。 在感謝 google 這份付出的同時,我們也可以借此站在巨人的肩膀上,嘗試将其運用在自然語言處理領域的方方面面,讓人工智能的夢想更近一步。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!