学习笔记 发布于 更新于

Attention Is All You Need 论文阅读笔记

https://zhuanlan.zhihu.com/p/569527564参考博客

#Transformer#Attention Is All You Need#论文笔记#学习笔记

这篇内容基于原始 Notion 导出整理,保留原始笔记主线,并做了轻度标题分层、排版优化与导出残留清理。

一、论文背景

https://zhuanlan.zhihu.com/p/569527564参考博客

机器翻译,就是将某种语言的一段文字翻译成另一段文字。

由于翻译没有唯一的正确答案,用准确率来衡量一个机器翻译算法并不合适。因此,机器翻译的数据集通常会为每一条输入准备若干个参考输出。统计算法输出和参考输出之间的重复程度,就能评价算法输出的好坏了。这种评价指标叫做BLEU Score。这一指标越高越好。

2603.15031v1.pdf

RNN处理翻译问题

在深度学习时代早期,人们使用RNN(循环神经网络)来处理机器翻译任务。一段输入先是会被预处理成一个token序列。RNN会对每个token逐个做计算,并维护一个表示整段文字整体信息的状态。根据当前时刻的状态,RNN可以输出当前时刻的一个token。

所谓token,既可以是一个单词、一个汉字,也可能是一个表示空白字符、未知字符、句首字符的特殊字符。

具体来说,在第t轮计算中,输入是上一轮的状态以及这一轮的输入token ,输出这一轮的状态以及这一轮的输出token 。

动图封面

编码器-解码器出现(解决输入输出等长问题)

这种简单的RNN架构仅适用于输入和输出等长的任务。然而,大多数情况下,机器翻译的输出和输入都不是等长的。因此,人们使用了一种新的架构。前半部分的RNN只有输入,后半部分的RNN只有输出(上一轮的输出会当作下一轮的输入以补充信息)。两个部分通过一个状态来传递信息。把该状态看成输入信息的一种编码的话,前半部分可以叫做“编码器”,后半部分可以叫做“解码器”。这种架构因而被称为编码器-解码器架构。

注意力机制初登场(解决较长文章的翻译问题)

这种架构存在不足:编码器和解码器之间只通过一个隐状态来传递信息。在处理较长的文章时,这种架构的表现不够理想。为此,有人提出了基于注意力的架构。这种架构依然使用了编码器和解码器,只不过解码器的输入是编码器的状态的加权和,而不再是一个简单的中间状态。每一个输出对每一个输入的权重叫做注意力,注意力的大小取决于输出和输入的相关关系。这种架构优化了编码器和解码器之间的信息交流方式,在处理长文章时更加有效。

尽管注意力模型的表现已经足够优秀,但所有基于RNN的模型都面临着同样一个问题:RNN本轮的输入状态取决于上一轮的输出状态,这使RNN的计算必须串行执行。因此,RNN的训练通常比较缓慢。

在这一背景下,抛弃RNN,只使用注意力机制的Transformer横空出世了。

二、摘要与引言

摘要

摘要传递的信息非常简练:

  • 当前最好的架构是基于注意力的”encoder-decoder”架构。这些架构都使用了CNN或RNN。这篇文章提出的Transformer架构仅使用了注意力机制,而无需使用CNN和RNN。
  • 两项机器翻译的实验表明,这种架构不仅精度高,而且训练时间大幅缩短。

引言

引言的第一段回顾了RNN架构。以LSTM和GRU为代表的RNN在多项序列任务中取得顶尖的成果。许多研究仍在拓宽循环语言模型和”encoder-decoder”架构的能力边界。

第二段就开始讲RNN的不足了。RNN要维护一个隐状态,该隐状态取决于上一时刻的隐状态。这种内在的串行计算特质阻碍了训练时的并行计算(特别是训练序列较长时,每一个句子占用的存储更多,batch size变小,并行度降低)。有许多研究都在尝试解决这一问题,但是,串行计算的本质是无法改变的。

上一段暗示了Transformer的第一个设计动机:提升训练的并行度。

第三段讲了Transformer的另一个设计动机:注意力机制。注意力机制是当时最顶尖的模型中不可或缺的组件。这一机制可以让每对输入输出关联起来,而不用像早期使用一个隐状态传递信息的”encoder-decoder”模型一样,受到序列距离的限制。然而,几乎所有的注意力机制都用在RNN上的。

既然注意力机制能够无视序列的先后顺序,捕捉序列间的关系,为什么不只用这种机制来构造一个适用于并行计算的模型呢?

因此,在这篇文章中,作者提出了Transformer架构。这一架构规避了RNN的使用,完全使用注意力机制来捕捉输入输出序列之间的依赖关系。这种架构不仅训练得更快了,表现还更强了。

通过摘要和引言,基本可以看清 Transformer 架构的设计动机。作者一方面想克服 RNN 难以并行的缺点,另一方面又想充分利用没有串行限制的注意力机制,于是提出了一个仅依赖注意力机制的模型。最终结果也非常突出:训练速度明显加快,同时模型表现超过了当时其他主流架构。

三、注意力机制

文章在介绍 Transformer 架构时采用的是自顶向下的方式。但是在刚开始阅读时,如果对各个模块还不熟悉,直接理解整体框架会有一些阻碍。因此,这里更适合按自底向上的方式来梳理 Transformer。

先看 3.2 节。这一节介绍了 Transformer 里最核心的机制——注意力。在阅读这部分文字之前,先抽象地理解一下注意力机制究竟在做什么。

注意力计算的一个例子

“注意力”这个名字本身并不算特别直观。更容易理解的一种说法,其实是“全局信息查询”。做一次“注意力”计算,可以类比成去数据库里做一次查询。假设现在有这样一个以人名为 key(键)、以年龄为 value(值)的数据库:

{ 张三: 18, 张三: 20, 李四: 22, 张伟: 19 }

现在有一个 query(查询),问题是“所有叫张三的人,年龄平均值是多少”。如果写成程序,可以把字符串“张三”和所有 key 做比较,找出所有“张三”对应的 value,把这些年龄值相加,再取平均数。这个平均数是 (18+20)/2=19

但很多时候,查询并不会这么明确。比如,也可能只想查询“所有姓张的人的年龄平均值”。这时,不再比较 key == 张三,而是比较 key[0] == 张。这个平均数对应 (18+20+19)/3=19

查询也可能更模糊一些,模糊到无法用简单的判断语句来完成。因此,更通用的方法是把 query 和 key 都建模成向量。之后,对 query 和 key 之间计算一个相似度(比如向量内积),再以这个相似度为权重,计算 value 的加权和。这样,不管查询多么抽象,都可以把 query、key 建模成向量,用向量相似度代替查询判断语句,用加权和代替直接取值再求平均值。“注意力”本质上指的就是这里的权重。

把这种方法代入刚刚那个例子里,先把所有 key 建模成向量,可能得到这样一个新的数据库:

{ [1, 2, 0]: 18, # 张三 [1, 2, 0]: 20, # 张三 [0, 0, 2]: 22, # 李四 [1, 4, 0]: 19 # 张伟 }

假设 key[0] == 1 表示姓张,那么“所有姓张的人的年龄平均值”这个查询就可以表示成向量 [1, 0, 0]。用这个 query 和所有 key 算出的权重是:

dot([1, 0, 0], [1, 2, 0]) = 1 dot([1, 0, 0], [1, 2, 0]) = 1 dot([1, 0, 0], [0, 0, 2]) = 0 dot([1, 0, 0], [1, 4, 0]) = 1

接下来就可以用这些权重计算平均值。注意,算平均值时权重和应该为 1,因此可以先用 softmax 把这些权重归一化,再计算 value 的加权和。

softmax([1, 1, 0, 1]) = [1/3, 1/3, 0, 1/3] dot([1/3, 1/3, 0, 1/3], [18, 20, 22, 19]) = 19

这样就用向量运算代替了判断语句,完成了数据库的全局信息查询。那三个 1/3,就是 query 对每个 key 的注意力。

放缩点乘注意力(Scaled Dot-Product Attention) (3.2.1节)

刚刚完成的这组计算,已经很接近 Transformer 里的注意力机制了。论文里把它称为放缩点乘注意力(Scaled Dot-Product Attention)。它的公式是:

image.png

  • 查询(Q) :表示当前元素对其他元素的关注点。它类似于“询问”某个问题,用于确定当前元素需要从其他元素中获取哪些信息。
  • 键(K) :表示每个元素的特征。它类似于“标签”,帮助模型决定当前元素对哪些其他元素重要。
  • 值(V) :表示每个元素的具体信息,包含需要从当前元素中提取的内容。

先看看 Q、K、V 在刚刚那个例子里分别对应什么。

K其实就是key向量的数组

K = [[1, 2, 0], [1, 2, 0], [0, 0, 2], [1, 4, 0]]

V 就是 value 向量的数组。而在刚刚那个例子里,value 都是实数。实数其实也可以看作长度为 1 的向量,因此那个例子里对应的是

V = [[18], [20], [22], [19]]

在刚刚那个例子里只做了一次查询,因此更准确地说,操作应该写成

image.png

其中,query q就是[1, 0, 0]了。

实际上,也可以一次做多组 query。把所有 q 打包成矩阵 Q,就得到了公式

image.png

**dk(放缩命名的由来)**就是 query 和 key 向量的长度。由于 query 和 key 要做点乘,这两种向量的长度必须一致。value 向量的长度倒是可以不一致,论文里把 value 向量长度记作 dv。在这个例子里,dk=3,dv=1。

为什么要用一个和dk成比例的项来放缩QKT呢?

这是因为,softmax 在绝对值较大的区域梯度较小,梯度下降速度会变慢。因此,被 softmax 的点乘数值需要尽可能控制在较合适的范围内。一般来说,向量越长,点乘值也越容易偏大;除以一个与 dk 相关的量,能够防止点乘值过大

刚才也提到,其实是在算query和key的相似度。而算相似度并不只有求点乘这一种方式。 另一种常用的注意力函数叫做加性注意力,它用一个单层神经网络来计算两个向量的相似度。相比之下,点乘注意力算起来快一些。出于性能上的考量,论文使用了点乘注意力。

自注意力

让一句话中的每个单词都去向其他单词查询信息,就能为每一个单词生成更有意义的向量表示。

自注意力是3.2.3节里提及的内容。我认为,学完注意力的原理后,立刻去学自注意力能够更快地理解注意力机制。当然,论文里并没有对自注意力进行过多的引入,初学者学起来会非常困难。因此,这里我参考《深度学习专项》里的介绍方式,用一个更具体的例子介绍了自注意力。

在大致明白注意力机制本质上是“全局信息查询”,并掌握注意力公式之后,再以 Transformer 的自注意力为例,进一步理解注意力的意义。

自注意力模块的目的是为每一个输入 token 生成一个向量表示。这个表示不仅反映 token 本身的性质,还反映它在句子中的具体语境。比如翻译“简访问非洲”这句话时,第三个字“问”在中文里有很多个意思,比如询问、慰问等。这里需要得到的是它在当前句子中的具体含义。而在这个例子里,“问”字和前一个字组成了“访问”,因此更接近“询问”这个意义,而不是“慰问”。这也就是“问”字在这句话中的上下文化表示。

image.png

image.png

image.png

image.png

从上一节可以看出,注意力本质上是全局信息查询;而这一节对应的是注意力的一种具体应用:

通过让一句话中的每个单词都去向其他单词查询信息,就能为每一个单词生成更有意义的向量表示。

这里还留着一个问题:每个单词的 query、key、value 是怎么得来的?这就对应 Transformer 里的另一种机制——多头注意力。

多头注意力 (3.2.2节)

image.png

Transformer 模型架构

看懂了注意力机制,可以回过头阅读3.1节学习Transformer的整体架构了。

论文里的图 1 是 Transformer 的架构图。不过在还没读后面章节时,有些模块其实还不熟,因此这一轮阅读更适合先只关注模型主干,先看清 encoder 和 decoder 之间是怎么组织起来的。

目前只先掌握了多头注意力模块的原理,对模型主干中的三个模块还会有疑问:

  1. Add & Norm
  2. Feed Forward
  3. 为什么一个多头注意力前面加了Masked

下面依次看这三个模块。

残差连接(3.1节)

image.png

前馈网络

架构图中的前馈网络(Feed Forward)其实就是一个全连接网络。具体来说,这个子网络由两个线性层组成,中间用ReLU作为激活函数。

image.png

整体架构与掩码多头注意力

到这里,模型的整体架构已经基本可以看清。只有把整个模型的运行原理串起来,才能进一步理解多头注意力前面的 masked 是怎么来的。

论文第3章开头介绍了模型的运行原理。和多数强力的序列转换模型一样,Transformer使用了encoder-decoder的架构。早期基于RNN的序列转换模型在生成序列时一般会输入前i个单词,输出 i+1个单词。

image.png

Transformer 默认会并行地输出结果。而在推理时,序列必须得串行生成。直接调用Transformer的并行输出逻辑会产生非常多的冗余运算量。推理的代码实现可以进行优化。

具体来说,输入序列会经过个结构相同的层。每层由多个子层组成。第一个子层是多头注意力层,准确来说,是多头自注意力。这一层可以为每一个输入单词提取出更有意义的表示。之后数据会经过前馈网络子层。最终,输出编码结果。

得到编码结果后,就进入解码器部分。解码器的输入是当前已经生成的序列,该序列会经过一个掩码(masked)多头自注意力子层。这里先不展开 mask 的具体实现,暂时把它当成普通的多头自注意力层来看;它的作用和编码器中的类似,都是为了提取更有意义的表示。

接下来,数据还会经过一个多头注意力层。这个层比较特别,它的K,V来自,Q来自上一层的输出。为什么会有这样的设计呢?这种设计来自于早期的注意力模型。如下图所示,在早期的注意力模型中,每一个输出单词都会与每一个输入单词求一个注意力,以找到每一个输出单词最相关的某几个输入单词。用注意力公式来表达的话,Q就是输出单词,K, V就是输入单词。

经过第二个多头注意力层后,和编码器一样,数据会经过一个前馈网络。最终,网络并行输出各个时刻的下一个单词。

这种并行计算有一个要注意的地方。在输出第个单词时,模型不应该提前知道时刻之后的信息。因此,应该只保留时刻之前的信息,遮住后面的输入。这可以通过添加掩码实现。添加掩码的一个不严谨的示例如下表所示:

输入输出
(y1, —, —, —)y2
(y1, y2, —, —)y3
(y1, y2, y3, —)y4

这就是为什么解码器的多头自注意力层前面有一个masked。在论文中,mask是通过令注意力公式的softmax的输入为来实现的(softmax的输入为,注意力权重就几乎为0,被遮住的输出也几乎全部为0)。每个mask都是一个上三角矩阵。

嵌入层

看完了Transformer的主干结构,再来看看输入输出做了哪些前后处理。

和其他大多数序列转换任务一样,Transformer主干结构的输入输出都是词嵌入序列。词嵌入,其实就是一个把one-hot向量转换成有意义的向量的转换矩阵。在Transformer中,解码器的嵌入层和输出线性层是共享权重的——输出线性层表示的线性变换是嵌入层的逆变换,其目的是把网络输出的嵌入再转换回one-hot向量。如果某任务的输入和输出是同一种语言,那么编码器的嵌入层和解码器的嵌入层也可以共享权重。

论文中写道:“输入输出的嵌入层和softmax前的线性层共享权重”。这个描述不够清楚。如果输入和输出的不是同一种语言,比如输入中文输出英文,那么共享一个词嵌入是没有意义的。

嵌入矩阵的权重乘了一个。

由于模型要预测一个单词,输出的线性层后面还有一个常规的softmax操作。

位置编码

现在,Transformer的结构图还剩一个模块没有读——位置编码。无论是RNN还是CNN,都能自然地利用到序列的先后顺序这一信息。然而,Transformer的主干网络并不能利用到序列顺序信息。因此,Transformer使用了一种叫做“位置编码”的机制,对编码器和解码器的嵌入输入做了一些修改,以向模型提供序列顺序信息。

嵌入层的输出是一个向量数组,也就是词嵌入向量序列。设数组的位置叫作位置索引,向量的某一维叫作通道维。这里需要为每一个向量里的每一个数添加一个实数编码,这种编码方式要满足以下性质:

  1. 对于同一个不同的,即对于一个词嵌入向量的不同元素,它们的编码要各不相同。
  2. 对于向量的同一个维度处,不同的编码不同。且间要满足相对关系,即。

要满足这两种性质,可以先构造这样一种编码函数:

即对于每一个位置,用小数点后的3个十进制数位来表示不同的。之间也满足相对关系。

但是,这种编码并不利于网络学习。更理想的情况是所有编码的数值规模比较接近,并且落在一个相对稳定的范围内。为此,Transformer 使用了三角函数作为编码函数。这种位置编码(Positional Encoding, PE)的公式如下。

不同,则三角函数的周期不同。同不同周期的三角函数值不重复。这满足上面的性质1。另外,根据三角函数的和角公式:

是  的一个线性函数,即不同的pos之间有相对关系。这满足性质2。

本文作者也尝试了用可学习的函数作为位置编码函数。实验表明,二者的表现相当。作者还是使用了三角函数作为最终的编码函数,这是因为三角函数能够外推到任意长度的输入序列,而可学习的位置编码只能适应训练时的序列长度。

为什么用自注意力

在论文的第四章,作者用自注意力层对比了循环层和卷积层,探讨了自注意力的一些优点。

自注意力层是一种和循环层和卷积层等效的计算单元。它们的目的都是把一个向量序列映射成另一个向量序列,比如说编码器把映射成中间表示。论文比较了三个指标:每一层的计算复杂度、串行操作的复杂度、最大路径长度。

前两个指标很容易懂,第三个指标最大路径长度需要解释一下。最大路径长度表示数据从某个位置传递到另一个位置的最大长度。比如对边长为n的图像做普通卷积操作,卷积核大小3x3,要做次卷积才能把信息从左上角的像素传播到右下角的像素。设卷积核边长为,则最大路径长度。如果是空洞卷积的话,像素第一次卷积的感受野是3x3,第二次是5x5,第三次是9x9,以此类推,感受野会指数级增长。这种卷积的最大路径长度是。

可以从这三个指标分别讨论自注意力的好处。先看序列操作复杂度。如引言所写,循环层最大的限制在于不能并行训练;而自注意力层和卷积一样可以完全并行。

再看每一层的复杂度。设是序列长度,是词嵌入向量长度。其他架构的复杂度有,而自注意力是。一般模型的会大于,自注意力的计算复杂度也会低一些。

最后是最大路径长度。注意力本来就是全局查询操作,可以在的时间里完成所有元素间信息的传递。它的信息传递速度远胜卷积层和循环层。

为了降低每层的计算复杂度,可以改进自注意力层的查询方式,让每个元素查询最近的个元素。本文仅提出了这一想法,并没有做相关实验。

实验与结果

本工作测试了“英语-德语”和“英语-法语”两项翻译任务。使用论文的默认模型配置,在8张P100上只需12小时就能把模型训练完。本工作使用了Adam优化器,并对学习率调度有一定的优化。模型有两种正则化方式:1)每个子层后面有Dropout,丢弃概率0.1;2)标签平滑(Label Smoothing)。Transformer在翻译任务上胜过了所有其他模型,且训练时间大幅缩短。

论文同样展示了不同配置下Transformer的消融实验结果。

实验A表明,计算量不变的前提下,需要谨慎地调节和的比例,太大太小都不好。这些实验也说明,多头注意力比单头是要好的。

实验B表明,增加可以提升模型性能。作者认为,这说明计算key, value相关性是比较困难的,如果用更精巧的计算方式来代替点乘,可能可以提升性能。

实验C, D表明,大模型是更优的,且dropout是必要的。

如正文所写,实验E探究了可学习的位置编码。可学习的位置编码的效果和三角函数几乎一致。

总结

为了改进RNN不可并行的问题,这篇工作提出了Transformer这一仅由注意力机制构成的模型。Transformer的效果非常出色,不仅训练速度快了,还在两项翻译任务上胜过其他模型。

作者也很期待Transformer在其他任务上的应用。对于序列长度比较大的任务,如图像、音频、视频,可能要使用文中提到的只关注局部的注意力机制。由于序列输出时仍然避免不了串行,作者也在探究如何减少序列输出的串行度。

现在来看,Transformer是近年来最有影响力的深度学习模型之一。它先是在NLP中发扬光大,再逐渐扩散到了CV等领域。文中的一些预测也成为了现实,现在很多论文都在讨论如何在图像中使用注意力,以及如何使用带限制的注意力以降低长序列导致的计算性能问题。

我认为,对于深度学习的初学者,不管是研究什么领域,都应该仔细学习Transformer。在学Transformer之前,最好先了解一下RNN和经典的encoder-decoder架构,再学习注意力模型。有了这些基础,读Transformer论文就会顺利很多。读论文时,最重要的是看懂注意力公式的原理,再看懂自注意力和多头注意力,最后看一看位置编码。其他一些和机器翻译任务相关的设计可以不用那么关注。