干貨 | BERT fine-tune 終極實(shí)踐教程

從11月初開(kāi)始担钮,google-research就陸續(xù)開(kāi)源了BERT的各個(gè)版本。google此次開(kāi)源的BERT是通過(guò)tensorflow高級(jí)API—— tf.estimator進(jìn)行封裝(wrapper)的尤仍。因此對(duì)于不同數(shù)據(jù)集的適配箫津,只需要修改代碼中的processor部分,就能進(jìn)行代碼的訓(xùn)練宰啦、交叉驗(yàn)證和測(cè)試苏遥。

奇點(diǎn)機(jī)智技術(shù)團(tuán)隊(duì)將結(jié)合利用BERT在AI-Challenger機(jī)器閱讀理解賽道的實(shí)踐表現(xiàn)以及多年的NLP經(jīng)驗(yàn)積累,為大家奉上BERT在中文數(shù)據(jù)集上的fine tune全攻略赡模。

在自己的數(shù)據(jù)集上運(yùn)行 BERT

BERT的代碼同論文里描述的一致田炭,主要分為兩個(gè)部分。一個(gè)是訓(xùn)練語(yǔ)言模型(language model)的預(yù)訓(xùn)練(pretrain)部分漓柑。另一個(gè)是訓(xùn)練具體任務(wù)(task)的fine-tune部分教硫。在開(kāi)源的代碼中,預(yù)訓(xùn)練的入口是在run_pretraining.py而fine-tune的入口針對(duì)不同的任務(wù)分別在run_classifier.pyrun_squad.py辆布。其中run_classifier.py適用的任務(wù)為分類任務(wù)瞬矩。如CoLA、MRPC锋玲、MultiNLI這些數(shù)據(jù)集景用。而run_squad.py適用的是閱讀理解(MRC)任務(wù),如squad2.0和squad1.1惭蹂。

預(yù)訓(xùn)練是BERT很重要的一個(gè)部分伞插,與此同時(shí),預(yù)訓(xùn)練需要巨大的運(yùn)算資源剿干。按照論文里描述的參數(shù)蜂怎,其Base的設(shè)定在消費(fèi)級(jí)的顯卡Titan x 或Titan 1080ti(12GB RAM)上,甚至需要近幾個(gè)月的時(shí)間進(jìn)行預(yù)訓(xùn)練置尔,同時(shí)還會(huì)面臨顯存不足的問(wèn)題杠步。不過(guò)所幸的是谷歌滿足了Issues#2里各國(guó)開(kāi)發(fā)者的請(qǐng)求,針對(duì)大部分語(yǔ)言都公布了BERT的預(yù)訓(xùn)練模型榜轿。因此在我們可以比較方便地在自己的數(shù)據(jù)集上進(jìn)行fine-tune幽歼。

下載預(yù)訓(xùn)練模型

對(duì)于中文而言,google公布了一個(gè)參數(shù)較小的BERT預(yù)訓(xùn)練模型谬盐。具體參數(shù)數(shù)值如下所示:

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

模型的下載鏈接可以在github上google的開(kāi)源代碼里找到甸私。對(duì)下載的壓縮文件進(jìn)行解壓,可以看到文件里有五個(gè)文件飞傀,其中bert_model.ckpt開(kāi)頭的文件是負(fù)責(zé)模型變量載入的皇型,而vocab.txt是訓(xùn)練時(shí)中文文本采用的字典诬烹,最后bert_config.json是BERT在訓(xùn)練時(shí),可選調(diào)整的一些參數(shù)弃鸦。

修改 processor

任何模型的訓(xùn)練绞吁、預(yù)測(cè)都是需要有一個(gè)明確的輸入,而B(niǎo)ERT代碼中processor就是負(fù)責(zé)對(duì)模型的輸入進(jìn)行處理唬格。我們以分類任務(wù)的為例家破,介紹如何修改processor來(lái)運(yùn)行自己數(shù)據(jù)集上的fine-tune。在run_classsifier.py文件中我們可以看到购岗,google對(duì)于一些公開(kāi)數(shù)據(jù)集已經(jīng)寫了一些processor汰聋,如XnliProcessor,MnliProcessor,MrpcProcessorColaProcessor。這給我們提供了一個(gè)很好的示例喊积,指導(dǎo)我們?nèi)绾吾槍?duì)自己的數(shù)據(jù)集來(lái)寫processor烹困。

對(duì)于一個(gè)需要執(zhí)行訓(xùn)練、交叉驗(yàn)證和測(cè)試完整過(guò)程的模型而言注服,自定義的processor里需要繼承DataProcessor韭邓,并重載獲取label的get_labels和獲取單個(gè)輸入的get_train_examples,get_dev_examplesget_test_examples函數(shù)措近。其分別會(huì)在main函數(shù)的FLAGS.do_train溶弟、FLAGS.do_evalFLAGS.do_predict階段被調(diào)用。
這三個(gè)函數(shù)的內(nèi)容是相差無(wú)幾的瞭郑,區(qū)別只在于需要指定各自讀入文件的地址辜御。

get_train_examples為例,函數(shù)需要返回一個(gè)由InputExample類組成的list屈张。InputExample類是一個(gè)很簡(jiǎn)單的類擒权,只有初始化函數(shù),需要傳入的參數(shù)中g(shù)uid是用來(lái)區(qū)分每個(gè)example的阁谆,可以按照train-%d'%(i)的方式進(jìn)行定義碳抄。text_a是一串字符串,text_b則是另一串字符串场绿。在進(jìn)行后續(xù)輸入處理后(BERT代碼中已包含剖效,不需要自己完成) text_a和text_b將組合成[CLS] text_a [SEP] text_b [SEP]的形式傳入模型。最后一個(gè)參數(shù)label也是字符串的形式焰盗,label的內(nèi)容需要保證出現(xiàn)在get_labels函數(shù)返回的list里璧尸。

舉一個(gè)例子,假設(shè)我們想要處理一個(gè)能夠判斷句子相似度的模型熬拒,現(xiàn)在在data_dir的路徑下有一個(gè)名為train.csv的輸入文件爷光,如果我們現(xiàn)在輸入文件的格式如下csv形式:

1,你好,您好
0,你好,你家住哪 

那么我們可以寫一個(gè)如下的get_train_examples的函數(shù)。當(dāng)然對(duì)于csv的處理澎粟,可以使用諸如csv.reader的形式進(jìn)行讀入蛀序。

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

同時(shí)對(duì)應(yīng)判斷句子相似度這個(gè)二分類任務(wù)欢瞪,get_labels函數(shù)可以寫成如下的形式:

def get_labels(self):
    return ['0','1']

在對(duì)get_dev_examplesget_test_examples函數(shù)做類似get_train_examples的操作后,便完成了對(duì)processor的修改徐裸。其中get_test_examples可以傳入一個(gè)隨意的label數(shù)值引有,因?yàn)樵谀P偷念A(yù)測(cè)(prediction)中l(wèi)abel將不會(huì)參與計(jì)算。

修改 processor 字典

修改完成processor后倦逐,需要在在原本main函數(shù)的processor字典里譬正,加入修改后的processor類,即可在運(yùn)行參數(shù)里指定調(diào)用該processor檬姥。

 processors = {
      "cola": ColaProcessor,
      "mnli": MnliProcessor,
      "mrpc": MrpcProcessor,
      "xnli": XnliProcessor, 
      "selfsim": SelfProcessor #添加自己的processor
  }

運(yùn)行 fine-tune

之后就可以直接運(yùn)行run_classsifier.py進(jìn)行模型的訓(xùn)練曾我。在運(yùn)行時(shí)需要制定一些參數(shù),一個(gè)較為完整的運(yùn)行參數(shù)如下所示:

export BERT_BASE_DIR=/path/to/bert/chinese_L-12_H-768_A-12 #全局變量 下載的預(yù)訓(xùn)練bert地址
export MY_DATASET=/path/to/xnli #全局變量 數(shù)據(jù)集所在地址

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 \ #模型參數(shù)
  --train_batch_size=32 \
  --learning_rate=5e-5 \
  --num_train_epochs=2.0 \
  --output_dir=/tmp/selfsim_output/ #模型輸出路徑

BERT 源代碼里還有什么

在開(kāi)始訓(xùn)練我們自己fine-tune的BERT后健民,我們可以再來(lái)看看BERT代碼里除了processor之外的一些部分抒巢。
我們可以發(fā)現(xiàn),process在得到字符串形式的輸入后秉犹,在file_based_convert_examples_to_features里先是對(duì)字符串長(zhǎng)度蛉谜,加入[CLS]和[SEP]等一些處理后,將其寫入成TFrecord的形式崇堵。這是為了能在estimator里有一個(gè)更為高效和簡(jiǎn)易的讀入型诚。

我們還可以發(fā)現(xiàn),在create_model的函數(shù)里鸳劳,除了從modeling.py獲取模型主干輸出之外狰贯,還有進(jìn)行fine-tune時(shí)候的loss計(jì)算。因此赏廓,如果對(duì)于fine-tune的結(jié)構(gòu)有自定義的要求涵紊,可以在這部分對(duì)代碼進(jìn)行修改。如進(jìn)行NER任務(wù)的時(shí)候幔摸,可以按照BERT論文里的方式摸柄,不只讀第一位的logits,而是將每一位logits進(jìn)行讀取既忆。

BERT這次開(kāi)源的代碼驱负,由于是考慮在google自己的TPU上高效地運(yùn)行,因此采用的estimator是tf.contrib.tpu.TPUEstimator,雖然TPU的estimator同樣可以在gpu和cpu上運(yùn)行尿贫,但若想在gpu上更高效地做一些提升电媳,可以考慮將其換成tf.estimator.Estimator,于此同時(shí)model_fn里一些tf.contrib.tpu.TPUEstimatorSpec也需要修改成tf.estimator.EstimatorSpec的形式,以及相關(guān)調(diào)用參數(shù)也需要做一些調(diào)整庆亡。在轉(zhuǎn)換成較普通的estimator后便可以使用常用的方式對(duì)estimator進(jìn)行處理匾乓,如生成用于部署的.pb文件等。

GitHub Issues 里一些有趣的內(nèi)容

從google對(duì)BERT進(jìn)行開(kāi)源開(kāi)始又谋,Issues里的討論便異称捶欤活躍娱局,BERT論文第一作者Jacob Devlin也積極地在Issues里進(jìn)行回應(yīng),在交流討論中咧七,產(chǎn)生了一些很有趣的內(nèi)容衰齐。

在GitHub Issues#95 中大家討論了BERT模型在今年AI-Challenger比賽上的應(yīng)用。我們也同樣嘗試了BERT在AI-Challenger的機(jī)器閱讀理解(mrc)賽道的表現(xiàn)继阻。如果簡(jiǎn)單得地將mrc的文本連接成一個(gè)長(zhǎng)字符串的形式耻涛,可以在dev集上得到79.1%的準(zhǔn)確率。

如果參考o(jì)penAI的GPT論文里multi-choice的形式對(duì)BERT的輸入輸出代碼進(jìn)行修改則可以將準(zhǔn)確率提高到79.3%瘟檩。采用的參數(shù)都是BERT默認(rèn)的參數(shù)抹缕,而單一模型成績(jī)?cè)谫惖赖膖est a排名中已經(jīng)能超過(guò)榜單上的第一名。因此墨辛,在相關(guān)中文的任務(wù)中卓研,bert能有很大的想象空間。

在GitHub Issues#123 中睹簇,@hanxiao給出了一個(gè)采用ZeroMQ便捷部署B(yǎng)ERT的service奏赘,可以直接調(diào)用訓(xùn)練好的模型作為應(yīng)用的接口。同時(shí)他將BERT改為一個(gè)大的encode模型太惠,將文本通過(guò)BERT進(jìn)行encode磨淌,來(lái)實(shí)現(xiàn)句子級(jí)的encode。此外垛叨,他對(duì)比了多GPU上的性能伦糯,發(fā)現(xiàn)bert在多GPU并行上的出色表現(xiàn)。

總結(jié)

總的來(lái)說(shuō)嗽元,google此次開(kāi)源的BERT和其預(yù)訓(xùn)練模型是非常有價(jià)值的,可探索和改進(jìn)的內(nèi)容也很多喂击。相關(guān)數(shù)據(jù)集上已經(jīng)出現(xiàn)了對(duì)BERT進(jìn)行修改后的復(fù)合模型剂癌,如squad2.0上哈工大(HIT)的AoA + DA + BERT以及西湖大學(xué)(DAMO)的SLQA + BERT。 在感謝google這份付出的同時(shí)翰绊,我們也可以借此站在巨人的肩膀上佩谷,嘗試將其運(yùn)用在自然語(yǔ)言處理領(lǐng)域的方方面面,讓人工智能的夢(mèng)想更近一步监嗜。

對(duì)NLP領(lǐng)域感興趣的朋友谐檀,歡迎投簡(jiǎn)歷到 jobs@naturali.io,2018機(jī)器閱讀理解技術(shù)競(jìng)賽冠軍團(tuán)隊(duì)期待你的加入裁奇!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末桐猬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子刽肠,更是在濱河造成了極大的恐慌溃肪,老刑警劉巖免胃,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惫撰,居然都是意外死亡羔沙,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門厨钻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扼雏,“玉大人,你說(shuō)我怎么就攤上這事夯膀∧馗颍” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵棍郎,是天一觀的道長(zhǎng)其障。 經(jīng)常有香客問(wèn)我,道長(zhǎng)涂佃,這世上最難降的妖魔是什么励翼? 我笑而不...
    開(kāi)封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮辜荠,結(jié)果婚禮上汽抚,老公的妹妹穿的比我還像新娘。我一直安慰自己伯病,他們只是感情好造烁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著午笛,像睡著了一般惭蟋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上药磺,一...
    開(kāi)封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天告组,我揣著相機(jī)與錄音,去河邊找鬼癌佩。 笑死木缝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的围辙。 我是一名探鬼主播我碟,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼姚建!你這毒婦竟也來(lái)了矫俺?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恳守,沒(méi)想到半個(gè)月后考婴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡催烘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年沥阱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伊群。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡考杉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舰始,到底是詐尸還是另有隱情崇棠,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布丸卷,位于F島的核電站枕稀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谜嫉。R本人自食惡果不足惜萎坷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沐兰。 院中可真熱鬧哆档,春花似錦、人聲如沸住闯。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)比原。三九已至插佛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間春寿,已是汗流浹背朗涩。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绑改,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓兄一,卻偏偏與公主長(zhǎng)得像厘线,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子出革,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容