面向自然语言处理的迁移学习(二)(3)https://developer.aliyun.com/article/1522412
7.2.3 聊天机器人的应用
直觉上应该能够无需对此应用进行重大修改即可采用 GPT。幸运的是,微软的人员已经通过模型 DialoGPT 完成了这一点,该模型最近也被包含在 transformers 库中。它的架构与 GPT 相同,只是增加了特殊标记,以指示对话中参与者的回合结束。在看到这样的标记后,我们可以将参与者的新贡献添加到启动上下文文本中,并通过直接应用 GPT 来生成聊天机器人的响应,迭代重复这个过程。自然地,预训练的 GPT 模型在会话文本上进行了微调,以确保响应是适当的。作者们使用 Reddit 主题进行了微调。
让我们继续构建一个聊天机器人吧!在这种情况下,我们不会使用管道,因为在撰写本文时,该模型尚未通过该 API 公开。这使我们能够对比调用这些模型进行推理的不同方法,这对你来说是一个有用的练习。
首先要做的事情是通过以下命令加载预训练模型和分词器:
from transformers import GPT2LMHeadModel, GPT2Tokenizer ❶ import torch ❷ tokenizer = GPT2Tokenizer.from_pretrained("microsoft/DialoGPT-medium") model = GPT2LMHeadModel.from_pretrained("microsoft/DialoGPT-medium")
❶ 请注意,DialoGPT 模型使用 GPT-2 类。
❷ 我们在这里使用 Torch 而不是 TensorFlow,因为 transformers 文档中默认选择的是 Torch 平台。
此处值得强调几点。首先,请注意我们使用的是 GPT-2 模型类,这与我们先前讨论的 DialoGPT 作为该架构的直接应用是一致的。另外,请注意我们可以与这些 GPT 特定的模型类交换使用AutoModelWithLMHead
和AutoTokenizer
类。这些实用程序类会检测用于加载指定模型的最佳类别,例如,在这种情况下,它们将检测到最佳要使用的类别为GPT2LMHeadModel
和GPT2Tokenizer
。浏览 transformers 库文档时,你可能会遇到这些实用程序类,了解它们的存在对你的代码更一般化是有好处的。最后请注意,这里使用的是 GPT 的“LMHead”版本。这意味着从普通 GPT 得到的输出将通过一个线性层和一个归一化层,然后转换成一个维度等于词汇表大小的概率向量。最大值对应于模型正确训练的情况下下一个最有可能的令牌。
与我们加载的预训练 DialoGPT 模型进行对话的代码如列表 7.1 所示。我们首先指定最多五个回应的最大数量。然后,我们编码用户在每个轮次的对话,将对话添加到聊天历史记录中,并将其传递给加载的预训练 DialoGPT 模型,以生成下一个响应。
列表 7.1 与预训练 DialoGPT 模型进行对话,最多五个聊天机器人响应
conversation_length = 5 ❶ for step in range(conversation_length): new_user_inputs_ids = tokenizer.encode(input("User: ") + tokenizer.eos_token, return_tensors='pt') ❷ bot_input_ids = torch.cat([chat_history_ids, new_user_inputs_ids], dim=1) ❸ if step > 0 else new_user_inputs_ids chat_history_ids = model.generate(bot_input_ids, max_length=1000, pad_token_id=tokenizer.eos_token_id) ❹ print("DialoGPT: {}".format(tokenizer.decode(chat_history_ids[:, bot_input_ids.shape[-1]:][0], skip_special_tokens=True))) ❺
❶ 五行聊天
❷ 编码新用户输入,添加一个句子结束标记,并返回张量
❸ 将新输入添加到聊天历史记录中
❹ 使用聊天历史记录作为上下文生成最多 max_length 令牌的响应
❺ 显示响应
一个人可能整天都和这个机器人玩耍!我们很开心地向它提问各种问题,并以各种方式提示它。我们在这里包括了其中一个交流以供您娱乐:
User: Hi there. DialogGPT: Hi there. User: How are you today? DialogGPT: I'm good, how are you? User: Good! How much money do you have? DialogGPT: I have about 100k. User: What will you spend it on? DialogGPT: I'm not sure, I'm not sure what I want to spend it on. User: Make a decision, life is short. DialogGPT: I'm going to go with a lot of things.
接下来这短暂对话的另一端很可能是一个人,不是吗?那是否意味着它通过了图灵测试?并非如此,下面的交流说明了这一点:
User: Hi robot. DialogGPT: Hello, human. User: Huh? DialogGPT: I'm a bot. User: OK, what is your name? DialogGPT: Robot. I'm a robot. User: All right then. DialogGPT: Robot. I'm a robot. User: Say something else. DialogGPT: Robot. I'm a robot.
当你增加允许的对话轮次数量时,你会发现机器人会陷入重复的与话题无关的回复中。这类似于 GPT 开放式文本生成随着生成文本长度的增加变得更加荒谬。改善这一点的一个简单方法是保持固定的局部上下文大小,其中模型只受到该上下文内的对话历史的提示。当然,这意味着对话不总是考虑整个对话的上下文——这是必须对任何给定应用进行实验探索的一个权衡。
想象一下 GPT-3 在这些问题上的表现会有多好,是不是令人兴奋?在本书的最后一章中,我们将简要讨论更多关于 GPT-3 的细节,并介绍一个最近推出的更小但同样值得关注的开源替代品:EleutherAI 的 GPT-Neo。它已经可以在 transformers 库中使用,并且可以通过将 model
字符串设置为 EleutherAI 提供的模型名称之一来直接使用。[¹³]我们还附上了一个伴随笔记本,在其中展示了它在本章练习中的应用。经过检查,你应该会发现它的性能更好,但自然也会有显著更高的成本(最大模型的权重超过 10 GB!)。
在下一章中,我们将讨论变压器家族中可能最重要的成员——BERT。
总结
- 变压器架构使用自注意力机制来构建文本的双向上下文。这使得它成为了近期在自然语言处理中占主导地位的语言模型。
- 变压器允许对序列中的令牌进行独立处理。这比处理顺序的双向 LSTM 实现了更大的并行性。
- 变压器是翻译应用的不错选择。
- 在训练过程中,生成预训练变压器使用因果建模目标。这使得它成为文本生成的首选模型,例如聊天机器人应用。
- A. Vaswani 等人,“Attention Is All You Need”,NeurIPS(2017)。
- A. Radford 等人,“通过生成预训练来改善语言理解”,arXiv(2018)。
- M. E. Peters 等人,“BERT:用于语言理解的深度双向变压器的预训练”,NAACL-HLT(2019)。
github.com/google-research/bert/blob/master/multilingual.md
- A. Radford 等人,“通过生成预训练来改善语言理解”,arXiv(2018)。
- A. Vaswani 等人,“Attention Is All You Need”,NeurIPS(2017)。
github.com/jessevig/bertviz
huggingface.co/Helsinki-NLP
opus.nlpl.eu/JW300.php
marian-nmt.github.io/
- A. Radford 等人,“通过生成式预训练提高语言理解能力”,arXiv(2018)。
- Y. Zhang 等人,“DialoGPT:面向对话回应生成的大规模生成式预训练”,arXiv(2019)。
huggingface.co/EleutherAI
第八章:使用 BERT 和多语言 BERT 的 NLP 深度迁移学习
本章包括
- 使用预训练的双向编码器表示来自变换器(BERT)架构来执行一些有趣的任务
- 使用 BERT 架构进行跨语言迁移学习
在这一章和上一章,我们的目标是介绍一些代表性的深度迁移学习建模架构,这些架构依赖于最近流行的神经架构——transformer¹——来进行关键功能的自然语言处理(NLP)。这可以说是当今 NLP 中最重要的架构。具体来说,我们的目标是研究一些建模框架,例如生成式预训练变换器(GPT),² 双向编码器表示来自变换器(BERT),³ 和多语言 BERT(mBERT)。⁴ 这些方法使用的神经网络的参数比我们之前介绍的深度卷积和循环神经网络模型更多。尽管它们体积更大,但由于它们在并行计算架构上的比较效率更高,它们的流行度急剧上升。这使得实际上可以开发出更大更复杂的模型。为了使内容更易理解,我们将这些模型的覆盖分成两章/部分:我们在上一章中介绍了变换器和 GPT 神经网络架构,而在接下来的这章中,我们将专注于 BERT 和 mBERT。
作为提醒,BERT 是基于 transformer 的模型,我们在第三章和第七章中简要介绍过。它是使用masked modeling objective进行训练来填补空白。此外,它还经过了“下一个句子预测”任务的训练,以确定给定句子是否是目标句子后的合理跟随句子。mBERT,即“多语言 BERT”,实际上是针对 100 多种语言同时预训练的 BERT。自然地,这个模型特别适用于跨语言迁移学习。我们将展示多语言预训练权重检查点如何促进为初始未包含在多语言训练语料库中的语言创建 BERT 嵌入。BERT 和 mBERT 均由 Google 创建。
本章的第一节深入探讨了 BERT,并将其应用于重要的问答应用作为一个独立的示例。该章节通过实验展示了预训练知识从 mBERT 预训练权重转移到新语言的 BERT 嵌入的迁移。这种新语言最初并未包含在用于生成预训练 mBERT 权重的多语料库中。在这种情况下,我们使用加纳语 Twi 作为示例语言。
让我们在下一节继续分析 BERT。
8.1 双向编码器表示来自变换器(BERT)
在本节中,我们介绍了可能是最受欢迎和最具影响力的基于 Transformer 的神经网络架构,用于自然语言处理的迁移学习——双向编码器表示的 Transformer(BERT)模型,正如我们之前提到的,它也是以流行的Sesame Street角色命名的,向 ELMo 开创的潮流致敬。回想一下 ELMo 本质上就是变压器做的事情,但是使用的是循环神经网络。我们在第一章首次遇到了这两种模型,在我们对自然语言处理迁移学习历史的概述中。我们还在第三章中使用了它们进行了一对分类问题,使用了 TensorFlow Hub 和 Keras。如果您不记得这些练习,可能有必要在继续本节之前进行复习。结合上一章,这些模型的预览使您对了解模型的更详细功能处于一个很好的位置,这是本节的目标。
BERT 是早期预训练语言模型,开发于 ELMo 和 GPT 之后,但在普通语言理解评估(GLUE)数据集的大多数任务中表现出色,因为它是双向训练的。我们在第六章讨论了 ELMo 如何将从左到右和从右到左的 LSTM 组合起来实现双向上下文。在上一章中,我们还讨论了 GPT 模型的掩码自注意力如何通过堆叠变压器解码器更适合因果文本生成。与这些模型不同,BERT 通过堆叠变压器编码器而不是解码器,为每个输入标记同时实现双向上下文。回顾我们在第 7.2 节中对 BERT 每个层中的自注意力的讨论,每个标记的计算都考虑了两个方向上的每个其他标记。而 ELMo 通过将两个方向放在一起实现了双向性,GPT 是一种因果单向模型。BERT 每一层的同时双向性似乎给了它更深层次的语言上下文感。
BERT 是通过掩码语言建模(MLM)填空预测目标进行训练的。在训练文本中,标记被随机掩码,模型的任务是预测掩码的标记。为了说明,再次考虑我们示例句子的略微修改版本,“他不想在手机上谈论细胞,他认为这个话题很无聊。” 为了使用 MLM,我们可以将其转换为“他不想在手机上谈论细胞,一个[MASK],他认为这个话题很无聊。” 这里的[MASK]是一个特殊标记,指示哪些词已被省略。然后,我们要求模型根据其在此之前观察到的所有文本来预测省略的词。经过训练的模型可能会预测掩码词 40%的时间是“conversation”,35%的时间是“subject”,其余 25%的时间是“topic”。在训练期间重复执行这个过程,建立了模型对英语语言的知识。
另外,BERT 的训练还使用了下一句预测(NSP)目标。在这里,训练文本中的一些句子被随机替换为其他句子,并要求模型预测句子 B 是否是句子 A 的合理续篇。为了说明,让我们将我们的示例句子分成两个句子:“他不想谈论手机上的细胞。他认为这个话题很无聊。” 然后我们可能删除第二个句子,并用略微随机的句子替换它,“足球是一项有趣的运动。” 一个经过适当训练的模型需要能够检测前者作为潜在的合理完成,而将后者视为不合理的。我们通过具体的编码练习示例来讨论 MLM 和 NSP 目标,以帮助您理解这些概念。
在下一小节中,我们简要描述了 BERT 架构的关键方面。我们接着介绍了将 transformers 库中的管道 API 概念应用于使用预训练 BERT 模型进行问答任务。我们随后通过示例执行填空 MLM 任务和 NSP 任务。对于 NSP 任务,我们直接使用 transformers API 来帮助您熟悉它。与上一章节类似,我们在这里没有明确地在更具体的目标数据上对预训练的 BERT 模型进行调优。然而,在本章的最后一节中,我们将在单语 Twi 数据上微调多语言 BERT 模型。
8.1.1 模型架构
您可能还记得第 7.1.1 节中我们可视化了 BERT 自注意力时,BERT 本质上是图 7.1 中原始编码器-解码器变换器架构的一组叠加编码器。BERT 模型架构如图 8.1 所示。
图 8.1 BERT 架构的高级表示,显示堆叠的编码器、输入嵌入和位置编码。顶部的输出在训练期间用于下一句预测和填空遮蔽语言建模目标。
正如我们在介绍中讨论的,并且如图所示,在训练期间,我们使用下一句预测(NSP)和遮蔽语言建模(MSM)目标。BERT 最初以两种风味呈现,BASE 和 LARGE。如图 8.1 所示,BASE 堆叠了 12 个编码器,而 LARGE 堆叠了 24 个编码器。与之前一样——在 GPT 和原始 Transformer 中——通过输入嵌入将输入转换为向量,并向它们添加位置编码,以给出输入序列中每个标记的位置感。为了考虑下一句预测任务,其中输入是句子 A 和 B 的一对,添加了额外的段编码步骤。段嵌入指示给定标记属于哪个句子,并添加到输入和位置编码中,以产生输入到编码器堆栈的输出。我们的示例句对的整个输入转换在图 8.2 中可视化:“他不想在手机上谈论细胞。他认为这个主题非常无聊。”
图 8.2 BERT 输入转换可视化
此时提到[CLS]
和[SEP]
特殊标记的简要说明值得一提。回想一下,[SEP]
标记分隔句子并结束它们,如前几节所讨论的。另一方面,[CLS]
特殊标记被添加到每个输入示例的开头。输入示例是 BERT 框架内部用来指代标记化的输入文本的术语,如图 8.2 所示。[CLS]
标记的最终隐藏状态用作分类任务的聚合序列表示,例如蕴涵或情感分析。[CLS]
代表“分类”。
在继续查看以下小节中使用一些这些概念的具体示例之前,请记得,在第三章中首次遇到 BERT 模型时,我们将输入首先转换为输入示例,然后转换为特殊的三元组形式。这些是输入 ID,输入掩码和段 ID。我们在这里复制了列表 3.8 以帮助你记忆,因为当时这些术语尚未被介绍。
列表 3.8(从第三章复制)将数据转换为 BERT 期望的形式,训练
def build_model(max_seq_length): ❶ in_id = tf.keras.layers.Input(shape=(max_seq_length,), name="input_ids") in_mask = tf.keras.layers.Input(shape=(max_seq_length,), name="input_masks") in_segment = tf.keras.layers.Input(shape=(max_seq_length,), name="segment_ids") bert_inputs = [in_id, in_mask, in_segment] bert_output = BertLayer(n_fine_tune_layers=0)(bert_inputs) ❷ dense = tf.keras.layers.Dense(256, activation="relu")(bert_output) pred = tf.keras.layers.Dense(1, activation="sigmoid")(dense) model = tf.keras.models.Model(inputs=bert_inputs, outputs=pred) model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"]) model.summary() return model def initialize_vars(sess): ❸ sess.run(tf.local_variables_initializer()) sess.run(tf.global_variables_initializer()) sess.run(tf.tables_initializer()) K.set_session(sess) bert_path = "https:/ /tfhub.dev/google/bert_uncased_L-12_H-768_A-12/1" tokenizer = create_tokenizer_from_hub_module(bert_path) ❹ train_examples = convert_text_to_examples(train_x, train_y) ❺ test_examples = convert_text_to_examples(test_x, test_y) # Convert to features (train_input_ids,train_input_masks,train_segment_ids,train_labels) = ❻ convert_examples_to_features(tokenizer, train_examples, ❻ max_seq_length=maxtokens) ❻ (test_input_ids,test_input_masks,test_segment_ids,test_labels) = convert_examples_to_features(tokenizer, test_examples, max_seq_length=maxtokens) model = build_model(maxtokens) ❼ initialize_vars(sess) ❽ history = model.fit([train_input_ids, train_input_masks, train_segment_ids], ❾ train_labels,validation_data=([test_input_ids, test_input_masks, test_segment_ids],test_labels), epochs=5, batch_size=32)
❶ 用于构建模型的函数
❷ 我们不重新训练任何 BERT 层,而是将预训练模型用作嵌入,并在其上重新训练一些新层。
❸ Vanilla TensorFlow 初始化调用
❹ 使用 BERT 源代码库中的函数创建兼容的分词器
❺ 使用 BERT 源代码库中的函数将数据转换为 InputExample 格式
❻ 将 InputExample 格式转换为三元 BERT 输入格式,使用 BERT 源存储库中的函数
❼ 构建模型
❽ 实例化变量
❾ 训练模型
如前一章节所述,输入 ID 只是词汇表中对应标记的整数 ID——对于 BERT 使用的 WordPiece 分词,词汇表大小为 30,000。由于变换器的输入长度是由列表 3.8 中的超参数 max_seq_length
定义的,因此需要对较短的输入进行填充,对较长的输入进行截断。输入掩码只是相同长度的二进制向量,其中 0 对应填充标记 ([PAD]
),1 对应实际输入。段 ID 与图 8.2 中描述的相同。另一方面,位置编码和输入嵌入由 TensorFlow Hub 模型在内部处理,用户无法访问。可能需要再次仔细阅读第三章才能充分理解这种比较。
尽管 TensorFlow 和 Keras 仍然是任何自然语言处理工程师工具箱中至关重要的组件——具有无与伦比的灵活性和效率——但 transformers 库无疑使这些模型对许多工程师和应用更加易于接近和使用。在接下来的小节中,我们将使用该库中的 BERT 应用于问题回答、填空和下一个句子预测等关键应用。
8.1.2 问题回答的应用
自然语言处理领域的开端以来,问题回答一直吸引着计算机科学家的想象力。它涉及让计算机在给定某些指定上下文的情况下自动回答人类提出的问题。潜在的应用场景仅受想象力限制。突出的例子包括医学诊断、事实检查和客户服务的聊天机器人。事实上,每当你在谷歌上搜索像“2010 年超级碗冠军是谁?”或“2006 年谁赢得了 FIFA 世界杯?”这样的问题时,你正在使用问题回答。
让我们更加仔细地定义问题回答。更具体地说,我们将考虑 抽取式问题回答,定义如下:给定上下文段落 p 和问题 q,问题回答的任务是产生 p 中答案所在的起始和结束整数索引。如果 p 中不存在合理的答案,系统也需要能够指示这一点。直接尝试一个简单的例子,如我们接下来使用预训练的 BERT 模型和 transformers pipelines API 做的,将帮助你更好地具体了解这一点。
我们从世界经济论坛⁵中选择了一篇有关口罩和其他封锁政策对美国 COVID-19 大流行的有效性的文章。我们选择文章摘要作为上下文段落。请注意,如果没有文章摘要可用,我们可以使用相同库中的摘要流水线快速生成一个。以下代码初始化了问答流水线和上下文。请注意,这种情况下我们使用了 BERT LARGE,它已经在斯坦福问答数据集(SQuAD)⁶上进行了微调,这是迄今为止最广泛的问答数据集。还请注意,这是 transformers 默认使用的任务,默认模型,我们不需要显式指定。但是,我们为了透明度而这样做。
from transformers import pipeline qNa= pipeline('question-answering', model= 'bert-large-cased-whole-word-masking-finetuned-squad', tokenizer='bert-large-cased-whole-word-masking-finetuned-squad') ❶ paragraph = 'A new study estimates that if the US had universally mandated masks on 1 April, there could have been nearly 40% fewer deaths by the start of June. Containment policies had a large impact on the number of COVID-19 cases and deaths, directly by reducing transmission rates and indirectly by constraining people’s behaviour. They account for roughly half the observed change in the growth rates of cases and deaths.'
❶ 这些模型通常会被默认加载,但我们明确指出以保持透明度。使用已在 SQuAD 上进行了微调的模型非常重要;否则,结果将很差。
在初始化了流水线之后,让我们首先看看是否能够通过询问文章的主题来自动提取文章的精髓。我们用以下代码来实现:
ans = qNa({'question': 'What is this article about?','context': f'{paragraph}'}) print(ans)
这产生了以下输出,我们可能会认为这是一个合理的回答:
{'score': 0.47023460869354494, 'start': 148, 'end': 168, 'answer': 'Containment policies'}
注意,0.47 相对较低的分数表明答案缺少一些上下文。类似“遏制政策对 COVID-19 的影响”可能是更好的回答,但因为我们正在进行提取式问答,而这个句子不在上下文段落中,所以这是模型能做到的最好的。低分数可以帮助标记此回答进行人工双重检查和/或改进。
为什么不问一些更多的问题?让我们看看模型是否知道文章中描述的是哪个国家,使用以下代码:
ans = qNa({'question': 'Which country is this article about?', 'context': f'{paragraph}'}) print(ans)
这产生了以下输出,正如以前的分数约为 0.8 所示,完全正确:
{'score': 0.795254447990601, 'start': 34, 'end': 36, 'answer': 'US'}
讨论的是哪种疾病?
ans = qNa({'question': 'Which disease is discussed in this article?', 'context': f'{paragraph}'}) print(ans)
输出完全正确,信心甚至比之前更高,达到了 0.98,如下所示:
{'score': 0.9761025334558902, 'start': 205, 'end': 213, 'answer': 'COVID-19'}
那时间段呢?
ans = qNa({'question': 'What time period is discussed in the article?', 'context': f'{paragraph}'}) print(ans)
与输出相关联的 0.22 的低分数表明结果质量差,因为文章中讨论了 4 月至 6 月的时间范围,但从未在连续的文本块中讨论,可以为高质量答案提取,如下所示:
{'score': 0.21781831588181433, 'start': 71, 'end': 79, 'answer': '1 April,'}
然而,仅选择一个范围的端点能力已经是一个有用的结果。这里的低分数可以提醒人工检查此结果。在自动化系统中,目标是这样的较低质量答案成为少数,总体上需要很少的人工干预。
在介绍了问答之后,在下一小节中,我们将解决 BERT 训练任务的填空和下一句预测。
8.1.3 应用于填空和下一句预测任务
我们在这一节的练习中使用了上一小节的文章。让我们立即开始编写一个用于填写空白的流程,使用以下代码:
from transformers import pipeline fill_mask = pipeline("fill-mask",model="bert-base-cased",tokenizer="bert-base-cased")
注意,在这里我们使用的是 BERT BASE 模型。这些任务对任何 BERT 模型的训练来说都是基本的,所以这是一个合理的选择,不需要特殊的微调模型。初始化适当的流程后,我们现在可以将它应用于上一小节中文章的第一句话。我们通过用适当的掩码标记[MASK]
来删除“cases”这个词,并使用以下代码向模型提供已省略的词进行预测:
fill_mask("A new study estimates that if the US had universally mandated masks on 1 April, there could have been nearly 40% fewer [MASK] by the start of June")
输出显示,最高的是“deaths”,这是一个可能合理的完成。即使剩下的建议也可以在不同的情境下起作用!
[{'sequence': '[CLS] A new study estimates that if the US had universally mandated masks on 1 April, there could have been nearly 40% fewer deaths by the start of June [SEP]', 'score': 0.19625532627105713, 'token': 6209}, {'sequence': '[CLS] A new study estimates that if the US had universally mandated masks on 1 April, there could have been nearly 40% fewer executions by the start of June [SEP]', 'score': 0.11479416489601135, 'token': 26107}, {'sequence': '[CLS] A new study estimates that if the US had universally mandated masks on 1 April, there could have been nearly 40% fewer victims by the start of June [SEP]', 'score': 0.0846652239561081, 'token': 5256}, {'sequence': '[CLS] A new study estimates that if the US had universally mandated masks on 1 April, there could have been nearly 40% fewer masks by the start of June [SEP]', 'score': 0.0419488325715065, 'token': 17944}, {'sequence': '[CLS] A new study estimates that if the US had universally mandated masks on 1 April, there could have been nearly 40% fewer arrests by the start of June [SEP]', 'score': 0.02742016687989235, 'token': 19189}]
我们鼓励你尝试从各种句子中省略各种单词,以确信这几乎总是非常有效的。在节省篇幅的情况下,我们的附带笔记本会为几个更多的句子做到这一点,但我们不在这里打印这些结果。
然后我们继续进行下一个句子预测(NSP)任务。在写作本文时,此任务尚未包含在 pipelines API 中。因此,我们将直接使用 transformers API,这也将让您更加熟悉它。我们首先需要确保已安装 transformers 3.0.0 以上的版本,因为该任务仅在该阶段的库中包含。我们使用以下代码实现这一点;在写作本文时,Kaggle 默认安装了较早的版本:
!pip install transformers==3.0.1 # upgrade transformers for NSP
升级版本后,我们可以使用以下代码加载一个 NSP-specific BERT:
from transformers import BertTokenizer, BertForNextSentencePrediction ❶ import torch from torch.nn.functional import softmax ❷ tokenizer = BertTokenizer.from_pretrained('bert-base-cased') model = BertForNextSentencePrediction.from_pretrained('bert-base-cased') model.eval() ❸
❶ NSP-specific BERT
❷ 计算原始输出的最终概率
❸ PyTorch 模型默认是可训练的。为了更便宜的推断和可执行重复性,将其设置为“eval”模式,如此处所示。通过 model.train()将其设置回“train”模式。对于 TensorFlow 模型不适用!
作为健全性检查,首先我们要确定第一句和第二句是否从模型的角度来看是合理的完成。我们使用以下代码进行检查:
prompt = "A new study estimates that if the US had universally mandated masks on 1 April, there could have been nearly 40% fewer deaths by the start of June." next_sentence = "Containment policies had a large impact on the number of COVID-19 cases and deaths, directly by reducing transmission rates and indirectly by constraining people’s behavior." encoding = tokenizer.encode(prompt, next_sentence, return_tensors='pt') logits = model(encoding)[0] ❶ probs = softmax(logits) ❷ print("Probabilities: [not plausible, plausible]") print(probs)
❶ 输出是一个元组;第一项描述了我们追求的两个句子之间的关系。
❷ 从原始数字计算概率
注意代码中的术语logits
。这是 softmax 函数的原始输入。通过 softmax 将logits
传递,可以得到概率。代码的输出确认找到了正确的关系,如下所示:
Probabilities: [not plausible, plausible] tensor([[0.1725, 0.8275]], grad_fn=<SoftmaxBackward>)
现在,让我们将第二个句子替换为一个有点随机的“Cats are independent.” 这将产生以下结果:
Probabilities: [not plausible, plausible] tensor([0.7666, 0.2334], grad_fn=<SoftmaxBackward>)
看起来一切都如预期的那样工作!
现在,你应该已经非常清楚 BERT 在训练中解决哪些任务了。需要注意的是,本章我们还没有将 BERT 调整到任何新域或任务特定的数据上进行微调。这是有意为之的,以帮助你在没有任何干扰的情况下了解模型架构。在下一节中,我们会演示如何进行微调,通过进行跨语言迁移学习实验。对于我们已经介绍过的所有其他任务,都可以采用类似的迁移学习方式进行,通过完成下一节练习,您将有很好的发挥空间去自己实践。