Transformer论文精读笔记 | 青训营

77 阅读33分钟

Attention is All You Need

论文地址:《Attention is All You Need》

代码地址:github.com/tensorflow/…

一、摘要

主流的序列转换模型都是基于复杂的循环或卷积神经网络,这个模型包含一个编码器和一个解码器。具有最好性能的模型在编码和解码之间通过一个注意力机制连接编码器。我们提出一个新的简单网络结构——Transformer,其仅仅是基于注意力机制,而完全不需要之前的循环或卷积。在两个机器翻译任务上的实验表明,该模型具有更好的性能,同时并行度更好,训练时间更少(泛化到其他任务效果也不错)。

在WMT 2014英语到德语翻译任务上,我们的模型达到了28.4BLEU,比之前最好的结果提高了2BLEU。在WMT 2014英语到法语翻译任务上,我们的模型在8个GPU上训练3.5天后,所得到的单个模型获得了41.8BLEU分数。我们在大型和有限的训练数据中,通过将其成功应用于英语句法解析,表明了Transformer可以很好地适用于其他任务。

可以看到这篇文章最开始只是针对机器翻译来写的,transformer在机器翻译上效果也很好。但是随着bert、GPT等把这种架构用在更多的NLP任务上,甚至后面CV和video等也可以使用注意力机制,整个工作就火出圈了。

二、结论

本文介绍了Transformer,这是第一个完全基于注意力的序列转换模型,用多头自注意力(multi-headed self-attention)代替了encoder-decoder架构中最常用的循环层。

对于翻译任务,Transformer可以比基于循环或卷积层的体系结构训练更快。在WMT 2014 English-to-German和and WMT 2014 English-to-French翻译任务中,我们取得了最好的结果。在前面的任务中,我们最好的模型甚至胜过以前发表过的所有整合模型。

我们对基于注意力的模型的未来感到兴奋, 并计划将Transformer应用于文本之外的涉及输入和输出模式的问题中任务,以有效处理大型输入&输出任务,如图像、音频和视频等。让生成不那么时序化是我们的另一个研究目标。

三、导论

序列建模和转换问题(如机器翻译)最新方法是LSTMGRN等。后面许多研究都围绕循环语言模型和编码器-解码器体系结构进行。

循环网络模型通常是考虑了输入和输出序列中的字符位置的计算。当前时刻隐藏状态ht,是由上一时刻隐藏状态ht-1和t时刻输入共同决定的。(把之前的信息都放在隐藏状态里,一个个传递下去是RNN处理时序问题的关键)。这种固有的时序模型难以并行化处理,计算性能就很差。这些年做了一些并行化改进,但问题依然存在。

另外还存在长距离衰减问题,在解码阶段中,越靠后的内容,翻译效果越差。除非你把ht维度设置的很高,把每一个时间步的信息都存下来。但这样就会造成内存开销很大。

attention在此之前,已经成功应用在encoder-decoder架构中,但主要是用在如何把编码器的信息有效地传递给解码器,所以是和RNN一起使用的。

本文提出的Transformer,不再使用循环神经层,而是纯基于注意力机制,来构造输入和输出之间的全局依赖关系。Transformer可以进行更多的并行化,训练时间更短但翻译效果更好。

四、背景

使用卷积神经网络替换循环神经网络,并行计算所有输入和输出位置的隐藏表示。是扩展神经GPU,ByteNet和ConvS2S的基础,因为这样可以减少时序计算。但是CNN对长序列难以建模(因为卷积计算时,卷积核/感受野比较小,如果序列很长,需要使用多层卷积才能使两个比较远的位置关联起来)。但是使用transformer的注意力机制的话,每次(一层)就能看到序列中所有的位置,就不存在这个问题。

关联来自两个任意输入或输出位置的数据所需的操作数量,随着距离增长,对于ConvS2S呈线性,对于ByteNet呈对数,而对于transformer使常数,因为一次就看到了。

但是卷积的好处是,输出可以有多个通道,每个通道可以认为是识别不同的模式,作者也想得到这种多通道输出的效果,所以提出了Multi-Head Attention多头注意力机制(模拟卷积多通道输出效果)。

self-attention,有时称为intra-attention,是一种关联单个序列的不同位置以计算序列表示的关联机制。在此之前已成功用于多种任务。但据我们所知,transformer是第一个完全依靠self-attention,而不使用卷积或循环的encoder-decoder转换模型。

五、模型架构

大部分神经序列转换模型都是用encoder-decoder结构。编码器把一个输入序列(x1,...xn)映射到一个连续的表示z=(z1,...zn)中。解码器对z中的每个元素,生成输出序列(y1,...ym),一个时间步生成一个元素。在每一步中,模型都是自回归的(auto-regressive),在生成下一个结果时,会将先前生成的结果加入输入序列来一起预测。(自回归模型的特点,过去时刻的输出可以作为当前时刻的输入)。

编码器和解码器序列可以不一样长,且编码器可以一次看到整个序列,但是解码器时一步步输出的。

transformer遵循这种整体架构,对编码器和解码器使用堆叠的自注意力逐点全连接层,分别如下图的左半部分和右半部分所示。

Alt text

  • Output(shifted right):解码器在t0时刻其实是没有输入的,其输入就是编码器的输出,所以这里的output(shifted right)就是逐个右移的意思。
  • Nx:模块堆叠N次

1、编码器和解码器

(1)编码器

由N=6个相同的encoder层堆栈组成,每层有两个子层。 ①multi-head self-attention

②FFNN(前馈神经网络层,Feed Forward Neural Network),其实就是MLP(多层感知机)。

  • 两个子层都是用残差连接(residual connection),然后进行归一化(layer normalization)
  • 每个子层的输出是LayerNorm(x + Sublayer(x)),其中Sublayer(x)是当前子层的输出。
  • 为了简单起见,模型中的所有子层以及嵌入层的向量维度都是dmodel = 512(如果输入输出维度不一样,残差连接就需要做投影,将其映射到统一维度)。(这和之前的CNN或MLP做法不一样,之前都会进行一些下采样)。

这种各层统一维度使得模型比较简单,只有N和dmodel两个参数需要调。这也影响到后面一系列网络,比如BERT和GPT等等。

(2)解码器

同样由N=6个相同的decoder层堆栈组成,每个层有3个子层。 ①Masked multi-head self-attention:在解码器中,self attention层只允许关注到输出序列中早于当前位置的单词。具体做法:在self attention分数经过softmax层之前,使用attention mask,屏蔽当前位置之后的那些位置,所以叫masked mult-head self-attention。(对应masked位置使用一个很大的负数-INF,使得softmax之后其对应值为0)

②Encoder-Decoder Attention:编码器输出最终向量,将会输入到每个解码器的Encoder-Decoder Attention层,用来帮解码器把注意力集中在输入序列的合适位置。

③FFNN

与编码器类似,每个子层都是用残差连接,的然后进行层归一化。假设一个transformer是由2层编码器和2层解码器组成的:

Alt text

为什么使用LN而不是BN?

Batch Normalization:在特征通道维度d做归一化,即归一化不同样本的同一特征。缺点:

  • 计算变长序列时,变长序列后面会pad 0,这些pad部分是没有意义的,这样进行特征维度归一化缺少实际意义。
  • 序列长度变化大时,计算出来的均值和方差抖动很大。
  • 预测时使用训练记录下来的全局均值和方差。如果预测时新样本特别长,超过训练时的长度,那么超过部分是没有记录的均值和方差的,预测会出现问题。

Layer Normalization:在样本维度b进行归一化,即归一化一个样本的所有特征。

  • NLP任务中一个序列的所有token都是统一语义空间,进行LN归一化有实际意义。
  • 因为是在每个样本内做的,序列边长使相比BN,计算的数值更稳定。
  • 不需要存一个全局的均值和方差,预测样本长度不影响最终结果。

Alt text

为什么MLP Block先升维再降维?

神经网络中线性连接可以写成dl=Wl \cdot x。其中三者维度分别使mx1、mxn、nx1。

  • m > n:升维,将特征进行各种类型的特征组合,提高模型分辨能力
  • m < n:降维,去除区分度低的组合特征。所以一般神经网络都是先做宽再做窄。

2、注意力机制

attention函数可以被描述为将query和一组key-value对映射到输出,其中query、key、value和输出都是向量。输出被计算为value的加权求和,所以输出和value的维度一致。每个value的权重由query与对应key计算所得(不同注意力机制有不同的算法)。

(1)缩放的点积注意力(Scaled Dot-Product Attention)

  • 其输入为query、key(维度是dk)以及values(维度是dv
  • 计算query和所有key的点积,得到两个向量的相似度(结果越大相似度越高);然后对每个点积结果除以dk\sqrt{d_k}
  • 点积结果输入softmax函数获得value的权重
  • 最后对value进行加权求和

在实践中,我们同时计算一组query的attention函数,并将它们组合成一个矩阵Q。key和value也一起组成矩阵K和V。我们计算的输出矩阵为:

Attention(Q,K,V)=softmax(QKTdk)VAttention(Q,K,V)=softmax(\frac{QK^{T}}{\sqrt{d_k}})V

K、V矩阵的序列长度是一样的(加权求和),而Q矩阵的序列长度可以和前两者不同;这种情况发生在:解码器部分的Encoder-Decoder Attention层中,Q矩阵是来自解码器输出tgt,而K、V矩阵则是来自编码器最后的输出memory。即tgt2 = self.multihead_attn(tgt,memory,memory,attn_mask=memory_mask,key_padding_mask=memory_key_padding_mask)[0]。 但是Q和K的维度必须一样,因为要计算点积。

Alt text

有两个最常用的attention函数:

  • 加法attention:ATTanh(qW+kU)A^TTanh(qW + kU)使用具有单个隐层的前馈网络计算,q和k维度不一致也可以进行;
  • 上面提到的点积(乘法)attention

除了缩放因子1dk\frac{1}{\sqrt{d_k}}之外,点积Attention跟我们的算法一样(所以作者的注意力叫缩放点积注意力)。虽然理论上点积attention和加法attention复杂度相似,但在实践中,点积attention可以使用高度优化的矩阵乘法来实现,因此点积attention计算更快、更节省空间。

当dk的值比较小时,这两个机制的性能相差相近,当dk比较大时,加法attention比不带缩放的点积attention性能好。我们怀疑,维度dk很大时,点积结果也变得很大,将softmax函数推向具有极小梯度的区域。为了抵消这种影响,我们将点积缩小1dk\frac{1}{\sqrt{d_k}}倍。

(2)多头注意力

MultiHead(Q,K,V)=Concat(head1,...,headn)WOMultiHead(Q,K,V) = Concat(head_1,...,head_n)W^O whereheadi=Attention(QWiQ,KWiK,VWiV) where\quad head_i = Attention(QW^Q_i,KW^K_i,VW^V_i)

其中映射由权重矩阵完成: WiQRdmodel×dkW^Q_i \in R^{d_{model}\times d_k} WiKRdmodel×dkW^K_i \in R^{d_{model}\times d_k} ,WiVRdmodel×dvW^V_i \in R^{d_{model}\times d_v} and WORhdv×dmodelW^O \in R^{hd_v\times d_{model}}

我们采用h = 8个平行attention层或者叫head。对于这些head中的每一个,我们使用dk=dv=dmodel/h=64d_k = d_v = d_{model} / h = 64,总计算成本与具有全部维度的单个head attention相似。

Alt text

①输入X和8组权重矩阵WQ,WK,WVW^Q,W^K,W^V相乘,得到8组Q,K,V矩阵。进行attention计算,得到8组Z矩阵(假设head=8)

②把8组矩阵拼接起来,乘以权重矩阵WO{W^O},将其映射回d维向量(相当于多维特征进行汇聚),得到最终的矩阵Z。这个矩阵包含了所有注意力头的信息。

③矩阵Z会输入到FFNN层(前馈神经网络层接收的也是1个矩阵,而不是8个。其中每行的向量表示一个词)。

使用多头注意力的好处:
1、多语义匹配:本身缩放点积注意力是没什么参数可以学习的,就是计算点积、softmax、加权和而已。但是使用Multi-head attention后,投影到低维的权重矩阵WQ,WK,WVW^Q,W^K,W^V是可以学习的,而且有h=8次学习机会。使得模型可以在不同语义空间下学到不同的语义表示,也拓展了模型关注不同位置的能力。类似卷积中多通道的感觉。
例如,“小明养了一只猫,它特别调皮可爱,他非常喜欢它”。“猫”从指代的角度看,与“它”的匹配度最高,但从属性的角度看,与“调皮”“可爱”的匹配度最高。标准的Attention模型无法处理这种多语义的情况。
2、注意力结果互斥:自注意力结果需要经过softmax归一化,导致自注意力结果之间是互斥的,无法同时关注多个输入。使用多组自注意力模型产生多组不同的注意力结果,则不同组注意力模型可能关注到不同的输入,从而增强模型的表达能力。

(3)注意力在模型中的应用

transformer中用3种不同的方式使用multi-head attention:

  • multi-head self attention:标准的多头自注意力层,用在encoder的第一个多头自注意力层。所有key,value和query来自同一个地方,即encoder中前一层的输出。在这种情况下,encoder中的每个位置都可以关注到encoder上一层的所有位置。
  • masked-self-attention:用在decoder中,序列的每个位置只允许看到当前位置之前的所有位置,这是为了保持解码器的自回归特性,防止看到未来位置的信息。
  • encoder-decoder attention:用于encoder block的第二个多头自注意力层。query来自前面的decoder层,而keys和values来自encoder的输出memory。这使得decoder中的每个位置都能关注到输入序列的所有位置。

encoder-decoder attention层可以使解码器在每个时间步,把注意力集中到输入序列中感兴趣的位置。比如英译中“hello world”,解码器在输出“你”的时候,解码器的输入q对“hello”的相似度应该是最高的,这样模型就将注意力主要集中在hello上,即生成单词时更关注源语言序列中更相关的词。(这就是attention如何在encoder传递信息到decoder时起到的作用)。

3、基于位置的前馈神经网络(Position-wise Feed-Forward Networks)

编码器和解码器中的每个层都包含一个全连接的前馈网络,该前馈网络分别且相同地应用于每个位置。该前馈网络包括两个线性变换,并在两个线性变换中间有一个ReLU激活函数。 FFN(x)=max(0,xW1+b1)W2+b2FFN(x) = max(0,xW_1 + b_1)W_2 + b_2 Position就是序列中每个token,Position-wise就是把MLP对每个token作用一次,且作用的是同一个MLP。说白了就是MLP值作用于最后一个维度d=512。

因为前面的attention层已经抓取了输入序列的相关信息,并做了一次汇聚(拼接后W映射回d维)。所以attention层结果已经有了序列中我感兴趣的信息,所以后面在做MLP投影映射到想要的语义空间时,只需要对每个position(token)单独做MLP就行。

从attention抽取序列信息到MLP映射到需要的语义空间(非线性变换),就整个是transformer的处理信息的基础过程。

尽管两层都是线性变换,但它们在层与层之间使用不同的参数。另一种描述方式是两个内核大小为1的卷积。输入和输出的维度都是dmodeld_{model}=512,内层维度是dff=2048d_{ff} = 2048(也就是说第一层输入512维,输出2048维;第二层输入2048维,输出512维)

对比transformer和RNN,发现两者都是使用MLP来进行语义空间的转换,但区别是二者传递信息的方式不同: Alt text

  • RNN是把上一时刻信息作为输入(和t时刻输入一起),传递给当前时刻,并用MLP做语义转换。
  • Transformer是通过attention层直接关联到全局的序列信息,然后用MLP做语义转换。

4、词嵌入和softmax

我们使用学习到的embedding将输入token和输出token转换维dmodeld_{model}维的向量。我们还使用普通的线性变换和softmax函数将解码器输出转换为预测的下一个token的概率。在我们的模型中,输入输出两个嵌入层,和pre-softmax线性变换共享相同的权重矩阵(这样训练起来简单一些)。最后我们将这些权重乘以dmodel\sqrt{d_{model}}(比如512)。

这是因为一般会把一个向量的L2Norm学到接近1,这样向量维度越大,这样学到的权重值就会很小。但是位置编码是不会这样学成L2Norm(L2范数)接近1的。所以把权重乘上dmodel\sqrt{d_{model}}之后,token embedding和位置编码Positional Encoding才接近统一量级。(都在-1到1之间)

5、位置编码(Positional Encoding)

Attention计算时本身是不考虑位置信息的,这样序列顺序变化结果也是一样的。所以我们必须在序列中加入关于词符相对或者绝对位置的一些信息。

  为此,我们将“位置编码”添加到token embedding中。二者维度相同(例如dmodel\sqrt{d_{model}} =512),所以可以相加。有多种位置编码可以选择,例如通过学习得到的位置编码和固定的位置编码。

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

PE(pos,2i)=sin(pos/100002i/dmodel)PE_{(pos,2i)} = sin(pos/10000^{2i/d_{model}}) PE(pos,2i+1)=cos(pos/100002i/dmodel)PE_{(pos,2i+1)} = cos(pos/10000^{2i/d_{model}})

其中pos是位置,i是维度。也就是说,位置编码的每个维度对应于一个正弦曲线。这些波长形成一个从2π2\pi到到 100002π10000 \cdot 2\pi的几何级数。我们选择这个函数是因为我们假设它会让模型很容易学习对相对位置的关注,因为对任意确定的偏移k,PEpos+kPE_{pos+k}可以表示为PEposPE_{pos}的线性函数。最终编码向量每个元素值都是在-1到1之间。

此外,我们会将编码器和解码器堆栈中的embedding和位置编码的和再加一个dropout。对于基本模型,我们使用的dropout比例是Pdrop=0.1{P_{drop}=0.1}

六、为什么使用自注意力机制

本节,我们比较self-attention与循环层和卷积层的各个方面,我们使用self-attention是考虑到解决三个问题。

  • 每层计算的总复杂度,越少越好
  • 顺序计算量,越少代表并行度越高。(顺序计算量就是下一步需要前面多少步计算完成)
  • 网络中长距离依赖之间的路径长度

影响长距离依赖性能力的一个关键因素是前向和后向信号在网络中传播的路径长度。输入和输出序列中任意位置之间的这些路径越短,学习长距离依赖性就越容易。因此,我们还比较了由不同图层类型组成的网络中任意两个输入和输出位置之间的最大路径长度。

Alt text

Attention:

  • 计算复杂度:矩阵Q*K,两个矩阵都是n行d列,所以相乘时复杂度是O(n2d)O(n^2 \cdot d),其它还有一些计算量但影响不大;
  • 顺序计算量:矩阵里面并行度是很高的,整个计算主要就是矩阵乘法,所以可以认为顺序计算量就是O(1);
  • 最大路径长度:也就是从一个点关联到任何一个点的路径长度。attention是一次看到整个序列,所以只需要一次操作,复杂度为O(1)

其它的暂时不写了。k就是卷积核大小,一般是3、5之类的;而n和d现在的模型都是做到几百几千,所以可以认为前三种操作,计算复杂度差不多,但是并行度是attention和卷积更好;且attention在信息的融合上更好(最大路径长度=1)。

  实际上attention对模型的假设更少,导致模型需要更多的数据和更大的模型才能训练到和RNN或CNN差不多的效果。所以现在基于transformer的模型都是很大,训练起来很贵。

七、评价

 Transformer(attention机制)几乎能用在所有NLP任务上,类CNN对整个CV领域的革新(不需要那么多的特征提取或者模型建模,学会CNN就行了)。Transformer也是一样,不需要那么多的文本预处理,不需要为每个任务设计不同的架构。

而且现在transformer在CV、语音、video等领域也广泛使用,等于一个架构可以适用所有领域,任何一点突破在别的领域都能被使用,减少技术的应用时间。 而且Transformer可以融合多模态的数据(文字、图片、语音等),大家都要同一个架构提取特征的话,可以都抽取到同一个语义空间,使得我们可以用文字、图片、语音等训练更大更好的模型。

  虽然Transformer效果这么好,但是对它的理解还在初级阶段。

最新的一些结果表明,attention在里面只是起到一个聚合序列信息的作用 ,但是后面的MLP/残差连接是缺一不可的,如果去掉的话,模型是基本训练不出什么的。

Attention不会对序列的顺序建模,为何能打败RNN?RNN可以显式地建模序列信息,不是应该比attention更好。现在大家觉得attention使用了更广泛的归纳偏置,使得他能处理更一般化的信息;这也是attention没有做空间上的假设,但是比CNN/RNN能做到更好的效果。代价就是假设更一般,所以抓取数据信息能力变差,必须使用更大的模型和更多的数据才能训练到一个比较好的效果。

八、补充

以上是论文里面对transformer的介绍,但我感觉还是云里雾里的,所以在此做一些补充。

1、Encoder-Decoder中的Attention机制

在Encoder-Decoder框架中,输入数据的全部信息被保存在了C。而这个C很容易受到输入句子长度的影响。当句子过长时,C就有可能存不下这些信息,导致模型后续的精度下降。Attention机制对于这个问题的解决方案是在decoder阶段每个时间点输入的C都是不一样的。而这个C,会根据当前要输出的y,去选取最适合y的上下文信息。

Alt text

从图上可以看出,在Decoder结构中,每一个时间点的输入都是不同的。这就是attention机制起作用的地方。对于Encoder中包含的输入内容的信息,attention机制会根据当前的输出,对Encoder中获得的输入做一个权重分配。这一点和人类的注意力也很像。当人在翻译句子中某个词的时候,肯定也会有所针对的看原句中的对应部分,而不是把原句所有词都同等看待。

举个例子:

Encoder模型中的h1,h2,h3,h4可以看做是输入‘我爱中国’所代表的信息。如果不做处理的话,那么c就是一个包含h1到h4全部信息的一个状态。现在对于不同的隐状态h,配以不同的权重a。这样,在输出不同的词的时候,虽然h的值都一样,但h对应的a的值是不同的。这就会导致c是不一样的。在输出每一个y的时候,输入进来的c都是不同的。

这个过程与人的注意力非常相像。以图中的翻译为例,输出的一个词 I 与中文的我关系最密切,所以h1分配的权重最大,也就是将翻译的注意力集中在h1。这里的权重a,是通过训练得来的。对于aija_{ij}而言,是通过decoder的上一个隐状态ci和encoder的隐状态hj学习得来的。

Alt text

具体到RNN模型的decoder模型来讲,在时刻i,如果要生成yi单词,我们可以得到在生成yi之前的时刻i-1时,隐层节点i-1时刻的输出值Hi1H_{i-1},而我们的目的是要计算生成yi时输入句子中的每个词对yi来说的注意力分配概率分布,那么可以用Target输出句子i-1时刻的隐层节点状态Hi1H_{i-1}去和输入句子Source中每个单词对应的RNN隐层节点状态hj进行对比,即通过函数 F(hj,Hi1) F(h_j,H_{i-1}) 来获得目标单词yi和每个输入单词对应的对齐可能性,这个F函数在不同论文里可能会采取不同的方法,然后函数F的输出经过Softmax进行归一化就得到了符合概率分布取值区间的注意力分配概率分布数值。 Alt text

总结一下,对于encoder-decoder模型中的attention机制:

在decoder阶段,生成最后的输出时,

y1=f1(c1) y_1 = f_1(c_1)

y2=f1(c2,y1) y_2 = f_1(c_2,y_1)

y3=f3(cn,yn1) y_3 = f_3(c_n,y_{n-1})

其中,每个ci包含了对于最初输入句子中每个词的注意力分配。 ci=g(ai1f2(x1),ai2f2(x2),...,aijf2(xj)) c_i = g(a_{i1}*f_2(x_1),a_{i2}*f_2(x_2),...,a_{ij}*f_2(x_j) )

这里f2表示encoder模型中的变换函数。在RNN实例中,f2的结果就是RNN模型中的隐层状态值h。g函数通常使用求和函数。

ci=j=1Lxaijhjc_i = \sum_{j=1}^{L_x}a_{ij}h_j 这里的L表示句子的长度。

2、Attention机制

如果离开上面提到的encoder-decoder框架,单纯讨论attention机制,会发现attention机制的核心就是从大量的信息中有选择地选择重要信息,捕获到对当前任务有用的重要信息。

我们把输入的内容作为source,生成的目标作为target。

source可以看成由一个个的<key,value>对组成的,target里面含有不同的query。

Attention机制的作用就是计算每一个query在source中的值。 Attention(Query,Source)=i=1LxSimilarity(Query,keyi)valueiAttention(Query,Source)=\sum_{i=1}^{L_x}Similarity(Query,key_i)*value_i

整个计算过程分为两步:

  • 计算source中所有的Key与query的相似性,并对计算得到的全部相似性做softmax归一化处理
  • 根据第一步中得到的权重,对value进行加权求和。

Alt text

3、self-attention

在前面机器翻译的例子中,输入和输出的长度是不一样的。因为语言不同,所以句子长度通常不一样。在self-attention中,我们可以认为source=target。self-attention可以捕捉到句子里的长依赖关系。

比如,对于句子

The animal didn't cross the street because it was too tired.

这里想要知道it代指的是什么,self-attention可以捕捉到句子中的长依赖关系(self-attention的核心是用文本中的其他词来增强目标词的语义表示,从而更好利用上下文信息)将其结果可视化如图所示。

Alt text

传统的RNN,LSTM网络,如果面对长句子的话,这种距离较远的依赖关系相比之下很难捕获到,因为根据RNN/LSTM的结构,需要按顺序进行序列计算,所以距离越远,关系越难捕捉。而self-attention是针对句子中所有词两两计算,不存在距离长短这一说。此外,self-attention相比循环网络还有另外一个优点是便于并行计算。

(1)self-attention的具体计算

首先,对于每个词向量,我们创建3个向量,分别为query,key,value

这三个向量是词向量与训练好的权重矩阵WQW^Q,WKW^K,WVW^V 分别相乘得到的。

Alt text

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

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

接下来,对于每个q而言,分别计算这个q与所有k的得分。计算公式 score=qkdk score= \frac{q*k}{\sqrt{d_k}}

这里除以分母的作用是为了后面计算中由稳定的梯度。对于q1而言,经过计算后,获得了 score1,score2,...,scoren score_1,score_2,...,score_n n维句子的长度。

下一步把这些score进行softmax归一化,然后将归一化的结果与value向量相乘,获得最后的结果。

Alt text

当遇到一个词的时候,整个计算流程就和刚才介绍的一样。那么整个self-attention的作用是什么呢?其实是针对输入的句子,构建了一个attention map。假设输入句子是"I have a dream",整个句子作为输入,矩阵运算后,得到一个4*4的attention map。

Alt text

(2)通过矩阵运算实现自注意力机制

在实际应用场景中,为了提高计算速度,我们采用的是矩阵的方式,直接计算出Query,Key,Value的矩阵,然后把embedding的值与三个矩阵直接相乘,把得到的新矩阵Q与K相乘,乘以一个常数,做softmax操作,最后乘上V矩阵。

Alt text Alt text

这种通过query和key的相似性程度来确定value的权重分布的方法被称为scaled dot-product attention: Attention(Q,K,V)=softmax(QKTdk)V Attention(Q,K,V)=softmax(\frac{QK^T}{\sqrt{d_k}})V

Q,K,V的大小为[batch_size,seq_length,hidden_size]

(3)self-attention知识点

①self-attention的时间复杂度是怎么计算的?

self-attention时间复杂度:O(n2d)O({n^2}\cdot d),这里n是序列的长度,d是embedding的维度,不考虑batch维度。

self-attention包括三个步骤:相似度计算,softmax和加权平均。

Alt text

它们分别的时间复杂度是:

  • 相似度计算:可以看作大小为(n,d)和(d,n)的两个矩阵相乘:(n,d)(d,n)=O(n2d)(n,d)*(d,n)=O({n^2} \cdot d),得到一个(n,n)的矩阵。

  • softmax:直接计算,时间复杂度为O(n)。

  • 加权平均:可以看作大小为(n,n)和(n,d)的两个矩阵相乘:(n,n)(n,d)=O(n2d)(n,n)*(n,d)=O({n^2} \cdot d),得到一个(n,d)的矩阵。

再提一下transformer中的Multi-Head Attention,多头Attention,简单来说就是多个self-attention的组合,作用类似于CNN中的多核。

多头的实现不是循环的计算每个头,而是通过transposes and reshapes,用矩阵乘法来完成。

transformer/BERT中把d,也就是hidden_size/embedding_size这个维度做了reshape拆分:

hidden_size(d)=num_attention_heads(m)attention_head_size(a),即d=ma

并将num_attention_heads维度transpose到前面,使得Q和K的维度都是(m,n,a),这里不考虑batch维度。

这样点积可以看作大小为(m,n,a)和(m,a,n)的两个张量相乘,得到一个(m,n,n)的矩阵,其实就相当于m个头,时间复杂度是O(n2m2a)=O(n2dm)O(n^2 \cdot m^2 \cdot a)=O(n^2 \cdot d \cdot m)

因此Multi-Head Attention时间复杂度就是O(n2dm)O(n^2 \cdot d \cdot m),而实际上,张量乘法可以加速,因此实际复杂度会更低一些。

②不考虑多头的原因,self-attention中词向量不乘QKV参数矩阵,会怎么样?

对于Attention机制,都可以用统一的query/key/value模式去解释,而对于self-attention,一般会说它的q=k=v,这里的相等实际上是指它们来自同一个基础向量,而在实际计算时,它们是不一样的,因为这三者都是乘了QKV参数矩阵的。那如果不乘,每个词对应的q,k,v就是完全一样的。

在self-attention中,sequence中的每个词都会和sequence中的每个词做点积去计算相似度,也包括这个词本身。

在相同量级的情况下,qi与ki点积的值会是最大的(可以从“两数和相同的情况下,两数对应的积最大”类比过来)。 而乘以QKV参数矩阵,会使得每个词的q,k,v都不一样,能很大程度上减轻上述的影响。

③在常规attention中,一般有k=v,那self-attention可以吗?

self-attention实际只是attention中的一种特殊情况,因此k=v是没有问题的,也即K,V参数矩阵相同。

扩展到Multi-Head Attention中,乘以Q、K参数矩阵后,其实就已经保证了多头之间的差异性了,在q和k点积+softmax得到相似度后,从常规attention的角度,觉得再去乘以和k相等的v会更合理。

4、位置编码

在以前的模型中,NLP的每个sequence都是一个token一个token地输入到模型中。比如“我喜欢吃洋葱”,那么输入模型地顺序就是“我”,“喜”,“欢”,“吃”,“洋”,“葱”。 Alt text

transformer模型引入了self-attention来解决这个问题。self-attention地输入方式如下: Alt text

对于self-attention,它可以一次性将所有的字都当作输入。但是NLP的输入是有特点的,其特点是输入的文本要按照一定的顺序才可以。因此文本的顺序是带有一部分语义关系的。比如下面两句话,不同的语序有不同的语义。

句子1:我喜欢吃洋葱
句子2:洋葱喜欢吃我

所以对于transformer,为了更好发挥并行输入的特点,首先要解决的问题就是要让输入的内容具有一定的位置信息。在原论文中,为了引入位置信息,加入了position机制。

(1)位置编码原则

方法一:

在0-1范围内,第一个token分配为0,最后一个token分配为1,其余token按照文章的长度平均分配。具体形式如下:

我喜欢吃洋葱【0,0.16,0.32...1】
我真的不喜欢吃洋葱【0,0.125,0.25...1】

问题:若句子长度不同,那么位置编码就不同,所以无法表示句子之间有什么相似性。

方法二:1-n正整数范围分配

按照输入顺序,一次分配给token所在的索引位置。

我喜欢吃洋葱【1,2,3,4,5,6】 我真的不喜欢吃洋葱【1,2,3,4,5,6,7,8,9】

问题:句子越长,后面的值越大,数字越大说明这个位置占的权重越大,这样的方式无法凸显每个位置的真实权重。

一种好的位置编码方案需要满足以下几条要求:

  • 它能为每个时间步输出一个独一无二的编码;
  • 不同长度的句子之间,任何两个时间步之间的距离应该保持一致;
  • 模型应该能毫不费力地泛化到更长的句子,它的值应该是有界的;
  • 它必须是确定性的。

(2)transformer的相对位置

相对位置即关注一个token与另一个token距离差几个token。比如:位置1和位置2的距离比位置3和位置10的距离更近,位置1和位置2与位置3和位置4都只相差1。

还是按照上面"我喜欢吃洋葱"中的“我”为例,看看相对位置关系是什么样子的:

Alt text

使用相对位置的方法,我们可以清晰的知道单词之间的距离远近的关系。

Transformer的作者们提出了一个简单但非常创新的位置编码方法,能够满足上述所有的要求。首先,这种编码不是单一的一个数值,而是包含句子中特定位置信息的d维向量(非常像词向量),向量如下: Alt text

将这个向量与模型的嵌入词向量ψ(wt) \psi(w_t)直接相加喂给模型:ψ(wt)=ψ(wt)+pt\psi'(w_t)=\psi(w_t) + \vec{p_t}

为了保证这种相加操作正确,让位置向量(PE)的维度等于词向量(WE)的维度。

关于每个元素的说明:

1)其中,频率wkw_k定义如下: wk=1100002k/d w_k = \frac{1}{10000^{2k/d}} 2)这里的t就是每个token的位置,比如位置1,位置2以及位置n

为什么可以表示相对距离?

假设某一个token的位置是POS,若另一个token表示为POS+k,那就表明这个位置距上一个token为k。若这时我们需要看看一个位置POS和POS+k这两个字符的关系。按照位置编码的公式,我们可以计算位置编码:

PE(pos+k,2i)=sin(wi(pos+k))=sin(wipos)cos(wik)+cos(wipos)sin(wik)PE_{(pos+k,2i)} = sin(w_i \cdot (pos + k)) = sin(w_ipos)cos(w_ik)+cos(w_ipos)sin(w_ik) PE(pos+k,2i+1)=cos(wi(pos+k))=cos(wipos)cos(wik)sin(wipos)sin(wik)PE_{(pos+k,2i+1)} = cos(w_i \cdot (pos + k)) = cos(w_ipos)cos(w_ik) - sin(w_ipos)sin(w_ik)

上面公式中,有部分是似曾相识的: PEpos,2i=sin(pos100002idmodel)PE_{pos,2i}=sin(\frac{pos}{10000{\frac{2i}{d_{model}}}}) PEpos,2i+1=cos(pos100002idmodel)PE_{pos,2i+1}=cos(\frac{pos}{10000{\frac{2i}{d_{model}}}})

根据上面的公式,似曾相识的部分带入PEpos+kPE_{pos+k}的公式中,带入结果为:

PE(pos+k,2i)=cos(wik)PE(pos,2i)+sin(wik)PE(pos,2i+1)PE_{(pos+k,2i)} = cos(w_ik)PE_{(pos,2i)}+sin(w_ik)PE_{(pos,2i+1)} PE(pos+k,2i+1)=cos(wik)PE(pos,2i+1)sin(wik)PE(pos,2i)PE_{(pos+k,2i+1)} = cos(w_ik)PE_{(pos,2i+1)}-sin(w_ik)PE_{(pos,2i)}

距离k是一个常数,所以上面公式sin()和cos()的计算值也是常数,可以表示为: u=cos(wik),v=sin(wik) u=cos(w_ik),v=sin(w_ik) 这样就可以将PE写成一个矩阵的乘法:

Alt text

从上面的矩阵乘法角度看到,位置POS的编码与位置POS+k的编码是线性关系。

但是上面的操作也只能看到线性关系,怎样可以更直白地知道每个token的距离关系?

为了解答上面的问题,我们将PEposPE_{pos}PEpos+kPE_{pos+k}相乘 (两个向量相乘),可以得到如下结果: Alt text

相乘后的结果为一个余弦的加和。这里影响值的因素就是k。若两个token的距离越大,也就是K越大,根据余弦函数的性质可以知道,两个位置向量的相乘结果越小。这样的关系可以得到,如果两个token距离越远则乘积的结果越小。

这样的方式虽说可以表示出相对的距离关系,但是也是有局限的。其中一个比较大的问题是:只能的到相对关系,无法得到方向关系。所谓的方向关系就是,对于两个token谁在谁的前面,或者谁在谁的后面是无法判断的。数学表示如下:

PEpos+kPEpos=PEposkPEpos PE_{pos+k}PE_{pos}=PE_{pos-k}PE_{pos}

(3)BERT的位置编码和Transformer的位置编码区别

transformer的输入只包含两部分,token embedding词向量和position enbedding位置向量,且position embedding用的是函数式(正余弦函数),而BERT的Position embedding位置向量是参数式(可学习的),且segment embedding段落向量用于区分两个句子(第一个句子为0,第二个句子为1)。