首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

【精读】Transformer模型深度解读

翻译:雷锋字幕组(Icarus、)

在过去的一年里,《注意力就是你所需要的》中的Transformer被很多人所关注。除了在翻译质量上产生重大改进外,它还为许多其他NLP任务提供了一个新的架构。这篇论文本身写得非常清楚,但传统的观点是,它的正确实现相当困难。

在这篇文章中,我以逐行实现的形式呈现了论文的 "注释 "版本。我对原论文中的一些章节进行了重新排序和删除,并在全文中添加了注释。这个文档本身就是一个工作笔记,应该是一个完全可用的实现。总共有400行库代码,可以在4个GPU上每秒处理27000个token。

要想跟上,你首先需要安装PyTorch。完整的笔记本也可以在github或Google Colab上使用免费的GPU。

预先设置

目录

Prelims

背景资料

模型结构

编码器

解码器

注意事项

注意力在我们的模型中的应用

编码器和解码器堆栈

位置导向的前馈网络

嵌入和Softmax

位置编码

完整模型

训练

标签平滑化

批量和遮蔽

训练回路

硬件和时间表

优化器

正规化

第一个例子

合成数据

损失计算

greedy解码

一个现实例子

加载数据

迭代器

多GPU训练

训练系统

附加组件:BPE、搜索、平均值

结果

  注意力可视化

结论

背景资料

减少顺序计算的目标也构成了扩展神经GPU、ByteNet和ConvS2S的基础,它们都使用卷积神经网络作为基本构件,对所有输入和输出位置并行计算隐藏表示。在这些模型中,将两个任意输入或输出位置的信号关联起来所需的运算次数随着位置之间的距离而增长,对于ConvS2S来说是线性的,对于ByteNet来说是对数的。这使得学习远距离位置之间的依赖关系变得更加困难。在Transformer中,这种情况被减少到了一个恒定的操作次数,尽管代价是由于注意力加权位置的平均化而导致有效分辨率的降低,我们用多头注意力来抵消这种影响。

自注意,有时也被称为内注意,是一种将单个序列的不同位置联系起来以计算序列的表示的注意机制。自我注意已被成功地应用于各种任务中,包括阅读理解、抽象概括、文本内涵和学习任务无关的句子表征。端到端记忆网络是基于循环注意机制而不是序列对齐的循环,并且已经被证明在简单语言问题回答和语言建模任务上表现良好。

然而,据我们所知,Transformer是第一个完全依靠自我注意力来计算其输入和输出的表征,而不使用序列对齐的RNNs或卷积的转换模型。

模型结构

大多数竞争性神经序列转导模型都有一个编码器-解码器结构(cite)。在这里,编码器将输入的符号表示序列(x1,...,xn)映射为连续表示序列z=(z1,...,zn)。在给定z的情况下,解码器每次生成一个符号的输出序列(y1,...,ym)。在每一步中,该模型都是自动递减的(cite),在生成下一个符号时,消耗之前生成的符号作为额外的输入。

Transformer沿用了这种整体架构,编码器和解码器都采用堆叠式自关注和点对点的全连接层,分别如图1的左半部分和右半部分所示。

编码器和解码器堆栈

编码器

编码器是由N=6个相同的层组成的堆栈。

我们在两个子层周围分别采用残差连接(cite),然后进行层归一化(cite)。

也就是说,每个子层的输出是LayerNorm(x+Sublayer(x)),其中Sublayer(x)是子层自己实现的函数。我们在每个子层的输出中应用dropout(引用),然后再加到子层的输入中并进行归一化。

为了方便这些残差连接,模型中的所有子层以及嵌入层都会产生维度为dmodel=512dmodel=512的输出。

每层有两个子层。第一层是一个多头自注意机制,第二层是一个简单的、基于位置的全连接前馈网络。

解码器

解码器也是由N=6层相同的堆栈组成。

除了每个编码器层中的两个子层外,解码器还插入了第三个子层,它在编码器堆栈的输出上执行多头关注。与编码器类似,我们在每个子层周围采用残余连接,然后进行层归一化。

我们还修改了解码器堆栈中的自注意力子层,以防止位置对后续位置的关注。这种遮蔽,加上输出嵌入偏移这一个位置的事实,确保位置ii的预测只能依赖于小于i的这个位置的已知输出。

笔者注:注意力掩码下方显示了每个tgt词(行)被允许看的位置(列)。在训练过程中,单词会被屏蔽,以便关注之后的单词。

注意事项

注意函数可以描述为将一个查询和一组键值对映射到一个输出,其中查询、键、值和输出都是向量。输出是以值的加权和来计算的,其中分配给每个值的权重是由查询与对应键的兼容性函数计算出来的。我们把我们的特殊注意力称为 "Scaled Dot-Product注意力"。输入由维度为dk的查询和键,以及维度为dv的值组成。我们计算查询与所有键的点积,每一个点积除以√dk,然后应用一个softmax函数来获得值的权重。

在实际应用中,我们对一组查询同时计算关注函数,打包成一个矩阵QQ。键和值也一起打包成矩阵KK和VV。我们计算输出的矩阵为:

最常用的两个注意力函数是加法注意力(cite),和点积(乘法)注意力。点积注意力除了缩放因子为1/√dk外,与我们的算法相同。加法注意力使用单层隐藏层的前馈网络计算兼容性函数。虽然两者在理论复杂度上相似,但点积注意力在实践中更快,更节省空间,因为它可以使用高度优化的矩阵乘法代码来实现。

虽然对于dk的小值,两种机制的表现相似,但对于dk的大值,加法注意力的表现优于点积注意力,而没有缩放(引用)。我们怀疑,对于dk的大值,点积的幅度会变大,将softmax函数推到它的梯度极小的区域(为了说明点积为什么会变大,假设qq和kk的分量是均值00、方差11的独立随机变量。那么它们的点积q⋅k=∑dki=1qiki,其均值为00,方差为dk)。

为了抵消这种影响,我们将点乘积的比例为1/√dk。

多头注意使模型能够共同注意来自不同位置的不同表征子空间的信息。在单注意头的情况下,平均化会抑制这一点。

其中,投影为参数矩阵

在这项工作中,我们采用了h=8h=8个平行的注意层,或者说头。对于其中的每一个层,我们使用 :

由于每个头的维度减少,总的计算成本与全维度的单头注意力相似。

注意力在我们的模型中的应用

Transformer以三种不同的方式使用多头注意:

1. 在 "编码器-解码器关注 "层中,查询来自前一个解码器层,而内存键和值来自编码器的输出。这使得解码器中的每个位置都能在输入序列的所有位置上进行关注。这模仿了序列到序列模型中典型的编码器-解码器的注意机制,如(引用)。

2. 编码器包含自注意层。在自注意层中,所有的键、值和查询都来自同一个地方,在这种情况下,就是编码器中上一层的输出。编码器中的每个位置都可以参加编码器前一层的所有位置。

3. 同样,解码器中的自注意层允许解码器中的每个位置都可以参加解码器中的所有位置,直到并包括该位置。我们需要防止解码器中的左向信息流,以保持自动递减特性。我们通过屏蔽(设置为-∞)softmax的输入中所有对应非法连接的值,在scaled dot- product attention里面实现。

位置导向的前馈网络

除了注意力子层,我们的编码器和解码器中的每个层都包含一个完全连接的前馈网络,该网络分别和相同地应用于每个位置。这包括两个线性变换,中间有一个ReLU激活。

虽然不同位置的线性变换是相同的,但它们在层与层之间使用不同的参数。另一种描述方式是内核大小为1的两个卷积。输入和输出的维度为dmodel=512,内层的维度为dff=2048。

嵌入和Softmax

与其他序列转导模型类似,我们使用学习的嵌入将输入令牌和输出令牌转换为维数dmodel的向量。我们还使用通常的学习线性变换和softmax函数将解码器输出转换为预测的下一个标记概率。在我们的模型中,我们在两个嵌入层和预softmax线性变换之间共享相同的权重矩阵,类似于(引用)。在嵌入层中,我们将这些权重乘以√dmodel。

位置编码

由于我们的模型不包含递归和卷积,为了使模型能够利用序列的顺序,我们必须注入一些关于序列中标记的相对或绝对位置的信息。为此,我们在编码器和解码器堆栈底部的输入嵌入中加入 "位置编码"。位置编码与嵌入的维度dmodel相同,因此两者可以相加。位置编码有很多选择,有学习的和固定的(引用)。

在本工作中,我们使用不同频率的正弦和余弦函数。

其中pos是位置,i是维度。即位置编码的每个维度对应一个正弦波。波长形成从2π到10000⋅2π的几何级数。我们选择这个函数是因为我们假设它可以让模型很容易地学会通过相对位置来参加,因为对于任何固定的偏移量k,PEpos+k可以表示为PEpos的线性函数。

此外,我们对编码器和解码器堆栈中的嵌入和位置编码的总和应用了 dropout。对于基础模型,我们使用Pdrop=0.1的速率。

下面的位置编码会根据位置加入一个正弦波。每个维度的波的频率和偏移量是不同的。

我们还试验了使用学习的位置嵌入(cite)来代替,并发现这两个版本产生的结果几乎是相同的。我们选择了正弦版本,因为它可能允许模型外推到比训练过程中遇到的序列长度更长的序列。

完整模型

在这里,我们定义了一个函数,它可以接受超参数并产生一个完整的模型。

训练

本节介绍了我们模型的训练制度。

笔者注:我们停下来做一个快速的插曲,介绍一些训练标准编码器解码器模型所需的工具。首先,我们定义了一个批处理对象,它保存了用于训练的src和目标句子,以及构建掩码。

批量和屏蔽

笔者注:接下来我们创建一个通用的训练和评分函数来跟踪损失。我们传递了一个通用的损失计算函数,它也处理参数更新。

训练循环

训练数据和批处理

我们在标准的WMT 2014英德数据集上进行了训练,该数据集由大约450万句子对组成。句子使用字节对编码,其共享的源-目标词汇约为37000个tokens。对于英语-法语,我们使用了明显更大的WMT 2014英法数据集,该数据集由36M句子组成,并将tokens拆分为32000个词片词汇。

句子对被按近似序列长度分批在一起。每个训练批次都包含一组句子对,包含大约25000个源标记和25000个目标标记。

笔者注:我们将使用火炬文本进行批处理。这将在下面详细讨论。在这里,我们在torchtext函数中创建批处理,以确保我们的批处理大小垫到最大batchsize不超过一个阈值(25000,如果我们有8个gpus)。

硬件和时间表

我们在一台拥有8个NVIDIA P100 GPU的机器上训练我们的模型。对于我们的基础模型,使用本文中描述的超参数,每个训练步骤大约需要0.4秒。我们总共训练了100,000步或12小时的基础模型。对于我们的大模型,步长为1.0秒。大模型的训练时间为30万步(3.5天)。

优化器

我们使用了Adam优化器(引用),β1=0.9,β2=0.98,ϵ=10^-9。我们根据公式,在训练过程中改变学习率。

这相当于对第一个warmupstepswarmupsteps训练步数线性增加学习率,此后按步数的倒平方根按比例减少。我们使用warmupsteps=4000。

注意:这部分非常重要。需要用这个设置的模型进行训练。

该模型在不同模型大小和优化超参数下的曲线示例。

正规化

标签平滑化

在训练过程中,我们采用了价值ϵls=0.1ϵls=0.1的标签平滑(引用)。这伤害了迷惑性,因为模型学会了更多的不确定,但提高了准确性和BLEU得分。

笔者注:我们使用KL div loss实现标签平滑。而不是使用一个一热的目标分布,我们创建了一个分布,有信心的正确的单词和其余的平滑质量分布在整个词汇。

在这里,我们可以看到一个例子,说明质量是如何根据置信度分配给单词的。

标签平滑实际上是开始惩罚模型,如果它对给定的选择非常自信的话。

一个例子

我们可以先尝试一个简单的复制任务。从一个小词汇中给定一组随机的输入符号,目标是生成回这些相同的符号。

综合数据

损失计算

greedy解码

这个代码为了简单起见,使用贪婪的解码来预测翻译。

一个真实的例子

笔者注:现在我们考虑使用IWSLT德英翻译任务的一个真实世界的例子。这个任务比论文中考虑的WMT任务小得多,但它说明了整个系统。我们还展示了如何使用多GPU处理来使其真正快速。

数据加载

笔者注:我们将使用 torchtext 和 spacy 加载数据集进行标记化。

批量对速度有很大的影响。我们希望有非常均匀的批次,绝对最小的填充。要做到这一点,我们必须对默认的 torchtext 批量进行一些改进。这段代码修补了他们的默认批处理,以确保我们搜索到的句子足够多,从而找到紧凑的批处理。

迭代器

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200925A05U7N00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券