Transformer由google在2017年发表的论文《Attention is All You Need》中提出。从当前的时间节点来看,毫无疑问Transformer是目前NLP领域最强悍的特征提取器。
因为先天的序列结构,所以在Transformer之前,RNN是NLP里的主角。但是也因为这种序列结构的依赖性,使得RNN受制于并行计算能力。Transformer是叠加的"Self Attention"构成的深度网络,因此其并行计算能力是与生俱来的。由于这种先天性的优势,近年在NLP领域Transformer不断抢戏RNN,相信在不久的将来Transformer会完全取代RNN的地位。废话不多说,接下来我们具体看看Transformer的运行机制。
首先上一张Transformer的整体结构图:
乍一看,感觉结构很复杂。其实不然,我们从宏观角度来看,上图左边是encoder,右边是decoder,简化一下就是下面这样(借用机器翻译例子,后面的图依然如此,就不一一说明了):
结合figure1,这里的ENCODERS是由N个小的encoder堆叠而成的,同样DECODERS也是又相同数量小的decoder堆叠而成。内部展开就是下面这样:
上图中展开的所有ENCODER结构都一样,但是他们的参数不共享。DECODER们也是如此。
接着,我们把ENCODER和DECODER继续展开:
ENCODER分两层:自注意力层、前馈神经网络层。自注意力层帮助编码器在对每个单词编码时关注单词的上下文单词。
DECODER分三层:自注意力层、编码-解码注意力层、前馈神经网络层。编码-解码注意力层用来关注输入句子的相关部分。
到这里,我们已经大概知道了Transformer的结构。下面我们从张量的角度来看看一个输入句子在模型不同部分的变化,以及最后是如何变成输出的。
编码器
在最底层编码器的输入端,我们通过词嵌入算法把输入句子的每个词转换为向量,整个句子形成一个向量列表。向量列表的维度就是词向量的维度,大小一般是数据集中最长句子的长度。
只有最底层的编码器接收的是原始词向量,后续上层的编码器接收的是前一个编码器的输出。
如上图所示,输入序列中每个词都会流经编码器的两个子层。下面我们来看看这里的自注意力层是怎么计算的。
第一步,对于每个单词,我们需要创造一个查询向量、一个键向量、一个值向量。这些是通过词向量与三个权重矩阵相乘得到的。他们都是有助于计算和理解注意力机制的抽象概念。
上面三个向量在维度上比词向量更低。他们的维度是64,词向量和编码器的输入/输出维度是512。实际上这种维度更小只是基于架构上的选择。
第二步,计算得分。比如我们要计算句子中第一个词的自注意力向量,那就用句子中每个词对这个词进行打分,这些分数决定了每个单词对编码当前位置的贡献。打分的计算方法就是,用打分单词的键向量与目标单词的查询向量进行点积计算。
第三步和第四步,将分数除以8,然后做归一化。
这里的8是论文中使用的键向量的维数64的平方根,这会让梯度更稳定。也可以用其他值,8只是默认值。
第五步,将每个值向量乘以softmax后的分数。这里的直觉是希望关注语义上的相关单词,并弱化不相关的单词。
第六步,对加权值向量求和,即得到自注意力层在该位置的输出。
到这里,自注意力的计算就完成了,整个计算流程就像下图这样:
实际工程中,上述的计算是以矩阵形式完成的,以便加快运算速度,下面我们看看矩阵运算是怎么操作的。
第一步,计算三个矩阵。我们把输入句子的词向量放到矩阵X中,然后乘以训练的权重矩阵()。
X矩阵中每一行对应输入句子中的一个单词。
第二步,我们把上述步骤2到6的操作合为一个公式来计算。
多头注意力,论文中通过这玩意儿进一步完善了自注意力层,并提高了性能。
1、扩展了模型专注于不同位置的能力。
2、他给出了注意力层的多个“表示子空间”。对于多头注意力机制,我们有多个查询/键/值权重矩阵集(Transformer使用8个注意力头)。这些集合中每一个都是随机初始化的,在训练之后每个集合都被用来将输入词向量(或来自较低层编码器/解码器的向量)投影到不同的表示子空间中。
Transformer使用8个注意力头,这样我们通过8次不同的权重矩阵运算,得到8个不同的Z矩阵。但是前馈层只需要一个矩阵。这里我们需要把这些矩阵拼接在一起,然后用一个附加的权重矩阵与他相乘。
到这里,其实模型缺少对输入序列顺序的理解。为此,我们需要在输入中添加位置编码。
Transformer为每个输入的词嵌入添加了一个向量,这些向量遵循模型学习到的特定模式,这有助于确定每个单词的位置,或序列中不同单词之间的距离。
论文中作者设计了一种位置编码规则,公式如下:
其中,表示单词的位置,表示单词的维度。
作者这么设计编码规则的原因是考虑到在NLP任务中,除了单词的绝对位置,单词的相对位置也很重要。根据公式和,表明位置的位置向量可以表示为位置的特征向量的线性变化,这为模型捕捉单词之间的相对位置关系提供了非常大的便利。
最后,还有一点需要被提及。编码器的每个子层周围都有一个残差链接,并且都跟着一个"归一化"。所以编码器内部看起来就像下面这样:
解码器
看完编码器,我们接着看解码器。最后一个编码器的输出会转化为一个包含向量K(键向量)和V(值向量)的注意力向量集。这些向量将被用于自身的编码-解码注意力层(编码-解码注意力层帮助解码器关注输入序列)。编码-解码注意力层的工作方式很像多头自注意力层,只不过他的K和V矩阵来自编码器的输出,Q矩阵来自他下层的自注意力层的输出。
解码器中的自注意力层表现的模式与编码器不同。这里的自注意力层只被允许处理输出序列中更靠前的那些位置。在softmax之前,他会把后面的位置给隐去(把他们设为-inf)。因为在机器翻译中,解码过程是顺序操作的,也就是当解码第个特征向量时,我们只能看到第及其之前的解码结果,论文中把这种情况下的multi-head attention称作masked multi-head attention。
解码器的细节展开就想下图DECODER#1这样:
最后的Linear和Softmax
线性变换层是一个全连接神经网络,它把解码组件最后的输出向量映射到一个比它大得多的向量(被称作对数几率(logits)向量)。
假设我们的模型从训练集中学习一万个不同的单词,所以对数几率向量在这里就是一个1x10000的向量,每个数值对应一个单词的分数。最后这些分数经过softmax层转换成概率,概率最高的位置对应的单词就是当前时间步的输出。
参考:
https://zhuanlan.zhihu.com/p/48508221
https://jalammar.github.io/illustrated-transformer/