当前主流的序列转录模型,主要依赖较为复杂的循环或卷积神经网络,此类神经网络一般包括编码器(Encoder)和解码器(Decoder)结构。在性能最好的模型之中,也会在编码器和解码器之间使用注意力机制。
本文提出了一种新的简单的架构(Transformer),该架构完全基于注意力机制,而不涉及循环或卷积神经网络。在两个机器翻译任务上的实验表明,Transformer具有更佳的性能,更好的并行度,更少的训练时间。模型在WMT2014英译德翻译任务上获得了28.4%的BLEU成绩,比现有的最好成绩高2%以上。
序列转录模型:给定一个序列,生成另一个序列,比如机器翻译。
BLEU Score:机器翻译里常用的一个衡量标准。
(这篇文章一开始是针对机器翻译这个小任务来写的,但是随着BERT、GPT等把这个架构用在更多的语言处理任务上,整个工作就出圈了。最近还用在了图像和视频上,几乎什么东西都能用。)
RNN、LSTM、GRU是用于解决序列建模和转录问题(如语言建模和机器翻译)的主流方法。这里有两个比较主流的模型:
在RNN中给定一个序列,它的计算方式是沿着这个序列从左到右一步一步往前做。假设序列是一个句子的话,操作对象就是一个一个的词,对第 t t t个词会计算出一个输出叫做 h t h_t ht(也叫做它的隐藏状态), h t h_t ht是由前面一个词的隐藏状态 h t − 1 h_{t-1} ht−1和当前第 t t t个词本身决定的,这样就可以把前面学到的历史信息通过 h t − 1 h_{t-1} ht−1放到当下,然后和当前的词做一些计算,而后得到输出。
RNN处理时序信息存在的问题:
在这篇文章之前,注意力机制(Attention)已经成功用在了编码器和解码器之中,主要用于如何将编码器的东西有效地传递给解码器,常与RNN结合使用。
本文提出来的Transformer是一个新的模型,不再使用循环神经网络,而是仅使用注意力机制。该模型并行度较高,能够在较短时间内做到比之前更好的结果。
卷积网络在做计算的时候,每次看的到是一个比较小的窗口,如果两个像素相隔比较远的话,需要用很多层卷积堆积起来,才能够把这两个隔得比较远的像素融合起来。但如果是使用Transformer里面的注意力机制的话,每一次都能够看到所有的像素,使用一层就能看到整个序列。
卷积的好处是可以做多个输出通道,一个输出通道可以认为是一个不一样的识别模式。作者也想要实现这种效果,所以提出了一个Multi-headed attention(多头注意力机制),用于模拟卷积神经网络中多输出通道的一个效果。
序列模型中,比较好的是编码器和解码器结构。
编码器: 对于一个长为 n n n的 ( x 1 , . . . , x n ) (x_1, ..., x_n) (x1,...,xn)输入序列,编码器会把它映射成一个长也为 n n n的 ( z 1 , . . . , z n ) (z_1, ..., z_n) (z1,...,zn)向量表示,并将其作为输出序列。换句话说,编码器能够把原始输入变成机器学习可以理解的一系列向量。
解码器: 拿到编码器的输出序列,解码器会根据它来生成一个长为 m m m的 ( y 1 , . . . , y m ) (y_1, ..., y_m) (y1,...,ym)序列,这里 n n n和 m m m可以相同,也可以不同。解码器和编码器最大的不同之处在于,在解码器中,词是一个一个生成的(对于编码器来说,可能一次性就能看全整个句子,而在解码的时候只能一个一个的生成),在过去时刻的输出也会作为当前时刻的输入,即自回归(auto-regressive)。
Transformer使用了编码器和解码器结构,具体来讲它是将self-attention、point-wise和fully connected layers一个一个堆在一起。
上图是Transformer的模型架构,也是编码器和解码器结构。左半边是编码器,右半边是解码器。左下角的Inputs是编码器的输入,如果是中文翻英文的话,输入就是中文的句子。右下角的Outputs是解码器的输入,解码器在做预测的时候是没有输入的,实际上这个输入是指解码器将之前时刻的一些输出作为输入(shifted right就是一个一个往右移)。Input Embedding表示嵌入层,进来的是一个一个的词,需要将它们表示成向量。Positional Encoding。Multi-Head Attention、Feed Forward、Add & Norm组合起来成为一个Transformer Block,类似于残差网络的残差块。N代表层数,由多个Transformer Block堆叠而成。编码器的输出会作为解码器的输入,解码器和编码器有点像,但是多了Masked Multi-Head Attention。编码器和解码器都可以认为是由几个部分组成的块重复N次。上图所示的确是一个标准的编码器和解码器结构,只是说中间的每一块有所不同,还有就是编码器和解码器之间的连接有所不同。
Transformer编码器: 该编码器由6个(N=6)完全一样的层组成。
考虑一个最简单的二维输入情况,在这种情况下输入为一个矩阵,每一行是一个样本,每一列是一个特征。
BatchNorm所干的事情就是每一次在一个小mini-batch里,将每一列(若干样本的特征)数据的均值变成0,方差变成1。在训练的时候是在每一个小批量里面计算均值跟方差;对于预测,训练时会把所有数据扫一遍,然后计算全局的均值和方差,并将它们保存,以便在预测的时候再使用。BatchNorm会学习缩放因子和偏移因子两个参数,使得特征向量可以变成均值和方差为任意某个值的东西,增强表征能力。
LayerNorm和BatchNorm在很多时候几乎是一样的。对于一个同样的二维输入来说,LayerNorm是对每个样本做归一化,而不是多个样本。BatchNorm是将每个列(多个样本的同一通道特征)的均值变为0,方差变为1;LayerNorm是将每个行(一个样本的所有通道特征)的均值变为0,方差变为1。因此,LayerNorm可以认为是把整个数据转置一下放到BatchNorm中处理,然后出来的结果再转置回去。
在Transformer或者正常的RNN中,输入是一个三维的东西,x轴表示序列(sequence),y轴表示特征向量(feature),z轴表示样本数量(batch)。可以想象成一个样本由一个句子序列组成,一个句子有多个词,每个词能映射成一个特征向量,然后多个样本构成一个矩形体,如下图所示。
如果用BatchNorm的话,每次对多个样本的同一位置特征进行处理,如上图中的蓝色正方形所示。相当于把这一块切下来拉成一个向量,然后进行均值和方差的计算。
如果用LayerNorm的话,每次对一个样本的所有位置特征进行处理,如上图中的黄色正方形所示。
切法不一样会带来不同的效果,为什么LayerNorm用的多一点?
Transformer解码器: 解码器跟编码器很像,也是由6个(N=6)一样的层组成,每个层里也有Multi-Head Attention和Feed Forward两个子层。
与编码器不同的是,解码器中用了第三个子层Masked Multi-Head Attention,它同样是多头的注意力机制,也用了残差连接和LayerNorm。
解码器中做的是自回归,即当前时刻的输入来自前面一些时刻的输出,意味着在做预测的时候只能看到前面时刻的输出,而不能看到后面时刻的输出。
在注意力机制中,每一次都能够看到完整的输入,但是在解码的时候要避免看到完整的输入。也就是说在解码器训练的时候,预测第 t t t个时刻的输出,不应该看到 t t t时刻以后的那些输入。具体做法是增加一个带掩码的多头注意力机制,这个Masked的作用是保证输入进来的时候, t t t时刻只会看到之前的输出,而不会看到之后的输入,从而保证训练和测试时候的行为是一致的。
注意力机制(Attention): 注意力函数可以被描述为将一个Query和一组Key-Value对映射成输出,其中Query、Keys、Values和输出都是向量。输出是Values的加权和,因此输出的维度和Value的维度是一样的。分配给每个Value的权重都是通过Query与相应Key的相似度函数计算得到的(compatibility function,不同的注意力机制有不同的算法,不同的相似度函数导致不一样的注意力版本)。
Scaled Dot-Product Attention: 如上图所示。
输入由Querys和Keys组成,它们的维度都是 d k d_k dk(维度可以不一样,不一样的话有别的计算方法)
Values的维度为 d v d_v dv(输出的维度也应该是 d v d_v dv)
具体计算,一个query向量和所有的key向量做内积,然后除以 d k \sqrt {d_k} dk( d k d_k dk为向量的长度),而后通过Softmax函数得到所有value的权重(query向量和key向量的内积可以看作是相似度计算,内积越大,就表示这两个向量的相似度越高;如果内积为零,即正交,则表示这两个向量没有相似度。使用Softmax得到的权重具有非负、和为1的特性,这对于权重来说比较好),最后将这些权重作用在values上,进行加权和计算就能得到输出
在实际操作中,不会一个一个的做运算,而是将一组query整合成一个矩阵Q,query的个数与key-value对的个数可能不一样,但query向量的长度与key向量的长度一定是一样的,这样才能进行内积运算
给定Q和K两个矩阵,通过矩阵相乘就会得到一个 m × n m×n m×n的矩阵,如下图所示,它的每一行(如图中蓝色线条所示)就是一个query向量和所有key向量的内积值。得到的矩阵会除以 d k \sqrt {d_k} dk,再送入Softmax函数做概率计算(对每一行做Softmax,行与行之间是独立的),这样就能得到权重。权重矩阵再乘V(V是一个n行 d v d_v dv列的矩阵,每一行表示一个value向量),得到一个 m × d v m×d_v m×dv的矩阵,这个矩阵的每一行就是所需要的输出。
一般有两种比较常见的注意力机制:
这里为什么要除以 d k \sqrt {d_k} dk:
在Scaled Dot-Product Attention中,Mask主要是为了避免在第 t t t时刻的时候看到以后时刻的东西。具体来说,假设query和key是等长的,长度都为n,而且在时间上能对应起来,对第 t t t时刻的 q t q_t qt做计算的时候,应该只是看到 k 1 k_1 k1到 k t − 1 k_{t-1} kt−1,而不应该看到 k t k_t kt和它之后的东西,因为 k t k_t kt在当前时刻还没有。
但是在做注意力机制的时候,会发现其实 q t q_t qt在跟矩阵K里全部的东西做运算,从 k 1 k_1 k1一直算到 k n k_n kn,只要保证在计算权重的时候,不要用到后面的东西就可以了。
Mask的具体操作就是,对于 q t q_t qt和 k t k_t kt之后的用于计算的那些值,把它们替换成非常大的负数,这些大的负数在进入Softmax做指数运算后就会变成0,因而经过Softmax处理后出来的对应权重都会变成0,而 k t k_t kt之前所对应的值都会有权重。
这样在计算输出的时候就只用到了V对应的 v 1 v_1 v1到 v t − 1 v_{t-1} vt−1的结果,而 v t v_t vt后面的东西并没有用到。
所以Mask的效果是在训练的时候,让第 t t t个时刻的query只看到对应的前面的那一些key-value对,使得在做预测的时候能够进行一一对应。
Multi-Head Attention: 与其做一次单个的注意力函数,不如把整个Q、K、V投影到一个低维,投影h次,然后做h次的注意力函数,再将每一个函数结果并在一起,再投影回来得到最终的输出,如下图所示。
原始的values(V)、keys(K)、querys(Q)进入一个线性层(线性层将其投影到比较低的维度,投影h次),然后再做一个scaled dot-product attention,做h次,得到h个输出,再把这些输出向量全部合并到一起,最后做一次线性的投影,然后回到Multi-head attetion。
为什么要做多头注意力机制:
dot-product的注意力机制中没有什么可学习的参数,具体函数就是内积。有时候为了识别不一样的模式,希望有一些不一样的计算像素的办法
如果是用加性注意力机制的话,里面其实是有一个权重可以学习的。但本文使用的是内积,它的做法是先投影到低维,这个投影的w是可以学的,也就是说,有h次机会希望可以学到不一样的投影方法,使得再投影进去的度量空间中能够匹配不同模式所需要的相似函数,最后把所得到的东西再做一次投影(这里有点像在卷积神经网络里面有多个输出通道的感觉)
具体的计算(公式如下图),在Multi-head的情况下,还是以前的Q、K、V,但是输出已经是不同头的输出做Contact运算再投影到一个 W O W^O WO里面。对每一个头,就是把Q、K、V通过不同的可以学习的 W Q W^Q WQ、 W K W^K WK、 W V W^V WV投影到 d v d_v dv上面,再做注意力函数,然后再出来就可以了
在本文中,h是等于8的,也就是用了8个头。做注意力机制的时候,因为有残差连接的存在,使得输入和输出的维度是一样的。所以在投影的时候,投影的维度就是输出的维度除以h(输出维度是512,除以8之后,每一次把它投影到64维的维度,然后在这个维度上面计算注意力函数,最后再投影回来)。
Transformer通过三种方式来使用多头注意力机制,分别就是模型结构中的三个注意力层:
注意力机制如何在编码器和解码器之间传递信息的时候起作用:解码器根据当前的输入向量在编码器的输出里挑选感兴趣的东西,也就是去注意感兴趣的东西,那些不那么感兴趣的东西就可以忽略掉。
Position-wise Feed-Forward Networks: 其实就是一个fully connected feed-forward network,就是一个MLP,但是不同之处在于它是applied to each position seperately and identically(就是把同一个MLP对每个词作用一次,即position-wise,说白了就是MLP只是作用在最后一个维度)
position:输入序列中有很多个词,每一个词就是一个点,这些点就是position
具体公式如下图所示, x W 1 + b 1 xW_1+b_1 xW1+b1就是一个线性层, m a x max max就是ReLU激活层,最后再有一个线性层
在注意力层的输入(每一个query对应的输出)的长为512, x x x就是一个512的向量, W 1 W_1 W1会把512投影成2048(等价于将它的维度扩大了4倍),因为有残差连接,所以还需要投影回去,因此 W 2 W_2 W2又把2048投影回512
这其实就是一个单隐藏层的MLP,中间的隐藏层将输入扩大4倍,最后输出的时候又回到输入的大小(如果用Pytorch来实现的话其实就是把两个线性层放在一起,而不需要改任何参数,因为Pytorch在输入为3维的时候,默认就是在最后一个维度做计算)
Embedding: 因为输入是一个一个的词(或者叫词源,token),需要将其映射成向量。Embedding的作用就是给任何一个词,学习一个长为d的向量来表示它(本文将d设置为512)
本文中这三个Embedding是一样的权重,这样的话训练起来会简单一点。另外还将权重乘了 d \sqrt d d,维度一大的话,权重值就会变小,之后要加上positional encoding,它不会随着长度变长把它的long固定住,因此乘上了根号d之后,使得它们在scale方面差不多。
Positional Encoding: Attention没有时序信息,输出为value的加权和,权重是query和key之间的距离,和序列信息无关(也就是说给定一句话,把顺序任意打乱之后,经过Attetion出来的结果都是一样的,顺序会变,但是值不会变。这样是存在问题的,所以需要把时序信息加入进来)。
RNN是如何添加时序信息的?RNN将上一个时刻的输出作为下一个时刻的输入来传递历史信息。
Attention是在输入里面加入时序信息,将输入词所在的位置信息加入到输入里面(positional encoding),具体公式如下所示。
在计算机中,数字是用一定长度的向量来表示的。词在嵌入层会表示成一个长为d的向量,同样可以用一个长为d的向量来表示数字,也就是词的位置。这些数字的不同计算方法是使用周期不一样的sin和cos函数的值,所以说任何一个值可以用长为d的向量来表示,然后这个长为d的记录了时序信息的向量和嵌入层相加,就完成了将时序信息加进数据中的操作。因为是cos和sin的函数值是在+1和-1之间抖动的,所以乘了一个 d \sqrt d d,使得每个数字也是差不多在±1的数值区间里面。
为什么要使用自注意力:
上表比较了四种不一样的层。第一个是自注意力层,第二个是循环层,第三个是卷积层,第四个是构造出来的受限的自注意力。第一列是计算复杂度,越低越好;第二列是顺序的计算,越少越好。指的是在算Layer的时候,下一步计算必须要等前面多少步计算完成,相当于是说非并行度;第三列说的是信息从一个数据点走到另一个数据点要走多远,越短越好。n是序列的长度,d是向量的长度。
整个自注意力就是几个矩阵做运算,其中一个矩阵运算是query矩阵(n×d)乘以key矩阵(也是n×d),两个矩阵相乘,算法复杂度就是n平方乘以d,别的矩阵运算复杂度也是一样的。因为只是牵涉到矩阵的运算,矩阵中可以认为并行度是比较高的,所以是o(1)。最大长度是说从一个点的信息想跳到另一个点要走多少步,在attention里面,一个query可以跟所有的key做运算,而且输出是所有value的加权和,所以query对任何一个很远的key-value对只要一次就能过来,所以长度是比较短的。
Transformer是第一个完全基于注意力机制的序列转录模型,用Multi-headed self-attention取代了原编码器和解码器结构中最常用的循环层。对于机器翻译任务,Transformer的训练速度要比循环或卷积网络快很多,而且训练效果也要更好。作者对于这种完全基于注意力机制的模型感到很兴奋,想把它应用在文本以外的其他任务上,比如图像、音频、视频。