這個月谷歌提出的 BERT 受到了很多關注,該研究憑藉預訓練模型重新整理了 11 項 NLP 任務的當前最優效能記錄。論文作者表示這個月月末會放出程式碼與預訓練模型,但目前還沒有釋出。因此很多研究者嘗試使用中等資料集降低計算力,或使用 OpenAI 的 Transformer 預訓練模型作為初始化條件。
本文介紹的兩個 BERT 實現專案分別基於 TensorFlow 和 Keras,其中基於 TensorFlow 的專案會使用中等資料集與其它技巧降低計算力,並發現使用 TextCNN 代替 Transformer 主幹網路,且保留 BERT 預訓練任務也能得到非常好的效果。而基於 Keras 的專案嘗試使用預訓練的 OpenAI Transformer 作為初始化權重,並以較小的計算力重新訓練 BERT 預訓練模型,再將該預訓練的 BERT 應用到不同任務。
這兩個專案都在嘗試使用 BERT 核心思想,並以較小的計算成本應用於其它 NLP 任務。當然如果讀者希望使用大型 BERT 預訓練模型,還需要等谷歌官方釋出程式碼與模型。
BERT 簡介
BERT 的全稱是基於 Transformer 的雙向編碼器表徵,其中「雙向」表示模型在處理某一個詞時,它能同時利用前面的詞和後面的詞兩部分資訊。這種「雙向」的來源在於 BERT 與傳統語言模型不同,它不是在給定所有前面詞的條件下預測最可能的當前詞,而是隨機遮掩一些詞,並利用所有沒被遮掩的詞進行預測。下圖展示了三種預訓練模型,其中 BERT 和 ELMo 都使用雙向資訊,OpenAI GPT 使用單向資訊。
如上所示為不同預訓練模型的架構,BERT 可以視為結合了 OpenAI GPT 和 ELMo 優勢的新模型。其中 ELMo 使用兩條獨立訓練的 LSTM 獲取雙向資訊,而 OpenAI GPT 使用新型的 Transformer 和經典語言模型只能獲取單向資訊。BERT 的主要目標即在 OpenAI GPT 的基礎上對預訓練任務做一些改進,以同時利用 Transformer 深度模型與雙向資訊的優勢。
BERT 的核心過程非常簡潔,它會先從資料集抽取兩個句子,其中第二句是第一句的下一句機率是 50%,這樣就能學習句子之間的關係。其次隨機去除兩個句子中的一些詞,並要求模型預測這些詞是什麼,這樣就能學習句子內部的關係。最後再將經處理的句子傳入大型 Transformer 模型,並透過兩個損失函式同時學習上面兩個目標就能完成訓練。
TensorFlow 實現專案簡介
BERT 最近在 10 幾項 NLP 任務上取得了新進展,這個專案是《BERT:Pre-training of Deep Bidirectional Transformers for Language Understanding》和《Attention is all You Need》這兩篇論文的 tensorflow 實現。
這個專案提供了預訓練方法與程式碼,並做了一些調整以加快收斂速度。這一份 TensorFlow 實現在使用中等資料集下計算力並不是太大,所以感興趣的讀者也可以嘗試使用。當然,希望使用大型預訓練 BERT 模型的讀者可以等谷歌釋出官方模型。
專案地址:https://github.com/brightmart/bert_language_understanding
預訓練和微調實驗
專案作者把 Transfprmer 換成 TextCNN,替換了 BERT 的主幹網路,結果發現使用大量原始資料用遮蔽語言模型預訓練的模型可以顯著提高效能,因此他們認為預訓練和微調策略是獨立於模型和預訓練任務的。因此,可以修正主幹網路新增更多的預訓練任務或者定義一些新的預訓練任務,預訓練不限於遮蔽語言模型或預測下一句的任務。讓人驚訝的是,對於中等規模的資料集(比如說一百萬條資料)來說,即使不使用外部資料,只要藉助於預訓練任務(如帶掩碼的語言模型),效能也可以大幅提升,而且模型可以更快地收斂。有時在微調階段,訓練僅需幾個 epoch。
目的
雖然開放原始碼(tensor2tensor)和 Transformer 與 BERT 的官方實現實現即將到來,但是有可能很難懂,不易理解。該專案的作者並不打算完全複製原始檔案,而是要應用主要思想,以更好的方式解決 NLP 問題。本文的大部分工作是去年由另一個 GitHub 專案修改完成的:文字分類(https://github.com/brightmart/text_classification)。
效能
下圖是在中等規模資料集(cail2018,45 萬)上文字分類任務的測試效果,其中採用 BERT 預訓練思想的 TextCNN 要顯著優於其它模型:
下圖是在小規模資料集(private,10 萬)上的測試效果:
TensorFlow 實現專案細節
使用方法
如果想在 Masked 語言模型上預訓練 BERT 模型,並在新 NLP 任務上使用它,那麼使用方法主要可以分為兩步驟。值得注意的是,該專案並沒有提供預訓練模型,所以需要大量計算力的預訓練過程仍然需要自行執行。
1. 透過 BERT 預訓練語言模型
python train_bert_lm.py [DONE]
2. 在新任務微調模型
python train_bert_fine_tuning.py [Done]
在專案作者的試驗中,即使在微調的起點,剛剛從預訓練模型恢復引數也能獲得比從頭訓練更低的損失。預訓練模型的 F1 值同樣要比從頭訓練的高,且從頭訓練的 F1 值還需要從零開始增長。
此外為了快速測試新想法與模型,可以將超引數 test_mode 設定為 True,在這種模式下模型只會載入少量的資料進行測試,因此訓練非常迅速。
在基本步驟中,還可以透過 Transform 解決文字分類問題:
python train_transform.py [DONE, but a bug exist prevent it from converge, welcome you to fix, email: brightmart@hotmail.com]
預訓練和微調過程還有其它一些可選超引數,它們控制了模型的大小與訓練過程:
d_model:模型維度,預設為 [512]。
num_layer:層級數,預設為 [6]。
num_header:自注意力機制的 Head 數,預設為 [8]。
d_k:Key(K)和 Query(Q)的維度,預設為 [64]。
d_v:Value(V)的維度,預設為 [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).
實現細節
首先,TensorFlow 的實現環境比較簡單:python 3+ tensorflow 1.10。其次,實現時要注意以下問題:
1. 預訓練和微調階段之間有哪些能夠共享和無法共享的引數?
基本來說,預訓練和微調階段的主幹網路使用的所有引數都能共享。
既然可以共享很多引數,那微調階段只需學習很少的引數,此外這兩個階段的詞嵌入也可以共享。
因此,在微調的初始階段已經學習了大部分引數。
2. 如何實現帶 Mask 的語言模型?
為了讓事情變得簡單點,可以根據檔案生成很多句子。把每個句子截斷並填充到相同的長度,然後隨機選擇一個單詞,用 [MASK]、單詞本身或一個隨機單詞替換它。
3. 如何使微調階段變得更高效並同時不影響在預訓練階段學到的結果和知識?
在微調階段使用較小的學習率,因此只需在很小的範圍內進行調整。
Keras 實現
基於 TensorFlow 的實現同樣沒有提供預訓練語言模型,這樣的模型在預訓練階段會需要大量的計算力,這樣的計算力需求對於很多研究者與開發者都是接受不了的。但是現在的官方實現與預訓練模型仍然沒有放出來,因此有開發者利用 OpenAI 預訓練的 Transformer 作為初始化引數,並訓練新的 BERT 預訓練模型,這種方式大大降低了計算力需求。
專案地址:https://github.com/Separius/BERT-keras
在這個 Keras 實現專案中,作者用預訓練的 OpenAI Transformer 作為初始化條件,並訓練新的 BERT,專案作者表示這樣可以不使用 TPU 而實現預訓練。以下展示了 Keras 實現的主要語句,包括載入 OpenAI Transformer 預訓練模型、載入 BERT 模型和儲存新的預訓練權重等。
# 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!
作者表示這個專案有一些很重要說明,針對不同的任務與需求,可以根據這些說明修改模型結構和預訓練過程。
這個庫的核心觀點是使用 OpenAI 的預訓練模型作為訓練新模型的初始狀態,因此透過 GPU 就能訓練 BERT。
透過 Keras 載入 OpenAI 模型已經在 TensorFlow 後端和 Theano 後端得到測試。
對於大多數 NLP 模型,能使用這個專案定義的資料生成器和任務後設資料,即使在其它框架中也是。
資料集和 Transformer 都會執行一些單元測試,如果你不太瞭解程式碼可以閱讀這些測試。
還可以使用其它編碼器進行訓練,例如 LSTM 或 BiQRNN 等。
當官方程式碼釋出後會發生什麼?資料讀取器仍然會保持穩定,甚至可以匯入官方釋出的權重到這個庫中(作者認為他會完成這一過程,因為實際的 Transformer 還是比較容易實現的)
作者強烈建議閱讀專案中的 tutorial.ipynb 檔案,它展示了整個專案的使用過程。
重要的程式碼概念
任務:有兩個一般任務,句子級任務(如下一句預測和情感分析)和 token 級任務(如詞性標註和命名實體識別)。
句子:「句子」表示一段帶有標籤和所有內容的例項,它為每個任務提供了一個目標(句子級任務的單個標註值,token 級任務的每個 token 標籤)和一個掩碼;對於 token 級任務,除了忽略 padding 外,還要使用第一個符號向量預測類別(BERT 中的 [CLS] 符號)。
TaskWeightScheduler:專案作者希望從語言模型開始訓練,然後遷移到到分類任務,我們可以用這個類快速實現。
special_tokens:pad, start, end, delimiter, mask