BERT
***** 2020年3月11日更新:更小的BERT模型 *****
這是在《深閱讀的學生學得更好:預訓練緊湊模型的重要性》(arXiv:1908.08962)中提到的24種較小規模的英文未分詞BERT模型的發布。
我們已經證明,標準的BERT架構和訓練目標在各種模型大小上都是有效的,不僅僅是BERT-Base和BERT-Large。這些小型BERT模型旨在用于計算資源有限的環境。它們可以像原始BERT模型一樣進行微調。然而,在知識蒸餾的背景下,它們最有效,此時微調標簽由更大、更準確的教師模型生成。
我們的目標是讓資源較少的機構能夠進行研究,并鼓勵社區尋求不同于增加模型容量的創新方向。
所有24個模型可以從這里下載,或者從下面的表格中單獨下載:
H=128 | H=256 | H=512 | H=768 | |
---|---|---|---|---|
L=2 | 2/128(BERT-Tiny) | 2/256 | 2/512 | 2/768 |
L=4 | 4/128 | 4/256(BERT-Mini) | 4/512(BERT-Small) | 4/768 |
L=6 | 6/128 | 6/256 | 6/512 | 6/768 |
L=8 | 8/128 | 8/256 | 8/512(BERT-Medium) | 8/768 |
L=10 | 10/128 | 10/256 | 10/512 | 10/768 |
L=12 | 12/128 | 12/256 | 12/512 | 12/768(BERT-Base) |
注意,此次發布的BERT-Base模型只是為了完整性,它是在與原始模型相同的條件下重新訓練的。
以下是GLUE測試集上的相應分數:
模型 | 得分 | CoLA | SST-2 | MRPC | STS-B | QQP | MNLI-m | MNLI-mm | QNLI(v2) | RTE | WNLI | AX |
---|---|---|---|---|---|---|---|---|---|---|---|---|
BERT-Tiny | 64.2 | 0.0 | 83.2 | 81.1/71.1 | 74.3/73.6 | 62.2/83.4 | 70.2 | 70.3 | 81.5 | 57.2 | 62.3 | 21.0 |
BERT-Mini | 65.8 | 0.0 | 85.9 | 81.1/71.8 | 75.4/73.3 | 66.4/86.2 | 74.8 | 74.3 | 84.1 | 57.9 | 62.3 | 26.1 |
BERT-Small | 71.2 | 27.8 | 89.7 | 83.4/76.2 | 78.8/77.0 | 68.1/87.0 | 77.6 | 77.0 | 86.4 | 61.8 | 62.3 | 28.6 |
BERT-Medium | 73.5 | 38.0 | 89.6 | 86.6/81.6 | 80.4/78.4 | 69.6/87.9 | 80.0 | 79.1 | 87.7 | 62.2 | 62.3 | 30.5 |
對于每個任務,我們在以下列表中選擇了最佳微調超參數,并進行了4個周期的訓練:
- 批次大小:8, 16, 32, 64, 128
- 學習率:3e-4, 1e-4, 5e-5, 3e-5
如果您使用這些模型,請引用以下論文:
@article{turc2019,title={Well-Read Students Learn Better: On the Importance of Pre-training Compact Models},author={Turc, Iulia and Chang, Ming-Wei and Lee, Kenton and Toutanova, Kristina},journal={arXiv preprint arXiv:1908.08962v2 },year={2019}
}
***** 2019年5月31日新更新:整體單詞掩碼模型 *****
這是對預處理代碼改進后產生的多個新模型的發布。
在原來的預處理代碼中,我們隨機選擇WordPiece令牌進行掩碼。例如:
輸入文本:the man jumped up , put his basket on phil ##am ##mon ' s head
原掩碼輸入:[MASK] man [MASK] up , put his [MASK] on phil [MASK] ##mon ' s head
新的技術稱為整體單詞掩碼。在這種情況下,我們總是同時掩碼一個單詞的所有對應令牌。整體掩碼率保持不變。
整體單詞掩碼輸入:the man [MASK] up , put his basket on [MASK] [MASK] [MASK] ' s head
訓練仍然是相同的 - 我們仍然獨立預測每個被掩碼的WordPiece令牌。改進來自于原來的預測任務對于被拆分為多個WordPiece的單詞來說太“容易”。
這可以通過在create_pretraining_data.py
中傳遞標志--do_whole_word_mask=True
來啟用。
帶有整體單詞掩碼的預訓練模型鏈接如下。數據和訓練否則完全相同,模型具有與原始模型相同的結構和詞匯。我們只包含BERT-Large模型。當使用這些模型時,請在論文中明確說明您正在使用BERT-Large的整體單詞掩碼變體。
-
BERT-Large, 不區分大小寫(整體單詞掩碼): 24層,1024隱藏,16頭,3.4億參數
-
BERT-Large, 區分大小寫(整體單詞掩碼): 24層,1024隱藏,16頭,3.4億參數
模型名稱 | SQUAD 1.1 F1/EM | 多任務NLI準確性 |
---|---|---|
BERT-Large, 不區分大小寫(原版) | 91.0/84.3 | 86.05 |
BERT-Large, 不區分大小寫(整體單詞掩碼) | 92.8/86.7 | 87.07 |
BERT-Large, 區分大小寫(原版) | 91.5/84.8 | 86.09 |
BERT-Large, 區分大小寫(整體單詞掩碼) | 92.9/86.7 | 86.46 |
***** 2019年2月7日新更新:TensorFlow Hub模塊 *****
BERT已上傳至TensorFlow Hub。請參閱run_classifier_with_tfhub.py
以了解如何使用TF Hub模塊,或在Colab中運行瀏覽器中的示例。
***** 2018年11月23日新更新:未規范化多語言模型 + 泰語 + 蒙古語 *****
我們上傳了一個新的多語言模型,該模型在輸入時不進行任何規范化(不區分大小寫、不移除音標、不進行Unicode標準化),并額外包括泰語和蒙古語。
建議在開發多語言模型時使用此版本,特別是對于使用非拉丁字母的語言。
這不需要任何代碼更改,可以在以下位置下載:
- BERT-Base, 多語言區分大小寫: 104種語言,12層,768隱藏,12頭,1.1億參數
***** 2018年11月15日新更新:SOTA SQuAD 2.0系統 *****
我們發布了代碼更改以重現我們的83%F1 SQuAD 2.0系統,目前在排行榜上領先3%。有關詳細信息,請參閱README的SQuAD 2.0部分。
***** 2018年11月5日新更新:第三方PyTorch和Chainer版本的BERT可用 *****
來自HuggingFace的NLP研究人員提供了與我們的預訓練檢查點兼容的PyTorch版本的BERT,并且能夠復制我們的結果。Sosuke Kobayashi也提供了Chainer版本的BERT(謝謝!)。我們沒有參與PyTorch實現的創建和維護,所以請直接向該存儲庫的作者提問。
***** 2018年11月3日新更新:多語言和中文模型可用 *****
我們提供了兩種新的BERT模型:
- BERT-Base, 多語言(不推薦,改用
多語言區分大小寫
):102種語言,12層,768隱藏,12頭,1.1億參數 - BERT-Base, 中文:簡體中文和繁體中文,12層,768隱藏,12頭,1.1億參數
我們為中文使用基于字符的分詞,而其他所有語言則使用WordPiece分詞。兩個模型都可以無縫使用,無需任何代碼更改。我們確實更新了tokenization.py
中的BasicTokenizer
的實現以支持中文字符分詞,因此如果分叉了它,請進行更新。但是,我們并未更改分詞API。
更多信息,請參閱多語言README。
***** 結束新信息 *****
引言
BERT,即雙向編碼器表示(Bidirectional Encoder Representations)從轉換器(Transformers),是一種新的語言表征預訓練方法,它在各種自然語言處理(NLP)任務中取得了最先進的結果。
詳細描述BERT并提供多個任務完整結果的學術論文可在此找到:[1810.04805] BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding。
舉幾個數字,在SQuAD v1.1問答任務中的成績如下:
SQuAD v1.1 領先榜(2018年10月8日) | 測試EM | 測試F1 |
---|---|---|
第1名合集 - BERT | 87.4 | 93.2 |
第2名合集 - nlnet | 86.0 | 91.7 |
第1名單模型 - BERT | 85.1 | 91.8 |
第2名單模型 - nlnet | 83.5 | 90.1 |
還有多項自然語言推理任務的成績:
系統 | MultiNLI | Question NLI | SWAG |
---|---|---|---|
BERT | 86.7 | 91.1 | 86.3 |
OpenAI GPT(先前最好) | 82.2 | 88.1 | 75.0 |
此外還包括許多其他任務。
重要的是,這些成果幾乎無需針對特定任務設計神經網絡架構。
如果你已經了解BERT,并希望立即開始操作,只需下載預先訓練好的模型,然后在幾分鐘內使用BERT進行先進的微調。
什么是BERT?
BERT是一種預訓練語言表示的方法,意味著我們在大型文本語料庫(如維基百科)上訓練一個通用的“語言理解”模型,然后用該模型來執行我們關心的下游NLP任務(例如,問答)。BERT超越了以前的方法,因為它是首個無監督、深度雙向的預訓練NLP系統。
無監督意味著BERT僅使用純文本語料庫進行訓練,這一點很重要,因為許多語言在網絡上有大量公開可用的純文本數據。
預訓練表示可以是上下文無關的或是上下文相關的,而上下文相關的表示則可以是單向的或雙向的。例如,像word2vec或GloVe這樣的上下文無關模型為詞匯表中的每個詞生成一個單一的“詞嵌入”表示,所以“bank”在“bank deposit”和“river bank”中的表示相同。而上下文相關模型則根據句子中其他詞生成每個詞的表示。
BERT建立在最近關于預訓練上下文表示的工作之上——包括Semi-supervised Sequence Learning、Generative Pre-Training、ELMo以及ULMFit——但關鍵在于這些模型都是單向的或淺度雙向的。這意味著每個詞僅根據其左側(或右側)的詞進行上下文化。例如,在句子“I made a bank deposit”中,“bank”的單向表示僅基于“I made a”,而不包括“deposit”。一些早期工作結合了分別來自左文境和右文境模型的表示,但只是以“淺”的方式。BERT利用左右文境——“I made a ... deposit”——從深度神經網絡的底層對“bank”進行表示,因此是深度雙向的。
BERT為此采用了一種簡單的方法:我們將輸入中15%的單詞遮蓋,整個序列通過深雙向的Transformer編碼器運行,然后僅預測被遮蓋的單詞。例如:
輸入: the man went to the [MASK1] . he bought a [MASK2] of milk.
標簽: [MASK1] = store; [MASK2] = gallon
為了學習句子間的關系,我們也對一項簡單的任務進行訓練,這個任務可以從任何單語語料庫生成:給定兩個句子A
和B
,“B”是否實際上是緊跟在A
后面的句子,還是語料庫中的隨機句子?
句子A: the man went to the store .
句子B: he bought a gallon of milk .
標簽: IsNextSentence
句子A: the man went to the store .
句子B: penguins are flightless .
標簽: NotNextSentence
然后,我們在大型語料庫(維基百科+BookCorpus)上用一個大模型(12層到24層Transformer)進行長時間(1M步更新)訓練,這就是BERT。
使用BERT有兩個階段:預訓練和微調。
預訓練相對昂貴(在4到16個云TPU上花費四天時間),但對于每種語言來說都是一次性的過程(當前模型僅支持英文,但多語言模型將在不久后發布)。我們將發布論文中訓練的多個預訓練模型,它們是在Google上預先訓練的。大多數NLP研究者無需從頭開始訓練自己的模型。
微調成本較低。論文中所有的結果都可以在最多1小時的單個Cloud TPU上,或幾個小時的GPU上復現,從同一個預訓練模型開始。例如,SQuAD可以在單個Cloud TPU上大約30分鐘內完成訓練,達到Dev F1分數91.0%,這是單系統的最新狀態。
BERT的另一個重要方面是它可以非常容易地適應多種類型的NLP任務。在論文中,我們展示了幾乎沒有任務特定修改的情況下,在句子級(如SST-2)、句子對級(如MultiNLI)、詞級(如NER)和跨度級(如SQuAD)任務上的最佳結果。
此存儲庫已發布什么?
我們發布了以下內容:
- 包含BERT模型架構(主要是標準的Transformer架構)的TensorFlow代碼。
- 論文中
BERT-Base
和BERT-Large
的小寫和全拼版本的預訓練檢查點。 - TensorFlow代碼,用于一鍵復制論文中最重要微調實驗,包括SQuAD、MultiNLI和MRPC。
此存儲庫中的所有代碼均與CPU、GPU和Cloud TPU兼容并可直接使用。
預訓練模型
我們發布了論文中的BERT-Base
和BERT-Large
模型。Uncased
表示在WordPiece分詞之前,文本已經轉換為小寫,例如,John Smith
變為john smith
。Uncased
模型還會移除所有重音標記。Cased
意味著保留真實的大小寫和重音標記。通常情況下,除非您知道對于您的任務(如命名實體識別或詞性標注),大小寫信息是重要的,否則Uncased
模型表現會更好。
這些模型都根據源代碼相同的許可證(Apache 2.0)發布。
如果使用帶大小寫的模型,請確保在訓練腳本中傳遞--do_lower=False
參數。(或者如果您使用自己的腳本,直接向FullTokenizer
傳遞do_lower_case=False
。)
模型鏈接如下(右鍵點擊,選擇“另存為...”):
- BERT-Large, Uncased (Whole Word Masking): 24層,1024個隱藏單元,16個頭,340M參數
- BERT-Large, Cased (Whole Word Masking): 24層,1024個隱藏單元,16個頭,340M參數
- BERT-Base, Uncased: 12層,768個隱藏單元,12個頭,110M參數
- BERT-Large, Uncased: 24層,1024個隱藏單元,16個頭,340M參數
- BERT-Base, Cased: 12層,768個隱藏單元,12個頭,110M參數
- BERT-Large, Cased: 24層,1024個隱藏單元,16個頭,340M參數
- BERT-Base, Multilingual Cased (New, recommended): 支持104種語言,12層,768個隱藏單元,12個頭,110M參數
- BERT-Base, Multilingual Uncased (Orig, not recommended)?(不推薦,建議使用
Multilingual Cased
替代):支持102種語言, 12層,768個隱藏單元,12個頭,110M參數 - BERT-Base, Chinese: 中文簡體與繁體,12層,768個隱藏單元,12個頭,110M參數
每個.zip
文件包含以下三個項目:
- 一個TensorFlow檢查點文件(
bert_model.ckpt
),其中包含預訓練權重(實際上是3個文件)。 - 詞匯文件(
vocab.txt
),用于映射WordPiece到單詞ID。 - 配置文件(
bert_config.json
),指定模型的超參數。
使用BERT進行微調
重要提示:論文中所有結果是在具有64GB內存的單個Cloud TPU上進行微調得到的。目前,在只有12GB至16GB內存的GPU上無法重現大多數BERT-Large
的結果,因為可以容納的最大批次大小太小。我們正在努力更新此存儲庫以允許在GPU上實現更大的有效批量大小。有關更多細節,請參閱內存溢出問題部分。
此代碼已測試過TensorFlow 1.11.0版本,同時支持Python2和Python3(盡管更全面地測試了Python2,因為這是Google內部使用的版本)。
使用BERT-Base
的微調示例應該能夠在至少具有12GB內存的GPU上,按照給定的超參數運行。
在Cloud TPU上微調
以下大部分示例假設您將在本地機器上使用像Titan X或GTX 1080這樣的GPU運行訓練和評估。
但是,如果您有要訓練的Cloud TPU,請在run_classifier.py
或run_squad.py
中添加以下標志:
--use_tpu=True \--tpu_name=$TPU_NAME
請參考Google Cloud TPU教程,了解如何使用Cloud TPU。或者,您可以使用Google Colab筆記本 "BERT FineTuning with Cloud TPUs"。
在Cloud TPU上,預訓練模型和輸出目錄需要在Google Cloud Storage上。例如,如果您有一個名為some_bucket
的存儲桶,您可能會使用以下標志:
--output_dir=gs://some_bucket/my_output_dir/
預訓練模型的未壓縮文件也可以在Google Cloud Storage的gs://bert_models/2018_10_18
文件夾中找到。例如:
export BERT_BASE_DIR=gs://bert_models/2018_10_18/uncased_L-12_H-768_A-12
句子(和句子對)分類任務
在運行這個例子之前,您需要通過運行這個腳本下載GLUE數據,然后將其解壓到目錄$GLUE_DIR
。接下來,下載BERT-Base
檢查點并將其解壓縮到目錄$BERT_BASE_DIR
。
這個示例代碼會在微軟研究的Paraphrase語料庫(MRPC)上微調BERT-Base
,該語料庫僅包含3,600個示例,可以在大多數GPU上用幾分鐘的時間完成微調。
export BERT_BASE_DIR=/path/to/bert/uncased_L-12_H-768_A-12
export GLUE_DIR=/path/to/gluepython run_classifier.py \--task_name=MRPC \--do_train=true \--do_eval=true \--data_dir=$GLUE_DIR/MRPC \--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=2e-5 \--num_train_epochs=3.0 \--output_dir=/tmp/mrpc_output/
您應看到如下輸出:
***** Eval results *****eval_accuracy = 0.845588eval_loss = 0.505248global_step = 343loss = 0.505248
這意味著開發集準確率為84.55%。像MRPC這樣的小型數據集即使從同一個預訓練檢查點開始,其開發集準確性也會有很大差異。如果多次重新運行(確保指向不同的output_dir
),您應看到介于84%和88%之間的結果。
有幾個預訓練模型已在run_classifier.py
中開箱即用,因此使用BERT進行任何單句或多句分類任務應該很容易遵循這些示例。
注意:您可能會看到一條消息Running train on CPU
。這實際上只是指它不在Cloud TPU上運行,包括GPU。
分類器預測
一旦訓練了分類器,您就可以在推理模式下使用它,通過使用--do_predict=true
命令。需要在輸入文件夾中有一個名為test.tsv的文件。輸出將在名為test_results.tsv的輸出文件夾中創建。每行將包含每個樣本的輸出,列是類別概率。
export BERT_BASE_DIR=/path/to/bert/uncased_L-12_H-768_A-12
export GLUE_DIR=/path/to/glue
export TRAINED_CLASSIFIER=/path/to/fine/tuned/classifierpython run_classifier.py \--task_name=MRPC \--do_predict=true \--data_dir=$GLUE_DIR/MRPC \--vocab_file=$BERT_BASE_DIR/vocab.txt \--bert_config_file=$BERT_BASE_DIR/bert_config.json \--init_checkpoint=$TRAINED_CLASSIFIER \--max_seq_length=128 \--output_dir=/tmp/mrpc_output/
SQuAD 1.1
斯坦福問答數據集(SQuAD)是一個流行的問題回答基準數據集。BERT(在發布時)幾乎無需針對任務特定的網絡架構修改或數據增強,就能在此數據集上取得最先進的結果。然而,它確實需要半復雜的預處理和后處理來應對:(a) 變長度的SQuAD上下文段落,以及 (b) 用于SQuAD訓練的字符級答案注釋。這些處理在run_squad.py
中實現并文檔化。
首先,你需要下載該數據集。SQuAD網站似乎不再鏈接到v1.1版本的數據集,但所需的文件可以在這里找到:
- train-v1.1.json
- dev-v1.1.json
- evaluate-v1.1.py
將這些文件下載到目錄?$SQUAD_DIR
。
由于內存限制,在12GB-16GB GPU上無法重現論文中的最新SQuAD結果(事實上,即使批處理大小設為1也不適用于12GB GPU上的BERT-Large
)。然而,一個相當強大的BERT-Base
模型可以在GPU上使用以下超參數進行訓練:
python run_squad.py \--vocab_file=$BERT_BASE_DIR/vocab.txt \--bert_config_file=$BERT_BASE_DIR/bert_config.json \--init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \--do_train=True \--train_file=$SQUAD_DIR/train-v1.1.json \--do_predict=True \--predict_file=$SQUAD_DIR/dev-v1.1.json \--train_batch_size=12 \--learning_rate=3e-5 \--num_train_epochs=2.0 \--max_seq_length=384 \--doc_stride=128 \--output_dir=/tmp/squad_base/
開發集的預測結果會被保存到output_dir
中的名為predictions.json
的文件里:
python $SQUAD_DIR/evaluate-v1.1.py $SQUAD_DIR/dev-v1.1.json ./squad/predictions.json
這應該會輸出類似如下結果:
{"f1": 88.41249612335034, "exact_match": 81.2488174077578}
你應該能看到與BERT-Base
報道的88.5%相似的結果。
如果你有訪問Cloud TPU的權限,你可以用BERT-Large
進行訓練。以下是獲得大約90.5%-91.0% F1單系統評分的一組(與論文略有不同)一致的超參數,僅基于SQuAD進行訓練:
python run_squad.py \--vocab_file=$BERT_LARGE_DIR/vocab.txt \--bert_config_file=$BERT_LARGE_DIR/bert_config.json \--init_checkpoint=$BERT_LARGE_DIR/bert_model.ckpt \--do_train=True \--train_file=$SQUAD_DIR/train-v1.1.json \--do_predict=True \--predict_file=$SQUAD_DIR/dev-v1.1.json \--train_batch_size=24 \--learning_rate=3e-5 \--num_train_epochs=2.0 \--max_seq_length=384 \--doc_stride=128 \--output_dir=gs://some_bucket/squad_large/ \--use_tpu=True \--tpu_name=$TPU_NAME
例如,用這些參數進行一次隨機運行,會產生如下開發集分數:
{"f1": 90.87081895814865, "exact_match": 84.38978240302744}
如果你在此次訓練之前先在一個epoch上對TriviaQA進行微調,結果會更好,但你需要將TriviaQA轉換為SQuAD JSON格式。
SQuAD 2.0
此模型也在run_squad.py
中實現并記錄。
要運行SQuAD 2.0,首先需要下載數據集。所需文件如下所示:
- train-v2.0.json
- dev-v2.0.json
- evaluate-v2.0.py
將這些文件下載到目錄?$SQUAD_DIR
。
在Cloud TPU上,你可以用以下方式運行BERT-Large
:
python run_squad.py \--vocab_file=$BERT_LARGE_DIR/vocab.txt \--bert_config_file=$BERT_LARGE_DIR/bert_config.json \--init_checkpoint=$BERT_LARGE_DIR/bert_model.ckpt \--do_train=True \--train_file=$SQUAD_DIR/train-v2.0.json \--do_predict=True \--predict_file=$SQUAD_DIR/dev-v2.0.json \--train_batch_size=24 \--learning_rate=3e-5 \--num_train_epochs=2.0 \--max_seq_length=384 \--doc_stride=128 \--output_dir=gs://some_bucket/squad_large/ \--use_tpu=True \--tpu_name=$TPU_NAME \--version_2_with_negative=True
假設你已將輸出目錄的所有內容復制到本地名為squad/
的目錄下。最初的開發集預測將在squad/predictions.json
中,每個問題的最佳非空答案與無答案(" ")之間的得分差異會在squad/null_odds.json
文件中。
運行以下腳本來調整預測空值與非空值答案的閾值:
python $SQUAD_DIR/evaluate-v2.0.py $SQUAD_DIR/dev-v2.0.json ./squad/predictions.json --na-prob-file ./squad/null_odds.json
假設腳本輸出了“best_f1_thresh”THRESH。(典型值介于-1.0和-5.0之間)。現在,你可以重新運行模型以生成使用派生閾值的預測,或者從squad/nbest_predictions.json
中提取相應的答案。
python run_squad.py \--vocab_file=$BERT_LARGE_DIR/vocab.txt \--bert_config_file=$BERT_LARGE_DIR/bert_config.json \--init_checkpoint=$BERT_LARGE_DIR/bert_model.ckpt \--do_train=False \--train_file=$SQUAD_DIR/train-v2.0.json \--do_predict=True \--predict_file=$SQUAD_DIR/dev-v2.0.json \--train_batch_size=24 \--learning_rate=3e-5 \--num_train_epochs=2.0 \--max_seq_length=384 \--doc_stride=128 \--output_dir=gs://some_bucket/squad_large/ \--use_tpu=True \--tpu_name=$TPU_NAME \--version_2_with_negative=True \--null_score_diff_threshold=$THRESH
內存溢出問題
論文中所有的實驗都是在擁有64GB設備RAM的Cloud TPU上進行微調的。因此,如果你使用只有12GB-16GB RAM的GPU,并且使用了論文中描述的相同超參數,很可能會遇到內存不足的問題。
影響內存使用的因素包括:
-
max_seq_length
:發布的模型是用序列長度高達512進行訓練的,但可以通過縮短最大序列長度來節省大量內存。這在示例代碼中的max_seq_length
標志處控制。 -
train_batch_size
:內存使用量也直接與批處理大小成比例。 -
模型類型,
BERT-Base
與BERT-Large
:BERT-Large
模型比BERT-Base
需要更多的內存。 -
優化器:BERT的默認優化器是Adam,它需要大量的額外內存來存儲
m
和v
向量。切換到更節省內存的優化器可以減少內存使用,但也可能影響結果。我們尚未嘗試過其他用于微調的優化器。
使用默認的訓練腳本(run_classifier.py
和run_squad.py
),我們在配備了TensorFlow 1.11.0的單個Titan X GPU(12GB RAM)上進行了最大批處理大小的基準測試:
系統 | 序列長度 | 最大批處理大小 |
---|---|---|
BERT-Base | 64 | 64 |
... | 128 | 32 |
... | 256 | 16 |
... | 320 | 14 |
... | 384 | 12 |
... | 512 | 6 |
BERT-Large | 64 | 12 |
... | 128 | 6 |
... | 256 | 2 |
... | 320 | 1 |
... | 384 | 0 |
... | 512 | 0 |
不幸的是,對于BERT-Large
來說,這些最大批處理大小如此之小,以至于無論使用什么學習率,都會損害模型的準確性。我們正在努力向這個倉庫添加代碼,以便在GPU上使用更大的有效批處理大小。代碼將以以下一種(或兩種)技術為基礎:
-
梯度積累:在微型批次中的樣本通常獨立于梯度計算(不包括這里未使用的批量歸一化)。這意味著在執行權重更新之前,可以累積多個較小微型批次的梯度,這與單次較大的更新完全等價。
-
梯度檢查點:深度神經網絡訓練期間,GPU/TPU內存的主要用途是在前向傳遞中緩存用于反向傳遞高效計算的中間激活。"梯度檢查點"通過智能地重新計算激活來交換內存與計算時間。
但是,這在當前版本中并未實現。
利用BERT提取固定特征向量(類似ELMo)
在某些情況下,而不是對整個預訓練模型進行端到端的微調,獲取“預訓練上下文嵌入”是有益的。這些是通過預訓練模型隱藏層生成的每個輸入令牌的固定上下文表示,可以緩解大部分內存不足的問題。
我們提供了腳本extract_features.py
,使用方法如下:
# 句子A和句子B以|||分隔符隔開,適用于像問答和蘊含這樣的句子對任務。
# 對于單句輸入,每行放一個句子,并不要使用分隔符。
echo 'Who was Jim Henson ? ||| Jim Henson was a puppeteer' > /tmp/input.txtpython extract_features.py \--input_file=/tmp/input.txt \--output_file=/tmp/output.jsonl \--vocab_file=$BERT_BASE_DIR/vocab.txt \--bert_config_file=$BERT_BASE_DIR/bert_config.json \--init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \--layers=-1,-2,-3,-4 \--max_seq_length=128 \--batch_size=8
這將在一個JSON文件中創建一行對應一行輸入的BERT激活值,其中layers
參數指定的(-1是Transformer的最后一層隱含層等)。
請注意,此腳本會生成非常大的輸出文件(默認情況下,每個輸入令牌大約15kb)。
如果需要在原始單詞和分詞化后的單詞之間保持對齊(用于投影訓練標簽),請參閱下面的“分詞”部分。
**注意:**您可能會看到如Could not find trained model in model_dir: /tmp/tmpuB5g5c, running initialization to predict.
的消息。這是預期中的,它意味著我們使用了init_from_checkpoint()
?API而不是保存模型API。如果沒有指定檢查點或指定了無效的檢查點,這個腳本會報錯。
分詞
對于句子級別的任務(或句子對任務),分詞很簡單。只需遵循run_classifier.py
和extract_features.py
中的示例代碼。基本步驟是:
-
實例化一個
tokenizer = tokenization.FullTokenizer
對象。 -
使用
tokens = tokenizer.tokenize(raw_text)
對原始文本進行分詞。 -
將長度截斷至最大序列長度。(您可以使用最多512個,但為了內存和速度考慮,盡可能短一些)。
-
在正確位置添加
[CLS]
和[SEP]
令牌。
詞級和跨度級任務(例如SQuAD和NER)更復雜,因為需要在輸入文本和輸出文本之間維護對齊,以便將訓練標簽投影回去。SQuAD是一個特別復雜的例子,因為其輸入標簽基于字符,并且SQuAD段落常常超過我們的最大序列長度。請查看run_squad.py
的代碼,了解我們如何處理這個問題。
在描述處理詞級任務的一般方法之前,理解我們的分詞器到底在做什么是很重要的。它主要分為三個步驟:
-
文本規范化:將所有空白字符轉換為空格,(對于“Uncased”模型)將輸入小寫并去除重音標記。例如:
John Johanson's, → john johanson's,
。 -
標點符號分割:在標點符號兩側都進行切割(即,在所有標點符號周圍添加空格)。標點符號定義為(a)具有“P*”Unicode類的任何字符,(b)非字母/數字/空間的ASCII字符(例如,實際上不是標點符號的字符如$) 。例如:
john johanson's, → john johan ##son ' s ,
-
詞塊分詞:將上述過程的輸出應用空格分詞,并對每個單獨的單詞應用WordPiece分詞。(我們的實現直接基于
tensor2tensor
的版本,見鏈接)。例如:john johan ##son ' s , → john johan ##son ' s ,
這種方案的優點是與大多數現有的英語分詞器“兼容”。例如,假設你有一個部分-of-speech標注任務,如下所示:
輸入: John Johanson 's house
標簽: NNP NNP POS NN
分詞后的輸出將是這樣:
分詞: john johan ##son ' s house
關鍵的是,這將與原始文本John Johanson's house
(在's前沒有空格)的輸出相同。
如果你有預分詞的詞級注釋,你可以獨立地對每個輸入詞進行分詞,并確定性地維護從原始詞到分詞詞的映射:
### 輸入
orig_tokens = ["John", "Johanson", "'s", "house"]
labels = ["NNP", "NNP", "POS", "NN"]### 輸出
bert_tokens = []# 原始詞到分詞詞的映射將會是一個int -> int映射,分別對應`orig_tokens`索引和`bert_tokens`索引。
orig_to_tok_map = []tokenizer = tokenization.FullTokenizer(vocab_file=vocab_file, do_lower_case=True)bert_tokens.append("[CLS]")
for orig_token in orig_tokens:orig_to_tok_map.append(len(bert_tokens))bert_tokens.extend(tokenizer.tokenize(orig_token))
bert_tokens.append("[SEP]"]# bert_tokens == ["[CLS]", "john", "johan", "##son", "'", "s", "house", "[SEP]"]
# orig_to_tok_map == [1, 2, 4, 6]
現在,orig_to_tok_map
可用于將labels
投影到分詞表示中。
存在一些常見的英語分詞策略會導致與BERT的預訓練方式略有不匹配。例如,如果輸入分詞將像do n't
這樣的縮寫分開,就會產生不匹配。如果可能,你應該先處理數據,將其恢復為原始樣式的文本。如果不方便,這種不匹配可能不會有太大影響。
使用BERT進行預訓練
我們發布了針對任意文本語料庫進行“掩碼語言模型”和“下一句預測”的代碼。請注意,這不是論文中所使用的精確代碼(原文本用C++編寫,有些額外的復雜性),但此代碼確實生成了論文中描述的預訓練數據。
運行數據生成的方法如下。輸入是一個純文本文件,每行一個句子。(對于“下一句預測”任務,確保這些都是實際的句子)。文檔由空行分隔。輸出是一組序列化的tf.train.Example
,格式為TFRecord
文件。
你可以使用現成的NLP工具包(如spaCy)來執行句子切分。create_pretraining_data.py
腳本會將段落連接在一起,直到達到最大序列長度,以減少填充導致的計算浪費(有關更多細節,請參閱腳本)。然而,你可能想要有意在輸入數據中添加少量噪音(例如,隨機截斷2%的輸入段),使模型在微調時對非句子輸入更具魯棒性。
該腳本將所有例子存儲在內存中,所以對于大型數據文件,你應該將輸入文件劃分為多個部分,并多次調用該腳本。(可以在run_pretraining.py
中傳遞文件通配符,例如tf_examples.tf_record*
)。
max_predictions_per_seq
是每個序列的最大掩碼語言模型預測數。你應該設置為大約max_seq_length
乘以masked_lm_prob
(腳本不會自動這樣做,因為這個值需要在兩個腳本中傳入)。
python create_pretraining_data.py \--input_file=./sample_text.txt \--output_file=/tmp/tf_examples.tfrecord \--vocab_file=$BERT_BASE_DIR/vocab.txt \--do_lower_case=True \--max_seq_length=128 \--max_predictions_per_seq=20 \--masked_lm_prob=0.15 \--random_seed=12345 \--dupe_factor=5
以下是進行預訓練的步驟。如果從頭開始預訓練,不要包含init_checkpoint
。模型配置(包括詞匯表大小)在bert_config_file
中指定。此演示代碼只預訓練幾個步驟(20步),但在實踐中,你可能希望將num_train_steps
設為10000步以上。傳遞給run_pretraining.py
的max_seq_length
和max_predictions_per_seq
參數必須與create_pretraining_data.py
中相同。
python run_pretraining.py \--input_file=/tmp/tf_examples.tfrecord \--output_dir=/tmp/pretraining_output \--do_train=True \--do_eval=True \--bert_config_file=$BERT_BASE_DIR/bert_config.json \--init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \--train_batch_size=32 \--max_seq_length=128 \--max_predictions_per_seq=20 \--num_train_steps=20 \--num_warmup_steps=10 \--learning_rate=2e-5
這會產生如下的輸出:
***** Eval results *****global_step = 20loss = 0.0979674masked_lm_accuracy = 0.985479masked_lm_loss = 0.0979328next_sentence_accuracy = 1.0next_sentence_loss = 3.45724e-05
由于我們的sample_text.txt
文件很小,這個示例訓練會在幾步內過度擬合該數據,產生不真實的高度準確度數字。
預訓練提示和注意事項
- 如果使用自定義詞匯表,請確保在
bert_config.json
中更改vocab_size
。如果不這樣做,當您在GPU或TPU上訓練時,可能會因為未檢查的越界訪問而出現NaN值。 - 如果您的任務有一個大型特定領域的語料庫(如“電影評論”或“科學論文”),那么從BERT檢查點開始,在您的語料庫上額外進行幾步預訓練可能是有益的。
- 我們在論文中使用的學習率是1e-4。但是,如果您從現有BERT檢查點開始進行附加預訓練步驟,應使用較小的學習率(例如,2e-5)。
- 目前的BERT模型僅支持英文,但我們計劃在未來不久(希望是在2018年11月底之前)發布一個預訓練了多種語言的多語言模型。
- 因為注意力計算與序列長度平方成正比,因此較長的序列會不成比例地昂貴。換句話說,64個序列長度為512的一批數據要比256個序列長度為128的一批數據成本高得多。全連接/卷積成本相同,但512長度序列的注意力成本要高得多。因此,一個好的策略是先用序列長度為128進行9萬步預訓練,然后再用序列長度為512進行額外的1萬步預訓練。非常長的序列主要用來學習位置嵌入,這些可以相對較快地學習到。請注意,這確實需要使用不同的
max_seq_length
值生成兩次數據。 - 如果從零開始預訓練,要做好準備,預訓練在計算上是昂貴的,特別是在GPU上。如果從頭開始預訓練,我們推薦的方案是在單個可搶占Cloud TPU v2上預訓練一個
BERT-Base
,大約需要兩周時間,費用約為500美元(基于2018年10月的價格)。當只在一個Cloud TPU上訓練時,相比于論文中的設置,您需要降低批次大小。
預訓練數據
我們將無法發布論文中使用的預處理數據集。對于維基百科,建議的預處理方法是下載最新版的dump,使用WikiExtractor.py提取文本,然后進行必要的清理以將其轉換為純文本。
不幸的是,收集BookCorpus的研究人員不再提供公共下載。古騰堡項目數據集是一個稍小一些(約2億字)的舊公共領域書籍集合。
Common Crawl是另一個非常大的文本集合,但您可能需要進行大量的預處理和清理工作,才能從中提取出用于預訓練BERT的可用語料庫。
學習新的WordPiece詞匯表
此存儲庫不包含用于學習新WordPiece詞匯表的代碼。原因是論文中使用的代碼是用C++實現的,并依賴于谷歌內部的庫。對于英文,通常最好直接使用我們的詞匯表和預訓練模型。對于學習其他語言的詞匯表,有很多開源選項可供選擇。然而,請注意它們與我們的tokenization.py
庫不兼容:
-
Google的SentencePiece庫
-
tensor2tensor的WordPiece生成腳本
-
Rico Sennrich的Byte Pair Encoding庫
在Colab中使用BERT
如果您想在Colab中使用BERT,可以從筆記本 "BERT FineTuning with Cloud TPUs" 開始。截至本文寫作時間(2018年10月31日),Colab用戶可以完全免費訪問Cloud TPU。?注意:每個用戶限一個,資源有限,需要Google云端平臺賬戶和存儲空間(盡管可以通過注冊GCP免費獲得存儲信用),并且這種功能將來可能不再可用。點擊鏈接的BERT Colab獲取更多信息。
常見問題解答(FAQ)
這個代碼與Cloud TPUs兼容嗎?關于GPU呢?
是的,此倉庫中的所有代碼都可以直接與CPU、GPU和Cloud TPU一起使用。但GPU訓練僅限單GPU。
我收到了內存不足錯誤,是怎么回事?
請參閱有關內存不足問題的部分以獲取更多信息。
有PyTorch版本可用嗎?
沒有官方的PyTorch實現。然而,來自HuggingFace的NLP研究人員提供了一個與BERT相兼容的PyTorch版本,可以匹配我們預訓練的檢查點并重現我們的結果。我們未參與PyTorch實現的創建或維護,所以請將任何相關問題指向該存儲庫的作者。
有Chainer版本可用嗎?
沒有官方的Chainer實現。但是,Sosuke Kobayashi提供了一個與BERT兼容的Chainer版本,可以匹配我們的預訓練檢查點并重現我們的結果。我們未參與Chainer實現的創建或維護,所以請將任何問題直接發送給該存儲庫的作者。
是否會發布其他語言的模型?
是的,我們計劃在未來不久發布一個多語言的BERT模型。我們不能保證具體會包括哪些語言,但它很可能是包含大多數有顯著規模維基百科的語言的單一模型。
是否會發布大于BERT-Large
的模型?
至今我們還沒有嘗試訓練比BERT-Large
更大的模型。如果我們能取得顯著的改進,可能會發布更大的模型。
此庫的許可證是什么?
所有的代碼和模型都根據Apache 2.0許可發布。請參閱LICENSE
文件以獲取更多信息。
如何引用BERT?
目前,請參考以下Arxiv論文:
@article{devlin2018bert,title={BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding},author={Devlin, Jacob and Chang, Ming-Wei and Lee, Kenton and Toutanova, Kristina},journal={arXiv preprint arXiv:1810.04805},year={2018}
}