「如何实现流动式软件发布」线上课堂开课啦,快来报名参与课堂抽奖吧~ 了解详情
写点什么

谷歌最强 NLP 预训练模型 BERT 正式开源

2018 年 11 月 09 日

谷歌最强NLP预训练模型BERT正式开源

AI 前线导读: 近日,谷歌 AI 的一篇 NLP 论文引起了社区极大的关注与讨论,被认为是 NLP 领域的极大突破。谷歌大脑研究科学家 Thang Luong Twitter 表示,这项研究开启了 NLP 领域的新时代。该论文介绍了一种新的语言表征模型 BERT——来自 Transformer 的双向编码器表征。BERT 是首个在大批句子层面和 token 层面任务中取得当前最优性能的基于微调的表征模型,其性能超越许多使用任务特定架构的系统,刷新了 11 项 NLP 任务的当前最优性能记录。

目前 BERT 模型已经正式开源!这意味着所有 NLP 从业者都可以试用这个强大的 NLP 预训练模型并结合到自己的工作中。


首先附上开源代码传送门:https://github.com/google-research/bert


原论文链接:https://arxiv.org/abs/1810.04805


该开源项目亮点如下:


  1. 独立的 TensorFlow 代码,有简单的 API 且无依赖关系。

  2. 链接到论文中的 BERT-Base 和 BERT-Large 预训练版本。

  3. 一键复制论文中的 MultiNLI 和 SQuAD v1.1 结果。

  4. 包含预训练数据生成和训练的代码。

  5. 支持跳转到 Colab,可以用免费的云端 TPU 运行 BERT。

  6. 最后附上了常见问题解答。


几个常见问题的解答:


  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
复制代码


句子(和句子对)分类任务

在运行这个示例之前,你必须通过这个脚本(https://gist.github.com/W4ngatang/60c2bdb54d156a41194446737ce03e2e)下载 GLUE 数据(https://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-12export 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 网站(https://rajpurkar.github.io/SQuAD-explorer/)不再提供 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.py $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}
复制代码


使用 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 中的示例代码即可。句子级任务的基本流程:


  1. 实例化 tokenizer = tokenization.FullTokenizer;

  2. 使用 tokens = tokenizer.tokenize(raw_text)对原始文本进行标记;

  3. 截断到最大序列长度(最多可以使用 512,但处于内存和速度方面的考虑,最好使用短一点的);

  4. 在正确的位置添加[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   houseLabels: NNP  NNP      POS NN
复制代码


标记化输出如下所示:


Tokens: john johan ##son ' s house
复制代码


如果你有一个带有单词级注解的预标记表示,你可以单独标记每个输入单词,并对齐原始单词和标记化单词:


### Inputorig_tokens = ["John", "Johanson", "'s",  "house"]labels      = ["NNP",  "NNP",      "POS", "NN"]
### Outputbert_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,建议下载最新的转储(https://dumps.wikimedia.org/enwiki/latest/enwiki-latest-pages-articles.xml.bz2),使用 WikiExtractor.py 提取文本,然后进行必要的清理将其转换为纯文本。


可惜的是,收集 BookCorpus 的研究人员不再提供公开下载。 Guttenberg 数据集(https://web.eecs.umich.edu/~lahiri/gutenberg_dataset.html)是公开可用的一个较小(2 亿个单词)的旧书集合。


Common Crawl(http://commoncrawl.org/)是另一个非常大的文本集合,但你可能需要进行预处理和清理才能提取可用的语料库以进行 BERT 预训练。


在 Colab 中使用 BERT

如果你想将 BERT 与 Colab 一起使用,可以从“BERT FineTuning with Cloud TPU”(https://colab.sandbox.google.com/github/tensorflow/tpu/blob/master/tools/colab/bert_finetuning_with_cloud_tpus.ipynb)开始。在撰写本文时(2018 年 10 月 31 日),Colab 用户可以完全免费访问一个 Cloud TPU。每个用户可以使用一个,可用性有限,需要一个带有存储空间的 Google Cloud Platform 帐户,并且在未来可能无法再使用。


英文原文:


https://github.com/google-research/bert


2018 年 11 月 09 日 16:587521
用户头像

发布了 731 篇内容, 共 378.5 次阅读, 收获喜欢 1886 次。

关注

评论

发布
暂无评论
发现更多内容

十一周作业

走走,停停……

智能警务平台搭建,公安一体化警务实战解决方案

t13823115967

智慧公安 智慧警务系统开发

mybatis分页插件如何实现?

田维常

mybatis

《Tensorflow:实战Google深度学习框架》.pdf

田维常

线程池的7种创建方式,强烈推荐你用它...

王磊

Java

深入浅出 Go - sync/atomic 源码分析

哈希说

golang

英特尔正式发布全新一代内存和存储产品

新闻科技资讯

Android uni-app 封装原生插件

anyRTC开发者

uni-app android 音视频 跨平台 聊天室

实践大于一切!Alibaba最新MySQL性能优化+高可用架构全彩版PDF

Java架构追梦

Java MySQL 学习 架构 面试

未雨绸缪,数据保护之NBU介质备份

华为云开发者社区

安全 数据 保护

六个步骤,从零开始教你搭建基于WordPress的个人博客

华为云开发者社区

网站 WordPress 搭建

记一次网络请求连接超时的事故

AI乔治

Java 架构 HTTP

亿级流量背后战场,京东11.11大促全方位技术揭秘

京东科技开发者

云计算

智慧社区服务平台开发,平安小区建设

t13823115967

智慧城市 平安小区

JVM的艺术—JAVA内存模型

云流

Java jdk JVM

IT民工闲话·点一盏灯

IT民工大叔

成长 IT 传承

警惕”被讲故事“ | 读《叙事改变人生》

邓瑞恒Ryan

读书笔记 哲学 创业心态 社会学 世界观

刚刚,阿里云知行动手实验室正式开放公测了

阿里巴巴云原生

阿里云 开发者 云原生 k8s dubbo

盘点 2020 | 一枚程序员的跑步之路

Simon

程序员 跑步 锻炼 盘点2020

微信昵称可以加雪花了,个性又好看

程序员生活志

工具 微信名 雪花

我是如何拿到蚂蚁金服offer?看完2020年Java研发岗复盘经验总结,是时候让面试官懵逼了

比伯

Java 编程 架构 面试 程序人生

iOS面试基础知识 (二)

iOSer

ios 面试题 iOS面试

年终盘点 | 七年零故障支撑 双11 的消息中间件 RocketMQ,怎么做到的?

阿里巴巴云原生

阿里云 开源 云原生 中间件 消息队列

为了SpringBoot提交Tomcat执行,我总结了这么多

996小迁

Java tomcat 架构 springboot

需求管理的6个最佳方法

PingCode

项目管理 程序人生 敏捷开发

最简单的 K8S 部署文件编写姿势,没有之一!

万俊峰Kevin

golang Kubernetes

实战排查|为什么遮挡推流摄像头,会导致播放绿屏?

阿里云视频云

音视频 WebRTC RTC bug RTMP

智能合约DAPP软件系统开发

开發I852946OIIO

系统开发

原来只想简单看一下String源码,没想到整理了这么多知识点

小Q

Java 学习 编程 面试 string

3. 搞定收工,PropertyEditor就到这

YourBatman

Spring Framework 类型转换 PropertyEditor

tron波场智能合约系统软件开发|tron波场智能合约APP开发

开發I852946OIIO

系统开发

谷歌最强NLP预训练模型BERT正式开源-InfoQ