UPDATE
2.26.2020
为代码解析部分配上了Jay Ammar The Illustrated GPT-2 的图示,为想阅读源码的朋友缓解疼痛!
深入理解Transformer------从论文到代码
- UPDATE
- 1. Attention Is All You Need
- 2. The Illustrated Transformer翻译
-
- 2.1 A High-Level Look
- 2.2 Bringing The Tensors Into The Picture
- 2.3 Now We’re Encoding!
- 2.4 Self-Attention at a High Level
- 2.5 Self-Attention in Detail
- 2.6 Matrix Calculation of Self-Attention
- 2.7 The Beast With Many Heads
- 2.8 Representing The Order of The Sequence Using Positional Encoding(使用位置编码表示序列的顺序)
- 2.9 The Residuals
- 2.10 The Decoder Side
- 2.11 The Final Linear and Softmax Layer
- 2.12 Recap Of Training(回顾)
- 2.13 The Loss Function
- 3. 代码详解
1. Attention Is All You Need
1.1 摘要
提出了完全基于注意力机制,避免使用循环和卷积的新的网络结构。
1.2 介绍
RNN模型通常沿输入和输出序列的符号位置进行因子计算,将位置与计算时间中的步骤对齐。它们产生一系列的隐藏状态 h t h_{t} ht,作为先前隐藏状态的函数 h t − 1 h_{t-1} ht−1和位置输入 t t t。这种内在的顺序性因为内存约束限制了跨实例的批处理,排除了并行化训练的可能性,而在处理较长的序列长度时,并行化又是至关重要的。
最近的工作通过因子分解技巧( factorization tricks)和条件计算(conditional computation)在计算效率方面取得了显著的提高,然而,序列计算的基本准则仍然存在。
注意力机制已经成为各种任务中序列模型和转换模型的一个重要组成部分,允许不考虑输入或输出序列间距离的依赖关系建模。然而,在除少数情况外的所有情况下,这种注意机制与一个递归网络结合使用。
在这项工作中,我们提出了Transformer,这是一个避免循环的模型架构,而不是完全依赖于注意机制来绘制输入和输出之间的全局依赖关系。
1.3 背景
减少顺序计算的目的也形成了扩展神经网络GPU、ByteNet和 ConvS2S的基础,所有这些都使用卷积神经网络作为基本构建块,并行计算所有输入和输出位置的隐藏表示。在这些模型中,将来自两个任意输入或输出位置的信号关联起来所需的操作数随位置之间的距离而增长, ConvS2S是线性的,ByteNet是对数的,这使得学习远距离位置之间的依赖性变得更加困难。在Transformer中,这被减少到了固定数目的操作数(this reduced to a constant number of operations),尽管由于平均注意力加权位置而降低了有效分辨率,但我们用多头注意抵消了这种影响。
自注意(Self-attention),有时被称为内注意(intra-attention),是一种注意机制,它将单个序列的不同位置联系起来,以计算序列的表示。自注意在阅读理解、抽象概括、文本蕴涵和学习任务无关的句子表征等任务中得到了成功的运用。
端到端的记忆网络是基于一种循环注意机制,而不是顺序排列的循环(sequence-aligned recurrence),在简单的语言问答和语言建模任务中表现良好。
1.4 模型结构
大多数有竞争力的神经序列转换模型都有编码器-解码器结构。在这里,编码器将由 ( x 1 , . . . , x n ) (x_{1},...,x_{n}) (x1,...,xn)符号表示的输入序列映射到连续表示序列 z = ( z 1 , . . . , z n ) \textbf{z}=(z_{1},...,z_{n}) z=(z1,...,zn)。对于给定的 z \textbf{z} z 解码器每次生成输出序列 ( y 1 , . . . , y n ) (y_{1},...,y_{n}) (y1,...,yn)的一个元素。在每一步,模型都是自回归的,在生成下一步时,将先前生成的符号作为附加输入。
Transformer遵循这个整体架构,使用堆叠的自注意(self-attention)和点对点( point-wise),编码器和解码器都具备全连接层,如图的左半部分和右半部分所示
1.5.1 编码器与解码器
1.5.1.1 编码器
编码器由 N = 6 N=6 N=6 个相同层的堆栈组成。每层有两个子层。
第一种是一种多头部的自注意机制,
第二种是一种简单的、按位置全连接的前馈网络。
我们在两个子层周围使用一个残差连接,然后进行层规范化。
也就是说,每个子层的输出是LayerNorm(x+ Sublayer(x)) 其中Sublayer(x)是子层自身应用的函数。
为了方便使用残差连接,模型中的所有子层以及嵌入层都会产生dimension d m o d e l d_{model} dmodel=512的输出。
1.5.1.2 解码器
解码器也是由 N = 6 N=6 N=6 个相同层的堆栈组成。多出来的第三个子层对编码器堆栈的输出执行多个头部注意力计算(严格来讲是对编码器的输出和解码器经过Masked多头注意力层后的输入进行计算)。
我们还修改解码器堆栈中的自注意子层,以防止位置影响后续位置(to prevent positions from attending to subsequent positions)。这种掩蔽(masking),加上输出嵌入偏移了一个位置(the output embeddings are offset by one position),确保对位置 i i i 的预测只能依赖于小于 i i i 的位置的已知输出。
1.5.2 注意力
注意力函数可以描述为将查询和一组键值对映射到输出,其中查询(Q)、键(K)、值(V)和输出都是向量。输出被计算为值的加权和,其中分配给每个值的权重由查询的兼容函数( compatibility function)和相应的键计算。
1.5.2.1 缩放点乘注意力
我们称我们特殊的注意力为 “Scaled Dot-Product Attention”,输入由 d k d_{k} dk维 的查询和键以及 d v d_{v} dv维 的值组成。我们使用所有键计算与查询的点积,每个除以 d k \sqrt{d_{k}} dk ,然后应用softmax函数获得值的权重。
实际操作时,我们同时计算一组查询的注意力函数,并将其打包成一个矩阵 Q Q Q。键和值也打包到矩阵中 K K K 和 V V V 中。我们将输出矩阵计算为:
最常用的两个注意力函数是additive(加)注意力和点积注意力。点积注意力除了比例因子
1 d k \frac{1}{\sqrt{d_{k}}} dk1 之外与我们的算法相同,加注意力使用具有单个隐藏层的前馈网络计算兼容性函数。虽然二者在理论复杂度上相似,但在实际应用中,点积注意力计算速度更快,空间效率更高,因为它可以使用高度优化的矩阵乘法代码来实现。
而对于较小的 d k \sqrt{d_{k}} dk 值,两种机制的表现是相似的,加注意力在不缩放较大的 d k \sqrt{d_{k}} dk 值的情况下优于点积注意力。我们怀疑,对于大的 d k \sqrt{d_{k}} dk ,点积在数量级上增长很大,将softmax函数推到梯度极小的区域。为了抵消这一影响,我们将点积除以 d k \sqrt{d_{k}} dk 。
1.5.2.2 多头注意力
与用 d m o d e l − d i m e n s i o n a l d_{model}-dimensional dmodel−dimensional 的键、值和查询执行单个注意力函数,使用另一种可学习的线性投影(projection)分别对查询、键和值进行 h h h 次线性投影(projection)将它们投影到 d k d_{k} dk, d k d_{k} dk 和 d v d_{v} dv 维会更有效,然后,在每个查询、键和值的投影版本上,我们并行地执行注意力函数,即产生 d v − d i m e n s i o n a l d_{v}-dimensional dv−dimensional 的输出值。它们被连接在一起,然后再次投影,从而得到最终值。多头注意力允许模型在不同的位置共同关注来自不同表示子空间的信息。只有一个注意力头,平均化抑制了这种情况。
在这项工作中,我们使用了 h = 8 h=8 h=8个平行的注意力层,或者说头部。对于每一个头,我们使用 d k = d v = d m o d e l / h = 64 d_{k}=d_{v}=d_{model}/h=64 dk=dv=dmodel/h=64 。由于每个头部的维数减少,总的计算代价与全维度的单头部注意力的计算代价相似。
1.5.2.3 注意力在我们模型中的应用
Transformer以三种不同的方式使用多头注意:
- 在“encoder-decoder attention”层中,查询来自上一个解码器层,记忆键和值来自编码器的输出,这使得解码器中的每一个位置都能参与输入序列中的所有位置,这模仿了典型的编码器-解码器注意力机制。
- 编码器包含自注意力层。在自注意力层中,所有键、值和查询都来自同一个位置,在这种情况下,是编码器中前一层的输出。编码器中的每个位置都可以对应编码器前一层中的所有位置。
- 类似地,解码器中的自注意力层允许解码器中的每个位置关注解码器中直到并包括该位置的所有位置。为了保持解码器的自回归特性,需要防止解码器中的向左信息流。我们在缩放点积注意力中通过屏蔽(设置为 - ∞ -∞ -∞)softmax输入中与非法连接相对应的所有值实现了这一点。
1.5.2.4 位置前馈网络
除了注意子层之外,我们的编码器和解码器中的每个层都包含一个完全连接的前馈网络,它分别和相同地应用于每个位置。这包括两个线性转换,中间有一个ReLU激活。
虽然不同位置的线性变换是相同的,但它们从一层到另一层使用不同的参数。另一种描述方法是两个核大小为1的卷积。输入输出的维数 d m o d e l d_{model} dmodel 为512,内层 d f f d_{ff} dff 为2048。
1.5.2.5 Embeddings 和 Softmax
与其他序列转导(transduction )模型类似,我们使用可学习的 Embeddings 将输入Tokens和输出Tokens转换为维度 d m o d e l d_{model} dmodel 的向量。我们还使用常用的可学习的线性变换和Softmax函数将解码器输出转换为预测的下一个Token的概率。在我们的模型中,我们在两个嵌入层之间共享相同的权值矩阵和预softmax linear变换。在嵌入层中,我们将这些权重乘以 d k \sqrt{d_{k}} dk 。
1.5.2.6 位置嵌入
由于我们的模型不包含循环和卷积,为了使模型能够利用序列的顺序,我们必须注入一些关于序列中tokens的相对或绝对位置的信息。为此,我们将“位置编码”添加到编码器和解码器堆栈底部的输入嵌入中。位置编码与嵌入具有相同的维度 d m o d e l d_{model} dmodel ,以使将两者相加。位置编码有很多选择,可学习的和可固定的。
这里,我们使用不同频率的正余弦函数:
其中, p o s pos pos 是位置, i i i 是维度。也就是说,位置编码的每个维度都对应于一个正弦曲线。波长形成一个从