1. 程式人生 > >谷歌官宣:全面超越人類的最強NLP預訓練模型BERT開源了!

谷歌官宣:全面超越人類的最強NLP預訓練模型BERT開源了!

來源 | Google Research GitHub
編譯 | 無明、Natalie
編輯 | Natalie

AI 前線導讀: 近日,谷歌 AI 的一篇 NLP 論文引起了社群極大的關注與討論,被認為是 NLP 領域的極大突破。谷歌大腦研究科學家 Thang Luong Twitter 表示,這項研究開啟了 NLP 領域的新時代。該論文介紹了一種新的語言表徵模型 BERT——來自 Transformer 的雙向編碼器表徵。BERT 是首個在大批句子層面和 token 層面任務中取得當前最優效能的基於微調的表徵模型,其效能超越許多使用任務特定架構的系統,重新整理了 11 項 NLP 任務的當前最優效能記錄。
剛剛,谷歌正式將其開源!這意味著所有NLP從業者都可以試用這個強大的NLP預訓練模型並結合到自己的工作中。
更多優質內容請關注微信公眾號“AI 前線”(ID:ai-front)

首先附上開原始碼傳送門:

github.com/google-rese…

原論文連結:

arxiv.org/abs/1810.04…

該開源專案亮點如下

  1. 獨立的 TensorFlow 程式碼,有簡單的 API 且無依賴關係。

  2. 連結到論文中的 BERT-Base 和 BERT-Large 預訓練版本。

  3. 一鍵複製論文中的 MultiNLI 和 SQuAD v1.1 結果。

  4. 包含預訓練資料生成和訓練的程式碼。

  5. 可以連結到 Colab,從而使用免費的雲端 TPU 執行 BERT。

幾個常見問題解答

  1. 我們計劃很快釋出一個多語言模型(在 60 種語言上訓練的大型共享 WordPiece 詞彙,並對中文做了特殊處理)。

  2. 現有的基於 PyTorch(或其他框架)的版本無法與檢查點相容(因為如果沒有我們的程式碼,這是不可能做到的)。我們希望有人能夠建立一個 op-for-op 重新實現 modeling.py,以便建立一個與我們的檢查點相容的 PyTorch 模型,尤其是我們計劃在將來發布更多的檢查點(例如,多語言模型)。

  3. 我們還沒有在 SQuAD 2.0 上執行過這個模型,我們想把它作為一項練習留給讀者來完成:)

  4. 你不一定要在雲端 TPU 上進行訓練,但是在 GPU 上訓練 BERT-Large 模型可能會出現嚴重的記憶體不足問題。在 GPU 上執行 BERT-Base 通常可以正常工作(與我們在論文中使用的相比,你可能需要降低 Batch Size,但如果你同時也對學習率做了調整,那麼最終結果應該是類似的)。我們正在嘗試找出在 GPU 上執行 BERT-Large 的最佳解決方法。

以下內容編譯自 BERT 開源專案 Readme 檔案(略有精簡)。

什麼是 BERT?

BERT 是預訓練語言表示的方法,也即我們基於大型文字語料庫(如維基百科)訓練通用的“語言理解”模型,然後將模型用於下游的 NLP 任務(如問答) 。BERT 比之前的方法更優,因為它是第一個用於預訓練 NLP 的無監督、深度雙向系統。

無監督意味著 BERT 只使用純文字語料庫進行訓練,這點很重要,因為網路上有很多公開的純文字資料。

預訓練表示也可以是無上下文或有上下文的,有上下文的表示又可以是單向或雙向的。word2vec 或 GloVe 這類無上下文模型為詞彙表中的每個單詞生成單個“詞袋”表示,因此“bank”與“bank deposit”和“river bank”具有相同的表示。相反,上下文模型基於句子中其他單詞生成每個單詞的表示。

BERT 建立在最近的預訓練上下文表示工作的基礎之上,包括半監督序列學習、生成預訓練、ELMo 和 ULMFit,這些模型都是單向或淺雙向的。也就是說,每個單詞僅使用左側(或右側)的單詞進行語境化。例如,在“I made a bank deposit”這個句子中,“bank”的單向表示基於“I made a”而不是“deposit”。之前的一些工作以“淺層”的方式將來自左上下文和右上下文模型的表示結合在一起,而 BERT 使用左右上下文來表示“bank”——從深度神經網路的最底部開始,所以它是深度雙向的。

BERT 使用一種簡單的方法:我們將輸入的 15%的單詞遮蔽掉,讓整個序列通過深度雙向 Transformer 編碼器,然後僅預測被遮蔽的單詞。例如:

Input: the man went to the [MASK1] . he bought a [MASK2] of milk.
Labels: [MASK1] = store; [MASK2] = gallon
複製程式碼

為了學習句子之間的關係,我們還訓練一個簡單的任務:給定兩個句子 A 和 B,那麼 B 是 A 的下一個句子還是隻是語料庫中的一個隨機句子?

Sentence A: the man went to the store .
Sentence B: he bought a gallon of milk .
Label: IsNextSentence
複製程式碼
Sentence A: the man went to the store .
Sentence B: penguins are flightless .
Label: NotNextSentence
複製程式碼

然後,我們基於大型語料庫(Wikipedia + BookCorpus)訓練了一個模型(12 層到 24 層 Transformer),花了很長一段時間(1 百萬個更新步驟),那就是 BERT。

使用 BERT 需要兩個階段:預訓練和微調。

預訓練的成本相當高(在 4 到 16 個 Cloud TPU 上訓練需要 4 天時間),而且對於每一種語言,都是一次性的程式(目前的模型僅限英語,更多語言模型將在不久的將來發布)。我們正在釋出一些預訓練的模型,這些模型是在 Google 上預先訓練過的。大多數 NLP 研究人員不需要從頭開始訓練自己的模型。

微調的成本較低。論文中提到的所有結果都可以在單個 Cloud TPU 上進行訓練,最多花 1 個小時,或者在 GPU 上花幾個小時即可。

預訓練模型 我們在論文中釋出了 BERT-Base 和 BERT-Large 模型。Uncased 是指文字在 WordPiece 標記化之前已經轉換成小寫,例如“John Smith”轉換成“john smith”。Uncased 模型還移除了重音標記。Cased 是指保留真實的大小寫和重音標記。通常,除非你的任務需要大小寫(例如,命名實體識別或詞性標註),否則 Uncased 模型會更好。

這些模型都是基於 Apache 2.0 許可進行發行。

模型連結:

每個.zip 檔案包含三個專案:

  • 包含預訓練的權重(實際上是 3 個檔案)的 TensorFlow 檢查點(bert_model.ckpt)。

  • 用於將 WordPiece 對映到 word id 的詞彙檔案(vocab.txt)。

  • 配置檔案(bert_config.json),指定模型的超引數。

使用 BERT 進行微調

微調示例使用了 BERT-Base,它應該能夠使用給定的超引數在配備至少 12GB RAM 的 GPU 上執行。

在 Cloud TPU 上進行微調

下面的大多數示例都假設你將使用 Titan X 或 GTX 1080 這樣的 GPU 在本地計算機上執行訓練 / 評估。

不過,如果你可以訪問 Cloud TPU,只需將以下標誌新增到 run_classifier.py 或 run_squad.py:

--use_tpu=True \
 --tpu_name=$TPU_NAME
複製程式碼

在 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
複製程式碼

句子(和句子對)分類任務

在執行這個示例之前,你必須通過這個指令碼(gist.github.com/W4ngatang/6… GLUE 資料(gluebenchmark.com/tasks),並將其解… GLUE_DIR)。接下來,下載 BERT-Base 檢查點並將其解壓縮到另一個目錄中(目錄變數可以設定為BERT_BASE_DIR)。

這個示例針對微軟 Research Paraphrase Corpus(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/glue

python 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.845588
 eval_loss = 0.505248
 global_step = 343
 loss = 0.505248
複製程式碼

dev 集的準確率為 84.55%。MRPC 在 dev 集準確率方面有很大的差異,即使是從相同的預訓練檢查點開始。如果重新執行幾次(確保要指向不同的 output_dir),你應該會看到結果在 84%到 88%之間。

其他一些預訓練模型是在 run_classifier.py 中實現的,所以應該可以直接按照這些示例將 BERT 用於任何單句或句子對分類任務。

SQuAD

斯坦福問答資料集(SQuAD)是一個非常流行的問答基準資料集。BERT(在釋出時)在 SQuAD 上獲得了最好的結果,幾乎沒有進行特定任務的網路架構修改或資料增強。不過,它確實需要半複雜資料預處理和後處理來處理 SQUAD 上下文段落的可變長度性質,以及用於 SQuAD 訓練的字元級答案註解。run_squad.py 實現並記錄了處理過程。

要在 SQuAD 上執行訓練,首先需要下載這個資料集。SQuAD 網站(rajpurkar.github.io/SQuAD-explo… v1.1 資料集的連結,一些必要的檔案可以在這裡找到:

將這些下載到某個目錄(變數可以設定為 $SQUAD_DIR)。

由於記憶體限制,目前無法在 12GB-16GB 的 GPU 上再現最好的 SQuAD 結果。但是,可以使用下面這些超引數在 GPU 上訓練 BERT-Base 模型:

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=5e-5 \
 --num_train_epochs=2.0 \
 --max_seq_length=384 \
 --doc_stride=128 \
 --output_dir=/tmp/squad_base/
複製程式碼

dev 集預測結果將儲存到 output_dir 目錄的一個名為 predictions.json 的檔案中:

python SQUAD_DIR/evaluate-v1.1.pySQUAD_DIR/dev-v1.1.json ./squad/predictions.json 應該產生這樣的輸出:

{"f1": 88.41249612335034, "exact_match": 81.2488174077578}
複製程式碼

你應該看到論文中提到的 88.5%的 F1。

如果你可以訪問 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=48 \
 --learning_rate=5e-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
複製程式碼

例如,使用這些引數隨機進行一次會產生以下 dev 得分:

{"f1": 90.87081895814865, "exact_match": 84.38978240302744}
複製程式碼

使用 BERT 提取固定的特徵向量

在某些情況下,相比對整個預訓練模型進行端到端的微調,獲得預訓練的上下文嵌入可能會更好,這些嵌入是預訓練模型隱藏層生成的每個輸入標記的固定上下文表示。

例如,我們可能會這樣使用 extract_features.py 指令碼:

# Sentence A and Sentence B are separated by the ||| delimiter.
# For single sentence inputs, don't use the delimiter.
echo 'Who was Jim Henson ? ||| Jim Henson was a puppeteer' > /tmp/input.txt

python 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 檔案,其中包含由 layers 指定的每個 Transformer 層的 BERT 啟用(-1 是 Transformer 的最後隱藏層,並以此類推)。

請注意,這個指令碼將生成非常大的輸出檔案(預設情況下,每個輸入標記大約 15kb)。

如果你需要對齊原始單詞和標記化單詞,請參閱下面的標記化部分。

標記化(tokenization)

對於句子(或句子對)任務,標記化是非常簡單的。只需要遵循 run_classifier.py 和 extract_features.py 中的示例程式碼即可。句子級任務的基本流程:

例項化 tokenizer = tokenization.FullTokenizer;

使用 tokens = tokenizer.tokenize(raw_text)對原始文字進行標記;

截斷到最大序列長度(最多可以使用 512,但處於記憶體和速度方面的考慮,最好使用短一點的);

在正確的位置新增 [CLS] 和 [SEP] 標記。

單詞級和 span 級的任務(例如 SQuAD 和 NER)會複雜一些,因為你需要對齊輸入文字和輸出文字。SQuAD 是一個特別複雜的例子,因為輸入標籤是基於字元的,而 SQuAD 段落通常比我們的最大序列長度要長。請參閱 run_squad.py 中的程式碼,瞭解我們如何處理這個問題。

在我們描述處理單詞級任務的一般方法之前,需要先了解我們的標記器都做了哪些事情。它有三個主要步驟:

文字規範化:將所有空白字元轉換為空格,(對於 Uncased 模型)將輸入轉換為小寫並刪除重音標記。例如,“John Johanson’s”變成“john johanson’s”。

標點符號拆分:拆分兩側的所有標點符號(即在所有標點符號周圍新增空格)。標點符號是指具有 P* Unicode 內容或任何非字母 / 數字 / 空格 ASCII 字元。例如,“johanson’s,”變成“john johanson ' s ,”。

WordPiece 標記化:對上一步驟的輸出進行空格標記化,並對每個標記進行 WordPiece 標記化。例如,“john johanson ' s , ”變成“john johan ##son ' s ,”。

這個方案的優點是它與大多數現有的英語標記符“相容”。例如,假設你有一個詞性標記任務,如下所示:

Input:  John Johanson 's   house
Labels: NNP  NNP      POS NN
複製程式碼

標記化輸出如下所示:

Tokens: john johan ##son ' s house 如果你有一個帶有單詞級註解的預標記表示,你可以單獨標記每個輸入單詞,並對齊原始單詞和標記化單詞:

### Input
orig_tokens = ["John", "Johanson", "'s",  "house"]
labels      = ["NNP",  "NNP",      "POS", "NN"]

### Output
bert_tokens = []

# Token map will be an int -> int mapping between the `orig_tokens` index and
# the `bert_tokens` index.
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 進行預訓練

我們正在嘗試在任意文字語料庫上進行“masked LM”和“下一個句子預測”。請注意,這些程式碼不同於論文中所述的程式碼(原始程式碼是用 C++ 編寫的,有一些額外的複雜性),但可以生成論文中所述的預訓練資料。

輸入是純文字檔案,一行一個句子。文件使用空行進行分隔。輸出是一組序列化為 TFRecord 檔案格式的 tf.train.Example。

指令碼將整個輸入檔案的樣本儲存在記憶體中,對於大型資料檔案,需要將其分片並多次呼叫指令碼。

max_predictions_per_seq 是每個序列的 masked LM 預測的最大數量。你應該將其設定為 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 = 20
 loss = 0.0979674
 masked_lm_accuracy = 0.985479
 masked_lm_loss = 0.0979328
 next_sentence_accuracy = 1.0
 next_sentence_loss = 3.45724e-05
複製程式碼

請注意,由於 sample_text.txt 檔案非常小,這個示例將在幾個步驟之內出現過擬合,併產生不切實際的高準確率。

預訓練資料

我們將無法釋出論文中使用的預處理資料集。 對於 Wikipedia,建議下載最新的轉儲(dumps.wikimedia.org/enwiki/late… WikiExtractor.py 提取文字,然後進行必要的清理將其轉換為純文字。

可惜的是,收集 BookCorpus 的研究人員不再提供公開下載。 Guttenberg 資料集(web.eecs.umich.edu/~lahiri/gut… 億個單詞)的舊書集合。

Common Crawl(commoncrawl.org/)是另一個非常大的文字… BERT 預訓練。

在 Colab 中使用 BERT 如果你想將 BERT 與 Colab 一起使用,可以從“BERT FineTuning with Cloud TPU”(colab.sandbox.google.com/github/tens… 年 10 月 31 日),Colab 使用者可以完全免費訪問一個 Cloud TPU。每個使用者可以使用一個,可用性有限,需要一個帶有儲存空間的 Google Cloud Platform 帳戶,並且在未來可能無法再使用。

英文原文:

github.com/google-rese…