10. Transformer 笔记

本文是transformer 开山之作 Attention Is All You Need 的笔记, 并结合了 The Illustrated Transformer图解。

Abstract

现在主流序列转换都是基于复杂的RNN和CNN,其包含一个encoder和decoder。最好的模型是通过一个attention机制来连接encoder,decoder。作者提出了一个新的网络架构,Transformer,只基于attention,完全不用RNN和CNN。

两个翻译任务证明这个模型:又快又好。

  1. WMT 2014 英语-德语任务中BLEU为28.4,高了2 个BLEU
  2. 8xGPU 3.5天 在WMT 2014 英语-德语中,单一模型BLEU达到41.8

Transformer 还可以用于其他任务, 如英语成分分析等。

1. Introduction

RNN, LSTM, GRU在翻译和LM中占据中最领先的水平。无数努力都是在推进RNN模型和Encoder-Decoder的极限。

RNN 通常是对输入和输出序列的符号位置计算。在计算期间将位置和时间步对齐,它们根据前一步的隐藏状态$h_{t-1} $ 和位置$t $的输入产生隐藏状态$h_t$。这种固有序列天然妨碍训练样本的并行化, 这在长文本序列上变得至关重要, 因为内存大小限制了样本批次大小。 近期工作通过巧妙地因子分解和条件计算在计算效率方面取得重大进展。 也提升了模型表现,但顺序计算的约束依然存在。

在各种任务中,注意力机制,已经成为引人注目的序列建模和转换模型不可或缺的一部分,它允许建模依赖关系不考虑其在输入和输出序列中的距离。但少数情况下, 注意力机制和循环神经网络一起使用。

在这项工作中作者提出Transformer,这种模型架构避免循环并完全依赖于attention机制来绘制输入和输出之间的全局依赖关系。 Transformer允许进行更多的并行化,并且可以在八个P100 GPU上接受少至十二小时的训练后达到翻译质量的新的最佳结果。

2. Background

减少序列计算也构成Extended Neural GPU, ByteNet, ConvS2s的基础,它们都使用卷积神经网络作为基本构建模块,并行计算所有输入和输出位置的隐藏状态表示。

在这些模型中,关联两个任意输入和输出位置的信号所需要的操作次数会随着位置之间的距离而增加, ConS2S是线性的, ByteNet是对数级的。 这就使得学习远距离位置的依存关系更加困难。

在Transfomer中,这中操作减少到固定次数,尽管对attention-weighted 位置取平均降低了效果,但是我使用多头注意力来抵消这种影响。

注意力,有时也叫内在注意力, 是一种注意力机制,关联单一序列不同位置的来计算序列标识。其在不同任务上取得成功,如阅读理解, 概要总结, 文本蕴涵和学习与任务无关的句子表示。

端对端的记忆网络基于注意力机制而不是序列对齐循环,并且取得了简单语言QA和语言模型任务的不错表现。

Tansformer是第一个完全依靠self-attention来计算输入和输出表示, 而不是使用序列对齐RNNs或卷积。

3. Model Architecture

image-20210315145108581

大部分有竞争力的神经序列转换模型有encoder-decoder结构。这里,encoder映射一个用符号表示的输入序列$ (x_1, \cdots, x_n) $ 到 连续表示序列 $ \mathbf{z} = (z_1, \cdots, z_n)$ 。 给定 $\mathbf{z}$, 解码器然后生成一个符号输出序列 $(y_1, \cdots, y_m)$ , 每次一个元素。在每步上模型是自回归的,当生成下一个是, 消耗之前生成符号作为输入的附加部分。

Transformer 遵循这种整体架构,每个编码器和解码器都使用堆叠的自注意力和point-wise, 然后编码器和解码器都使用全连接层连接。

3.1 Encoder and Decoder Stacks

Encoder:编码器由6个完全相同的层堆叠而成。每层都有两个子层:

  1. 多头自注意力机制
  2. position-wise全连接前馈神经网络
  3. 残差连接两个子层, 然后接Normalize。每个子层的输出为 $ \text{LayerNorm} (x + \text{Sublayer}(x)) $, 其中 $ \text{Sublayer}(x) $是由子层本身实现的函数。

为了方便残差连接, 模型示意子层和嵌入层产生的输出维度都是$d_{model} = 512 $

Decoder: 编码器也有6个完全相同的层堆叠而成。

除了编码器层中的两个子层之外, 解码器插入第3个子层,该层对编码器堆叠层的输出执行多头注意力机制。跟编码器类似,作者在每个子层也用残差连接,然后层normalization。 还修改解码器堆叠层自注意力位置, 来防止关注到后面位置。masking 结合outout的embedding都右移了一个位置的事实, 确保位置$i$ 的预测仅仅依靠比$i$小的已知位置。

借用 jalammar 教授的图来展示下整个架构。

从整体来说就是输入源语言,经过Transformer后,输出目标语言。

image-20210315195700172

而Transformer就是Encoder and Decoder Stacks 形成的:

image-20210315195916637

将Encoder-Decoder展开就是:(6个相同的encoder+6个相同的decoder, 在decoder中,会接受输入和encoder的隐藏状态)

image-20210315200058798

而Encoder可以分为:

image-20210315200344415

简化下,Encoder和Decoder如下:

image-20210315200515464

  1. 先将每个单词作为512维词嵌入向量输入, (这里画四格只是为了示意, 也可以选其它维度,后面再讲)

image-20210321150620504

  1. 词嵌入向量组成序列输入之后,每个单词会流经编码器的每个子层。

image-20210321151001758

接下来作者看看Transformer的一个核心特性,在这里输入序列中每个位置的单词都有自己独特的路径流入编码器。在自注意力层中,这些路径之间存在依赖关系。而前馈(feed-forward)层没有这些依赖关系。因此在前馈(feed-forward)层时可以并行执行各种路径。

一个编码器接收向量列表作为输入,接着将向量列表中的向量传递到自注意力层进行处理,然后传递到前馈神经网络层中,将输出结果传递到下一个编码器中。真正编码阶段如下:

image-20210321152053247

3.2 Attention

An attention function can be described as mapping a query and a set of key-value pairs to an output, where the query, keys, values, and output are all vectors. The output is computed as a weighted sum of the values, where the weight assigned to each value is computed by a compatibility function of the query with the corresponding key.

Attention函数可以描述为映射query和一组key-value对到输出,其中query、keys、values和输出都是向量。 输出为value的加权和,其中分配给每个value的权重,用query和其对应的key的点积并通过softmax来计算。

首先作者了解一下如何使用向量来计算自注意力,然后来看它实怎样用矩阵来实现。

计算自注意力的第一步就是从每个编码器的输入向量(每个单词的词向量)中生成三个向量。也就是说对于每个单词,作者创造一个查询向量query、一个键向量key和一个值向量Value。这三个向量是通过词嵌入与三个权重矩阵后相乘创建的。

这些新向量比词嵌入向量维度更低,维度为64,而词嵌入和编码器的输入/输出向量的维度是512. 但实际上不强求维度更小,这只是一种基于架构上的选择,它可以使多头注意力(multiheaded attention)的大部分计算保持不变。

image-20210321153715679

整体来说,作者那$\mathbf{x}_1$乘以 $\mathbf{W}^q$, 得到$\mathbf{q}_1$, (先不要想突然冒出来的$W^q$是怎么来的, 只要把它当做可以学到,这样计算是可以得到一个值的)。那么同样可以得到$\mathbf{k}_1$, $\mathbf{v}_1$,最后输入序列的每个词都有其对应的$\mathbf{k}, \mathbf{q}, \mathbf{v}$。

  • 下面来看看, $\mathbf{k}, \mathbf{q}, \mathbf{v}$怎么计算分数:

image-20210321154358562

如上图, $\mathbf{q}_1 \cdot \mathbf{k}_1 = 112, \ \mathbf{q}_1 \cdot \mathbf{k}_2 = 96$, 就是拿$\mathbf{q} $值去和 $ \mathbf{k} $ 的每个分量相乘,这步可以理解为拿着$\mathbf{q}$去查询相应的分数(两个向量相乘, 来比较其相似度)。

  • image-20210321155238135

再将分数除以8(8是论文中使用的键向量的维数64的平方根,这会让梯度更稳定。这里也可以使用其它值,8只是默认值),然后通过softmax传递结果。softmax的作用是使所有单词的分数归一化,得到的分数都是正值且和为1。

  • 将每个值向量乘以softmax分数(这是为了准备之后将它们求和)。这里的直觉是希望关注语义上相关的单词,并弱化不相关的单词(例如,让它们乘以0.001这样的小数)。
  • image-20210321155853186

对加权值向量求和(译注:自注意力的另一种解释就是在编码某个单词时,就是将所有单词的表示(值向量)进行加权求和,而权重是通过该词的表示(键向量)与被编码词表示(查询向量)的点积并通过softmax得到。),然后即得到自注意力层在该位置的输出(在作者的例子中是对于第一个单词)。比如$z_1$ 就是

上面是向量注意力机制的实现, 接下来是矩阵运算实现自注意力机制

image-20210321161048510

第一步是计算查询矩阵、键矩阵和值矩阵。为此,作者将将输入句子的词嵌入表示为矩阵$X$,将其乘以作者训练的权重矩阵($W^Q,W^K,W^V$)。

  • 这里,矩阵$X$的每一行代表着输入句子中的一个单词的词向量。那么其大小为$ m \times 512$, 而 $\mathbf{v}$ 为 $m \times 64$, 因此$W^Q$的size为$ 512 \times 64$

    这就是图中3个格子和4个格子的差异原因。

最后,因为是矩阵,可以用公式表示如下:

image-20210321161913245

Scaled Dot-Product Attention

这部分跟上面的有重复,只为了对比原文。

image-20210321160347624

“Scaled Dot-Product Attention” 如图2, 输入由一系列$ d_k $ 维的查询值和键, 以及$d_v $ 的值values构成 。计算 所有查询值 $ q$ 和 键$k$ 的点积, 除以 $ \sqrt d_k$, 再应用softmax来获得value的权重。

实际上,应用矩阵计算,公式如下:

两种常用的注意力函数是加法注意力和点乘注意力。除了放缩因子$ 1/\sqrt d_k $之外, 点乘注意力论文中一样。加法attention使用单个隐藏层的前馈网络来计算兼容性函数。 虽然两者在理论上的复杂性相似,但在实践中点积attention的速度更快、更节省空间,因为它可以使用高度优化的矩阵乘法代码来实现。

在$d_k$值比较小的时, 两种机制执行性能相近;

当$d_k $比较大时,加法attention比不带缩放的点积attention性能好.

作者怀疑,$d_k$ 比较大的时候, 点乘增长迅速,把softmax的值推向梯度非常小的区域(想一下softmax函数图像)。为了抵消这个, 采用缩放因子。

Multi-Head Attention

相比于 $ d_{model} $ 维 queries, keys, values的单头注意力函数, 作者发现将queries, keys, values 经过$h$次不同的线性投影后, 相应地,学习到的线性投影到$d_q, d_k, 和d_v$维效果更好。在每一次投影后的 query、key 和 value 上,作者并行地执行注意力函数,产生输出值,其维度为 $d_v$。这些输出值进行连接然后再次投影,产生最终的值, 如上图右。

多头注意力允许模型的不同表示子空间联合关注不同位置的信息。如果只有一个注意力头,平均值将抑制这些信息。

其中, 投影的参数矩阵为

这里采用h = 8 个并行的attention layer或者heads。对于每个head, 使用. 由于每个head的维度减小, 总的计算开销跟全维度的单头相似。

多头注意力机制在两方面提高了注意力层的性能:

1.它扩展了模型专注于不同位置的能力。在上面的例子中,虽然每个编码都在$z_1$中有或多或少的体现,但是它可能被实际的单词本身所支配。如果作者翻译一个句子,比如“The animal didn’t cross the street because it was too tired”,作者会想知道“it”指的是哪个词,这时模型的“多头”注意机制会起到作用。

2.它给出了注意力层的多个“表示子空间”(representation subspaces)。接下来作者将看到,对于“多头”注意机制,作者有多个查询/键/值权重矩阵集(Transformer使用八个注意力头,因此作者对于每个编码器/解码器有八个矩阵集合)。这些集合中的每一个都是随机初始化的,在训练之后,每个集合都被用来将输入词嵌入(或来自较低编码器/解码器的向量)投影到不同的表示子空间中.

image-20210321192403805

在“多头”注意力机制下,作者为每个他保持独立的查询、键、值的权重矩阵,从而得到不同的查询、键、值矩阵。按照论文中, 作者做不同的自注意力计算八次,将得到8个不同的$Z$矩阵

image-20210321192807930

而前馈层不需要8个不同的矩阵, 其只需要一个,所以将8个矩阵压缩成一个矩阵。实际上是把8个矩阵拼接在一起,然后用一个附加矩阵$W^O$ 与其相乘,得到融合的矩阵$Z$。

image-20210321193057238

这就是多头注意力的全部。将其集中在一个图片中如下:

image-20210321193318126

Applications of Attention in our Model

Transformer使用多头注意力在3个不同方面:

  • encoder-decoder attention 层中, queries来自于前一层的decoder层, 内存中的keys和values来自于 decoder 的输出。 这允许decoder中每个位置注意输入序列中的每个位置。这模仿seq2seq 典型的encoder-decoder 注意力机制。
  • 编码器包含self-attention层。 在self-attention层中,所有的keys、values和queries来自同一个地方,在这里, 是编码器中前一层的输出。 编码器中的每个位置都可以关注编码器前一层的所有位置。
  • 相似地, 解码器中的self-attention层允许解码器中的每个位置都关注解码器中直到并包括该位置的所有位置。 作者需要阻止解码器中的向左信息流来保持自回归性质。 通过屏蔽softmax的输入中所有不合法连接的值(设置为 $ -\infty$ ),作者在缩放的点积attention中实现了这一点。

3.3 Position-wise Feed-Forward Networks

除了附加的attention子层外,encoder和decoder的每层都包含一个前馈全连接层, 该层独立且同等第应用于每个位置。其由两个线性变换,中间加一个ReLU激活函数。

另一种描述方式是两个内核为1的卷积。输入和输出的维度都是, 内部层的维度为

3.4 Embeddings and Softmax

跟其他序列转换模型一样,们使用学习到的嵌入层将输入token和输出token转换为维度为 维的向量。

作者还使用普通的线性变换和softmax函数将解码器输出转换为预测的下一个token的概率。 在作者的模型中,两个嵌入层之间和pre-softmax线性变换共享相同的权重矩阵。 在嵌入层中,作者将这些权重乘以 .

3.5 Positional Encoding

由于作者的模型不包含循环和卷积,为了让模型利用序列的顺序,作者必须注入序列中关于token相对或者绝对位置的一些信息。

为此,作者在编码器堆和解码器堆的底部给输入 Embedding 添加“位置编码”。位置编码具有与 Embedding 相同的维数$d_{model} $,因此这两者可以相加。此外,有若干种位置编码的方式可供作者选择、学习和修正。

在这项工作中,作者使用不同频率的正弦和余弦函数作为位置编码:

其中 $\text{pos} $是位置,$i $是维度。也就是说,位置编码的每个维度对应于正弦曲线。波长形成从 的几何级数。作者之所以选择这个函数是因为作者假定它允许模型容易学习对相对位置的关注,因为对任意确定的偏移k , 能被表示成 的线性关系。

在google 开源实现get_timing_signal_1d() 可以找到它,作者这么设计的原因是考虑到在NLP任务中,除了单词的绝对位置,单词的相对位置也非常重要。根据公式 :

这表明位置 $k + p$ 的位置向量可以表示为位置 $k$ 的特征向量的线性变化,这为模型捕捉单词之间的相对位置关系提供了非常大的便利。

也用了embedding学习位置关系, 但结果两者差不多结果。 作者选择正弦曲线, 因为它可以允许模型推断比训练的输入的更长的序列。

Transformer为每个输入的词嵌入添加了一个向量。这些向量遵循模型学习到的特定模式,这有助于确定每个单词的位置,或序列中不同单词之间的距离。这里的直觉是,将位置向量添加到词嵌入中使得它们在接下来的运算中,能够更好地表达的词与词之间的距离。

image-20210321205135836

为了让模型理解单词的顺序,作者添加了位置编码向量,就是上图中的EMBEDDING WITH TIME SIGNAL 中的$\mathbf{x}$。

如果作者假设词嵌入的维数为4,则实际的位置编码如下:

image-20210321205449652

这个模式会是什么样子?

在下图中,每一行对应一个词向量的位置编码,所以第一行对应着输入序列的第一个词。每行包含512个值,每个值介于1和-1之间。作者已经对它们进行了颜色编码。

image-20210321210941801

20个字(行)的位置编码实例,词嵌入大小为512(列)。你可以看到它从中间分裂成两半。这是因为左半部分的值由一个函数(使用正弦)生成,而右半部分由另一个函数(使用余弦)生成。然后将它们拼在一起而得到每一个位置编码向量。

LayerNorm

与之相关的是Batch Normalization,这个技巧能够让模型收敛的更快。但是Batch Normalization有一个问题——它需要一个minibatch的数据,而且这个minibatch不能太小(比如1)。另外一个问题就是它不能用于RNN,因为同样一个节点在不同时刻的分布是明显不同的。当然有一些改进的方法使得可以对RNN进行Batch Normalization,比如论文Recurrent Batch Normalization,有兴趣的读者可以自行阅读 。

Transformer里使用了另外一种Normalization技巧,叫做Layer Normalization。作者可以通过对比Layer Normalization和Batch Normalization来学习。

假设作者的输入是一个minibatch的数据,作者再假设每一个数据都是一个向量,则输入是一个矩阵,每一行是一个训练数据,每一列都是一个特征。BatchNorm是对每个特征进行Normalization,而LayerNorm是对每个样本的不同特征进行Normalization,因此LayerNorm的输入可以是一行(一个样本)。

如下图所示,

image-20210323110020405

输入是(3,6)的矩阵,minibatch的大小是3,每个样本有6个特征。BatchNorm会对6个特征维度分别计算出6个mean和std,然后用这两个mean和std来分别对6个特征进行Normalization,(Batch Normalization是对每批中样本的特征normalize,那么特征维数是每个样本向量的长度,因此normalize后是6个mean和std).

计算公式如下:

而LayerNorm是分别对3个样本的6个特征求mean和std,因此可以得到3个mean和std,然后用这3个mean和std对3个样本来做Normalization,(LayerNorm是对每个样本进行normalize,样本数是3,因此normalize后是3个mean和std)

计算公式如下:

因为LayerNorm的每个样本都是独立计算的,因此minibatch可以很小甚至可以是1。实验证明LayerNorm不仅在普通的神经网络中有效,而且对于RNN也非常有效。

BatchNorm看起来比较直观,作者在数据预处理也经常会把输入Normalize成均值为0,方差为1的数据,只不过它引入了可以学习的参数使得模型可以更加需要重新缓慢(不能剧烈)的调整均值和方差。而LayerNorm似乎有效奇怪,比如第一个特征是年龄,第二个特征是身高,把一个人的这两个特征求均值和方差似乎没有什么意义。论文里有一些讨论,都比较抽象。当然把身高和年龄平均并没有什么意义,但是对于其它层的特征,作者通过平均”期望”它们的取值范围大体一致,也可能使得神经网络调整参数更加容易,如果这两个特征实在有很大的差异,模型也可以学习出合适的参数让它来把取值范围缩放到更合适的区间。

—— 引用自LayerNorm

残差连接

每个self-Attention层都会增加一个残差连接,然后是一个LayerNorm层。

如下所示:

image-20210323112019296

具体来说,就是输入$x_1, x_2$经过self-attention层后,输出$z_1, z_2$。

然后和残差连接的输入$x_1, x_2$加起来一起输入到LayerNorm,再输出到全连接层。

全连接层也是一个残差连接和一个LayerNorm,最后输出到最后一层。

image-20210323112523316

而Decoder类似于Encoder,如下图:区别在于Dncoder多了一个Encoder-Decoder Attention层。

Encoder-Decoder Attention层输入除了来自Self-Attention之外还有Encoder最后一层所有时刻的输出。其Query来自下一层,而Key和Value来自Encoder的输出。

image-20210323151539332

4. Why Self-Attention

在本节,作者比较self_attention 和CNN、RNN的各个方面,它们通常用于映射不同长度符号序列表示$(x_1, \cdots, x_n)$到另一个等长的序列$(z_1, \cdots, z_n)$, 其中$x_i, z_i \in \mathbb{R}^d$ ,例如经典的序列转换中的编码器或编码器隐藏层。使用Self-attention的动机是一下3个方面:

  1. 每层的总体计算复杂度。

  2. 大量计算能够并行,用最小序列操作数来衡量。

  3. 网络中长距离依赖的路径长度。学习长距离依赖是许多序列转换任务的关键。另外一个关键因素影响学习这种依赖是前向和方向信号必须穿过网络的路径长度。

    任何输入位置和输出序列的组合之间的路径越短,这种长距离依赖也就越容易学习到。

5. Training

Optimizer

使用Adam 优化器, $\beta_1=0.9, \beta_2=0.98 和 \epsilon = 10^{-9}$,学习率变化:

总结

优点:(1)虽然Transformer最终也没有逃脱传统学习的套路,Transformer也只是一个全连接(或者是一维卷积)加Attention的结合体。但是其设计已经足够有创新,因为其抛弃了在NLP中最根本的RNN或者CNN并且取得了非常不错的效果,算法的设计非常精彩,值得每个深度学习的相关人员仔细研究和品位。(2)Transformer的设计最大的带来性能提升的关键是将任意两个单词的距离是1,这对解决NLP中棘手的长期依赖问题是非常有效的。(3)Transformer不仅仅可以应用在NLP的机器翻译领域,甚至可以不局限于NLP领域,是非常有科研潜力的一个方向。(4)算法的并行性非常好,符合目前的硬件(主要指GPU)环境。

缺点:(1)粗暴的抛弃RNN和CNN虽然非常炫技,但是它也使模型丧失了捕捉局部特征的能力,RNN + CNN + Transformer的结合可能会带来更好的效果。(2)Transformer失去的位置信息其实在NLP中非常重要,而论文中在特征向量中加入Position Embedding也只是一个权宜之计,并没有改变Transformer结构上的固有缺陷。

​ ——详解Transformer

Inference

[1] Transformers and Self-Attention For Generative Models guest lecture by Ashish Vaswani

[2] The Annotated Transformer

[3*] The Illustrated Transformer

[4] Good Article Translation and Sharing — Attention Model

[5*] Transformer图解

[6] BERT泛读系列(一)——《Attention is All You Need》论文笔记

[7] 翻译

[8] Transformer-Attention Is All You Need笔记

[9] transformer

[10] attention-is-all-you-need

[11] 手把手教你用Pytorch-Transformers——实战(二)

[12] Transformer理论源码细节详解

[13] Shreya Gherani:BERT庖丁解牛

[14] 图解Transformer(完整版

[15] 保姆级教程:硬核图解Transformer