Transformer模型早在2017年就出现了,当时实验室的分享也有关于这个的。但我当时没有意识到这篇论文的厉害之处,听名字感觉像是那种昙花一现的论文,也没有关注它。直到最近出现了BERT这一神物之后,方才后知后觉此时Transformer已然这么有用!因此,这才仔仔细细地撸了这篇“古老”的论文和源码,这里将主要对照论文和相应的PyTorch源码进行逐一对照解读。因笔者能力有限,如有不详实之处,读者可移步至文末的传送门去看更多细节,并欢迎指出~
文章目录
前言
2017年6月,Google发布了一篇论文《Attention is All You Need》,提出了Transformer模型。正如论文的名称所说,其旨在全部利用Attention方式来替代掉RNN的循环机制,从而能并行化计算并实现提速。同时,在特定的任务上,这个模型也超过了当时Google神经机器翻译模型。笔者主要阅读了论文及两篇博客(链接见文末的传送门),这里主要是对这些内容做一个整合和提炼~
一. 背景
在Transformer出现之前,LSTM、GRU等RNN系列网络以及encoder-decoder+attention架构基本上铸就了所有NLP任务的铁桶江山。但RNN的一个缺陷在于是自回归的模型,只能串行的一步一步进行计算,无法并行化。因此有一些网络如ByteNet和ConvS2S都是以此为切入点,使用CNN作为基本构建模块,这样可以并行计算所有输入和输出位置的隐层表示。但在这些模型中,关联来自两个任意输入或输出位置的信号所需的操作数量会随着位置之间的距离而增长,如ConvS2S呈线性增长、ByteNet呈对数增长, 这使得学习较远位置之间的依赖变得更加困难。而在Transformer中,两个输入之间的距离对其计算来说没有影响,都是一样的,它没有使用RNN和卷积,可以进行并行计算。
二. Transformer整体架构
下面是从论文中截出的Transformer整体结构图:
这个图乍一看非常唬人,但实际上仔细看的话仍旧是熟悉的Encoder-Decoder架构,左边的是Encoder,右边的是Decoder。下面将一一进行剖析。
三. Transformer细节剖析
1. 编码器
首先来看Encoder部分(左半部分),它是由N层方框里面的内容堆叠起来的。对于每一层来说,都由两部分构成:一部分是multi-head self-attention机制,另一部分是一个简单的全连接前馈网络。在每一部分上,都使用残差+layer normalization来进行处理。论文中,这样的方框有6个,即 N = 6 N=6 N=6,模型的隐层单元数 d m o d e l = 512 d_{model} = 512 dmodel=512。
2. 自注意力机制
Encoder内部没有使用RNN,取而代之的是一种self-attention(自注意力)机制。
一般我们用的attention机制,可以抽象为输入一个查询(query),去查询键值对(key-value pair)中的key,然后得到一个概率分布,再据此对value进行加权相加,获取当前query下的注意力表征。而我们的query,往往是Decoder中某一个step的输出,key-value pair往往是encoder的输出。
论文里面使用的也是这种attention机制,只不过其query、key、value都是由encoder的输出经过不同的变换而来,也即self-attention,所有的东西都是自己。他们定义了一种叫“Scaled Dot-Product Attention”的计算方式,用于计算给定query、key和value下的注意力表征,如下图(左)所示:
这里的 Q Q Q、 K K K和 V V V分别表示query、key和value矩阵,它们的维度分别为 L q ∗ d k L_q * d_k Lq∗dk、 L k ∗ d k L_k * d_k Lk∗dk、 L k ∗ d v L_k * d_v Lk∗dv。计算公式为:
一般我们经常使用的attention计算方式有两种:一种是乘性attention,即使用内积的方式;另一种是加性attention,即使用额外一层隐藏层来计算。这两种计算方式理论上复杂度是差不多的,但乘性attention因为可以用矩阵运算,会更节省时间和空间。对照着上图(左)和公式来看,这个公式与乘性attention计算方式的唯一不同就在于使用了一个缩放因子 1 d k \frac{1}{\sqrt{d_k}} dk1。这里为何要进行缩放呢?论文中给出了解释:在 d k d_k dk比较小的时候,不加缩放的效果和加性attention的效果差不多,但当 d k d_k dk比较大的时候,不加缩放的效果就明显比加性attention的效果要差,怀疑是当 d k d_k dk增长的时候,内积的量级也会增长,导致softmax函数会被推向梯度较小的区域,为了缓解这个问题,加上了这个缩放项进行量级缩小。
论文里面还提到,只使用一个attention的计算方式未免太过单薄,所以他们提出了multi-head(多头)注意力机制。将注意力的计算分散到不同的子空间进行,以期能从多方面进行注意力的学习,具体做法如上图(右)所示。并行地将 Q Q Q、 K K K和 V V V通过不同的映射矩阵映射到不同的空间(每个空间是一个头),再分别在这些空间中对应着进行单个“Scaled Dot-Product Attention”的学习,最后将得到的多头注意力表征进行拼接,经过一个额外的映射层映射到原来的空间。其公式如下:
这里的 W i Q ∈ R d m o d e l ∗ d k W_i^Q \in R^{d_{model} * d_k} WiQ∈Rdmodel∗dk, W i K ∈ R d m o d e l ∗ d k W_i^K \in R^{d_{model} * d_k} WiK∈Rdmodel∗dk, W i V ∈ R d m o d e l ∗ d v W_i^V \in R^{d_{model} * d_v}