当前位置:   article > 正文

Transformers 自然语言处理(一)_github denis2054

github denis2054

原文:zh.annas-archive.org/md5/a1e65552fc41f3b5a667f63d9bed854c

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Transformers对于自然语言理解NLU)的重要性不言而喻,NLU 是自然语言处理NLP)的一个子集,在全球数字经济中已经成为人工智能的支柱之一。

Transformers模型标志着人工智能的新时代的开始。语言理解已成为语言建模、聊天机器人、个人助手、问答、文本摘要、语音转文本、情感分析、机器翻译等领域的支柱。我们正在见证社交网络与面对面的社交相比的扩张,电子商务与实体购物的竞争,数字新闻与传统媒体的竞争,流媒体与实体剧院的竞争,远程医生咨询与实体就诊的竞争,远程工作与现场任务的竞争以及数百个领域中类似的趋势。如果没有 AI 语言理解,社会将难以使用网络浏览器、流媒体服务以及任何涉及语言的数字活动。我们社会从物理信息到大规模数字信息的范式转变迫使人工智能进入了一个新时代。人工智能已经演化到了亿级参数模型来应对万亿字数据集的挑战。

Transformers架构既具有革命性又具有颠覆性。它打破了过去,摆脱了 RNN 和 CNN 的主导地位。 BERT 和 GPT 模型放弃了循环网络层,并用自注意力替换它们。Transformers模型胜过了 RNN 和 CNN。 2020 年代正在经历人工智能的重大变革。

Transformers编码器和解码器包含可以单独训练的注意力头,可以并行化最先进的硬件。注意力头可以在单独的 GPU 上运行,为亿级参数模型和即将推出的万亿级参数模型敞开大门。OpenAI 在拥有 10000 个 GPU 和 285000 个 CPU 核心的超级计算机上训练了一个有 1750 亿参数的 GPT-3 Transformers模型。

数据量的增加要求以规模训练 AI 模型。因此,Transformers为参数驱动 AI 开辟了新的时代。学会理解数亿个单词在句子中如何组合需要大量参数。

谷歌 BERT 和 OpenAI GPT-3 等Transformers模型已经将新兴技术推上了新的高度。Transformers可以执行它们未经过训练的数百种 NLP 任务。

Transformers还可以通过将图像嵌入为单词序列来学习图像分类和重建。本书将向您介绍最前沿的计算机视觉Transformers,如视觉TransformersViT),CLIP 和 DALL-E。

基础模型是完全训练的Transformers模型,可以在不进行微调的情况下执行数百种任务。在这个大规模信息时代,基础模型提供了我们所需要的工具。

想想需要多少人来控制每天在社交网络上发布的数十亿条消息的内容,以在提取其中的信息之前判断它们是否合法和道德。

想想需要多少人来翻译每天在网络上发布的数百万页。或者想象一下手动控制每分钟生成的数百万条消息需要多少人!

最后,想想需要多少人来记录每天在网络上发布的大量流媒体的转录。最后,想想替代 AI 图像字幕对那些持续出现在网上的数十亿张图片所需的人力资源。

这本书将带你从代码开发到提示设计,掌握控制 Transformer 模型行为的新 “编程” 技能。每一章都将带你从 Python、PyTorch 和 TensorFlow 的零基础开始,了解语言理解的关键方面。

你将学习到原始 Transformer、Google BERT、OpenAI GPT-3、T5 以及其他几个模型的架构。你将微调 transformer,从头开始训练模型,并学会使用强大的 API。Facebook、Google、Microsoft 和其他大型科技公司分享了大量数据集供我们探索。

你将与市场及其对语言理解的需求保持紧密联系,例如媒体、社交媒体和研究论文等领域。在数百个 AI 任务中,我们需要总结大量的研究数据,为经济的各个领域翻译文件,并为伦理和法律原因扫描所有的社交媒体帖子。

在整本书中,你将尝试使用 Python、PyTorch 和 TensorFlow 进行实践。你将了解关键的 AI 语言理解神经网络模型。然后,你将学习如何探索和实现 transformer。

在这个颠覆性的 AI 时代,你将学到成为工业 4.0 AI 专家所需的新技能。本书旨在为读者提供 Python 深度学习方面的知识和工具,以有效开发语言理解的核心内容。

本书适合谁阅读

本书不是 Python 编程或机器学习概念的入门。相反,它专注于机器翻译、语音转文字、文字转语音、语言建模、问答等多个 NLP 领域的深度学习。

最能从本书受益的读者有:

  • 熟悉 Python 编程的深度学习和 NLP 从业者。

  • 数据分析师和数据科学家想要对 AI 语言理解有个初步了解,以处理越来越多的以语言为驱动的功能。

本书涵盖的内容

第一部分:Transformer 架构简介

第一章什么是 Transformer?,在高层次上解释了 Transformer 是什么。我们将看看 Transformer 生态系统以及基础模型的特性。本章突出了许多可用的平台以及工业 4.0 AI 专家的演变。

第二章了解 Transformer 模型的架构初步,通过 NLP 的背景来理解 RNN、LSTM 和 CNN 深度学习架构是如何演变成打开新时代的 Transformer 架构的。我们将通过 Google Research 和 Google Brain 的作者们提出的独特 注意力就是一切 方法来详细了解 Transformer 的架构。我们将描述Transformers的理论。我们将用 Python 亲自动手来看看多头注意力子层是如何工作的。在本章结束时,你将了解到 Transformer 的原始架构。你将准备好在接下来的章节里探索 Transformer 的多种变体和用法。

第三章微调 BERT 模型,在原始 Transformer 的架构基础上构建。来自Transformers的双向编码表示BERT)向我们展示了一个感知 NLP 世界的新方式。BERT 不是分析过去的序列以预测未来的序列,而是关注整个序列!我们将首先了解 BERT 架构的关键创新,然后通过在 Google Colaboratory 笔记本中逐步微调 BERT 模型。像人类一样,BERT 可以学习任务,并且执行其他新任务而无需从头学习话题。

第四章从头开始预训练 RoBERTa 模型,使用 Hugging Face PyTorch 模块从头构建一个 RoBERTa Transformers模型。 这个Transformers模型将类似于 BERT 和 DistilBERT。首先,我们将在一个定制的数据集上从头开始训练一个分词器。然后,训练好的Transformers模型将在一个下游掩码语言建模任务中运行。

第 II 部分:将 Transformer 应用于自然语言理解和生成

第五章使用 Transformer 进行下游 NLP 任务,展示了使用下游 NLP 任务的魔力。一个预训练的Transformers模型可以被微调以解决一系列 NLP 任务,如 BoolQ、CB、MultiRC、RTE、WiC 等,在 GLUE 和 SuperGLUE 排行榜上占据主导地位。我们将介绍Transformers的评估过程、任务、数据集和指标。然后我们将使用 Hugging Face 的Transformers管道运行一些下游任务。

第六章使用 Transformer 进行机器翻译,定义机器翻译以了解如何从人类标准到机器传导方法。然后,我们将预处理来自欧洲议会的 WMT 法英数据集。机器翻译需要精确的评估方法,在本章中,我们将探讨 BLEU 评分方法。最后,我们将使用 Trax 实现一个 Transformer 机器翻译模型。

第七章超人类 Transformers 的崛起与 GPT-3 引擎,探讨了 OpenAI 的 GPT-2 和 GPT-3 transformers 的许多方面。我们将首先检查 OpenAI 的 GPT 模型的架构,然后解释不同的 GPT-3 引擎。接着我们将运行一个 GPT-2 345M 参数模型,并与其交互生成文本。接下来,我们将看到 GPT-3 游乐场的运作方式,然后编写一个用于 NLP 任务的 GPT-3 模型,并将结果与 GPT-2 进行比较。

第八章将 Transformer 应用于法律和金融文件以进行 AI 文本摘要,介绍了 T5 Transformer 模型的概念和架构。我们将使用 Hugging Face 的 T5 模型来进行文档摘要。我们将让 T5 模型摘要各种文档,包括 权利法案 中的样本,探讨将迁移学习方法应用于 Transformers 的成功和局限性。最后,我们将使用 GPT-3 将一些公司法文本摘要成二年级水平的语言。

第九章匹配 Tokenizers 和数据集,分析了分词器的限制,并研究了一些用于改进数据编码过程质量的方法。我们将首先构建一个 Python 程序来研究为什么一些单词会被 word2vector 分词器省略或误解。在此之后,我们将使用一个与分词器无关的方法来找出预训练分词器的限制。

我们将通过应用一些显示仍有很大改进空间的想法来改进 T5 摘要方法论。最后,我们将测试 GPT-3 的语言理解能力的限制。

第十章使用基于 BERT 的 Transformers 进行语义角色标注,探讨了 Transformers 如何学习理解文本内容。语义角色标注SRL)对人类来说是一个具有挑战性的练习。Transformers 可以产生令人惊讶的结果。我们将在 Google Colab 笔记本中实现由 AI 艾伦研究所设计的基于 BERT 的 Transformer 模型。我们还将使用他们的在线资源来可视化 SRL 输出。最后,我们将质疑 SRL 的范围,并理解其局限性背后的原因。

第三部分:高级语言理解技术

第十一章让你的数据说话:故事、问题和答案,展示了 Transformer 如何学会推理。Transformer 必须能够理解文本、故事,并展现出推理技能。我们将看到如何通过添加 NER 和 SRL 来增强问答过程。我们将为一个问题生成器建立蓝图,该生成器可以用于训练 Transformers 或作为独立解决方案。

第十二章检测客户情绪以进行预测,展示了Transformers如何改进情感分析。我们将使用斯坦福情感树库分析复杂句子,挑战几种Transformers模型,以理解一个序列的结构和逻辑形式。我们将看到如何使用Transformers进行预测,根据情感分析输出触发不同的动作。该章节最后通过使用 GPT-3 来解决一些边缘案例。

第十三章使用Transformers分析假新闻,深入探讨了假新闻的热门话题以及Transformers如何帮助我们理解我们每天看到的在线内容的不同视角。每天,在线社交媒体、网站以及各种实时通讯方式发布数十亿条消息、帖子和文章。使用前几章的几种技术,我们将分析气候变化和枪支管制的辩论,以及一位前总统的推文。我们将讨论在合理怀疑的基础上确定什么可以被认为是假新闻,以及哪些新闻是主观的道德和伦理问题。

第十四章解释黑匣子Transformers模型,通过可视化它们的活动揭开了Transformers模型的黑匣子。我们将使用 BertViz 来可视化注意力头,使用语言可解释性工具LIT)来进行主成分分析PCA)。最后,我们将使用 LIME 通过字典学习来可视化Transformers。

第十五章从自然语言处理到任务无关的Transformers模型,深入研究了先进模型 Reformer 和 DeBERTa,运用 Hugging Face 运行示例。Transformers可以将图像处理为单词序列。我们还将看到不同的视觉Transformers,如 ViT、CLIP 和 DALL-E。我们将对它们进行计算机视觉任务的测试,包括生成计算机图像。

第十六章Transformers驱动联合驾驶员的出现,探讨了工业 4.0 的成熟度。本章从使用非正式/随意英语的提示工程示例开始。接下来,我们将使用 GitHub Copilot 和 OpenAI Codex 从几行指令中创建代码。我们将看到视觉Transformers如何帮助自然语言处理Transformers可视化周围的世界。我们将创建一个基于Transformers的推荐系统,该系统可以由数字人类在您可能会进入的任何元宇宙中使用!

附录 ITransformers模型术语,检查了一个Transformers的高层结构,从堆栈和子层到注意力头。

附录 IITransformers模型的硬件约束,着眼于运行Transformers的 CPU 和 GPU 性能。我们将看到为什么Transformers和 GPU 以及Transformers是完美的匹配,最后进行一个测试,使用 Google Colab CPU、Google Colab 免费 GPU 和 Google Colab Pro GPU。

附录 III使用 GPT-2 进行通用文本完成,详细解释了使用 GPT-2 进行通用文本完成的内容,从第七章GPT-3 引擎的超人类转变

附录 IV使用 GPT-2 进行自定义文本完成,通过构建和训练 GPT-2 模型并使其与自定义文本交互,补充了第七章GPT-3 引擎的超人类转变

附录 V问题答案,提供了每章末尾问题的答案。

充分利用本书

本书中的大多数程序都是 Colaboratory 笔记本。您只需要一个免费的 Google Gmail 帐户,就可以在 Google Colaboratory 的免费虚拟机上运行这些笔记本。

您需要在计算机上安装 Python 才能运行一些教育性程序。

花费必要的时间阅读第二章开始理解Transformers模型的架构附录 ITransformers模型术语第二章 包含了原始Transformers的描述,该描述是由 附录 ITransformers模型术语 中解释的构建块构成的,这些构建块将在本书中实现。如果您觉得困难,可以从章节中提取一般的直观想法。当您在几章后对Transformers感到更加舒适时,您可以再回到这些章节。

阅读完每章后,考虑您如何为您的客户实施Transformers或使用它们提出新颖的想法来提升您的职业生涯。

请注意,我们在书的后面使用 OpenAI Codex,目前有等待名单。现在注册以避免长时间等待,请访问openai.com/blog/openai-codex/

下载示例代码文件

本书的代码包托管在 GitHub 上,地址为github.com/Denis2054/Transformers-for-NLP-2nd-Edition。我们还提供了来自我们丰富书目和视频目录的其他代码包,请查看github.com/PacktPublishing/

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的截图/图表的彩色图像。您可以在此处下载:static.packt-cdn.com/downloads/9781803247335_ColorImages.pdf

使用约定

本书中使用了几种文本约定。

CodeInText:指示在书中运行的句子和单词,文本中的代码单词,数据库表名,文件夹名,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄。例如,“但是,如果您想要探索代码,您可以在本章的 GitHub 代码库中找到 Google Colaboratory 的 positional_encoding.ipynb 笔记本和 text.txt 文件。”

代码块设置如下:

import numpy as np
from scipy.special import softmax 
  • 1
  • 2

当我们希望引起您对代码块的特定部分的注意时,相关行或项会以粗体显示:

The **black** cat sat on the couch and the **brown** dog slept on the rug. 
  • 1

任何命令行输入或输出都按如下方式编写:

vector similarity
[[0.9627094]] final positional encoding similarity 
  • 1
  • 2

粗体:表示新术语、重要词语或在屏幕上看到的文字,例如在菜单或对话框中,也会在文本中以此形式出现。例如:“在我们的情况下,我们正在寻找t5-large,一个我们可以在 Google Colaboratory 中顺利运行的 t5-large 模型。”

警告或重要提示以这种方式出现。

提示和技巧以这种方式出现。

第一章:什么是Transformers?

Transformers是工业化、同质化的后深度学习模型,设计用于超级计算机上的并行计算。通过同质化,一个Transformers模型可以在不经过微调的情况下执行各种任务。Transformers可以对数十亿条原始未标记数据进行自监督学习,其参数达到数十亿个。

这些特殊的后深度学习架构被称为基础模型。基础模型Transformers代表了 2015 年开始的第四次工业革命的顶峰,即机器对机器自动化将一切连接到一切。人工智能总体上,特别是自然语言处理NLP)对工业 4.0I4.0)的发展已经远远超越了过去的软件实践。

在不到五年的时间里,人工智能已经成为一个具有无缝 API 的有效云服务。以前的下载库和开发范式在许多情况下已经成为一种教育练习。

工业 4.0 项目经理可以进入 OpenAI 的云平台,注册,获取 API 密钥,并在几分钟内开始工作。用户然后可以输入文本,指定 NLP 任务,并获得由 GPT-3 Transformers引擎发送的响应。最后,用户可以转到 GPT-3 Codex,并创建没有编程知识的应用程序。提示工程是从这些模型中出现的新技能。

但是,有时候 GPT-3 模型可能不适合特定的任务。例如,项目经理、顾问或开发人员可能希望使用由谷歌 AI、亚马逊网络服务AWS)、艾伦人工智能研究所或 Hugging Face 提供的另一个系统。

项目经理应该选择在本地工作吗?还是应该直接在谷歌云、微软 Azure 或 AWS 上实施?开发团队应该选择 Hugging Face、Google Trax、OpenAI 还是 AllenNLP?人工智能专家或数据科学家是否应该使用几乎没有 AI 开发的 API?

答案是以上全部。您不知道未来的雇主、客户或用户可能想要或指定什么。因此,您必须准备好适应任何可能出现的需求。本书并未描述市场上存在的所有提供。但是,本书为读者提供了足够的解决方案,以适应工业 4.0 AI 驱动的 NLP 挑战。

本章首先从高层次解释了Transformers是什么。然后,本章解释了获取对实现Transformers的所有类型方法的灵活理解的重要性。平台、框架、库和语言的定义在市场上提供的 API 和自动化的数量下变得模糊。

最后,本章介绍了在嵌入式Transformers方面的工业 4.0 人工智能专家的角色。

在开始我们探索本书中描述的各种Transformers模型实现之前,我们需要解决这些关键概念。

本章涵盖以下主题:

  • 第四次工业革命,工业 4.0 的出现

  • 基础模型的范式变革

  • 引入提示工程,一门新的技能

  • Transformers的背景

  • 实施Transformers的挑战

  • 颠覆性的Transformers模型 API

  • 选择Transformers库的难度

  • 选择Transformers模型的困难

  • 工业 4.0 人工智能专家的新角色

  • 嵌入式Transformers

我们的第一步将是探索Transformers的生态系统。

Transformers的生态系统

Transformers模型代表了一种范式变革,需要一个新名称来描述它们:基础模型。因此,斯坦福大学创建了基础模型研究中心CRFM)。2021 年 8 月,CRFM 发表了一份 200 页长的论文(见参考文献部分),由一百多位科学家和专业人士所写:《关于基础模型的机遇和风险》。

基础模型并不是由学术界而是由大型科技行业所创建。例如,谷歌发明了Transformers模型,从而导致了 Google BERT。微软与 OpenAI 合作生产了 GPT-3。

大型科技公司不得不找到一个更好的模型来应对其数据中心中流动的百万吉字节数据的指数增长。因此,Transformers由此诞生。

让我们首先考虑工业 4.0,以了解需要具有工业化人工智能模型的需求。

工业 4.0

农业革命引发了第一次工业革命,引入了机械。第二次工业革命诞生了电力、电话和飞机。第三次工业革命是数字化的。

第四次工业革命,或工业 4.0,产生了无限的机器之间的连接:机器人、机器人、连接设备、自动驾驶汽车、智能手机、从社交媒体存储库搜集数据的机器人等。

反过来,这些数百万台机器和机器人每天生成数十亿的数据记录:图像、声音、文字和事件,如图 1.1所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.1:工业 4.0 的范围

工业 4.0 需要进行大规模的数据处理和决策的智能算法,以应对人类历史上前所未有的数据量而无需人为干预。

大型科技公司需要找到一个能够执行以往需要多个独立算法的各种任务的单一 AI 模型。

基础模型

Transformers具有两个显著特点:高度同质化和令人震惊的新兴特性。 同质化 使得可以使用一个模型执行各种任务。这些能力 通过 在超级计算机上训练千亿参数模型而得到。

这种范式变化使得基础模型成为深度学习后生态系统,如图 1.2 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.2:I4.0 人工智能专家的范围

基础模型,虽然设计了创新的架构,但是建立在人工智能的历史基础之上。因此,人工智能专家的技能范围正在拓展!

目前Transformers模型的生态系统与人工智能领域的任何其他演进都不同,并且可以总结为四个特性:

  • 模型架构

    该模型是工业化的。模型的层是相同的,它们专门设计用于并行处理。我们将在第二章 Transformers模型架构入门中详细介绍Transformers的架构。

  • 数据

    大型科技公司拥有人类历史上最庞大的数据来源,首先由第三次工业革命(数字化)产生,并由工业 4.0 推动至不可想象的规模。

  • 计算能力

    大型科技公司拥有前所未见规模的计算能力。例如,GPT-3 的训练速度约为 50 PetaFLOPS/秒,谷歌现在拥有领域特定的超级计算机,超过了 80 PetaFLOPS/秒。

  • 提示工程

    经过高度训练的Transformers可以通过提示来执行任务。提示以自然语言输入。然而,所使用的单词需要一定的结构,这使得提示成为一种元语言。

因此,基础模型是在亿万条数据和亿级参数的超级计算机上进行训练的Transformers模型。该模型然后可以执行各种任务,而无需进一步的微调。因此,基础模型的规模是独一无二的。这些完全训练的模型通常被称为引擎。只有 GPT-3、Google BERT 和少数几个Transformers引擎可以被视为基础模型。

我在本书中只会在提及 OpenAI 的 GPT-3 或 Google 的 BERT 模型时提及基础模型。这是因为 GPT-3 和 Google BERT 是在超级计算机上进行完全训练的。尽管其他模型对于有限的用途来说很有趣且有效,但由于缺乏资源,其他模型并未达到基础模型的同质化水平。

现在让我们探究一个基础模型是如何工作的,并且如何改变了我们开发程序的方式。

编程现在是否成为 NLP 的一个子领域?

Chen等人(2021 年)于 2021 年 8 月发表了一篇爆炸性的论文,介绍了 Codex,这是一个能将自然语言转换为源代码的 GPT-3 模型。Codex 是在 5400 万个公共 GitHub 软件仓库上进行训练的。Codex 可以生成有趣的自然语言到源代码,我们将在第十六章 Transformers驱动共同合作者的出现 中看到。

现在,编程是一种从自然语言到源代码语言的翻译任务吗?

编程是否正在成为 GPT-3 引擎的一项 NLP 任务?

在回答那个问题之前,让我们看一个例子。

请记住,Codex 是一种随机算法,所以元语言很棘手。如果您不小心正确地设计提示,可能不会生成您期望的结果。

当我试验 Codex 时,我创建了一些提示。这个例子只是为了让您了解 Codex 的工作原理,纯粹是为了教育目的。

我的提示是:

  • 在 Python 中“生成 200 个介于 1 到 100 之间的随机整数的随机分布”

  • “使用 matplotlib 绘制数据”

  • “创建具有 3 个质心的 k 均值聚类模型并拟合模型”

  • “打印聚类标签”

  • “绘制聚类”

  • “绘制带有质心的聚类”

Codex 自动将我的自然元语言提示翻译成 Python!

Codex 是一种随机模型,因此如果您再次尝试,可能不会完全再现相同的代码。您将不得不通过实验来学习元语言,直到能像驾驶赛车一样驾驭它!

Python 程序将自动生成,并可复制和测试:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets.samples_generator import make_blobs
# generate random data
np.random.seed(0)
X, y = make_blobs(n_samples=200, centers=3, n_features=2, cluster_std=2, random_state=0)
# plot the data
plt.scatter(X[:, 0], X[:, 1], s=50)
plt.show()
# create the k-means model
kmeans = KMeans(n_clusters=3, random_state=0)
# fit the model to the data
kmeans.fit(X)
# print the cluster labels
print(kmeans.labels_)
#plot the clusters
plt.scatter(X[:, 0], X[:, 1], c=kmeans.labels_, cmap='rainbow')
plt.show()
# plot the clusters with centroids
plt.scatter(X[:, 0], X[:, 1], c=kmeans.labels_, cmap='rainbow')
plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], c='black', s=100, alpha=0.5)
plt.show() 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

你可以复制粘贴这个程序。它有效。你也可以尝试 JavaScript,等等其他实验。

GitHub Copilot 现已与一些微软开发工具一起提供,我们将在第十六章《基于 Transformer 的协作工具的出现》中看到。如果您学会了提示工程元语言,您将在未来几年内缩短开发时间。

终端用户如果掌握了元语言,就能创建原型或小任务。未来,编码协助工具将会扩展。

我们将在第十六章中看到 Codex 在人工智能未来中的地位。

此时,让我们一睹人工智能专家美好未来的一瞥。

人工智能专家的未来

基础模型的社会影响不容小觑。提示工程已成为人工智能专家所需的一项技能。然而,人工智能专家的未来不能仅限于Transformers。人工智能和数据科学在 I4.0 中重叠。

AI 专家将参与使用经典人工智能、物联网、边缘计算等机器之间算法。AI 专家还将使用经典算法设计和开发有趣的机器人、服务器和各种连接设备之间的连接。

因此,这本书不仅限于提示工程,还涵盖了成为“工业 4.0 人工智能专家”或“I4.0 人工智能专家”所需的广泛设计技能。

提示工程是 AI 专家将必须开发的设计技能的子集。在这本书中,我将将未来的 AI 专家称为“工业 4.0 人工智能专家”。

现在让我们对Transformers如何优化 NLP 模型有一个总体了解。

使用Transformers优化 NLP 模型

循环神经网络RNNs),包括 LSTMs,几十年来一直将神经网络应用于自然语言处理的序列模型。然而,当面对长序列和大量参数时,使用递归功能达到了极限。因此,现在最先进的转换器模型占据了主导地位。

本节将简要介绍导致Transformers的自然语言处理背景,我们将在第二章中更详细地描述这一点,开始使用Transformers模型的架构。然而,首先,让我们直观地看一下Transformers的注意头,它取代了自然语言处理神经网络的 RNN 层。

Transformers的核心概念可以粗略地总结为“混合标记”。自然语言处理模型首先将单词序列转换为标记。循环神经网络在递归函数中分析标记。Transformers不会按顺序分析标记,而是将每个标记与序列中的其他标记相关联,如图 1.3所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.3:Transformers层的一个注意头

我们将在第二章详细讨论一个注意力头的细节。目前,图 1.3的要点是序列的每个单词标记都与序列的所有其他单词相关。这个模型为工业 4.0 的自然语言处理打开了大门。

让我们简要地了解一下Transformers的背景。

Transformers的背景

在过去的 100 多年里,许多伟大的思想家都致力于序列模式和语言建模。因此,机器逐渐学会了预测可能的单词序列。要列出所有使这一切发生的巨人,需要一整本书。

在本节中,我将与您分享一些我喜欢的研究人员,为Transformers的到来奠定基础。

在 20 世纪初,安德烈·马尔可夫介绍了随机值的概念,并创建了随机过程的理论。我们在人工智能中称之为马尔可夫决策过程MDP)、马尔可夫链马尔可夫过程。在 20 世纪初,马尔可夫证明了我们可以仅使用链的最后几个元素来预测链、序列的下一个元素。他将自己的方法应用到一个包含数千封信的数据集中,使用过去的序列来预测句子的后续字母。请记住,他没有计算机,但证明了一种至今在人工智能中仍在使用的理论。

1948 年,克劳德·香农出版了通信的数学理论。克劳德·香农奠定了基于源编码器、传输器和接收器或语义解码器的通信模型的基础。他创造了今天我们所知的信息论。

1950 年,艾伦·图灵发表了他的里程碑文章:计算机与智能。艾伦·图灵将这篇文章基于在二战期间解密德国消息的成功图灵机的机器智能。这些消息由一系列单词和数字组成。

1954 年,乔治城-IBM 实验使用计算机利用规则系统将俄语句子翻译成英语。 规则系统是一个运行规则列表的程序,该列表将分析语言结构。 规则系统仍然存在,并且无处不在。 但是,在某些情况下,机器智能可以通过自动学习模式来替代数十亿种语言组合的规则列表。

1956 年,约翰·麦卡锡首次使用了“人工智能”这个表达,当时确立了机器可以学习的事实。

1982 年,约翰·霍普菲尔德引入了一种称为霍普菲尔德网络或“联想”神经网络的循环神经网络RNN)。 约翰·霍普菲尔德受到 W.A. Little 的启发,后者在 1974 年撰写了大脑中持久状态的存在,为学习过程奠定了几十年的理论基础。 RNN 不断发展,以及我们今天所知的 LSTM 也随之出现。

RNN 高效地记忆了序列的持久状态,如图 1.4所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.4:RNN 过程

每个状态 S[n] 捕获 S[n-1] 的信息。 当网络的末端到达时,函数 F 将执行一个动作:转换、建模或任何其他类型的基于序列的任务。

在 1980 年代,Yann LeCun 设计了多功能的卷积神经网络CNN)。 他将 CNN 应用于文本序列,它们还适用于序列转换和建模。 它们还基于 W.A. Little 的持久状态,该状态逐层处理信息。 在 1990 年代,总结了几年的工作后,Yann LeCun 制作了 LeNet-5,导致了我们今天所知道的许多 CNN 模型。 但是,在处理冗长且复杂序列中的长期依赖关系时,CNN 的有效架构面临问题。

我们可以提到许多其他的名字、论文和模型,这些都会让任何 AI 专家感到自愧不如。 AI 领域的每个人似乎都走在正确的轨道上。 马尔可夫场、RNN 和 CNN 发展成了多种其他模型。 注意力的概念出现了:窥视序列中的其他令牌,而不仅仅是最后一个。 它被添加到了 RNN 和 CNN 模型中。

之后,如果 AI 模型需要分析需要增加计算机功率的更长序列,AI 开发人员使用了更强大的机器,并找到了优化梯度的方法。

对于序列到序列模型进行了一些研究,但并未达到预期。

看起来似乎没有其他方法可以取得更多进展。 这样过了三十年。 然后,从 2017 年末开始,工业化的最先进的 Transformer 带来了它的注意力头子层等等。 RNN 不再被视为序列建模的先决条件。

在深入研究原始 Transformer 的架构之前,我们将在 第二章开始使用 Transformer 模型的架构 中进行。让我们从高层次上审视一下学习和实现 transformer 模型应该使用的软件资源的范式变化。

我们应该使用什么资源?

工业 4.0 AI 已经模糊了云平台、框架、库、语言和模型之间的界限。Transformer 是新的,生态系统的范围和数量令人惊叹。Google Cloud 提供了可直接使用的 transformer 模型。

OpenAI 部署了一个几乎不需要编程的“Transformer” API。Hugging Face 提供了一个云库服务,列表是无穷无尽的。

本章将对我们将在本书中实施的一些 transformer 生态系统进行高层次的分析。

你选择的资源来实现 NLP 的 transformer 是至关重要的。这是一个在项目中生存的问题。想象一下现实生活中的面试或演示。想象一下你正在与你的未来雇主、你的雇主、你的团队或一个客户交谈。

你以一个出色的 PowerPoint 开始你的演示,比如说使用 Hugging Face。例如。 你可能会从一个经理那里得到一个负面的反应,他可能会说:“对不起,但是我们在这种类型的项目中使用的是 Google Trax,而不是 Hugging Face。你能实现 Google Trax 吗?” 如果你不这样做,那么游戏结束了。

专门从事 Google Trax 可能会出现同样的问题。但是,相反,你可能会得到一个希望使用 OpenAI 的 GPT-3 引擎和 API 而不进行开发的经理的反应。如果你专门使用 OpenAI 的 GPT-3 引擎和 API 而不进行开发,你可能会面临一个希望使用 Hugging Face 的 AutoML API 的项目经理或客户。最糟糕的事情是,一个经理接受了你的解决方案,但最终它根本不适用于该项目的 NLP 任务。

要牢记的关键概念是,如果你只关注你喜欢的解决方案,你很可能最终会随着船沉没。

关注你需要的系统,而不是你喜欢的那个。

这本书并不是为了解释市场上存在的每一个 transformer 解决方案。相反,这本书旨在为您解释足够的 transformer 生态系统,以便您能够灵活地适应您在 NLP 项目中面临的任何情况。

在本节中,我们将介绍您将面临的一些挑战。但首先,让我们从 API 开始。

Transformer 4.0 无缝 API 的崛起

我们现在已经深入到人工智能的工业化时代。微软、谷歌、亚马逊网络服务AWS)和 IBM 等公司提供的 AI 服务,没有任何开发人员或开发团队能够超越。科技巨头拥有百万美元的超级计算机,用于训练 transformer 模型和 AI 模型的大数据集。

大型科技巨头拥有广泛的企业客户群体,已经使用他们的云服务。因此,将Transformers API 添加到现有的云架构中所需的工作量比任何其他解决方案都要少。

一个小公司甚至一个个人都可以通过 API 访问最强大的Transformers模型,几乎不需要投资于开发。一名实习生可以在几天内实现 API。对于这样简单的实现,不需要成为工程师或拥有博士学位。

例如,OpenAI 平台现在为市场上一些最有效的Transformers模型提供 SaaS (软件即服务) API。

OpenAI Transformers模型如此有效且人性化,以至于目前的政策要求潜在用户填写请求表格。一旦请求被接受,用户就可以访问自然语言处理的宇宙!

OpenAI 的 API 的简单性令用户感到惊讶:

  1. 一键获取 API 密钥

  2. 在笔记本中导入 OpenAI 只需一行

  3. 提示 中输入您希望的任何 NLP 任务

  4. 您将以一定数量的 令牌(长度)作为 完成 收到响应

就是这样!欢迎来到第四次工业革命和 AI 4.0!

专注于仅代码解决方案的工业 3.0 开发者将进化为具有跨学科思维的工业 4.0 开发者。

4.0 开发者将学习如何设计一种方式来 展示 Transformers模型期望的内容,而不是像 3.0 开发者那样直观地 告诉 它要做什么。我们将通过 第七章GPT-3 引擎崛起的超人类Transformers 来探讨这种新方法。

AllenNLP 提供免费使用在线教育界面的Transformers。AllenNLP 还提供一个可以安装在笔记本中的库。例如,假设我们被要求实现共指消解。我们可以从在线运行示例开始。

共指消解任务涉及找到一个词所指的实体,就像在 图 1.5 中所示的句子中那样。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.5:在线运行 NLP 任务

“它” 这个词可能指的是网站或Transformers模型。在这种情况下,类似 BERT 的模型决定将 “it” 链接到Transformers模型。AllenNLP 提供了格式化的输出,如 图 1.6 所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.6:AllenNLP Transformers模型的输出

此示例可以在 demo.allennlp.org/coreference-resolution 上运行。Transformers模型不断更新,因此您可能会获得不同的结果。

虽然 API 可能满足许多需求,但它们也有限制。一个多用途的 API 可能在所有任务中都相当不错,但对于特定的 NLP 任务来说并不够好。使用Transformers进行翻译并不是一件容易的事情。在这种情况下,4.0 开发者、顾问或项目经理将不得不证明仅靠 API 无法解决所需的特定 NLP 任务。我们需要寻找一个可靠的库。

选择现成的 API 驱动库

在本书中,我们将探索几个库。例如,Google 在世界上拥有一些最先进的人工智能实验室。Google Trax 可以在 Google Colab 中只需几行代码进行安装。你可以选择免费或付费服务。我们可以获得源代码,调整模型,甚至在我们自己的服务器或谷歌云上进行训练。例如,从使用现成的 API 到为翻译任务定制转换器模型是一个逐步进行的过程。

但是,在某些情况下,这可能既具有教育意义,又彰显效果。我们将探索 Google 在翻译方面的最新发展,并在第六章《使用 Transformer 进行机器翻译》中实施 Google Trax。

我们已经看到像 OpenAI 这样的 API 需要有限的开发者技能,而像 Google Trax 这样的库则更深入地涉及代码。这两种方法表明,AI 4.0 API 将需要更多的开发,但在实施转换器时需要更少的工作。

其中应用了转换器的最著名的线上应用之一,除其他算法之外,就是 Google 翻译。Google 翻译可以在线使用,也可以通过 API 使用。

让我们尝试使用 Google 翻译将一个需要指代消解的句子从英语翻译成法语:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1.7:利用 Google 翻译进行指代消解的翻译

Google 翻译似乎已经解决了指代消解的问题,但是法语中的 transformateur 这个词是指电器设备。transformer 这个词是法语中的一个新词。在某些项目中可能需要人工智能专家具有语言和语言学技能。这种情况下并不需要进行重大开发。但是,在请求翻译之前,项目可能需要澄清输入。

这个例子表明,你可能需要与语言学家合作或者获取语言学技能来处理输入上下文。此外,可能需要进行大量开发来为上下文增加一个界面。

因此,我们可能仍然需要费一些力气才能使用 Google 翻译添加脚本。或者我们可能需要为特定的翻译需求找到一个转换器模型,比如 BERT、T5 或我们在本书中将要探索的其他模型。

随着解决方案范围的不断增加,选择一个模型并不是一件容易的事情。

选择一个转换器模型

大型科技公司主导着自然语言处理(NLP)市场。仅有谷歌、Facebook 和微软每天运行数十亿次 NLP 例程,增强了它们的人工智能模型无与伦比的力量。这些巨头现在提供了各种转换器模型,并拥有顶尖的基础模型。

然而,更小的公司看到庞大的自然语言处理市场后也加入了竞争。Hugging Face 现在也有免费或付费的服务方式。Hugging Face 要达到通过投入数十亿美元进入 Google 研究实验室和微软资助 OpenAI 而获得的效率水平将是一个挑战。基础模型的入口是在超级计算机上完全训练的Transformers,如 GPT-3 或 Google BERT。

Hugging Face 采取了不同的方法,为任务提供了广泛的Transformers模型选择,这是一种有趣的哲学。Hugging Face 提供灵活的模型。此外,Hugging Face 还提供高级 API 和开发者可控的 API。我们将在本书的几个章节中探讨 Hugging Face 作为一个教育工具和可能的特定任务解决方案。

然而,OpenAI 已经专注于全球最强大的少数几个Transformers引擎,并且可以以人类水平执行许多自然语言处理任务。我们将展示 OpenAI 的 GPT-3 引擎的威力在 第七章GPT-3 引擎崛起 中。

这些相反而经常冲突的策略给我们留下了一系列可能的实现方案。因此,我们必须定义工业 4.0 人工智能专家的角色。

工业 4.0 人工智能专家的角色

工业 4.0 正将所有事物与所有事物连接起来,无处不在。机器直接与其他机器通信。由人工智能驱动的物联网信号触发自动决策,无需人类干预。自然语言处理算法发送自动报告、摘要、电子邮件、广告等。

人工智能专家将不得不适应这个新时代日益自动化的任务,包括Transformers模型的实现。人工智能专家将有新的功能。如果我们按照一个人工智能专家将要执行的Transformers自然语言处理任务列出来,从头到尾,似乎一些高级任务不需要人工智能专家做任何开发。一个人工智能专家可以成为一个人工智能大师,提供设计思路、解释和实现。

Transformers对于人工智能专家的实际定义将随着生态系统的变化而变化。

让我们通过一些例子来说明:

  • API:OpenAI API 不需要一个 AI 开发者。一个网页设计师可以创建一个表单,一个语言学家或者专业领域专家(SME)可以准备提示输入文本。一个 AI 专家的主要角色将需要语言学技能来向 GPT-3 引擎展示如何完成任务,而不仅仅是告诉它们。例如,展示涉及处理输入的上下文。这个新任务被称为 提示工程提示工程师 在人工智能领域有着很好的发展前景!

  • Library:Google Trax 库需要一定量的开发工作才能开始使用预先训练好的模型。一个精通语言学和自然语言处理任务的 AI 专家可以处理数据集和输出。

  • 训练和调优:一些 Hugging Face 功能需要有限的开发量,提供 API 和库。然而,在某些情况下,我们仍然需要细致入微。在这种情况下,训练、调优模型和找到正确的超参数将需要人工智能专家的专业知识。

  • 开发级技能:在一些项目中,分词器和数据集不匹配,正如第九章中所解释的那样,匹配分词器和数据集。在这种情况下,与语言学家一起工作的人工智能开发人员可以发挥关键作用。因此,在这个层面上,计算语言学培训非常有用。

最近 NLP AI 的演进可以称为“嵌入式转换器”,正在扰乱 AI 开发生态系统:

  • GPT-3 转换器目前嵌入在几个 Microsoft Azure 应用程序中,例如 GitHub Copilot。正如本章节基础模型部分中所介绍的那样,Codex 是我们将在第十六章中探讨的另一个例子,基于转换器的合作者的出现

  • 嵌入式转换器不能直接访问,但提供自动开发支持,如自动代码生成。

  • 嵌入式转换器的使用对于最终用户来说是无缝的,辅助文本自动完成。

要直接访问 GPT-3 引擎,您必须首先创建一个 OpenAI 账户。然后您可以使用 API 或直接在 OpenAI 用户界面中运行示例。

我们将在第十六章中探索这个迷人的嵌入式转换器的新世界。但要充分利用该章节,您首先应该掌握前几章的概念、示例和程序。

工业 4.0 AI 专家的技能需要灵活性、跨学科知识,尤其是灵活性。本书将为人工智能专家提供各种转换器生态系统,以适应市场的新模式。

在潜入原始 Transformer 的迷人架构之前,现在是总结本章思想的时候了。

摘要

第四次工业革命,或工业 4.0,迫使人工智能发生深刻的演变。第三次工业革命是数字化的。工业 4.0 建立在数字革命的基础上,将一切与一切、无处不在的连接起来。自动化流程正在取代人类在重要领域的决策,包括 NLP。

RNN 存在限制,这些限制减缓了在快速移动世界中所需的自动化 NLP 任务的进展。转换器填补了这一空白。一个公司需要摘要、翻译和各种 NLP 工具来应对工业 4.0 的挑战。

因此,工业 4.0(I4.0)推动了人工智能产业化的时代。平台、框架、语言和模型概念的演进对工业 4.0 开发者构成了挑战。基础模型通过提供可以在没有进一步训练或微调的情况下执行各种任务的同质模型,弥合了第三次工业革命和 I4.0 之间的差距。

例如,AllenNLP 等网站提供了不需要安装的教育性 NLP 任务,但它也提供了在定制程序中实现转换器模型的资源。OpenAI 提供了一个 API,只需几行代码就可以运行强大的 GPT-3 引擎之一。Google Trax 提供了一个端到端库,Hugging Face 提供了各种转换器模型和实现。我们将在本书中探索这些生态系统。

工业 4.0 是与以前的人工智能的根本偏差,具有更广泛的技能集。例如,项目经理可以决定通过询问网页设计师创建一个接口来实现转换器的项目,通过提示工程来实现 OpenAI 的 API。或者,在必要时,项目经理可以要求人工智能专家下载 Google Trax 或 Hugging Face 来开发一个完整的项目,并使用定制的转换器模型。

工业 4.0 对开发者来说是一个改变游戏规则的因素,他们的角色将会扩展,并且需要更多的设计而不是编程。此外,嵌入式转换器将提供辅助代码开发和使用。这些新的技能集是一个挑战,但也打开了新的激动人心的视野。

第二章中,开始使用 Transformer 模型的架构,我们将开始使用原始 Transformer 的架构。

问题

  1. 我们仍处于第三次工业革命中。(True/False)

  2. 第四次工业革命正在将一切连接到一切。(True/False)

  3. 工业 4.0 开发者有时不需要进行 AI 开发。(True/False)

  4. 工业 4.0 开发者可能需要从零开始实现转换器。(True/False)

  5. 没有必要学习超过一个转换器生态系统,例如 Hugging Face。(True/False)

  6. 一个即用型的转换器 API 可以满足所有需求。(True/False)

  7. 公司将接受开发者最擅长的转换器生态系统。(True/False)

  8. 云转换器已经成为主流。(True/False)

  9. 一个转换器项目可以在笔记本电脑上运行。(True/False)

  10. 工业 4.0 人工智能专家将不得不更加灵活(True/False)

参考资料

加入我们书籍的 Discord 空间

参加书籍的 Discord 工作区,与作者进行每月的问我任何(Ask me Anything)的交流活动:

www.packt.link/Transformers

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第二章:起步使用 Transformer 模型的架构

语言是人类交流的本质。如果没有形成语言的词序列,文明将永远不会诞生。现在,我们大多生活在语言的数字表示世界中。我们的日常生活依赖于 NLP 数字化语言功能:网络搜索引擎、电子邮件、社交网络、帖子、推文、智能手机短信、翻译、网页、流媒体网站上的语音转文字、热线服务上的文字转语音以及许多其他日常功能。

第一章什么是 Transformer?,解释了 RNN 的局限性以及云 AI 转换器的诞生,占据了设计和开发的一部分份额。工业 4.0 开发者的角色是理解原始 Transformer 的架构以及随之而来的多个 transformer 生态系统。

2017 年 12 月,Google Brain 和 Google Research 发表了具有开创性意义的瓦斯瓦尼等人的Attention is All You Need论文。Transformer 诞生了。Transformer 超越了现有的最先进的 NLP 模型。Transformer 比以前的架构训练速度更快,并获得了更高的评估结果。因此,transformer 已成为 NLP 的关键组成部分。

Transformer 的注意头的思想是消除循环神经网络特征。在本章中,我们将揭开瓦斯瓦尼等人(2017)描述的 Transformer 模型的面纱,检查其架构的主要组件。我们将探索引人入胜的关注世界,并说明 Transformer 的关键组件。

本章涵盖以下主题:

  • Transformer 的架构

  • Transformer 的自注意力模型

  • 编码和解码堆栈

  • 输入和输出嵌入

  • 位置嵌入

  • 自注意力

  • 多头注意力

  • 掩模多头注意力

  • 残差连接

  • 规范化

  • 前馈网络

  • 输出概率

让我们直接深入原始 Transformer 架构的结构。

Transformer 的崛起:Attention is All You Need

2017 年 12 月,瓦斯瓦尼等人(2017)发表了他们具有开创性意义的论文Attention is All You Need。他们在 Google Research 和 Google Brain 进行了这项工作。在本章和本书中,我将称Attention is All You Need中描述的模型为“原始 Transformer 模型”。

附录 ITransformer 模型术语,可以帮助从深度学习词汇的经典用法过渡到 Transformer 词汇。附录 I总结了经典人工智能神经网络模型的一些变化。

在本节中,我们将看一下他们构建的 Transformer 模型的结构。在接下来的章节中,我们将探讨模型的每个组件内部的内容。

原始的 Transformer 模型是一个包含 6 个层叠的堆栈。第l层的输出是第l+1 层的输入,直到达到最终预测。左侧有一个 6 层编码器堆栈,右侧有一个 6 层解码器堆栈:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.1:Transformer 的架构

在左侧,输入通过一个注意力子层和一个前馈子层进入 Transformer 的编码器侧。在右侧,目标输出通过两个注意力子层和一个前馈网络子层进入 Transformer 的解码器侧。我们立即注意到这个架构中没有 RNN、LSTM 或 CNN。在这种架构中已经放弃了循环。

注意力已经取代了需要随着两个单词之间距离增加而增加的循环函数。注意力机制是一种“单词对单词”的操作。实际上它是一个标记到标记的操作,但为了保持解释简单,我们将其保持在单词级别上。注意力机制将找到每个单词与序列中所有其他单词的关系,包括正在分析的单词本身。让我们来看一下以下序列:

The cat sat on the mat. 
  • 1

注意力将在单词向量之间运行点积,并确定一个单词与所有其他单词(包括自己)的最强关系(“猫”和“猫”):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.2:关注所有单词

注意机制将提供单词之间更深的关系并产生更好的结果。

对于每个注意子层,原始的 Transformer 模型不是运行一个而是运行八个并行的注意机制以加快计算速度。我们将在下一节“编码器堆栈”中探讨这种架构。这个过程被称为“多头注意力”,提供:

  • 对序列的更广泛的深入分析

  • 排除了减少计算操作的循环先验

  • 并行化的实现,可以减少训练时间

  • 每个注意机制学习相同输入序列的不同视角

注意力取代了循环。然而,Transformer 还有其他几个创造性方面,它们和注意力机制一样关键,当我们深入研究架构时,你将会看到。

我们刚刚从外部看了 Transformer 的结构。现在让我们深入了解 Transformer 的每个组件。我们将从编码器开始。

编码器堆栈

原始 Transformer 模型的编码器和解码器的层都是层叠的层。编码器堆栈的每一层都有以下结构:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.3:Transformer 编码器堆栈的一层

原始编码器层结构对于 Transformer 模型的所有N=6 层保持不变。每一层包含两个主要的子层:一个多头注意力机制和一个完全连接的位置逐层前馈网络。

请注意,在 Transformer 模型中,每个主要子层子层(x)周围都有一个残差连接。这些连接将子层的未处理输入x传输到层标准化函数。这样,我们可以确定诸如位置编码之类的关键信息在传输过程中不会丢失。因此,每层的标准化输出如下:

层标准化 (x + 子层(x))

尽管编码器的N=6 层的结构是相同的,但每层的内容并不严格与上一层相同。

例如,嵌入子层仅出现在堆栈的底层。其他五层不包含嵌入层,这保证了经过所有层的编码输入是稳定的。

此外,多头注意力机制从第 1 层到第 6 层执行相同的功能。但它们并不执行相同的任务。每一层都从前一层学习,并探索关联序列中标记的不同方式。它寻找单词的不同关联,就像我们在解决填字游戏时寻找字母和单词的不同关联一样。

Transformer 的设计者引入了一个非常高效的约束。模型的每个子层的输出都具有恒定的维度,包括嵌入层和残差连接。该维度为d[model],可以根据您的目标设置为另一个值。在原始的 Transformer 架构中,d[model] = 512。

d[model]具有强大的影响。几乎所有关键操作都是点积。因此,维度保持稳定,这减少了计算操作的数量,减少了机器的消耗,并使跟踪信息在模型中流动变得更容易。

编码器的这个全局视图展示了 Transformer 的高度优化的架构。在接下来的几节中,我们将深入研究每个子层和机制。

我们将从嵌入子层开始。

输入嵌入

输入嵌入子层使用原始 Transformer 模型中学习的嵌入将输入标记转换为维度为d[model] = 512 的向量。输入嵌入的结构是经典的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.4:Transformer 的输入嵌入子层

嵌入子层的工作方式类似于其他标准的转换模型。标记器将一个句子转换为标记。每个标记器都有其方法,如 BPE、单词片段和句子片段方法。Transformer 最初使用了 BPE,但其他模型使用其他方法。

目标是相似的,选择取决于选择的策略。例如,应用于序列 Transformer is an innovative NLP model!的标记器将在一种模型中产生以下标记:

['the', 'transform', 'er', 'is', 'an', 'innovative', 'n', 'l', 'p', 'model', '!'] 
  • 1

您会注意到这个分词器将字符串标准化为小写字母并将其截断为子部分。分词器通常会提供一个用于嵌入过程的整数表示。例如:

text = "The cat slept on the couch.It was too tired to get up."
tokenized text= [1996, 4937, 7771, 2006, 1996, 6411, 1012, 2009, 2001, 2205, 5458, 2000, 2131, 2039, 1012] 
  • 1
  • 2

此时,标记化文本中没有足够的信息进行更深入的分析。标记化文本必须被嵌入。

Transformer 包含了一个学习到的嵌入子层。许多嵌入方法可以应用到标记化的输入中。

我选择了word2vec嵌入方法中谷歌在 2013 年发布的 skip-gram 架构来说明 Transformer 的嵌入子层。skip-gram 会关注窗口中的中心词,并预测上下文词。例如,如果 word(i)是一个两步窗口中的中心词,skip-gram 模型会分析 word(i-2),word(i-1),word(i+1),和 word(i+2)。然后窗口会滑动并重复这个过程。skip-gram 模型通常包含输入层,权重,隐藏层,以及包含标记化输入词的词嵌入的输出。

假设我们需要为以下句子进行嵌入:

The black cat sat on the couch and the brown dog slept on the rug. 
  • 1

我们将专注于两个词,blackbrown。这两个词的词嵌入向量应该是相似的。

由于我们必须为每个词生成一个大小为d[model] = 512 的向量,我们将为每个词获得大小为512的向量嵌入:

black=[[-0.01206071  0.11632373  0.06206119  0.01403395  0.09541149  0.10695464 0.02560172  0.00185677 -0.04284821  0.06146432  0.09466285  0.04642421 0.08680347  0.05684567 -0.00717266 -0.03163519  0.03292002 -0.11397766 0.01304929  0.01964396  0.01902409  0.02831945  0.05870414  0.03390711 -0.06204525  0.06173197 -0.08613958 -0.04654748  0.02728105 -0.078309040.04340003 -0.13192849 -0.00945092 -0.00835463 -0.06487109  0.05862355 -0.03407936 -0.00059001 -0.01640179  0.04123065 
-0.04756588  0.08812257 0.00200338 -0.0931043  -0.03507337  0.02153351 -0.02621627 -0.02492662 -0.05771535 -0.01164199 
-0.03879078 -0.05506947  0.01693138 -0.04124579 -0.03779858 
-0.01950983 -0.05398201  0.07582296  0.00038318 -0.04639162 
-0.06819214  0.01366171  0.01411388  0.00853774  0.02183574 
-0.03016279 -0.03184025 -0.04273562]] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

单词black现在用512维度表示。其他嵌入方法可以被使用,d[model]可以有更多维度。

brown的词嵌入也用512维度表示:

brown=[[ 1.35794589e-02 -2.18823571e-02  1.34526128e-02  6.74355254e-02
   1.04376070e-01  1.09921647e-02 -5.46298288e-02 -1.18385479e-02
   4.41223830e-02 -1.84863899e-02 -6.84073642e-02  3.21860164e-02
   4.09143828e-02 -2.74433400e-02 -2.47369967e-02  7.74542615e-02
   9.80964210e-03  2.94299088e-02  2.93895267e-02 -3.29437815e-027.20389187e-02  1.57317147e-02 -3.10291946e-02 -5.51304631e-02
  -7.03861639e-02  7.40829483e-02  1.04319192e-02 -2.01565702e-03
   2.43322570e-02  1.92969330e-02  2.57341694e-02 -1.13280728e-01
   8.45847875e-02  4.90090018e-03  5.33546880e-02 -2.31553353e-02
   3.87288055e-05  3.31782512e-02 -4.00604047e-02 -1.02028981e-01
   3.49597558e-02 -1.71501152e-02  3.55573371e-02 -1.77437533e-02
  -5.94457164e-02  2.21221056e-02  9.73121971e-02 -4.90022525e-02]] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

为了验证这两个词产生的词嵌入,我们可以使用余弦相似度来查看单词blackbrown的词嵌入是否相似。

余弦相似度使用欧几里得(L2)范数在一个单位球中创建向量。我们比较的向量的点积是这两个向量之间的余弦。更多关于余弦相似度理论的内容,您可以查阅 scikit-learn 的文档,以及其他很多来源:scikit-learn.org/stable/modules/metrics.html#cosine-similarity

在示例的嵌入中,大小为d[model] = 512 的黑色向量与大小为d[model] = 512 的棕色向量之间的余弦相似度是:

cosine_similarity(black, brown)= [[0.9998901]] 
  • 1

skip-gram 产生了两个彼此接近的向量。它检测到 black 和 brown 形成了一个颜色子集的词典。

Transformer 的后续层并不是空手起步。它们已经学会了词嵌入,这些词嵌入已经提供了有关如何关联这些词的信息。

然而,由于没有额外的向量或信息指示序列中单词的位置,很多信息都缺失了。

Transformer 的设计者提出了另一个创新特性:位置编码。

让我们看看位置编码是如何工作的。

位置编码

我们进入 Transformer 的这个位置编码函数时并不知道词在序列中的位置:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.5:位置编码

我们不能创建独立的位置向量,这将对 Transformer 的训练速度产生较高的成本,并使注意力子层过于复杂。这个想法是向输入嵌入中添加一个位置编码值,而不是添加额外的向量来描述序列中一个标记的位置。

工业 4.0 是实用的,且不受模型限制。原始 Transformer 模型只有一个包含词嵌入和位置编码的向量。我们将在 第十五章,从自然语言处理到任务不可知的 Transformer 模型 中探索使用一个独立的矩阵来进行位置编码的分离注意力。

Transformer 期望输出的每个向量都具有固定大小 d[model] = 512(或模型的其他常量值)。

如果我们回到我们在词嵌入子层中使用的句子,我们可以看到黑色和棕色可能在语义上相似,但在句子中相距甚远:

The `black` cat sat on the couch and the `brown` dog slept on the rug. 
  • 1

单词 black 处于位置 2,pos=2,而单词 brown 处于位置 10,pos=10

我们的问题是找到一种方法,向每个单词的词嵌入中添加一个值,以便它具有该信息。但是,我们需要向 d[model] = 512 维度添加一个值!对于每个单词嵌入向量,我们需要找到一种方法,为 blackbrown 的词嵌入向量的 range(0,512) 维度中的 i 提供信息。

有许多方法可以实现位置编码。本节将重点介绍设计者巧妙地使用单位球来表示位置编码,使用正弦和余弦值,因此保持小但有用。

Vaswani 等人(2017 年)提供了正弦和余弦函数,以便我们可以为每个位置和 d[model] = 512 的词嵌入向量的每个维度 i 生成不同的频率来生成位置编码 (PE):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们从词嵌入向量的开头开始,我们将从一个常数开始 (512),i=0,并以 i=511 结束。这意味着正弦函数将应用于偶数,余弦函数将应用于奇数。一些实现方式可能不同。在这种情况下,正弦函数的定义域可能是 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传,余弦函数的定义域可能是 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传。这将产生类似的结果。

在本节中,我们将使用 Vaswani 等人(2017 年)描述的函数方式。将其直译为 Python 伪代码产生了以下代码,用于表示位置向量 pe[0][i] 的位置 pos

def positional_encoding(pos,pe):
for i in range(0, 512,2):
         pe[0][i] = math.sin(pos / (10000 ** ((2 * i)/d_model)))
         pe[0][i+1] = math.cos(pos / (10000 ** ((2 * i)/d_model)))
return pe 
  • 1
  • 2
  • 3
  • 4
  • 5

Google Brain Trax 和 Hugging Face 等公司提供了用于单词嵌入部分和现在位置编码部分的即用型库。因此,你不需要运行我在本节中分享的代码。但是,如果你想探索代码,你可以在 Google Colaboratory 的 positional_encoding.ipynb 笔记本和本章的 GitHub 仓库中的 text.txt 文件中找到它。

在继续之前,你可能想看一下正弦函数的图表,例如 pos=2

例如,你可以谷歌以下图表:

plot y=sin(2/10000^(2*x/512)) 
  • 1

只需输入绘图请求:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.6:使用 Google 绘图

你将获得以下图表:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.7:图

如果我们回到这一部分正在解析的句子,我们可以看到 black 位于位置 pos=2,而 brown 位于位置 pos=10

The black cat sat on the couch and the brown dog slept on the rug. 
  • 1

如果我们将正弦和余弦函数直接应用于 pos=2,我们将获得大小为512的位置编码向量:

PE(2)= 
[[ 9.09297407e-01 -4.16146845e-01  9.58144367e-01 -2.86285430e-01
   9.87046242e-01 -1.60435960e-01  9.99164224e-01 -4.08766568e-02
   9.97479975e-01  7.09482506e-02  9.84703004e-01  1.74241230e-01
   9.63226616e-01  2.68690288e-01  9.35118318e-01  3.54335666e-01
   9.02130723e-01  4.31462824e-01  8.65725577e-01  5.00518918e-01
   8.27103794e-01  5.62049210e-01  7.87237823e-01  6.16649508e-01
   7.46903539e-01  6.64932430e-01  7.06710517e-01  7.07502782e-015.47683925e-08  1.00000000e+00  5.09659337e-08  1.00000000e+00
   4.74274735e-08  1.00000000e+00  4.41346799e-08  1.00000000e+00
   4.10704999e-08  1.00000000e+00  3.82190599e-08  1.00000000e+00
   3.55655878e-08  1.00000000e+00  3.30963417e-08  1.00000000e+00
   3.07985317e-08  1.00000000e+00  2.86602511e-08  1.00000000e+00
   2.66704294e-08  1.00000000e+00  2.48187551e-08  1.00000000e+00
   2.30956392e-08  1.00000000e+00  2.14921574e-08  1.00000000e+00]] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

我们还为位置 10 获得了大小为512的位置编码向量,pos=10

PE(10)= 
[[-5.44021130e-01 -8.39071512e-01  1.18776485e-01 -9.92920995e-01
   6.92634165e-01 -7.21289039e-01  9.79174793e-01 -2.03019097e-01
   9.37632740e-01  3.47627431e-01  6.40478015e-01  7.67976522e-01
   2.09077001e-01  9.77899194e-01 -2.37917677e-01  9.71285343e-01
  -6.12936735e-01  7.90131986e-01 -8.67519796e-01  4.97402608e-01
  -9.87655997e-01  1.56638563e-01 -9.83699203e-01 -1.79821849e-012.73841977e-07  1.00000000e+00  2.54829672e-07  1.00000000e+00
   2.37137371e-07  1.00000000e+00  2.20673414e-07  1.00000000e+00
   2.05352507e-07  1.00000000e+00  1.91095296e-07  1.00000000e+00
   1.77827943e-07  1.00000000e+00  1.65481708e-07  1.00000000e+00
   1.53992659e-07  1.00000000e+00  1.43301250e-07  1.00000000e+00
   1.33352145e-07  1.00000000e+00  1.24093773e-07  1.00000000e+00
   1.15478201e-07  1.00000000e+00  1.07460785e-07  1.00000000e+00]] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

当我们直观地将 Vaswani 等人(2017 年)的函数翻译成 Python 并查看结果时,我们希望检查结果是否有意义。

用于单词嵌入的余弦相似度函数对于更好地可视化位置的接近度非常方便:

cosine_similarity(pos(2), pos(10))= [[0.8600013]] 
  • 1

单词 blackbrown 的位置之间的相似度以及词汇领域(一起使用的单词组)的相似度是不同的:

cosine_similarity(black, brown)= [[0.9998901]] 
  • 1

位置的编码显示出比单词嵌入相似度更低的相似度值。

位置编码已经将这些词分开。请记住,单词嵌入会随用于训练它们的语料库而变化。现在的问题是如何将位置编码添加到单词嵌入向量中。

将位置编码添加到嵌入向量中

Transformer 的作者们发现了一种简单的方法,只需将位置编码向量简单地添加到单词嵌入向量中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.8:位置编码

如果我们回过头来,例如,提取 black 的单词嵌入,然后将其命名为 y[1] = black,我们就可以将其添加到通过位置编码函数获得的位置向量 pe(2)中。我们将获得输入单词 black 的位置编码 pc(black):

pc(black) = y[1] + pe(2)

解决方案很简单。然而,如果我们按照所示应用它,我们可能会丢失单词嵌入的信息,这将被位置编码向量最小化。

有许多可能性来增加 y[1] 的价值,以确保单词嵌入层的信息可以在后续层中有效使用。

许多可能性之一是向 y[1],即 black 的单词嵌入添加一个任意值:

y[1] * math.sqrt(d_model)

现在我们可以将单词black的位置向量加到其嵌入向量中,它们都是相同的大小(512):

for i in range(0, 512,2):
          pe[0][i] = math.sin(pos / (10000 ** ((2 * i)/d_model)))
          pc[0][i] = (y[0][i]*math.sqrt(d_model))+ pe[0][i]

          pe[0][i+1] = math.cos(pos / (10000 ** ((2 * i)/d_model)))
          pc[0][i+1] = (y[0][i+1]*math.sqrt(d_model))+ pe[0][i+1] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

得到的结果是维度为d[model] = 512*的最终位置编码向量:

pc(black)=
[[ 9.09297407e-01 -4.16146845e-01  9.58144367e-01 -2.86285430e-01
   9.87046242e-01 -1.60435960e-01  9.99164224e-01 -4.08766568e-024.74274735e-08  1.00000000e+00  4.41346799e-08  1.00000000e+00
   4.10704999e-08  1.00000000e+00  3.82190599e-08  1.00000000e+00
   2.66704294e-08  1.00000000e+00  2.48187551e-08  1.00000000e+00
   2.30956392e-08  1.00000000e+00  2.14921574e-08  1.00000000e+00]] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

对单词brown和序列中的所有其他单词应用相同的操作。

我们可以将余弦相似性函数应用于blackbrown的位置编码向量:

cosine_similarity(pc(black), pc(brown))= [[0.9627094]] 
  • 1

现在,通过我们应用的三个表示单词blackbrown的余弦相似性函数,我们对位置编码过程有了清晰的认识:

[[0.99987495]] word similarity
[[0.8600013]] positional encoding vector similarity
[[0.9627094]] final positional encoding similarity 
  • 1
  • 2
  • 3

我们看到初始单词嵌入的相似性较高,值为0.99。然后我们看到位置编码向量的位置 2 和 10 使这两个单词的相似性值降低为0.86

最后,我们将每个单词的单词嵌入向量添加到其相应的位置编码向量中。我们发现,这使得两个单词的余弦相似度为0.96

每个单词的位置编码现在包含初始单词嵌入信息和位置编码值。

位置编码的输出导致了多头注意力子层。

子层 1:多头注意力

多头注意力子层包含八个头,并带有后层规范化,将在子层输出中添加残差连接并对其进行规范化:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.9:多头注意力子层

本节开始讲解注意力层的架构。接着,以 Python 中的一个小模块实现了多头注意力的示例。最后,描述了后层规范化。

让我们从多头注意力的架构开始。

多头注意力的架构

编码器堆叠的第一层的多头注意力子层的输入是包含每个单词的嵌入和位置编码的向量。堆叠的下一层不会重新开始这些操作。

输入序列每个单词x[n]的向量维度是 d[model] = 512:

pe(x[n])=[d[1]=9.09297407e^-01, d[2]=-4.16146845e^-01, …, d[512]=1.00000000e+00]

每个单词x[n]的表示现在已经变成了512维的向量 d[model] = 512。

每个单词都映射到所有其他单词,以确定它在序列中的位置。

在下面的句子中,我们可以看到它可能与序列中的catrug相关:

Sequence =The cat sat on the rug and it was dry-cleaned. 
  • 1

模型将训练以确定it是与cat还是rug相关联。我们可以通过使用当前的512维度训练该模型进行大量的计算。

但是,通过分析一个d[model]块的序列,我们只能得到一个观点。此外,使用现在的512维度将需要相当长的计算时间来找到其他观点。

一个更好的方法是将每个单词x[n]的512维度划分为864维度。

然后我们可以并行运行 8 个“头”来加速训练,并获得每个单词如何与另一个相关的 8 个不同表示子空间:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.10:多头表示

现在可以看到有8个并行运行的头。其中一个头可能认为itcat很合适,另一个认为itrug很合适,另一个认为rugdry-cleaned很合适。

每个头的输出是形状为x * d[k]的矩阵Z[i]。多头注意力输出Z定义为:

Z = (Z[0], Z[1], Z[2], Z[3], Z[4], Z[5], Z[6], Z[7])

然而,Z必须被连接,这样多头子层的输出不是尺寸的序列,而是xm * d[model]矩阵的一行。

在退出多头注意力子层之前,Z的元素被连接:

MultiHead(output) = Concat(Z[0], Z[1], Z[2], Z[3], Z[4], Z[5], Z[6], Z[7]) = xd[model]

请注意,每个头都被连接成一个具有维度d[model] = 512 的z。多头层的输出遵循原始 Transformer 模型的约束。

在注意机制的每个头h[n]内,“单词”矩阵有三种表示:

  • 一个查询矩阵(Q)的维度为d[q] = 64,它寻求所有“单词”矩阵的键-值对。

  • 一个键矩阵(K)的维度为d[k] = 64,它将被训练以提供一个注意力值。

  • 一个值矩阵(V)的维度为d[v] = 64,它将被训练以提供另一个注意力值。

注意力被定义为“缩放点积注意力”,它在下面的方程中表示,我们将QKV代入其中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所有矩阵都具有相同的维度,这样可以相对简单地使用缩放点积来获得每个头的注意力值,然后连接 8 个头的输出Z

要获得QKV,我们必须使用它们的权重矩阵Q[w]、K[w]和V[w]训练模型,它们具有d[k] = 64 列和d[model] = 512 行。例如,Q是通过xQ[w]的点积获得的。Q将具有d[k] = 64 的维度。

您可以修改所有参数,例如层数、头部、d[model]、d[k]和 Transformer 的其他变量,以适应您的模型。本章描述了由Vaswani等人(2017 年)提出的原始 Transformer 参数。在修改或探索其他人设计的原始模型变体之前,了解原始架构是至关重要的。

Google Brain Trax、OpenAI 和 Hugging Face 等提供了可供我们在本书中使用的即用型库。

然而,让我们打开 Transformer 模型的机制,并在 Python 中动手实现来说明我们刚刚探索的架构,以便可视化该模型的代码并用中间图形表示出来。

我们将使用基本的 Python 代码,只用numpy和一个softmax函数在 10 个步骤中运行注意力机制的关键方面。

请记住,工业 4.0 开发者将面临同一算法的多种架构挑战。

现在让我们开始构建我们模型的Step 1以表示输入。

步骤 1:表示输入

Multi_Head_Attention_Sub_Layer.ipynb保存到你的 Google Drive(确保你有一个 Gmail 帐户),然后在 Google Colaboratory 中打开它。笔记本在本章的 GitHub 存储库中。

我们将从仅使用最小的 Python 函数开始,以低层次理解 Transformer 的工作原理。我们将使用基本代码探索多头注意力子层的内部工作:

import numpy as np
from scipy.special import softmax 
  • 1
  • 2

我们正在构建的注意力机制的输入被缩小到d[模型]==4,而不是d[模型]=512。这将输入x的向量维度缩小到d[模型]=4,更容易可视化。

x包含每个具有 4 个维度的3个输入,而不是512个:

print("Step 1: Input : 3 inputs, d_model=4")
x =np.array([[1.0, 0.0, 1.0, 0.0],   # Input 1
             [0.0, 2.0, 0.0, 2.0],   # Input 2
             [1.0, 1.0, 1.0, 1.0]])  # Input 3
print(x) 
  • 1
  • 2
  • 3
  • 4
  • 5

输出显示我们有 3 个d[模型]=4 的向量:

Step 1: Input : 3 inputs, d_model=4
[[1\. 0\. 1\. 0.]
 [0\. 2\. 0\. 2.]
 [1\. 1\. 1\. 1.]] 
  • 1
  • 2
  • 3
  • 4

我们模型的第一步准备好了:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.11:多头注意力子层的输入

现在我们将权重矩阵添加到我们的模型中。

步骤 2:初始化权重矩阵

每个输入都有 3 个权重矩阵:

  • Q[w]用来训练查询

  • K[w]用来训练键

  • V[w]用来训练值

这三个权重矩阵将应用于模型中的所有输入。

Vaswani等人(2017)描述的权重矩阵是d[K]==64 维。但是,让我们将矩阵缩小到d[K]==3。维度被缩小到3*4权重矩阵以更容易可视化中间结果并与输入x执行点积。

这个教育笔记本中矩阵的大小和形状是任意的。目标是通过注意力机制的整个过程。

三个权重矩阵是从查询权重矩阵开始初始化的:

print("Step 2: weights 3 dimensions x d_model=4")
print("w_query")
w_query =np.array([[1, 0, 1],
                   [1, 0, 0],
                   [0, 0, 1],
                   [0, 1, 1]])
print(w_query) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

输出是w_query权重矩阵:

w_query
[[1 0 1]
 [1 0 0]
 [0 0 1]
 [0 1 1]] 
  • 1
  • 2
  • 3
  • 4
  • 5

现在我们将初始化关键权重矩阵:

print("w_key")
w_key =np.array([[0, 0, 1],
                 [1, 1, 0],
                 [0, 1, 0],
                 [1, 1, 0]])
print(w_key) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

输出是关键权重矩阵:

w_key
[[0 0 1]
 [1 1 0]
 [0 1 0]
 [1 1 0]] 
  • 1
  • 2
  • 3
  • 4
  • 5

最后,我们初始化值权重矩阵:

print("w_value")
w_value = np.array([[0, 2, 0],
                    [0, 3, 0],
                    [1, 0, 3],
                    [1, 1, 0]])
print(w_value) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

输出是值权重矩阵:

w_value
[[0 2 0]
 [0 3 0]
 [1 0 3]
 [1 1 0]] 
  • 1
  • 2
  • 3
  • 4
  • 5

我们模型的第二步准备好了:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.12:添加到模型中的权重矩阵

现在我们将通过输入向量乘以权重矩阵来获得QKV

步骤 3:矩阵乘法以获得 Q、K 和 V

我们现在将输入向量乘以权重矩阵,以获得每个输入的查询、键和值向量。

在这个模型中,我们假设所有输入都有一个w_queryw_keyw_value权重矩阵。其他方法也是可能的。

让我们首先将输入向量乘以w_query权重矩阵:

print("Step 3: Matrix multiplication to obtain Q,K,V")
print("Query: x * w_query")
Q=np.matmul(x,w_query)
print(Q) 
  • 1
  • 2
  • 3
  • 4

输出是Q[1]==64 的向量=[1, 0, 2],Q[2]=[2,2, 2],Q[3]=[2,1, 3]:

Step 3: Matrix multiplication to obtain Q,K,V
Query: x * w_query
[[1\. 0\. 2.]
 [2\. 2\. 2.]
 [2\. 1\. 3.]] 
  • 1
  • 2
  • 3
  • 4
  • 5

现在我们将输入向量乘以w_key权重矩阵:

print("Key: x * w_key")
K=np.matmul(x,w_key)
print(K) 
  • 1
  • 2
  • 3

我们得到K[1]= [0, 1, 1],K[2]= [4, 4, 0],以及K[3]= [2 ,3, 1]的向量:

Key: x * w_key
[[0\. 1\. 1.]
 [4\. 4\. 0.]
 [2\. 3\. 1.]] 
  • 1
  • 2
  • 3
  • 4

最后,我们将输入向量乘以w_value权重矩阵:

print("Value: x * w_value")
V=np.matmul(x,w_value)
print(V) 
  • 1
  • 2
  • 3

我们得到V[1]= [1, 2, 3],V[2]= [2, 8, 0],以及V[3]= [2 ,6, 3]的向量:

Value: x * w_value
[[1\. 2\. 3.]
 [2\. 8\. 0.]
 [2\. 6\. 3.]] 
  • 1
  • 2
  • 3
  • 4

我们模型的第三步准备好了:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.13:生成了QKV

我们有了需要计算注意力分数的QKV值。

步骤 4:缩放注意力分数

注意力头现在实现了原始的 Transformer 方程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

步骤 4 关注QK

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于这个模型,我们将四舍五入 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 = 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 = 1.75 为 1,并将值代入方程的QK部分:

print("Step 4: Scaled Attention Scores")
k_d=1   #square root of k_d=3 rounded down to 1 for this example
attention_scores = (Q @ K.transpose())/k_d
print(attention_scores) 
  • 1
  • 2
  • 3
  • 4

中间结果显示为:

Step 4: Scaled Attention Scores
[[ 2\.  4\.  4.]
 [ 4\. 16\. 12.]
 [ 4\. 12\. 10.]] 
  • 1
  • 2
  • 3
  • 4

步骤 4 现在已完成。例如,x[1]的分数为[2,4,4]跨越了K向量的头部显示为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.14:输入#1 的缩放注意力分数

现在注意力方程将为每个向量的中间分数应用 softmax。

步骤 5:每个向量的缩放 softmax 注意力分数

现在我们对每个中间注意力分数应用 softmax 函数。与进行矩阵乘法不同,让我们放大到每个单独的向量:

print("Step 5: Scaled softmax attention_scores for each vector")
attention_scores[0]=softmax(attention_scores[0])
attention_scores[1]=softmax(attention_scores[1])
attention_scores[2]=softmax(attention_scores[2])
print(attention_scores[0])
print(attention_scores[1])
print(attention_scores[2]) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们为每个向量得到了缩放的 softmax 注意力分数:

Step 5: Scaled softmax attention_scores for each vector
[0.06337894 0.46831053 0.46831053]
[6.03366485e-06 9.82007865e-01 1.79861014e-02]
[2.95387223e-04 8.80536902e-01 1.19167711e-01] 
  • 1
  • 2
  • 3
  • 4

步骤 5 现在已完成。例如,所有键的x[1]的分数的 softmax 是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.15:所有键的输入#1 的 softmax 分数

现在我们可以用完整的方程计算最终的注意力值。

步骤 6:最终的注意力表示

现在我们可以通过将V代入来完成注意力方程:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们首先计算输入x[1]对步骤 67 的注意力分数。我们为一个词向量计算一个注意力值。当我们到达步骤 8 时,我们将将注意力计算推广到另外两个输入向量。

为了获得x[1]的 Attention(Q,K,V),我们将中间注意力分数逐个与 3 个值向量相乘,以放大方程的内部工作:

print("Step 6: attention value obtained by score1/k_d * V")
print(V[0])
print(V[1])
print(V[2])
print("Attention 1")
attention1=attention_scores[0].reshape(-1,1)
attention1=attention_scores[0][0]*V[0]
print(attention1)
print("Attention 2")
attention2=attention_scores[0][1]*V[1]
print(attention2)
print("Attention 3")
attention3=attention_scores[0][2]*V[2]
print(attention3)
Step 6: attention value obtained by score1/k_d * V
[1\. 2\. 3.]
[2\. 8\. 0.]
[2\. 6\. 3.]
Attention 1
[0.06337894 0.12675788 0.19013681]
Attention 2
[0.93662106 3.74648425 0\.        ]
Attention 3
[0.93662106 2.80986319 1.40493159] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

步骤 6 完成。例如,已计算了每个输入的x[1]的 3 个注意力值:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.16:注意力表示

现在需要将注意力值相加。

步骤 7:将结果求和

输入#1 的 3 个注意力值现在将被求和以获得输出矩阵的第一行:

print("Step 7: summed the results to create the first line of the output matrix")
attention_input1=attention1+attention2+attention3
print(attention_input1) 
  • 1
  • 2
  • 3

输出是输入#1 的输出矩阵的第一行:

Step 7: summed the results to create the first line of the output matrix
[1.93662106 6.68310531 1.59506841]] 
  • 1
  • 2

第二行将是下一个输入,例如输入#2 的输出。

我们可以在图 2.17中看到x[1]的求和注意力值:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.17:一个输入的求和结果

我们已完成了输入#1 的步骤。现在我们需要将所有输入的结果添加到模型中。

步骤 8:所有输入的步骤 1 至步骤 7

现在,Transformers可以使用与步骤 1 到步骤 7 描述的相同方法产生输入#2 和输入#3 的注意力值,用于一个注意力头。

从这一步开始,我们假设有 3 个学习权重的注意力值,d[model] = 64。现在我们想看到这些原始维度在达到子层输出时是什么样子的。

我们已经详细了解了小模型中的注意力表示过程。让我们直接看结果,假设我们已经生成了 3 个d[model]=64 维的注意力表示:

print("Step 8: Step 1 to 7 for inputs 1 to 3")
#We assume we have 3 results with learned weights (they were not trained in this example)
#We assume we are implementing the original Transformer paper.We will have 3 results of 64 dimensions each
attention_head1=np.random.random((3, 64))
print(attention_head1) 
  • 1
  • 2
  • 3
  • 4
  • 5

以下输出显示了z[0]的模拟,它代表了头 1 的d[model]=64 维的 3 个输出向量:

Step 8: Step 1 to 7 for inputs 1 to 3
[[0.31982626 0.99175996(61 squeezed values)0.16233212]
 [0.99584327 0.55528662(61 squeezed values)0.70160307]
 [0.14811583 0.50875291(61 squeezed values)0.83141355]] 
  • 1
  • 2
  • 3
  • 4

运行笔记本时,由于向量生成的随机性质,结果将有所不同。

现在,Transformers有一个头的输入的输出向量。下一步是生成 8 个头的输出,以创建注意力子层的最终输出。

步骤 9:注意力子层的头部输出

我们假设我们已经训练了注意力子层的 8 个头。现在,Transformers有 3 个输出向量(3 个输入向量,即单词或词片段)每个d[model] = 64 维:

print("Step 9: We assume we have trained the 8 heads of the attention sublayer")
z0h1=np.random.random((3, 64))
z1h2=np.random.random((3, 64))
z2h3=np.random.random((3, 64))
z3h4=np.random.random((3, 64))
z4h5=np.random.random((3, 64))
z5h6=np.random.random((3, 64))
z6h7=np.random.random((3, 64))
z7h8=np.random.random((3, 64))
print("shape of one head",z0h1.shape,"dimension of 8 heads",64*8) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

输出显示了一个头的形状:

Step 9: We assume we have trained the 8 heads of the attention sublayer
shape of one head (3, 64) dimension of 8 heads 512 
  • 1
  • 2

这 8 个头现在产生了Z

Z = (Z[0], Z[1], Z[2], Z[3], Z[4], Z[5], Z[6], Z[7])

Transformers将现在连接Z的 8 个元素以产生多头注意力子层的最终输出。

步骤 10:连接头部的输出

Transformers连接了Z的 8 个元素:

MultiHead(Output) = Concat (Z[0], Z[1], Z[2], Z[3], Z[4], Z[5], Z[6], Z[7]) W⁰ = xd[model]

请注意,Z被乘以W⁰,它是一个被训练的权重矩阵。在这个模型中,我们将假设W⁰是被训练并集成到连接函数中的。

Z[0]到Z[7]被连接在一起:

print("Step 10: Concantenation of heads 1 to 8 to obtain the original 8x64=512 ouput dimension of the model")
output_attention=np.hstack((z0h1,z1h2,z2h3,z3h4,z4h5,z5h6,z6h7,z7h8))
print(output_attention) 
  • 1
  • 2
  • 3

输出是Z的连接:

Step 10: Concatenation of heads 1 to 8 to obtain the original 8x64=512 output dimension of the model
[[0.65218495 0.11961095 0.9555153  ... 0.48399266 0.80186221 0.16486792]
 [0.95510952 0.29918492 0.7010377  ... 0.20682832 0.4123836  0.90879359]
 [0.20211378 0.86541746 0.01557758 ... 0.69449636 0.02458972 0.889699  ]] 
  • 1
  • 2
  • 3
  • 4

连接可以可视化为将Z的元素并排堆叠:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.18:注意力子层输出

连接产生了一个标准的d[model]=512 维的输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.19:连接 8 个头的输出

现在,层归一化将处理注意力子层。

后层归一化

每个Transformers的注意力子层和每个前馈子层之后都跟随后层归一化Post-LN):

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.20:后层归一化

后层归一化包含加法函数和层归一化过程。加法函数处理来自子层输入的残余连接。残余连接的目标是确保关键信息不会丢失。后层归一化或层归一化可以描述如下:

LayerNormalization (x + Sublayer(x))

Sublayer(x) 是子层本身。xSublayer(x) 输入步骤的可用信息。

LayerNormalization 的输入是由 x + Sublayer(x) 产生的向量 v。Transformer 的每个输入和输出都为 d[model] = 512,这标准化了所有过程。

存在许多层归一化方法,且从一个模型到另一个模型存在变化。v = x + Sublayer(x) 的基本概念可以由 LayerNormalization (v) 定义:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

变量是:

  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 是维度 dv 的均值。因此:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 是维度 d 的标准差 v。因此:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 是一个缩放参数。

  • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 是一个偏置向量。

这个版本的 LayerNormalization (v) 展示了许多可能的后归一化方法的基本思想。下一个子层现在可以处理后归一化或 LayerNormalization (v) 的输出。在这种情况下,子层是一个前馈网络。

子层 2:前馈网络

前馈网络 (FFN) 的输入是前一个子层的后归一化输出 d[model] = 512:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.21:前馈子层

FFN 子层可以描述如下:

  • 编码器和解码器中的 FFN 是全连接的。

  • FFN 是一种逐位置的网络。每个位置都是分别且相同方式处理的。

  • FFN 包含两个层,并应用 ReLU 激活函数。

  • FFN 层的输入和输出为 d[model] = 512,但内部层更大,为 d[ff] =2048。

  • FFN 可以被视为使用大小为 1 的卷积执行两次。

考虑到这个描述,我们可以描述优化和标准化的 FFN 如下:

FFN(x) = max (0, xW[1] + b[1]) W[2]+b[2]

FFN 的输出经过了后归一化,如前一节所述。然后输出被发送到编码器堆栈的下一层和解码器堆栈的多头注意力层。

现在让我们来探索解码器堆栈。

解码器堆栈

Transformer 模型的解码器层是与编码器层类似的层堆栈。每个解码器堆栈层具有以下结构:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2.22:Transformer 解码器堆栈的一层

对于 Transformer 模型的所有 N=6 层,解码器层的结构与编码器相同。每一层包含三个子层:多头掩码注意力机制,多头注意力机制和全连接的逐位置前馈网络。

解码器有一个第三个主要子层,即遮罩式多头注意力机制。在此子层的输出中,在给定位置,随后的单词被掩盖,以便 Transformer 在不看到序列的其余部分的情况下基于其推断。因此,在这个模型中,它无法看到序列的未来部分。

每个 Transformer 模型中的三个主要子层都被一个残差连接,Sublayer(x),像编码器堆栈中一样:

层归一化 (x + Sublayer(x))

嵌入层子层只存在于堆栈的底层,就像编码器堆栈一样。解码器堆栈的每个子层的输出都具有恒定的维度,d[model],就像编码器堆栈一样,包括嵌入层和残差连接的输出。

我们可以看到,设计者们努力创建了对称的编码器和解码器堆栈。

每个子层的结构和解码器功能与编码器类似。在本节中,当我们需要时,我们可以参考编码器来获得相同的功能。我们只关注解码器和编码器之间的差异。

输出嵌入和位置编码

解码器子层的结构与编码器子层的大部分相同。输出嵌入层和位置编码函数与编码器堆栈中的相同。

在我们通过 Vaswani 等人 (2017) 提出的模型中探索 Transformer 的用法时,输出是我们需要学习的翻译。我选择使用法语翻译:

Output=Le chat noir était assis sur le canapé et le chien marron dormait sur le tapis 
  • 1

这个输出是英文输入句子的法语翻译:

Input=The black cat sat on the couch and the brown dog slept on the rug. 
  • 1

输出的词经过词嵌入层,然后是位置编码函数,就像编码器堆栈的第一层一样。

让我们看一下解码器堆栈的多头注意力层的具体属性。

注意力层

Transformer 是一个自回归模型。它使用先前的输出序列作为额外的输入。解码器的多头注意力层使用与编码器相同的过程。

然而,遮罩式多头注意力子层 1 只允许注意力应用到当前位置及其之前的位置。未来的词对 Transformer 是隐藏的,这迫使它学会预测。

在遮罩式多头注意力子层 1 之后,像在编码器中一样,遵循一个后层归一化过程。

多头注意力子层 2 也只关注到 Transformer 正在预测的当前位置之前的位置,以避免看到它必须预测的序列。

多头注意力子层 2 从编码器中汲取信息,在点积注意力操作期间考虑编码器 (K, V)。该子层还从掩码的多头注意力子层 1(掩码关注)中汲取信息,在点积注意力操作期间还考虑子层 1(Q)。因此,解码器使用编码器的训练信息。我们可以将解码器的自注意力多头子层的输入定义为:

输入 _ 注意力 = (输出 _ 解码器 _ 子层 – 1(Q), 输出 _ 编码器 _ 层(K, V))

在编码器后,会跟随一个后层标准化过程。

然后 Transformer 会进入 FFN 子层,然后是一个后层标准化和线性层。

FFN 子层,后层标准化和线性层

FFN 子层的结构与编码器堆栈的 FFN 相同。FFN 的后层标准化像编码器堆栈的层标准化一样工作。

Transformer 仅每次产生一个元素的输出序列:

输出序列 = (y[1], y[2], … y[n])

线性层产生一个输出序列,其线性函数因模型而异,但依赖于标准方法:

y =w * x + b

wb是学习参数。

线性层因此会产生序列的下一个可能元素,softmax 函数将其转换为概率元素。

解码器层,如编码器层,将从层ll+1,一直到6层 Transformer 堆叠的顶层。

现在让我们看一下 Transformer 是如何训练的以及其性能如何。

训练和性能

最初的 Transformer 是在一个 450 万句对的英德数据集和一个 3600 万句对的英法数据集上进行训练的。

这些数据集来自机器翻译研讨会 (WMT),如果你希望探索 WMT 数据集,可以在以下链接找到:www.statmt.org/wmt14/

最初的 Transformer 基础模型在装有 8 个 NVIDIA P100 GPU 的计算机上进行了 100,000 步的训练,用时 12 个小时。大型模型则用了 3.5 天进行了 300,000 步的训练。

最初的 Transformer 在 WMT 英法数据集上取得了 41.8 的 BLEU 分数,优于所有以前的机器翻译模型。

BLEU 代表双语评估助手。它是一个评估机器翻译结果质量的算法。

Google 研究和 Google Brain 团队应用了优化策略来提高 Transformer 的性能。例如,使用了 Adam 优化器,但学习率通过首先经历线性增长的预热阶段,然后在之后降低。

应用了不同类型的正则化技术,如残差丢失和丢失,到嵌入之和上。此外,Transformer 应用了标签平滑,避免了通过过于自信的单热输出过拟合。它引入了更不准确的评估,并迫使模型更多、更好地训练。

几种其他 Transformer 模型变体导致了其他模型和用法,我们将在随后的章节中探讨。

在结束本章之前,让我们感受一下 Hugging Face 中可立即使用的变换器模型的简单性,例如。

Hugging Face 中的变换器模型

这一章中所见的一切都可以压缩成一个可立即使用的 Hugging Face 变换器模型。

通过 Hugging Face,你可以用三行代码实现机器翻译!

在 Google Colaboratory 中打开 Multi_Head_Attention_Sub_Layer.ipynb。将笔记本保存到您的 Google Drive 中(确保您有一个 Gmail 帐户)。转到最后两个单元格。

我们首先确保安装了 Hugging Face transformers:

!pip -q install transformers 
  • 1

第一个单元导入了包含几个变换器用法的 Hugging Face 管道:

#@title Retrieve pipeline of modules and choose English to French translation
from transformers import pipeline 
  • 1
  • 2

然后我们实现 Hugging Face 管道,其中包含了可立即使用的功能。在我们的案例中,为了说明本章的 Transformer 模型,我们激活了翻译模型并输入一句要从英语翻译成法语的句子:

translator = pipeline("translation_en_to_fr")
#One line of code!
print(translator("It is easy to translate languages with transformers", max_length=40)) 
  • 1
  • 2
  • 3

voilà!翻译显示出来了:

[{'translation_text': 'Il est facile de traduire des langues à l'aide de transformateurs.'}] 
  • 1

Hugging Face 展示了变换器架构如何在可立即使用的模型中使用。

摘要

在本章中,我们首先通过研究变换器架构可以揭示的令人震惊的远距离依赖关系入门。变换器可以执行从书面和口头序列到有意义表示的转换,这在自然语言理解NLU)的历史上前所未有。

这两个维度,转换的扩展和实现的简化,将人工智能推向了前所未有的水平。

我们探索了从转换问题和序列建模中移除 RNN、LSTM 和 CNN 的大胆方法,以构建 Transformer 架构。编码器和解码器的标准化维度的对称设计使得从一个子层到另一个子层的流程几乎无缝连接。

我们看到,除了移除循环网络模型外,变换器还引入了并行化层,减少了训练时间。我们还发现了其他创新,例如位置编码和掩码多头注意力。

灵活的、原始的 Transformer 架构为许多其他创新变体提供了基础,为更强大的转换问题和语言建模打开了道路。

在接下来的章节中,当描述原始模型的许多变体时,我们将更深入地了解 Transformer 架构的一些方面。

Transformer 的到来标志着一个新一代即用型人工智能模型的开始。例如,Hugging Face 和 Google Brain 使人工智能易于使用,只需几行代码即可实现。

在下一章节,Fine-Tuning BERT Models,我们将探讨原始 Transformer 模型的强大进化。

问题

  1. NLP 转换可以编码和解码文本表示。(真/假)

  2. 自然语言理解NLU)是自然语言处理NLP)的子集。(真/假)

  3. 语言建模算法根据输入序列生成可能的单词序列。(真/假)

  4. 一个 Transformer 是一个定制的 LSTM,带有一个 CNN 层。(真/假)

  5. 一个 Transformer 不包含 LSTM 或 CNN 层。(真/假)

  6. 注意力机制检查序列中的所有标记,而不仅仅是最后一个。(真/假)

  7. 一个 Transformer 使用位置向量,而不是位置编码。(真/假)

  8. 一个 Transformer 包含一个前馈网络。(真/假)

  9. Transformer 解码器的掩码多头注意力组件防止算法解析给定位置的序列其余部分正在处理的部分。(真/假)

  10. Transformers 能够比 LSTM 更好地分析长距离依赖关系。(真/假)

参考文献

加入我们书籍的 Discord 空间

加入书籍的 Discord 工作区,与作者进行每月的问我任何事会话:

www.packt.link/Transformers

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第三章:微调 BERT 模型

第二章开始使用Transformers模型的架构中,我们定义了原始 Transformer 架构的构建块。 将原始 Transformer 想象成用乐高^® 砖块构建的模型。 构建套件包含编码器、解码器、嵌入层、位置编码方法、多头注意力层、掩码多头注意力层、后层规范化、前馈子层和线性输出层等砖块。

砖块有各种大小和形状。你可以花几个小时使用相同的建筑套件构建各种模型!一些构建只需要一些砖块。其他构建将添加一个新的部件,就像我们使用乐高^® 组件构建模型时获得额外的砖块一样。

BERT 向 Transformer 构建套件添加了一个新部件:双向多头注意力子层。 当我们人类在理解一个句子时遇到问题时,我们不仅仅查看过去的单词。 BERT 像我们一样,同时查看同一句子中的所有单词。

本章将首先探讨来自Transformers的双向编码表示BERT)的架构。 BERT 以一种新颖的方式仅使用Transformers的编码器块,并且不使用解码器堆栈。

然后,我们将对预训练的 BERT 模型进行微调。 我们将对将进行微调的 BERT 模型进行预训练,该模型由第三方训练并上传到 Hugging Face。 Transformers可以进行预训练。 然后,例如,可以在几个 NLP 任务上对预训练的 BERT 进行微调。 我们将通过使用 Hugging Face 模块进行下游 Transformer 使用的这种迷人经历。

本章涵盖以下主题:

  • 来自Transformers的双向编码表示(BERT)

  • BERT 的架构

  • 两步 BERT 框架

  • 准备预训练环境

  • 定义预训练编码器层

  • 定义微调

  • 下游多任务处理

  • 构建微调的 BERT 模型

  • 加载可接受性判断数据集

  • 创建注意力掩码

  • BERT 模型配置

  • 衡量微调模型的性能

我们的第一步将是探索 BERT 模型的背景。

BERT 的架构

BERT 将双向注意引入了Transformers模型。 双向注意需要对原始 Transformer 模型进行许多其他更改。

我们将不会讨论第二章开始使用Transformers模型的架构中描述的Transformers构建块。 您可以随时查阅第二章,以查看有关Transformers构建块的某个方面的评论。 在本节中,我们将重点关注 BERT 模型的特定方面。

我们将重点关注Devlin等人(2018)设计的演进,描述了编码器堆栈。 我们将首先通过编码器堆栈,然后通过预训练输入环境的准备来描述 BERT 的两步框架:预训练和微调。

让我们首先探索编码器堆栈。

编码器堆栈

我们将从原始变换器模型中采用的第一个构建块是编码器层。正如在第二章 变换器模型架构入门 中描述的那样,编码器层显示在图 3.1中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.1:编码器层

BERT 模型不使用解码器层。BERT 模型具有编码器堆栈,但没有解码器堆栈。掩码的标记(隐藏要预测的标记)位于编码器的注意力层中,我们将在后续部分中放大 BERT 编码器层时看到。

原始变换器包含一个N=6 层的堆栈。原始变换器的维度数为d[model] = 512。原始变换器的注意力头数为A=8。原始变换器的头部维度是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

BERT 编码器层比原始变换器模型更大。

两个 BERT 模型可以使用编码器层构建:

  • BERT[BASE],其中包含N=12 编码器层。d[model] = 768,也可以表示为H=768,就像 BERT 论文中所述。多头注意力子层包含A=12 个头。每个头的维度z[A]与原始变换器模型中保持为 64:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 在连接之前,每个多头注意力子层的输出将是 12 个头的输出:

output_multi-head_attention={z[0], z[1], z[2],…, z[11]}

  • BERT[LARGE],其中包含N=24 编码器层。d[model] = 1024。多头注意力子层包含A=16 个头。每个头的维度z[A]与原始变换器模型中保持为 64:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 在连接之前,每个多头注意力子层的输出将是 16 个头的输出:

output_multi-head_attention={z[0], z[1], z[2],…, z[15]}

模型的大小可以总结如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.2:变换器模型

BERT 模型不仅限于这三种配置。这三种配置说明了 BERT 模型的主要方面。有许多变种是可能的。

尺寸和维度在 BERT 风格的预训练中起着至关重要的作用。BERT 模型就像人类一样。BERT 模型随着更多的工作内存(维度)和更多的知识(数据)产生更好的结果。学习大量数据的大型变换器模型将为下游自然语言处理任务更好地进行预训练。

让我们进入第一个子层,看看 BERT 模型中输入嵌入和位置编码的基本方面。

准备预训练输入环境

BERT 模型没有解码器层的堆栈。因此,它没有一个掩码的多头注意力子层。BERT 设计者指出,掩码了其余序列的掩码的多头注意力层妨碍了注意力过程。

掩码的多头注意力层掩盖了超出当前位置的所有标记。 例如,采用以下句子:

The cat sat on it because it was a nice rug. 
  • 1

如果我们刚到达单词it,编码器的输入可以是:

The cat sat on it<masked sequence> 
  • 1

这种方法的动机是防止模型看到它应该预测的输出。 这种从左到右的方法可以产生相当好的结果。

然而,模型不能通过这种方式学到很多。 要知道it指的是什么,我们需要看整个句子,达到rug这个词,并弄清楚it指的是地毯。

BERT 的作者想到了一个主意。为什么不预训练模型使用不同的方法来进行预测?

BERT 的作者提出了双向注意力,让一个注意力头同时从左到右和从右到左关注所有单词。 换句话说,编码器的自注意力屏蔽可以在不受解码器的掩码多头注意力子层的阻碍的情况下完成任务。

该模型经过了两项任务的训练。 第一种方法是掩码语言建模MLM)。 第二种方法是下一句预测NSP)。

让我们从掩码语言建模开始。

掩码语言建模

掩码语言建模不需要训练模型,即可预测由可见单词序列后跟掩码序列。

BERT 引入了对句子的双向分析,其中对句子的一个单词进行随机掩盖。

需要注意的是,BERT 应用了WordPiece,一种子词分词的方法,以及学习的位置编码,而不是正弦-余弦方法。

一个潜在的输入序列可能是:

The cat sat on it because it was a nice rug. 
  • 1

当模型到达单词it后,解码器将掩码注意力序列:

The cat sat on it <masked sequence>. 
  • 1

但是 BERT 编码器会将一个随机标记进行掩码以进行预测:

The cat sat on it [MASK] it was a nice rug. 
  • 1

多重注意力子层现在可以看到整个序列,运行自注意力过程,并预测被掩码的标记。

输入标记以一种棘手的方式进行了掩码,以便让模型训练时间更长,但产生更好的结果,采用三种方法:

  • 在数据集的 10%中,出其不意地不掩盖一个标记; 例如:

    The cat sat on it [because] it was a nice rug. 
    
    • 1
  • 在数据集的 10%中,用随机标记替换标记来出其不意; 例如:

    The cat sat on it [often] it was a nice rug. 
    
    • 1
  • 在 80%的数据集中,将一个标记替换为[MASK]标记; 例如:

    The cat sat on it [MASK] it was a nice rug. 
    
    • 1

作者们大胆的方法避免了过拟合,并迫使模型高效训练。

BERT 还经过训练用于执行下一句预测。

下一句预测

第二种训练 BERT 的方法是下一句预测NSP)。输入包含两个句子。 在 50%的情况下,第二个句子是文档的实际第二句。 在 50%的情况下,第二个句子是随机选择的,与第一个句子无关。

添加了两个新标记:

  • [CLS]是一个二元分类标记,添加到第一个序列的开头,以预测第二个序列是否跟随第一个序列。正样本通常是从数据集中取出的连续句子对。负样本是使用来自不同文档的序列创建的。

  • [SEP]是一个分隔标记,表示序列的结束。

例如,从一本书中摘录的输入句子可能是:

The cat slept on the rug. It likes sleeping all day. 
  • 1

这两个句子将成为一个完整的输入序列:

[CLS] the cat slept on the rug [SEP] it likes sleep ##ing all day[SEP] 
  • 1

这种方法需要额外的编码信息来区分序列 A 和序列 B。

如果我们将整个嵌入过程放在一起,我们得到:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.3:输入嵌入

输入嵌入由标记嵌入、段(句子、短语、单词)嵌入和位置编码嵌入求和得到。

BERT 模型的输入嵌入和位置编码子层可以总结如下:

  • 一系列单词被分解成WordPiece标记。

  • 一个[MASK]标记将随机替换初始的单词标记,用于掩码语言建模训练。

  • 为了分类目的,在序列的开头插入一个[CLS]分类标记。

  • 一个[SEP]标记分隔两个句子(段落、短语)用于 NSP 训练。

  • 将句子嵌入添加到标记嵌入中,使句子 A 的句子嵌入值与句子 B 不同。

  • 学习位置编码。原始 Transformer 的正弦-余弦位置编码方法没有被应用。

还有一些额外的关键特性:

  • BERT 在其多头注意力子层中使用双向注意力,打开了广阔的学习视野,并理解标记之间的关系。

  • BERT 引入了无监督嵌入的场景,使用未标记的文本预训练模型。无监督场景迫使模型在多头注意力学习过程中更加努力思考。这使得 BERT 学习语言的构建方式,并将这些知识应用到下游任务中,而不必每次都进行预训练。

  • BERT 也使用监督学习,在预训练过程中涵盖了所有基础知识。

BERT 改进了 Transformer 的训练环境。现在让我们看看预训练的动机以及它如何帮助微调过程。

预训练和微调 BERT 模型

BERT 是一个两步框架。第一步是预训练,第二步是微调,如图 3.4所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.4:BERT 框架

训练一个 Transformer 模型可能需要几个小时,甚至几天。设计架构和参数以及选择适当的数据集来训练 Transformer 模型需要相当长的时间。

预训练是 BERT 框架的第一步,可以分解为两个子步骤:

  • 定义模型的架构:层数、头数、维度和模型的其他构建模块

  • MLMNSP任务上训练模型

BERT 框架的第二步是微调,也可以分解为两个子步骤:

  • 用预训练的 BERT 模型的训练参数初始化所选的下游模型

  • 针对特定下游任务(如文本蕴涵识别RTE),问答(SQuAD v1.1SQuAD v2.0)和带有对抗生成的情境SWAG))微调参数

在本节中,我们介绍了微调 BERT 模型所需的信息。在接下来的章节中,我们将更深入地探讨本节提出的主题:

  • 第四章从头开始预训练 RoBERTa 模型中,我们将从头开始预训练一个类似 BERT 的模型的 15 个步骤。我们甚至会编译我们的数据,训练一个分词器,然后训练模型。这一章旨在首先讨论 BERT 的特定构建模块,然后微调现有模型。

  • 第五章变形金刚进行下游自然语言处理任务中,我们将遍历许多下游任务,探索GLUESQuAD v1.1SQuADSWAG和其他几个自然语言处理评估数据集。我们将运行几个下游变形金刚模型来说明关键任务。本章的目标是微调一个下游模型。

  • 第七章GPT-3 引擎的超人变形金刚的崛起中,将探讨 OpenAI GPT-2GPT-3变形金刚的架构和使用方法。BERT[BASE]被配置得接近于 OpenAI GPT,以展示它产生了更好的性能。然而,OpenAI 变形金刚也在不断进化!我们将看到它们如何达到了超人类的自然语言处理水平。

在本章中,我们将对语言可接受性语料库CoLA)上进行微调的 BERT 模型进行训练。下游任务基于Alex WarstadtAmanpreet SinghSamuel R. Bowman神经网络可接受性判断

我们将微调一个 BERT 模型,该模型将确定一个句子的语法可接受性。微调后的模型将获得一定水平的语言能力。

我们已经讨论了 BERT 的架构及其预训练和微调框架。现在让我们来微调一个 BERT 模型。

微调 BERT

本节将微调一个 BERT 模型,以预测可接受性判断的下游任务,并使用马修斯相关系数MCC)来衡量预测,这将在本章的使用马修斯相关系数进行评估部分进行解释。

在 Google Colab 中打开BERT_Fine_Tuning_Sentence_Classification_GPU.ipynb(确保你有一个电子邮箱账户)。笔记本在本书的 GitHub 存储库的Chapter03中。

笔记本中每个单元格的标题也与本章的每个小节的标题相同或非常接近。

我们将首先探讨为什么变形金刚模型必须考虑硬件约束。

硬件约束

Transformer 模型需要多处理硬件。在 Google Colab 的运行时菜单中,选择更改运行时类型,并在硬件加速器下拉列表中选择GPU

Transformer 模型是硬件驱动的。我建议在继续本章之前阅读附录 IITransformer 模型的硬件约束

程序将使用 Hugging Face 模块,接下来我们将安装它们。

安装 Hugging Face PyTorch 接口的 BERT

Hugging Face 提供了一个预训练的 BERT 模型。Hugging Face 开发了一个名为PreTrainedModel的基类。通过安装这个类,我们可以从预训练模型配置中加载模型。

Hugging Face 提供了 TensorFlow 和 PyTorch 模块。我建议开发者对两种环境都要熟悉。出色的人工智能研究团队会使用其中一种或两种环境。

在本章中,我们将按照以下方式安装所需的模块:

#@title Installing the Hugging Face PyTorch Interface for Bert
!pip install -q transformers 
  • 1
  • 2

安装将运行,或者满足要求的消息将被显示。

现在我们可以导入程序所需的模块。

导入模块

我们将导入所需的预训练模块,如预训练的BERT tokenizer和 BERT 模型的配置。导入了BERTAdam优化器以及序列分类模块:

#@title Importing the modules
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler
from keras.preprocessing.sequence import pad_sequences
from sklearn.model_selection import train_test_split
from transformers import BertTokenizer, BertConfig
from transformers import AdamW, BertForSequenceClassification, get_linear_schedule_with_warmup 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

tqdm导入一个漂亮的进度条模块:

from tqdm import tqdm, trange 
  • 1

现在我们可以导入广泛使用的标准 Python 模块:

import pandas as pd
import io
import numpy as np
import matplotlib.pyplot as plt
% matplotlib inline 
  • 1
  • 2
  • 3
  • 4
  • 5

如果一切顺利,不会显示任何消息,需要记住 Google Colab 已经在我们使用的虚拟机上预先安装了这些模块。

指定 CUDA 作为 torch 的设备

现在我们将指定 torch 使用计算统一设备架构CUDA)来利用 NVIDIA 卡的并行计算能力,为我们的多头注意力模型工作:

#@title Harware verification and device attribution
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
!nvidia-smi 
  • 1
  • 2
  • 3

输出可能会随 Google Colab 的配置而变化。请参阅附录 IITransformer 模型的硬件约束,了解解释和截图。

现在我们将加载数据集。

加载数据集

现在我们将基于 Warstadt等人(2018 年)的论文加载 CoLA

通用语言理解评估GLUE)将语言可接受性视为首要的自然语言处理任务。在第五章使用 Transformer 的下游自然语言处理任务中,我们将探讨 Transformer 必须执行的关键任务,以证明其效率。

笔记本中的以下单元格会自动下载必要的文件:

import os
!curl -L https://raw.githubusercontent.com/Denis2054/Transformers-for-NLP-2nd-Edition/master/Chapter03/in_domain_train.tsv --output "in_domain_train.tsv"
!curl -L https://raw.githubusercontent.com/Denis2054/Transformers-for-NLP-2nd-Edition/master/Chapter03/out_of_domain_dev.tsv --output "out_of_domain_dev.tsv" 
  • 1
  • 2
  • 3

你应该看到它们出现在文件管理器中:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.5:上传数据集

现在程序将加载数据集:

#@title Loading the Dataset
#source of dataset : https://nyu-mll.github.io/CoLA/
df = pd.read_csv("in_domain_train.tsv", delimiter='\t', header=None, names=['sentence_source', 'label', 'label_notes', 'sentence'])
df.shape 
  • 1
  • 2
  • 3
  • 4

输出显示了我们导入的数据集的形状:

(8551, 4) 
  • 1

显示了 10 行样本以可视化可接受性判断任务,并查看序列是否合理:

df.sample(10) 
  • 1

输出显示了 10行标记数据集,每次运行后可能会更改:

sentence_sourcelabellabel_notessentence

|

1742 
  • 1

|

r-67 
  • 1

|

1 
  • 1

|

NaN 
  • 1

|

they said that tom would n't pay up , but pay… 
  • 1

|

|

937 
  • 1

|

bc01 
  • 1

|

1 
  • 1

|

NaN 
  • 1

|

although he likes cabbage too , fred likes egg… 
  • 1

|

|

5655 
  • 1

|

c_13 
  • 1

|

1 
  • 1

|

NaN 
  • 1

|

wendy 's mother country is iceland . 
  • 1

|

|

500 
  • 1

|

bc01 
  • 1

|

0 
  • 1

|

* 
  • 1

|

john is wanted to win . 
  • 1

|

|

4596 
  • 1

|

ks08 
  • 1

|

1 
  • 1

|

NaN 
  • 1

|

i did n't find any bugs in my bed . 
  • 1

|

|

7412 
  • 1

|

sks13 
  • 1

|

1 
  • 1

|

NaN 
  • 1

|

the girl he met at the departmental party will... 
  • 1

|

|

8456 
  • 1

|

ad03 
  • 1

|

0 
  • 1

|

* 
  • 1

|

peter is the old pigs . 
  • 1

|

|

744 
  • 1

|

bc01 
  • 1

|

0 
  • 1

|

* 
  • 1

|

frank promised the men all to leave . 
  • 1

|

|

5420 
  • 1

|

b_73 
  • 1

|

0 
  • 1

|

* 
  • 1

|

i 've seen as much of a coward as frank . 
  • 1

|

|

5749 
  • 1

|

c_13 
  • 1

|

1 
  • 1

|

NaN 
  • 1

|

we drove all the way to buenos aires . 
  • 1

|

.tsv文件中每个样本包含四个以制表符分隔的列:

  • 第一列:句子的来源(代码)

  • 第二列:标签(0=不可接受,1=可接受)

  • 第三列:作者注释的标签

  • 第四列:待分类的句子

你可以在本地打开.tsv文件,阅读数据集的一些样本。程序现在将处理数据用于 BERT 模型。

创建句子,标签列表,并添加 BERT 标记

程序现在会按照本章准备预训练输入环境部分的描述创建句子:

#@ Creating sentence, label lists and adding Bert tokens
sentences = df.sentence.values
# Adding CLS and SEP tokens at the beginning and end of each sentence for BERT
sentences = ["[CLS] " + sentence + " [SEP]" for sentence in sentences]
labels = df.label.values 
  • 1
  • 2
  • 3
  • 4
  • 5

[CLS][SEP]现在已经添加。

程序现在激活了分词器。

激活 BERT 分词器

在本节中,我们将初始化一个预训练的 BERT 分词器。这将节省从头开始训练它所需的时间。

程序选择了一个小写分词器,激活它,并显示了第一个标记化的句子:

#@title Activating the BERT Tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True)
tokenized_texts = [tokenizer.tokenize(sent) for sent in sentences]
print ("Tokenize the first sentence:")
print (tokenized_texts[0]) 
  • 1
  • 2
  • 3
  • 4
  • 5

输出包含分类令牌和序列分割令牌:

Tokenize the first sentence:
['[CLS]', 'our', 'friends', 'wo', 'n', "'", 't', 'buy', 'this', 'analysis', ',', 'let', 'alone', 'the', 'next', 'one', 'we', 'propose', '.', '[SEP]'] 
  • 1
  • 2

程序现在将处理数据。

处理数据

我们需要确定一个固定的最大长度并为模型处理数据。数据集中的句子很短。但为了确保这一点,程序将序列的最大长度设置为128,然后进行填充:

#@title Processing the data
# Set the maximum sequence length. The longest sequence in our training set is 47, but we'll leave room on the end anyway. 
# In the original paper, the authors used a length of 512.
MAX_LEN = 128
# Use the BERT tokenizer to convert the tokens to their index numbers in the BERT vocabulary
input_ids = [tokenizer.convert_tokens_to_ids(x) for x in tokenized_texts]
# Pad our input tokens
input_ids = pad_sequences(input_ids, maxlen=MAX_LEN, dtype="long", truncating="post", padding="post") 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

序列已经被处理,现在程序创建了注意力掩码。

创建注意力掩码

现在就是过程中的一个棘手部分了。在前一个单元格中,我们添加了填充的序列。但我们想阻止模型对这些填充的标记执行注意力!

想法是为每个标记应用一个值为1的掩码,0 将用于填充:

#@title Create attention masks
attention_masks = []
# Create a mask of 1s for each token followed by 0s for padding
for seq in input_ids:
  seq_mask = [float(i>0) for i in seq]
  attention_masks.append(seq_mask) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

程序现在将分割数据。

将数据分割成训练集和验证集

程序现在执行标准的数据分割过程,将数据分成训练集和验证集:

#@title Splitting data into train and validation sets
# Use train_test_split to split our data into train and validation sets for training
train_inputs, validation_inputs, train_labels, validation_labels = train_test_split(input_ids, labels, random_state=2018, test_size=0.1)
train_masks, validation_masks, _, _ = train_test_split(attention_masks, input_ids,random_state=2018, test_size=0.1) 
  • 1
  • 2
  • 3
  • 4

数据已经准备好训练,但仍需调整为 torch。

将所有数据转换为 torch 张量

微调模型使用 torch 张量。程序必须将数据转换为 torch 张量:

#@title Converting all the data into torch tensors
# Torch tensors are the required datatype for our model
train_inputs = torch.tensor(train_inputs)
validation_inputs = torch.tensor(validation_inputs)
train_labels = torch.tensor(train_labels)
validation_labels = torch.tensor(validation_labels)
train_masks = torch.tensor(train_masks)
validation_masks = torch.tensor(validation_masks) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

转换结束了。现在我们需要创建一个迭代器。

选择批处理大小并创建迭代器

在这个单元格中,程序会选择批处理大小并创建一个迭代器。这个迭代器是避免加载所有数据到内存中并且配合 torch 的DataLoader巧妙的方式,可以批量训练大型数据集而不会使机器内存崩溃。

在这个模型中,批处理大小是32

#@title Selecting a Batch Size and Creating and Iterator
# Select a batch size for training. For fine-tuning BERT on a specific task, the authors recommend a batch size of 16 or 32
batch_size = 32
# Create an iterator of our data with torch DataLoader. This helps save on memory during training because, unlike a for loop, 
# with an iterator the entire dataset does not need to be loaded into memory
train_data = TensorDataset(train_inputs, train_masks, train_labels)
train_sampler = RandomSampler(train_data)
train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=batch_size)
validation_data = TensorDataset(validation_inputs, validation_masks, validation_labels)
validation_sampler = SequentialSampler(validation_data)
validation_dataloader = DataLoader(validation_data, sampler=validation_sampler, batch_size=batch_size) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

数据已经被处理并且准备就绪。程序现在可以加载并配置 BERT 模型。

BERT 模型配置

程序现在初始化了一个 BERT 小写配置:

#@title BERT Model Configuration
# Initializing a BERT bert-base-uncased style configuration
#@title Transformer Installation
try:
  import transformers
except:
  print("Installing transformers")
  !pip -qq install transformers

from transformers import BertModel, BertConfig
configuration = BertConfig()
# Initializing a model from the bert-base-uncased style configuration
model = BertModel(configuration)
# Accessing the model configuration
configuration = model.config
print(configuration) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

输出包含主要的 Hugging Face 参数,类似于以下内容(该库经常更新):

BertConfig {
  "attention_probs_dropout_prob": 0.1,
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "layer_norm_eps": 1e-12,
  "max_position_embeddings": 512,
  "model_type": "bert",
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "pad_token_id": 0,
  "type_vocab_size": 2,
  "vocab_size": 30522
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

来看看这些主要参数:

  • attention_probs_dropout_prob0.10.1的丢失率应用于注意力概率。

  • hidden_act: "gelu" 是编码器中的非线性激活函数。这是一个高斯误差线性单元的激活函数。输入按其大小加权,这使其非线性。

  • hidden_dropout_prob: 0.1 是应用于全连接层的 dropout 概率。在嵌入、编码器和池层中都可以找到全连接。输出并不总是对序列内容的良好反映。池化隐藏状态序列有助于改善输出序列。

  • hidden_size: 768 是编码层和池层的维度。

  • initializer_range: 0.02 是初始化权重矩阵的标准差值。

  • intermediate_size: 3072 是编码器的前馈层的维度。

  • layer_norm_eps: 1e-12 是层归一化层的 epsilon 值。

  • max_position_embeddings: 512 是模型使用的最大长度。

  • model_type: "bert" 是模型的名称。

  • num_attention_heads: 12 是头的数量。

  • num_hidden_layers: 12 是层数的数量。

  • pad_token_id: 0 是填充标记的 ID,以避免训练填充标记。

  • type_vocab_size: 2token_type_ids的大小,它标识序列。例如,“the dog[SEP] The cat.[SEP]"可以用 token IDs [0,0,0, 1,1,1]表示。

  • vocab_size: 30522 是模型用于表示input_ids的不同标记的数量。

有了这些参数,我们现在可以加载预训练模型。

加载 Hugging Face BERT uncased 基础模型

程序现在加载了预训练的 BERT 模型:

#@title Loading the Hugging Face Bert uncased base model 
model = BertForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)
model = nn.DataParallel(model)
model.to(device) 
  • 1
  • 2
  • 3
  • 4

我们已经定义了模型,定义了并行处理,并且将模型发送到设备。更多解释,请参见附录 IITransformer 模型的硬件限制

如果需要,该预训练模型可以进一步训练。通过详细探索架构,可以可视化每个子层的参数,就像下面的摘录所示:

BertForSequenceClassification(
  (bert): BertModel(
    (embeddings): BertEmbeddings(
      (word_embeddings): Embedding(30522, 768, padding_idx=0)
      (position_embeddings): Embedding(512, 768)
      (token_type_embeddings): Embedding(2, 768)
      (LayerNorm): BertLayerNorm()
      (dropout): Dropout(p=0.1, inplace=False)
    )
    (encoder): BertEncoder(
      (layer): ModuleList(
        (0): BertLayer(
          (attention): BertAttention(
            (self): BertSelfAttention(
              (query): Linear(in_features=768, out_features=768, bias=True)
              (key): Linear(in_features=768, out_features=768, bias=True)
              (value): Linear(in_features=768, out_features=768, bias=True)
              (dropout): Dropout(p=0.1, inplace=False)
            )
            (output): BertSelfOutput(
              (dense): Linear(in_features=768, out_features=768, bias=True)
              (LayerNorm): BertLayerNorm()
              (dropout): Dropout(p=0.1, inplace=False)
            )
          )
          (intermediate): BertIntermediate(
            (dense): Linear(in_features=768, out_features=3072, bias=True)
          )
          (output): BertOutput(
            (dense): Linear(in_features=3072, out_features=768, bias=True)
            (LayerNorm): BertLayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
        (1): BertLayer(
          (attention): BertAttention(
            (self): BertSelfAttention(
              (query): Linear(in_features=768, out_features=768, bias=True)
              (key): Linear(in_features=768, out_features=768, bias=True)
              (value): Linear(in_features=768, out_features=768, bias=True)
              (dropout): Dropout(p=0.1, inplace=False)
            )
            (output): BertSelfOutput(
              (dense): Linear(in_features=768, out_features=768, bias=True)
              (LayerNorm): BertLayerNorm()
              (dropout): Dropout(p=0.1, inplace=False)
            )
          )
          (intermediate): BertIntermediate(
            (dense): Linear(in_features=768, out_features=3072, bias=True)
          )
          (output): BertOutput(
            (dense): Linear(in_features=3072, out_features=768, bias=True)
            (LayerNorm): BertLayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        ) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

现在让我们来看一下优化器的主要参数。

优化器分组参数

程序现在将初始化模型参数的优化器。微调模型的起始点是初始化预训练模型的参数值(而不是它们的名称)。

优化器的参数包括权重衰减率以避免过拟合,还有一些参数被筛选了出来。

目标是为了为训练循环准备模型的参数:

##@title Optimizer Grouped Parameters
#This code is taken from:
# https://github.com/huggingface/transformers/blob/5bfcd0485ece086ebcbed2d008813037968a9e58/examples/run_glue.py#L102
# Don't apply weight decay to any parameters whose names include these tokens.
# (Here, the BERT doesn't have 'gamma' or 'beta' parameters, only 'bias' terms)
param_optimizer = list(model.named_parameters())
no_decay = ['bias', 'LayerNorm.weight']
# Separate the 'weight' parameters from the 'bias' parameters. 
# - For the 'weight' parameters, this specifies a 'weight_decay_rate' of 0.01\. 
# - For the 'bias' parameters, the 'weight_decay_rate' is 0.0\. 
optimizer_grouped_parameters = [
    # Filter for all parameters which *don't* include 'bias', 'gamma', 'beta'.
    {'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)],
     'weight_decay_rate': 0.1},

    # Filter for parameters which *do* include those.
    {'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)],
     'weight_decay_rate': 0.0}
]
# Note - 'optimizer_grouped_parameters' only includes the parameter values, not the names. 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

参数已经被准备并清理干净。它们已经为训练循环做好了准备。

训练循环的超参数

尽管似乎无害,但训练循环的超参数至关重要。例如,Adam 会激活权重衰减,并且还会经历一个温和的阶段。

学习率(lr)和温和率(warmup)在优化阶段的早期应该设为一个很小的值,并且在一定数量的迭代后逐渐增加。这可以避免大梯度和超出优化目标。

一些研究人员认为,在层归一化之前的子层输出水平上的梯度不需要预热率。解决这个问题需要进行多次实验运行。

优化器是一种称为BertAdam的 BERT 版本的 Adam:

#@title The Hyperparameters for the Training Loop 
optimizer = BertAdam(optimizer_grouped_parameters,
                     lr=2e-5,
                     warmup=.1) 
  • 1
  • 2
  • 3
  • 4

程序添加了一个精度测量函数来比较预测和标签:

#Creating the Accuracy Measurement Function
# Function to calculate the accuracy of our predictions vs labels
def flat_accuracy(preds, labels):
    pred_flat = np.argmax(preds, axis=1).flatten()
    labels_flat = labels.flatten()
    return np.sum(pred_flat == labels_flat) / len(labels_flat) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

数据准备就绪。参数准备就绪。现在是激活训练循环的时候了!

训练循环

训练循环遵循标准学习过程。epochs 数设置为4,并且损失和准确率的测量将被绘制。训练循环使用dataloader加载和训练批次。训练过程进行了测量和评估。

代码从初始化train_loss_set开始,它将存储将绘制的损失和准确率。它开始训练其 epochs 并运行标准训练循环,如下所示的摘录:

#@title The Training Loop
t = [] 
# Store our loss and accuracy for plotting
train_loss_set = []
# Number of training epochs (authors recommend between 2 and 4)
epochs = 4
# trange is a tqdm wrapper around the normal python range
for _ in trange(epochs, desc="Epoch"):./…
    tmp_eval_accuracy = flat_accuracy(logits, label_ids)

    eval_accuracy += tmp_eval_accuracy
    nb_eval_steps += 1
  print("Validation Accuracy: {}".format(eval_accuracy/nb_eval_steps)) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

输出使用trange包装器为每个epoch显示信息,for _ in trange(epochs, desc="Epoch")

***output***
Epoch:   0%|          | 0/4 [00:00<?, ?it/s]
Train loss: 0.5381132976395461
Epoch:  25%|██▌       | 1/4 [07:54<23:43, 474.47s/it]
Validation Accuracy: 0.788966049382716
Train loss: 0.315329696132929
Epoch:  50%|█████     | 2/4 [15:49<15:49, 474.55s/it]
Validation Accuracy: 0.836033950617284
Train loss: 0.1474070605354314
Epoch:  75%|███████▌  | 3/4 [23:43<07:54, 474.53s/it]
Validation Accuracy: 0.814429012345679
Train loss: 0.07655430570461196
Epoch: 100%|██████████| 4/4 [31:38<00:00, 474.58s/it]
Validation Accuracy: 0.810570987654321 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Transformer 模型发展非常迅速,可能会出现废弃消息甚至错误。Hugging Face 也不例外,当发生这种情况时,我们必须相应地更新我们的代码。

模型已经训练。我们现在可以显示训练评估。

训练评估

损失和准确率数值存储在train_loss_set中,这在训练循环开始时定义。

程序现在绘制测量结果:

#@title Training Evaluation
plt.figure(figsize=(15,8))
plt.title("Training loss")
plt.xlabel("Batch")
plt.ylabel("Loss")
plt.plot(train_loss_set)
plt.show() 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

输出是一张图表,显示训练过程进行得很顺利和高效:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3.6:每批次训练损失

模型已经微调。我们现在可以进行预测。

使用留存数据集进行预测和评估

BERT 下游模型是使用in_domain_train.tsv数据集训练的。程序现在将使用out_of_domain_dev.tsv文件中的留存(测试)数据集进行预测。目标是预测句子是否语法正确。

以下代码摘录显示了应用于训练数据的数据准备过程在留存数据集部分的代码中被重复使用:

#@title Predicting and Evaluating Using the Holdout Dataset 
df = pd.read_csv("out_of_domain_dev.tsv", delimiter='\t', header=None, names=['sentence_source', 'label', 'label_notes', 'sentence'])
# Create sentence and label lists
sentences = df.sentence.values
# We need to add special tokens at the beginning and end of each sentence for BERT to work properly
sentences = ["[CLS] " + sentence + " [SEP]" for sentence in sentences]
labels = df.label.values
tokenized_texts = [tokenizer.tokenize(sent) for sent in sentences]
.../... 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

程序然后使用dataloader运行批次预测:

# Predict 
for batch in prediction_dataloader:
  # Add batch to GPU
  batch = tuple(t.to(device) for t in batch)
  # Unpack the inputs from our dataloader
  b_input_ids, b_input_mask, b_labels = batch
  # Telling the model not to compute or store gradients, saving memory and speeding up prediction
  with torch.no_grad():
    # Forward pass, calculate logit predictions
    logits = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

预测的 logits 和标签被移动到 CPU 上:

 # Move logits and labels to CPU
  logits =  logits['logits'].detach().cpu().numpy()
  label_ids = b_labels.to('cpu').numpy() 
  • 1
  • 2
  • 3

预测和它们的真实标签已存储:

 # Store predictions and true labels
  predictions.append(logits)
  true_labels.append(label_ids) 
  • 1
  • 2
  • 3

程序现在可以评估预测了。

使用马修斯相关系数进行评估

马修斯相关系数MCC)最初是设计用来衡量二元分类的质量,并且可以修改为多类相关系数。每个预测可以使用四个概率进行二元分类:

  • TP = 真阳性

  • TN = 真阴性

  • FP = 假阳性

  • FN = 假阴性

布莱恩·W·马修斯,一位生物化学家,于 1975 年设计了它,受他的前辈的phi函数的启发。从那时起,它已经发展成为各种格式,如以下格式之一:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

MCC 产生的值在-1+1之间。 +1是预测的最大正值。 -1是反向预测。 0是平均随机预测。

GLUE 用 MCC 评估语言可接受性

MCC 是导入自sklearn.metrics的:

#@title Evaluating Using Matthew's Correlation Coefficient
# Import and evaluate each test batch using Matthew's correlation coefficient
from sklearn.metrics import matthews_corrcoef 
  • 1
  • 2
  • 3

创建了一组预测:

matthews_set = [] 
  • 1

MCC 值被计算并存储在matthews_set中:

for i in range(len(true_labels)):
  matthews = matthews_corrcoef(true_labels[i],
                 np.argmax(predictions[i], axis=1).flatten())
  matthews_set.append(matthews) 
  • 1
  • 2
  • 3
  • 4

由于库和模块版本更改,您可能会看到一些消息。 最终得分将基于整个测试集,但让我们看看各个批次的得分,以了解批次之间指标的变化。

各个批次的得分

让我们来看看各个批次的得分:

#@title Score of Individual Batches
matthews_set 
  • 1
  • 2

输出产生了预期的-1+1之间的 MCC 值:

[0.049286405809014416,
 -0.2548235957188128,
 0.4732058754737091,
 0.30508307783296046,
 0.3567530340063379,
 0.8050112948805689,
 0.23329882422520506,
 0.47519096331149147,
 0.4364357804719848,
 0.4700159919404217,
 0.7679476477883045,
 0.8320502943378436,
 0.5807564950208268,
 0.5897435897435898,
 0.38461538461538464,
 0.5716350506349809,
 0.0] 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

几乎所有的 MCC 值都是正值,这是个好消息。 让我们看看整个数据集的评估如何。

对整个数据集进行 Matthews 评估

MCC 是评估分类模型的一种实用方法。

该程序现在将为整个数据集聚合真实值:

#@title Matthew's Evaluation on the Whole Dataset
# Flatten the predictions and true values for aggregate Matthew's evaluation on the whole dataset
flat_predictions = [item for sublist in predictions for item in sublist]
flat_predictions = np.argmax(flat_predictions, axis=1).flatten()
flat_true_labels = [item for sublist in true_labels for item in sublist]
matthews_corrcoef(flat_true_labels, flat_predictions) 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

MCC 产生-1+1之间的相关值。 0是平均预测,-1是反向预测,1是完美预测。 在这种情况下,输出证实 MCC 是正值,表明模型和数据集之间存在关联:

0.45439842471680725 
  • 1

在 BERT 模型的微调最终积极评估中,我们对 BERT 训练框架有了整体的认识。

摘要

BERT 为 transformers 引入了双向关注。 从左到右预测序列并屏蔽未来的标记以训练模型具有严格限制。 如果屏蔽的序列包含我们正在寻找的意义,模型将产生错误。 BERT 同时关注序列的所有标记。

我们探索了 BERT 的架构,它只使用 transformers 的编码器堆栈。 BERT 被设计为一个两步框架。 框架的第一步是预训练一个模型。 第二步是微调模型。 我们为可接受性判断下游任务构建了一个微调的 BERT 模型。 微调过程经历了所有阶段的过程。 首先,我们加载了数据集和加载了模型的必要预训练模块。 然后训练模型,并测量其性能。

对一个预训练模型进行微调所需的机器资源比从头开始训练下游任务要少。 细调模型可以执行各种任务。 BERT 证明我们只需对模型进行两项训练预处理就能实现这一点,这本身就是了不起的。但是基于 BERT 预训练模型的训练参数产生多任务微调模型是异常的。

第七章具有 GPT-3 引擎的超人变形金刚的兴起,表明 OpenAI 已经达到了零调校水平。

在本章中,我们对 BERT 模型进行了微调。在下一章,第四章从头开始预训练 RoBERTa 模型,我们将深入研究 BERT 框架,并从头构建一个预训练的类 BERT 模型。

问题

  1. BERT 代表双向编码器来自Transformers。 (True/False)

  2. BERT 是一个两步框架。 步骤 1 是预训练。 步骤 2 是微调。 (True/False)

  3. 对 BERT 模型进行微调意味着从头开始训练参数。 (True/False)

  4. BERT 仅通过所有下游任务进行预训练。 (True/False)

  5. BERT 预训练采用掩码语言建模 (MLM)。 (True/False)

  6. BERT 预训练采用下一句预测 (NSP)。 (True/False)

  7. BERT 预训练数学函数。 (True/False)

  8. 问答任务是一个下游任务。 (True/False)

  9. BERT 预训练模型不需要标记化。 (True/False)

  10. 对 BERT 模型进行微调所需时间比预训练少。 (True/False)

参考资料

加入我们书的 Discord 空间

加入书籍的 Discord 工作空间,与作者进行每月的 向我提问 会话:

www.packt.link/Transformers

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/883425
推荐阅读
相关标签
  

闽ICP备14008679号