Transformer 修炼之道(二)、Encoder
Date: 2020/06/19
Author: CW
前言:
本篇文将对 Transformer 的 Encoder 作解析,内容应该是这个系列最丰富的一篇,因为后面的 Decoder 结构与 Encoder 几乎一致,只有些细节差异。
Encoder 的内容主要包括 多头自注意力层(Multi-Head Self-Attention) 和 前向反馈网络层(Feed Forward Network),前者用于捕捉特征之间的关系,后者是进一步编码学习,实质上等同于MLP(Multi-Layer Perceptron)。
Encoder 是由多个(通常6个)层组成的,在每个层内部都有残差连接和归一化操作,同时也包含了上述提及的多头自注意力层和前向反馈网络层。
Outline
I. Multi-Head Self-Attention
II. Feed Forward Network(FFN)
III. Add & Norm
IV. Summary
EncoderMulti-Head Self-Attention
在 Encoder 的第1层里,多头自注意力层的输入是特征向量矩阵(比如多个词向量组成的矩阵 ),而在后面的其它层,输入则是前一层的输出。多头自注意力层由多个自注意力层组成,其输出由其中的每个自注意力层的输出拼接而成,我们可以先来看看单个自注意力层的操作。
当自注意力层获得输入后,会对其做不同的线性变换分别生成 Q(Query)、K(Key)、V(Value) 三个矩阵。
Q、K、V 的生成然后按如下公式计算输出:
Self-Attention 计算公式形象点用图表示如下:
Self-Attention 计算图示这里除以原因是防止Q和K的点积值过大(即Q的列数,也是K的行数),避免在经过softmax后梯度太小。
作者的原话是:
We suspect that for large values of dk, the dot products grow large in magnitude, pushing the softmax function into regions where it has extremely small gradients.
这个计算过程的操作也称为 Scaled Dot-Product Attention 。
OK,至此,我们已经得到单个自注意力的输出,记为Z吧。而多头自注意力就是“多个头的自注意力”,通常“头数”(通俗理解为个数)设为8,这样就有8个自注意力的输出,最后将它们拼接起来再经过一个线性变换即可。
Multi-Head Attention Multi-Head Attention 输出示意图下面来看看代码实现:
多头自注意力代码实现 (i)clones是一个方法,代表将结构相同的层实例化n次(上图中n=4)。
clones接下来看看其前向过程:
多头自注意层代码实现(ii)注意,通过代码我们可了解到,“多头”的实现是将最后一个维度分割成多个,然后将其中每个独立作为“一个头”,所有头都处理完毕后,将分割出来的维度拼接在一起实现复原。
另外,上图中可以看到有个mask,这里先不谈,在下篇解析 Decoder 时会说明它的作用。
最后来了解下自注意力的计算是如何实现的,即上图中的compute_attn这个方法:
自注意力的实现注:以上mask_fill()方法应为mask_fill_(),这样scores才会原地改变
Feed Forward Network(FFN)
这部分就相对简单些,实质上就是两个全连接层,并且其中一个带ReLU激活,两层中间有Dropout。
FFN 计算公式男子汉话不多说,直接上代码:
FFN代码实现Add & Norm
在 Encoder 内部的每一层都有归一化操作和残差连接,先来看看归一化:
NormLayer在实现过程中,需要注意下上图红框部分所说的,如果直接令self.gamma=torch.ones(features)的话gamma这个参数是不会注入到模型parameters中的,这样就不能被学习到了。
残差连接是在每一层内部,将多头自注意力或前向反馈层的输入与输出(经过dropout)连接,注意,这里的实现是在输入多头自注意力或前向反馈层前先进行归一化,实际可根据情况作更改,比如在多头自注意力或前向反馈层后再进行归一化。
残差连接Summary
最后一部分来看看 Encoder 的整体实现:
EncoderEncoder 就是由多个以下 EncoderLayer 组成:
EncoderLayer最后
可以看出,Transformer 的结构还是相对简单而有规律的,但就是这么不复杂的一个东西,却意外地好使,就目前的趋势来看,编码+解码+注意力 这套玩法貌似能拓展到许多领域使用。