
BERT是Google的神经网络,通过大量的最新结果显示了许多任务。 使用BERT,您可以创建用于处理自然语言的AI程序:以任何形式回答问题,创建聊天机器人,自动翻译器,分析文本等。
Google已经发布了经过预训练的BERT模型,但是像机器学习一样,它们经常缺少文档。 因此,在本教程中,我们将学习如何在本地计算机以及Google Colab的免费服务器GPU上运行BERT神经网络。
为什么有必要
要将文本提交到神经网络的输入,您需要以某种方式将其呈现为数字形式。 最简单的方法是逐个字母地对每个神经网络入口应用一个字母。 然后,每个字母将使用从0到32的数字进行编码(加上某种用于标点符号的边距)。 这就是所谓的字符级。
但是,如果我们提出建议的不是一个字母,而是立即向神经网络的每个输入提交一个完整的单词(或至少一个音节),则会获得更好的结果。 它已经是单词级的了。 最简单的选择是用所有现有单词编译一个词典,并将该词典中单词的数量输入网络。 例如,如果单词“ dog”在词典中的1678位,则输入数字1678作为该单词的神经网络输入。
但是只有一种自然语言,即“狗”一词,一个人立即会出现许多关联:“蓬松”,“邪恶”,“一个人的朋友”。 是否有可能在神经网络的演示文稿中以某种方式编码我们思维的这一特征? 事实证明您可以。 为此,对单词编号重新排序就足够了,以便使含义紧密的单词靠在一起。 例如,以“ dog”为数字1678,以“蓬松”为数字1680。对于“茶壶”,数字为9000。如您所见,数字1678和1680比9000彼此更接近。
实际上,每个单词分配的不是一个数字,而是几个-一个向量,例如32个数字。 距离的测量方法是这些向量指向的点之间的距离在相应维度的空间中(对于32位长的向量,这是一个具有32个维度或32个轴的空间)。 这使您可以一次比较一个单词和几个含义相近的单词(取决于要计数的轴)。 此外,可以对向量进行算术运算。 一个经典的例子:如果从表示单词“ king”的向量中减去向量“ man”,并为单词“ woman”加上向量,则您将得到某个结果向量。 他将奇迹般地对应于“女王”一词。 确实,“国王是男人+女人=女王”。 魔术! 这不是一个抽象的例子,但它确实发生了 。 考虑到神经网络非常适合对其输入进行数学转换,因此显然可以提供这种方法的高效率。
这种方法称为嵌入。 所有机器学习包(TensorFlow,PyTorch)都允许神经网络的第一层放置一个特殊的嵌入层,该层会自动执行此操作。 也就是说,在神经网络的输入处,我们在字典中给出了通常的单词编号,并且自学习的嵌入层将每个单词转换为具有指定长度(例如32个数字)的向量。
但是他们很快意识到,在某些庞大的文本语料库(例如整个Wikipedia)上预先训练这样的单词向量表示,并在特定的神经网络中使用现成的单词向量,而不是每次都对其进行训练,将更有益处。
有几种方法可以将单词表示为向量;它们逐渐发展:word2vec,GloVe,Elmo。
在2018年夏天, OpenAI注意到 ,如果您在大量文本上对基于Transformer架构的神经网络进行预训练,那么它会出乎意料地大幅度显示出在许多不同类型的自然语言处理任务中的出色结果。 实际上,这种神经网络在其输出处为单词甚至整个短语创建矢量表示。 并且,通过在这种语言模型上悬挂一小块额外的一对神经元层,您可以为任何任务训练该神经网络。
Google的BERT是OpenAI的高级GPA网络(双向而非单向等),也是基于Transformer架构的。 目前,BERT在几乎所有流行的NLP基准测试中都是最先进的。
他们是如何做到的
BERT背后的想法非常简单:让我们用短语来馈入神经网络,在该短语中,我们用[MASK]替换15%的单词,然后训练神经网络预测这些被屏蔽的单词。
例如,如果将短语“我来过[MASK]并购买了[MASK]”发送到神经网络的输入,则输出中应显示单词“ store”和“ milk”。 这是来自官方BERT页的简化示例;在较长的句子中,可能的选项范围变小,并且神经网络的响应是明确的。
为了使神经网络学习理解不同句子之间的关系,我们将另外训练它以预测第二个短语是否是第一个短语的逻辑延续。 还是与第一个无关的随机短语。
因此,用了两句话:“我去了商店。” 和“在那里买牛奶”。神经网络应该回答这是合乎逻辑的。 如果第二个短语是“ Crucian sky Pluto”,那么我必须回答这个提议与第一个提议无关。 我们将在下面介绍这两种BERT模式。
这样,在16 TPU上训练了Wikipedia和书籍集BookCorpus的文本上的神经网络4天后,我们得到了BERT。
安装与设定
注意 :在本节中,我们将在本地计算机上启动并使用BERT。 要在本地GPU上运行此神经网络,您将需要具有4 GB或更高显存的NVidia GTX 970。 如果您只想在浏览器中运行BERT(为此甚至不需要在计算机上安装GPU),请转到Google Colab部分。
如果尚未安装TensorFlow,请先按照https://www.tensorflow.org/install中的说明进行安装。 要支持GPU,必须首先安装CUDA Toolkit 9.0,然后安装cuDNN SDK 7.2,然后再安装具有GPU支持的TensorFlow:
pip install tensorflow-gpu
基本上,这足以运行BERT。 但是没有这样的指令,您可以通过在run_classifier.py文件中整理源代码来自己编写指令 (Machine Learning中的通常情况是您必须使用源代码而不是文档)。 但是我们会更轻松地使用Keras BERT外壳程序(它对以后的网络微调也很有用,因为它提供了方便的Keras接口)。
为此,请安装Keras本身:
pip install keras
在Keras BERT之后:
pip install keras-bert
我们还将需要原始github BERT中的tokenization.py文件。 单击“原始”按钮,然后使用将来的脚本将其保存到文件夹中,或者下载整个存储库并从那里获取文件,或者使用以下代码https://github.com/blade1780/bert从存储库中获取副本。
现在是时候下载预训练的神经网络了。 BERT有多个选项,所有选项均在github.com/google-research/bert官方页面上列出。 我们将采用适用于104种语言的通用多语言“基于BERT的多语言案例”。 下载multi_cased_L-12_H-768_A-12.zip文件(632 Mb),并使用将来的脚本将其解压缩到该文件夹中。
一切准备就绪,创建BERT.py文件,然后会有一些代码。
导入所需的库并设置路径
由于我们必须将文本的普通行转换为特殊形式的标记,因此我们将为此创建一个特殊对象。 请注意do_lower_case = False,因为我们使用的是Cased BERT模型,该模型区分大小写。
tokenizer = tokenization.FullTokenizer(vocab_file=vocab_path, do_lower_case=False)
加载模型
model = load_trained_model_from_checkpoint(config_path, checkpoint_path, training=True) model.summary()
BERT可以以两种模式工作:猜测短语中遗漏的单词,或者猜测第二个短语在第一个短语之后是否合乎逻辑。 我们将同时做这两种选择。
对于第一种模式,您需要以以下格式提交词组:
[CLS] [MASK] [MASK]. [SEP]
神经网络应该返回一个完整的句子,并用单词代替面具:“我来到商店买了牛奶。”
对于第二种模式,必须将由分隔符分隔的两个短语都馈送到神经网络的输入:
[CLS] . [SEP] . [SEP]
神经网络必须回答第二个短语是否是第一个短语的逻辑延续。 还是一个与第一个无关的随机短语。
为了使BERT正常工作,您需要准备三个向量,每个向量的长度为512个数字:token_input,seg_input和mask_input。
Token_input将存储使用tokenizer转换为令牌的源代码。 字典中以索引形式出现的短语将在此向量的开头,其余部分将填充零。
在mask_input中,我们必须为[MASK]遮罩所在的所有位置都放置1,并用零填充其余位置。
在seg_input中,我们必须将第一个短语(包括开始的CLS和SEP分隔符)表示为0,将第二个短语(包括结束的SEP)表示为1,并用零填充其余部分到向量的末尾。
BERT不使用完整单词的字典,而是使用最常见的音节。 虽然它也有完整的文字。 您可以在下载的神经网络中打开vocab.txt文件,并查看神经网络在其输入处使用的词。 整个词都像法国。 但是大多数俄语单词需要分解成音节。 因此,应将“来”一词分解为“有”和“ ##去”。 为了帮助将常规的文本行转换为BERT所需的格式,我们使用了tokenization.py模块。
模式1:预测短语中由令牌[MASK]关闭的单词
输入短语输入到神经网络的输入
sentence = ' [MASK] [MASK].' print(sentence)
将其转换为令牌。 问题是,尽管vocab.txt在字典中有标记,但令牌生成器无法处理[CLS]和[MASK]等服务标记。 因此,我们将不得不使用[MASK]标记手动中断我们的行,并从中选择一段纯文本,以便使用令牌生成器将其转换为BERT令牌。 还要在短语的开头添加[CLS],在短语的末尾添加[SEP]。
sentence = sentence.replace(' [MASK] ','[MASK]'); sentence = sentence.replace('[MASK] ','[MASK]'); sentence = sentence.replace(' [MASK]','[MASK]')
令牌现在具有可以保证在字典中转换为索引的令牌。 让我们做吧:
token_input = tokenizer.convert_tokens_to_ids(tokens)
现在,在token_input中,需要将一系列数字(vocab.txt词典中的单词数字)馈送到神经网络的输入中。 仅保留将该向量扩展到512个元素的长度。 Python构造[0] * length创建一个长度为length的数组,并用零填充。 只需将其添加到我们的令牌中即可,该令牌在python中将两个数组合并为一个。
token_input = token_input + [0] * (512 - len(token_input))
现在创建一个长度为512的蒙版mask,在各处放置1,在令牌中出现数字103(与vocab.txt词典中的标记[MASK]相对应),并用0填充其余部分:
mask_input = [0]*512 for i in range(len(mask_input)): if token_input[i] == 103: mask_input[i] = 1
对于第一种BERT操作模式,seg_input必须完全用零填充:
seg_input = [0]*512
最后一步,您需要将python数组转换为形状为(1,512)的numpy数组,为此,我们将它们放在子数组[]中:
token_input = np.asarray([token_input]) mask_input = np.asarray([mask_input]) seg_input = np.asarray([seg_input])
好,做完了 现在运行神经网络的预测!
predicts = model.predict([token_input, seg_input, mask_input])[0] predicts = np.argmax(predicts, axis=-1) predicts = predicts[0][:len(tokens)]
现在将结果从令牌格式化回用空格分隔的字符串
out = []
并输出结果:
print('Result:', out)
在我们的示例中,短语“我来到[MASK]并购买了[MASK]”。 神经网络产生结果“房子”和“它”:“我来到房子并买了它。” 好吧,第一次还不错。 买房子绝对比喝牛奶好。
其他示例(我没有给出失败的示例,比成功的示例更多。在大多数情况下,网络给出的答案是空的):地球是来自太阳的第三个[MASK]
结果:星级
最好的三明治加黄油
结果:相遇
[面具]午饭应该睡觉之后
结果:
摆脱[面具]
结果:##哦-是某种诅咒吗? )
[面具]从门
结果:查看
用[MASK]锤子和钉子可以做橱柜
结果:帮助
如果明天不是? 例如,今天不是[MASK]!
结果:将
您怎么会厌倦忽略[MASK]?
结果:她
有日常的逻辑,有女性的逻辑,但对男性却一无所知[MASK]
结果:哲学
在女性中,到30岁时,就形成了适合任何[面具]的王子图像。
结果:人
白雪公主和七个小矮人以多数票投票赞成[面具],一票反对。
结果:村-首字母正确
以10分制为您的乏味评分:[MASK]分
结果:10
您的[MASK],[MASK]和[MASK]!
结果:爱我我-不,BERT,我一点都不想要
您可以输入英语短语(以及104种语言中的任何一种,其列表在此处 )
[面具]必须继续!
结果:我
模式2:检查两个词组的一致性
我们设置了两个连续的短语,这些短语将被馈送到神经网络的输入中
sentence_1 = ' .' sentence_2 = ' .' print(sentence_1, '->', sentence_2)
我们将以[CLS]短语_1 [SEP]短语_2 [SEP]格式创建令牌,并使用令牌生成器将纯文本转换为令牌:
tokens_sen_1 = tokenizer.tokenize(sentence_1) tokens_sen_2 = tokenizer.tokenize(sentence_2) tokens = ['[CLS]'] + tokens_sen_1 + ['[SEP]'] + tokens_sen_2 + ['[SEP]']
我们将字符串标记转换为数字索引(vocab.txt词典中的单词编号),并将向量扩展到512:
token_input = tokenizer.convert_tokens_to_ids(tokens) token_input = token_input + [0] * (512 - len(token_input))
在这种情况下,单词mask完全用零填充
mask_input = [0] * 512
但是,建议掩码应在第二个短语(包括最终的SEP)下填写单位,其他所有内容都用零填充:
seg_input = [0]*512 len_1 = len(tokens_sen_1) + 2
我们通过神经网络传递短语(这次的结果是在[1]中,而不是在[0]中,如上所示)
predicts = model.predict([token_input, seg_input, mask_input])[1]
然后我们得出第二个短语是正常单词而不是随机单词集的概率
print('Sentence is okey:', int(round(predicts[0][0]*100)), '%')
分为两个短语:
我来这家商店。 ->买了牛奶。
神经网络响应:
句子不错:99%
如果第二个短语是“ Crucian sky Pluto”,那么答案将是:
句子不错:4%
谷歌合作
Google免费提供了具有12 Gb视频内存的Tesla K80服务器GPU(现已提供TPU,但其配置要复杂一些)。 Colab的所有代码都应设计为Jupyter Notebook。 要在浏览器中启动BERT,只需打开链接
http://colab.research.google.com/github/blade1780/bert/blob/master/BERT.ipynb
从“ 运行时”菜单中,选择“全部运行” ,以便首次启动所有单元格时,将下载模型并连接必要的库。 如有必要,同意重置所有运行系统。
如果出了什么问题...确保在运行时->更改运行时类型菜单中选择了GPU和Python 3
如果连接按钮未处于活动状态,请单击它以成为已连接。
现在更改输入行句子 , 句子_1和句子_2 ,然后单击左侧的“播放”图标以仅启动当前单元格。 不再需要运行整个笔记本。
您甚至可以通过智能手机在Google Colab上运行BERT,但是如果无法打开BERT,则可能需要在浏览器设置中启用“完整版”复选框。
接下来是什么?
要为特定任务训练BERT,您需要在其顶部添加一层或两层简单的前馈网络,并且仅在不接触主BERT网络的情况下对其进行训练。 这可以在裸TensorFlow上或通过Keras BERT外壳完成。 针对特定领域的此类额外训练非常迅速,并且与卷积网络中的微调完全相似。 因此,对于SQuAD任务,您可以在30分钟内在一个TPU上训练神经网络(相比之下,在16个TPU上训练4天则可以训练BERT本身)。
为此,您将必须研究最后一层如何在BERT中表示,以及具有合适的数据集。 在正式的BERT页面https://github.com/google-research/bert上 ,有几个用于不同任务的示例,以及有关如何开始对云TPU进行再培训的说明。 其他所有内容都必须在文件run_classifier.py和extract_features.py中 查看 。
聚苯乙烯
此处提供的Google Colab的代码和jupyter笔记本托管在资源库中 。
不应期待奇迹。 不要指望BERT说话像一个人。 最新的技术水平并不意味着NLP的进展已达到可接受的水平。 这仅表示BERT比以前的型号更好,后者甚至更差。 强大的对话式AI仍然遥不可及。 此外,BERT主要是一种语言模型,而不是现成的聊天机器人,因此只有在对特定任务进行重新训练后,它才会显示出良好的效果。