论文: Attention Is All You Need

介绍Google基于self-attention提出的Transformer模型

1. 介绍

当前的机器翻译等相关的序列模型都是基于encoder-decoder架构的,而其中的encoder和decoder部分,很多时候都是围绕RNN和CNN做文章。

  • 基于RNN的编码

    RNN的编码通常是在时间维度上进行,它会沿着输入序列依次计算各隐藏状态h1,h2,...hth_1, h_2,...h_t ,而每次计算都会依靠前一个状态和当前输入,即 ht=f(ht1,xt)h_t = f(h_{t-1}, x_t) 。RNN这种计算方式导致它天生就不适合分布式的训练,对GPU的利用并不友好,性能上有很大瓶颈。

    除此之外,虽然有了LSTM,GRU等RNN的改进结构后,长期依赖问题得到了一定解决。但是RNN在处理这种长期依赖还是需要依靠在时间上进行计算叠加,是O(n)时间复杂度的。对于距离越远的依赖特征,越难捕获到。

  • 基于CNN的编码

    和RNN不同,CNN的编码更多是在空间上进行的,它将整个句子作为输入,然后通过不同的卷积层叠加提取特征。虽然同一层的CNN卷积是可以并行化处理的,不过要捕捉到远距离依赖,也需要靠更加抽象的深层卷积层才行。

  • attention机制

    近年来,attention机制在encoder,decoder中使用非常频繁。和RNN或者CNN相比,attention机制捕获长期依赖的方式可以达到O(1)的复杂度。无论是多远的依赖,attention都可以直接将它们进行关联。

在《Attention Is All You Need》这篇论文中,Google利用纯Attention机制,不加入任何RNN和CNN的结构,构造了一个称为Transformer的模型。它在机器翻译相关任务上刷新了成绩,而且非常适合并行化加速了模型的训练。

2. 背景

RNN和CNN在捕捉长距离依赖时都有各自的局限性,算法的复杂度都是随距离增长而增长的,而attention机制可以按常数时间复杂度捕获这些依赖。

普通的attention通常被用来构建source句子的词和target句子的词之间的联系,而self-attention是在单一句子上发现词之间的联系。作为一种新颖的句子表示方式,self-attention现在应用非常广泛,在文本摘要,阅读理解,句子表示上都有应用。

3. 模型结构

模型结构还是基于encoder-decoder的,如下图左右所示:

图中的Nx表示同样的结构堆叠而成。

3.1 堆叠而成的encoder和decoder

encoder

encoder由N=6个同样的结构层组成。每层由一个multi-head的self-attention结构和一个position-wise的前向全联接网络组成。multi-head是这篇文章提出的新概念,后面会讲。position-wise指的是对每个位置的向量做一个全联接,例如10个字,就是做10次全联接。

除此之外,在multi-head和feed-forward之后分别都有一个残差结构和归一化。即每个Sublayer的真实输出是LayerNorm(x + Sublayer(x))。为了简化模型,这里定义这些sublayer的输出和embedding的维度都是 dmodel=512d_{model} = 512

decoder

decoder部分和encoder部分的区别主要有两点。首先是在self-attention之外还加入了一层attention,用来对encoder的输出计算multi-head形式的attention。其次是在输出的上做self-attention的时候用到了一个mask,因为i时刻的输出只能在i时刻之前的输出序列上计算attention。

3.2 Attention

之前了解过Attention机制可以理解为计算query与key的相似度,然后根据相似度对value进行加权求和的过程。本文中使用的计算相似度的方法被称为Scaled Dot-Product Attention。

3.2.1 Scaled Dot-Product Attention

实际上,我们通常可以同时计算多个query的attention函数。把它们都放进一个矩阵Q,keys和values对应的矩阵分别为K和V.

其中,Q的维度为 ndkn * d_k , n代表query的个数, dkd_k 代表key的维度,K的维度为 mdkm * d_k ,其中m代表计算attention时key的个数,self-attention时可以理解为source的长度,V的维度自然就是 mdvm * d_v

输出矩阵则为: Attention(Q,K,V)=softmax(QKTdk)VAttention(Q, K, V) = softmax(\frac{QK^T}{\sqrt{d_k}})V scaled指的就是在做完点乘之后除以 dk\sqrt{d_k} ,这是为了防止点乘过大导致的softmax不够“soft”,即各个key上的概率分布接近于非0即1。

3.2.2 Multi-Head Attention

按照上面的Attention计算的话就是一个基于维度 dmodeld_{model} 的query, key, value的attention,不过这里作者发明了一个称为multi-head的attention结构,可以获得更好的效果。

公式为 MultiHead(Q,K,V)=Concat(head1,head2,...,headh)WOMultiHead(Q, K, V) = Concat(head_1, head_2, ..., head_h)W^O 其中 headi=Attention(QWiQ,KWiK,VWiV)head_i = Attention(QW^Q_i, KW^K_i, VW^V_i)

这里先将 dmodeld_{model} 的维度分别利用矩阵相乘进行线性变换,相对于 dmodeld_{model} 进行降维,其中 WiQW^Q_i WiKW^K_i 的维度均为 dmodeldkd_{model} * d_k WiVW^V_i 的维度为 dmodeldvd_{model}*d_v 随后对h个head进行concat就得到了 hdvhd_v 的输出维度,于是 WOW^O 的维度设置为 hdvdmodelhd_v * d{model}

在这里作者使用了 h=8h=8 个attention进行拼接,由于它们是参数不共享的,可以很容易的进行并行化计算。为了使最终计算复杂度和单一attention的时候接近,这里设置 dk=dv=dmodel/h=512/8=64d_k = d_v = d_{model} / h = 512/8 = 64

3.2.3 模型中使用的Attention

在Transformer模型中,attention有三种使用方法:

  • encoder-decoder的attention

    这个是encoder-decoder架构中attention的最经典用法,query是decoder中的上一时刻的状态,然后attention是在encoder的所有输出上。

  • encoder的self-attention

    这个是在source句子上做self-attention,每个字计算它和所有字的联系。

  • decoder的self-attention

    和encoder的self-attention相比,decoder的self-attention在于在计算一个位置的self-attention的时候考虑的source只能是它之前(包含当前位置)的字,对之后的字应该是不可见的。为了实现这样的功能,作者在scaled dot-product的时候用了一个mask。将之后的位置的key的dot-product设置为 -\infty

3.3 Position-wise的前向神经网络

从模型结构图可以看到,在每个sublayer的最终attention输出后都有一个前向神经网络。这个网络是position-wise的,对每个位置进行相同的feed-forward过程: FFN(x)=max(0,xW1+b1)W2+b2FFN(x) = max(0, xW_1 + b_1)W_2 + b_2 不过在层级之间,它们的参数是不同的。

3.4 Embeddings and Softmax

这里stacks中的每层模型都含有两个embeddings和一个linear-softmax,两个embedings分别在encoder和decoder中,这三个矩阵在同一层中是同一个维度为 ndmodeln*d_{model} 的矩阵。这也符合直觉,因为字在每一层所具备的“自身的含义”在该层的不同模块应该是保持不变的。

3.5 Positional Encoding

可以看到目前的模型并没有考虑任何位置信息,也就是说source打乱顺序对于生成target是没有任何影响的。我们的模型无法利用序列的位置信息,这在序列模型上显然是有很大缺陷的。特别是对于语言模型,字词的顺序是一个非常重要的特征。

基于这样的考虑,作者在这里使用了一个positional encoding的方法。其实类似的方法很多模型都用过,比如在对字进行建模的时候,经常除了字本身的embedding,还会加入一个粗分词后字的位置作为特征,这个特征有三种状态,“词开始”,“词中间”,“词结束”,分别对应一组特征向量。然后组合可以是求和,也可以是拼接。

这里我们使用的是求和的方式,因此位置的向量维度和字的embedding是一样的,都为 dmodel=512d_{model} = 512 。不过这里有一个不同是,一般情况下这种位置向量是训练得到的,而google这里是直接给出了公式计算这个向量:

PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos,2i+1)=cos(pos/100002i/dmodel)\begin{aligned} &PE{(pos, 2i)} = sin(pos/10000^{2i/d{model}})\\ & PE{(pos, 2i+1)} = cos(pos/10000^{2i/d_{model}})\end{aligned}

其中pos是位置,2i和2i+1都是是当前位置向量的index。作者测试发现这种方法和学习位置向量的方法得到的结果基本没有区别。

4. 为什么使用Self-Attention

首先是计算上带来的优化,每层的计算大大减小,而且相对于RNN的情形,self-Attention的计算是高度并行化的。对于长期依赖来说,self-attention的编码方式更加容易处理,可以直接将两个很远的字或词直接联系起来。

除此之外,self-attention可以带来很多可解释的模型结果。下面是作者在论文中给出的一些self-attention的可视化图。

上图显示的是encoder上一个长依赖的特性,可以看到making这个词对很多远距离的词都有联系,尤其是捕获到了making...more difficult这个短语结构。其中每个小方块表示的是multi-head中每个head的影响,共8个。

上图是捕获代词特征的一个例子,its很智能的表示了law和application的关系。

5. 训练

训练过程都是些常用的优化算法,这里用的是adam。然后讲一下作者在训练时使用的正则化的技巧。

Dropout

这里有两个地方用到了dropout:

  • 模型中每个sub-layer输出都有个类似残差网络的联结结构,在将其加入前进行dropout

  • embeding和position encoding输入层加一个dropout

这两个dropout作者均使用了0.1。

Label Smoothing 训练的时候,输出数据的分布通常是0或1的,不够soft。这样会容易导致过拟合情况的发生。除此之外,模型的预测过于自信,给出很高的置信度,这往往也会产生偏离实际情况的结果。

label smoothing就是为了缓解这种label标注时不够soft从而导致过拟合的情形: q(yx)=(1e)q(yx)+eu(y)q'(y|x) = (1-e)q(y|x) + e*u(y) 这里q(y|x)是标注label上表示的真实分布,u(y)是脱离训练样本的y的真实分布,通常可以通过统计出现的频数进行计算。比如说如果是分类问题的话,就是类型y的样本数/总样本数。

通过这样的方法,就相当于是给训练数据上引入了一些噪声,一定程度上可以防止过拟合。这里作者选择了e=0.1。

6.总结

Transformer模型完全使用attention机制进行建模,算法非常适合并行化,在效率提升的情况下仍然获得了好的结果。而且attention机制更符合人的直觉,可视化也展示了attention学到了人类语法中的一些特征。

除此之外,作者也指出了改进空间。当前的attention的每次都是考虑完整的序列,这对于很长的序列有的时候是不必要的,因此提出了一个restricted的版本,只考虑局部的attention(例如一定窗口长度内的词),这在处理例如图片,音频和视频等长序列输入的时候可能会更有优势。

作者的代码已经开源在了github上。

参考文献

[1] Attention Is All You Need - Ashish Vaswani, et al

Last updated