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}
}