来源 | Google Research GitHub
编译 | 无明、Natalie
编辑 | Nataliehtml
AI 前线导读: 近日,谷歌 AI 的一篇 NLP 论文引发了社区极大的关注与讨论,被认为是 NLP 领域的极大突破。谷歌大脑研究科学家 Thang Luong Twitter 表示,这项研究开启了 NLP 领域的新时代。该论文介绍了一种新的语言表征模型 BERT——来自 Transformer 的双向编码器表征。BERT 是首个在大批句子层面和 token 层面任务中取得当前最优性能的基于微调的表征模型,其性能超越许多使用任务特定架构的系统,刷新了 11 项 NLP 任务的当前最优性能记录。
刚刚,谷歌正式将其开源!这意味着全部NLP从业者均可以试用这个强大的NLP预训练模型并结合到本身的工做中。
更多优质内容请关注微信公众号“AI 前线”(ID:ai-front)python
首先附上开源代码传送门:git
github.com/google-rese…github
原论文连接:web
该开源项目亮点以下:api
独立的 TensorFlow 代码,有简单的 API 且无依赖关系。bash
连接到论文中的 BERT-Base 和 BERT-Large 预训练版本。微信
一键复制论文中的 MultiNLI 和 SQuAD v1.1 结果。网络
包含预训练数据生成和训练的代码。
能够连接到 Colab,从而使用免费的云端 TPU 运行 BERT。
几个常见问题解答:
咱们计划很快发布一个多语言模型(在 60 种语言上训练的大型共享 WordPiece 词汇,并对中文作了特殊处理)。
现有的基于 PyTorch(或其余框架)的版本没法与检查点兼容(由于若是没有咱们的代码,这是不可能作到的)。咱们但愿有人可以建立一个 op-for-op 从新实现 modeling.py,以便建立一个与咱们的检查点兼容的 PyTorch 模型,尤为是咱们计划在未来发布更多的检查点(例如,多语言模型)。
咱们尚未在 SQuAD 2.0 上运行过这个模型,咱们想把它做为一项练习留给读者来完成:)
你不必定要在云端 TPU 上进行训练,可是在 GPU 上训练 BERT-Large 模型可能会出现严重的内存不足问题。在 GPU 上运行 BERT-Base 一般能够正常工做(与咱们在论文中使用的相比,你可能须要下降 Batch Size,但若是你同时也对学习率作了调整,那么最终结果应该是相似的)。咱们正在尝试找出在 GPU 上运行 BERT-Large 的最佳解决方法。
如下内容编译自 BERT 开源项目 Readme 文件(略有精简)。
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 许可进行发行。
模型连接:
BERT-Base,Uncased:storage.googleapis.com/bert_models…
BERT-Large,Uncased:storage.googleapis.com/bert_models…
BERT-Base,Cased:storage.googleapis.com/bert_models…
BERT-Large,Cased:尚不可用,须要从新生成。
每一个.zip 文件包含三个项目:
包含预训练的权重(其实是 3 个文件)的 TensorFlow 检查点(bert_model.ckpt)。
用于将 WordPiece 映射到 word id 的词汇文件(vocab.txt)。
配置文件(bert_config.json),指定模型的超参数。
微调示例使用了 BERT-Base,它应该可以使用给定的超参数在配备至少 12GB RAM 的 GPU 上运行。
下面的大多数示例都假设你将使用 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)是一个很是流行的问答基准数据集。BERT(在发布时)在 SQuAD 上得到了最好的结果,几乎没有进行特定任务的网络架构修改或数据加强。不过,它确实须要半复杂数据预处理和后处理来处理 SQUAD 上下文段落的可变长度性质,以及用于 SQuAD 训练的字符级答案注解。run_squad.py 实现并记录了处理过程。
要在 SQuAD 上运行训练,首先须要下载这个数据集。SQuAD 网站(rajpurkar.github.io/SQuAD-explo… v1.1 数据集的连接,一些必要的文件能够在这里找到:
train-v1.1.json(rajpurkar.github.io/SQuAD-explo…
dev-v1.1.json(rajpurkar.github.io/SQuAD-explo…
将这些下载到某个目录(变量能够设置为 $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/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}
复制代码
在某些状况下,相比对整个预训练模型进行端到端的微调,得到预训练的上下文嵌入可能会更好,这些嵌入是预训练模型隐藏层生成的每一个输入标记的固定上下文表示。
例如,咱们可能会这样使用 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)。
若是你须要对齐原始单词和标记化单词,请参阅下面的标记化部分。
对于句子(或句子对)任务,标记化是很是简单的。只须要遵循 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”,就会出现不匹配。若是有可能,你应该预处理数据,将这些数据转换回原始文本,若是不行,这种不匹配可能也不是什么大问题。
咱们正在尝试在任意文本语料库上进行“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 账户,而且在将来可能没法再使用。
英文原文: