這個(gè)月谷歌提出的 BERT 受到了很多關(guān)注,該研究憑借預(yù)訓(xùn)練模型刷新了 11 項(xiàng) NLP 任務(wù)的當(dāng)前最優(yōu)性能記錄。論文作者表示這個(gè)月月末會(huì)放出代碼與預(yù)訓(xùn)練模型,但目前還沒有發(fā)布。因此很多研究者嘗試使用中等數(shù)據(jù)集降低計(jì)算力,或使用 OpenAI 的 Transformer 預(yù)訓(xùn)練模型作為初始化條件。
本文介紹的兩個(gè) BERT 實(shí)現(xiàn)項(xiàng)目分別基于 TensorFlow 和 Keras,其中基于 TensorFlow 的項(xiàng)目會(huì)使用中等數(shù)據(jù)集與其它技巧降低計(jì)算力,并發(fā)現(xiàn)使用 TextCNN 代替 Transformer 主干網(wǎng)絡(luò),且保留 BERT 預(yù)訓(xùn)練任務(wù)也能得到非常好的效果。而基于 Keras 的項(xiàng)目嘗試使用預(yù)訓(xùn)練的 OpenAI Transformer 作為初始化權(quán)重,并以較小的計(jì)算力重新訓(xùn)練 BERT 預(yù)訓(xùn)練模型,再將該預(yù)訓(xùn)練的 BERT 應(yīng)用到不同任務(wù)。
這兩個(gè)項(xiàng)目都在嘗試使用 BERT 核心思想,并以較小的計(jì)算成本應(yīng)用于其它 NLP 任務(wù)。當(dāng)然如果讀者希望使用大型 BERT 預(yù)訓(xùn)練模型,還需要等谷歌官方發(fā)布代碼與模型。
BERT 簡介
機(jī)器之心SyncedBERT小程序
BERT 的全稱是基于 Transformer 的雙向編碼器表征,其中「雙向」表示模型在處理某一個(gè)詞時(shí),它能同時(shí)利用前面的詞和后面的詞兩部分信息。這種「雙向」的來源在于 BERT 與傳統(tǒng)語言模型不同,它不是在給定所有前面詞的條件下預(yù)測(cè)最可能的當(dāng)前詞,而是隨機(jī)遮掩一些詞,并利用所有沒被遮掩的詞進(jìn)行預(yù)測(cè)。下圖展示了三種預(yù)訓(xùn)練模型,其中 BERT 和 ELMo 都使用雙向信息,OpenAI GPT 使用單向信息。
如上所示為不同預(yù)訓(xùn)練模型的架構(gòu),BERT 可以視為結(jié)合了 OpenAI GPT 和 ELMo 優(yōu)勢(shì)的新模型。其中 ELMo 使用兩條獨(dú)立訓(xùn)練的 LSTM 獲取雙向信息,而 OpenAI GPT 使用新型的 Transformer 和經(jīng)典語言模型只能獲取單向信息。BERT 的主要目標(biāo)即在 OpenAI GPT 的基礎(chǔ)上對(duì)預(yù)訓(xùn)練任務(wù)做一些改進(jìn),以同時(shí)利用 Transformer 深度模型與雙向信息的優(yōu)勢(shì)。
BERT 的核心過程非常簡潔,它會(huì)先從數(shù)據(jù)集抽取兩個(gè)句子,其中第二句是第一句的下一句概率是 50%,這樣就能學(xué)習(xí)句子之間的關(guān)系。其次隨機(jī)去除兩個(gè)句子中的一些詞,并要求模型預(yù)測(cè)這些詞是什么,這樣就能學(xué)習(xí)句子內(nèi)部的關(guān)系。最后再將經(jīng)處理的句子傳入大型 Transformer 模型,并通過兩個(gè)損失函數(shù)同時(shí)學(xué)習(xí)上面兩個(gè)目標(biāo)就能完成訓(xùn)練。
TensorFlow 實(shí)現(xiàn)項(xiàng)目簡介
BERT 最近在 10 幾項(xiàng) NLP 任務(wù)上取得了新進(jìn)展,這個(gè)項(xiàng)目是《BERT:Pre-training of Deep Bidirectional Transformers for Language Understanding》和《Attention is all You Need》這兩篇論文的 tensorflow 實(shí)現(xiàn)。
這個(gè)項(xiàng)目提供了預(yù)訓(xùn)練方法與代碼,并做了一些調(diào)整以加快收斂速度。這一份 TensorFlow 實(shí)現(xiàn)在使用中等數(shù)據(jù)集下計(jì)算力并不是太大,所以感興趣的讀者也可以嘗試使用。當(dāng)然,希望使用大型預(yù)訓(xùn)練 BERT 模型的讀者可以等谷歌發(fā)布官方模型。
項(xiàng)目地址:https://github.com/brightmart/bert_language_understanding
預(yù)訓(xùn)練和微調(diào)實(shí)驗(yàn)
項(xiàng)目作者把 Transfprmer 換成 TextCNN,替換了 BERT 的主干網(wǎng)絡(luò),結(jié)果發(fā)現(xiàn)使用大量原始數(shù)據(jù)用遮蔽語言模型預(yù)訓(xùn)練的模型可以顯著提高性能,因此他們認(rèn)為預(yù)訓(xùn)練和微調(diào)策略是獨(dú)立于模型和預(yù)訓(xùn)練任務(wù)的。因此,可以修正主干網(wǎng)絡(luò)添加更多的預(yù)訓(xùn)練任務(wù)或者定義一些新的預(yù)訓(xùn)練任務(wù),預(yù)訓(xùn)練不限于遮蔽語言模型或預(yù)測(cè)下一句的任務(wù)。讓人驚訝的是,對(duì)于中等規(guī)模的數(shù)據(jù)集(比如說一百萬條數(shù)據(jù))來說,即使不使用外部數(shù)據(jù),只要借助于預(yù)訓(xùn)練任務(wù)(如帶掩碼的語言模型),性能也可以大幅提升,而且模型可以更快地收斂。有時(shí)在微調(diào)階段,訓(xùn)練僅需幾個(gè) epoch。
目的
雖然開放源碼(tensor2tensor)和 Transformer 與 BERT 的官方實(shí)現(xiàn)實(shí)現(xiàn)即將到來,但是有可能很難懂,不易理解。該項(xiàng)目的作者并不打算完全復(fù)制原始文件,而是要應(yīng)用主要思想,以更好的方式解決 NLP 問題。本文的大部分工作是去年由另一個(gè) GitHub 項(xiàng)目修改完成的:文本分類(https://github.com/brightmart/text_classification)。
性能
下圖是在中等規(guī)模數(shù)據(jù)集(cail2018,45 萬)上文本分類任務(wù)的測(cè)試效果,其中采用 BERT 預(yù)訓(xùn)練思想的 TextCNN 要顯著優(yōu)于其它模型:
下圖是在小規(guī)模數(shù)據(jù)集(private,10 萬)上的測(cè)試效果:
TensorFlow 實(shí)現(xiàn)項(xiàng)目細(xì)節(jié)
使用方法
如果想在 Masked 語言模型上預(yù)訓(xùn)練 BERT 模型,并在新 NLP 任務(wù)上使用它,那么使用方法主要可以分為兩步驟。值得注意的是,該項(xiàng)目并沒有提供預(yù)訓(xùn)練模型,所以需要大量計(jì)算力的預(yù)訓(xùn)練過程仍然需要自行執(zhí)行。
1. 通過 BERT 預(yù)訓(xùn)練語言模型
python train_bert_lm.py [DONE]
2. 在新任務(wù)微調(diào)模型
python train_bert_fine_tuning.py [Done]
在項(xiàng)目作者的試驗(yàn)中,即使在微調(diào)的起點(diǎn),剛剛從預(yù)訓(xùn)練模型恢復(fù)參數(shù)也能獲得比從頭訓(xùn)練更低的損失。預(yù)訓(xùn)練模型的 F1 值同樣要比從頭訓(xùn)練的高,且從頭訓(xùn)練的 F1 值還需要從零開始增長。
此外為了快速測(cè)試新想法與模型,可以將超參數(shù) test_mode 設(shè)置為 True,在這種模式下模型只會(huì)加載少量的數(shù)據(jù)進(jìn)行測(cè)試,因此訓(xùn)練非常迅速。
在基本步驟中,還可以通過 Transform 解決文本分類問題:
python train_transform.py [DONE, but a bug exist prevent it from converge, welcome you to fix, email: brightmart@hotmail.com]
預(yù)訓(xùn)練和微調(diào)過程還有其它一些可選超參數(shù),它們控制了模型的大小與訓(xùn)練過程:
d_model:模型維度,默認(rèn)為 [512]。
num_layer:層級(jí)數(shù),默認(rèn)為 [6]。
num_header:自注意力機(jī)制的 Head 數(shù),默認(rèn)為 [8]。
d_k:Key(K)和 Query(Q)的維度,默認(rèn)為 [64]。
d_v:Value(V)的維度,默認(rèn)為 [64]。
default hyperparameter is d_model=512,h=8,d_k=d_v=64(big). if you have want to train the model fast, or has a small data set
or want to train a small model, use d_model=128,h=8,d_k=d_v=16(small), or d_model=64,h=8,d_k=d_v=8(tiny).
實(shí)現(xiàn)細(xì)節(jié)
首先,TensorFlow 的實(shí)現(xiàn)環(huán)境比較簡單:python 3+ tensorflow 1.10。其次,實(shí)現(xiàn)時(shí)要注意以下問題:
1. 預(yù)訓(xùn)練和微調(diào)階段之間有哪些能夠共享和無法共享的參數(shù)?
基本來說,預(yù)訓(xùn)練和微調(diào)階段的主干網(wǎng)絡(luò)使用的所有參數(shù)都能共享。
既然可以共享很多參數(shù),那微調(diào)階段只需學(xué)習(xí)很少的參數(shù),此外這兩個(gè)階段的詞嵌入也可以共享。
因此,在微調(diào)的初始階段已經(jīng)學(xué)習(xí)了大部分參數(shù)。
2. 如何實(shí)現(xiàn)帶 Mask 的語言模型?
為了讓事情變得簡單點(diǎn),可以根據(jù)文件生成很多句子。把每個(gè)句子截?cái)嗖⑻畛涞较嗤拈L度,然后隨機(jī)選擇一個(gè)單詞,用 [MASK]、單詞本身或一個(gè)隨機(jī)單詞替換它。
3. 如何使微調(diào)階段變得更高效并同時(shí)不影響在預(yù)訓(xùn)練階段學(xué)到的結(jié)果和知識(shí)?
在微調(diào)階段使用較小的學(xué)習(xí)率,因此只需在很小的范圍內(nèi)進(jìn)行調(diào)整。
Keras 實(shí)現(xiàn)
基于 TensorFlow 的實(shí)現(xiàn)同樣沒有提供預(yù)訓(xùn)練語言模型,這樣的模型在預(yù)訓(xùn)練階段會(huì)需要大量的計(jì)算力,這樣的計(jì)算力需求對(duì)于很多研究者與開發(fā)者都是接受不了的。但是現(xiàn)在的官方實(shí)現(xiàn)與預(yù)訓(xùn)練模型仍然沒有放出來,因此有開發(fā)者利用 OpenAI 預(yù)訓(xùn)練的 Transformer 作為初始化參數(shù),并訓(xùn)練新的 BERT 預(yù)訓(xùn)練模型,這種方式大大降低了計(jì)算力需求。
項(xiàng)目地址:https://github.com/Separius/BERT-keras
在這個(gè) Keras 實(shí)現(xiàn)項(xiàng)目中,作者用預(yù)訓(xùn)練的 OpenAI Transformer 作為初始化條件,并訓(xùn)練新的 BERT,項(xiàng)目作者表示這樣可以不使用 TPU 而實(shí)現(xiàn)預(yù)訓(xùn)練。以下展示了 Keras 實(shí)現(xiàn)的主要語句,包括加載 OpenAI Transformer 預(yù)訓(xùn)練模型、加載 BERT 模型和保存新的預(yù)訓(xùn)練權(quán)重等。
# this is a pseudo code you can read an actual working example in tutorial.ipynb
text_encoder = MyTextEncoder(**my_text_encoder_params) # you create a text encoder (sentence piece and openai's bpe are included)
lm_generator = lm_generator(text_encoder, **lm_generator_params) # this is essentially your data reader (single sentence and double sentence reader with masking and is_next label are included)
task_meta_datas = [lm_task, classification_task, pos_task] # these are your tasks (the lm_generator must generate the labels for these tasks too)
encoder_model = create_transformer(**encoder_params) # or you could simply load_openai()
trained_model = train_model(encoder_model, task_meta_datas, lm_generator, **training_params) # it does both pretraing and finetuning
trained_model.save_weights('my_awesome_model') # save it
model = load_model('my_awesome_model', encoder_model) # load it later and use it!
作者表示這個(gè)項(xiàng)目有一些很重要說明,針對(duì)不同的任務(wù)與需求,可以根據(jù)這些說明修改模型結(jié)構(gòu)和預(yù)訓(xùn)練過程。
這個(gè)庫的核心觀點(diǎn)是使用 OpenAI 的預(yù)訓(xùn)練模型作為訓(xùn)練新模型的初始狀態(tài),因此通過 GPU 就能訓(xùn)練 BERT。
通過 Keras 加載 OpenAI 模型已經(jīng)在 TensorFlow 后端和 Theano 后端得到測(cè)試。
對(duì)于大多數(shù) NLP 模型,能使用這個(gè)項(xiàng)目定義的數(shù)據(jù)生成器和任務(wù)元數(shù)據(jù),即使在其它框架中也是。
數(shù)據(jù)集和 Transformer 都會(huì)執(zhí)行一些單元測(cè)試,如果你不太了解代碼可以閱讀這些測(cè)試。
還可以使用其它編碼器進(jìn)行訓(xùn)練,例如 LSTM 或 BiQRNN 等。
當(dāng)官方代碼發(fā)布后會(huì)發(fā)生什么?數(shù)據(jù)讀取器仍然會(huì)保持穩(wěn)定,甚至可以導(dǎo)入官方發(fā)布的權(quán)重到這個(gè)庫中(作者認(rèn)為他會(huì)完成這一過程,因?yàn)閷?shí)際的 Transformer 還是比較容易實(shí)現(xiàn)的)
作者強(qiáng)烈建議閱讀項(xiàng)目中的 tutorial.ipynb 文件,它展示了整個(gè)項(xiàng)目的使用過程。
重要的代碼概念
任務(wù):有兩個(gè)一般任務(wù),句子級(jí)任務(wù)(如下一句預(yù)測(cè)和情感分析)和 token 級(jí)任務(wù)(如詞性標(biāo)注和命名實(shí)體識(shí)別)。
句子:「句子」表示一段帶有標(biāo)簽和所有內(nèi)容的實(shí)例,它為每個(gè)任務(wù)提供了一個(gè)目標(biāo)(句子級(jí)任務(wù)的單個(gè)標(biāo)注值,token 級(jí)任務(wù)的每個(gè) token 標(biāo)簽)和一個(gè)掩碼;對(duì)于 token 級(jí)任務(wù),除了忽略 padding 外,還要使用第一個(gè)符號(hào)向量預(yù)測(cè)類別(BERT 中的 [CLS] 符號(hào))。
TaskWeightScheduler:項(xiàng)目作者希望從語言模型開始訓(xùn)練,然后遷移到到分類任務(wù),我們可以用這個(gè)類快速實(shí)現(xiàn)。
special_tokens:pad, start, end, delimiter, mask