Transformer
本文介绍了Transformer结构, 是一种encoder-decoder, 用来处理序列问题, 常用在NLP相关问题中. 与传统的专门处理序列问题的encoder-decoder相比, 有以下的特点:
- 结构完全不依赖于CNN和RNN
- 完全依赖于self-attention机制, 是一种堆叠的self-attention
- 使用全连接层
- 逐点point-wise计算的
整个Transformer的结构图如下所示:
Encoder and Decoder Stacks
如上所说, Transformer是基于stacked self-attention的, stack方式具体为:
Encoder
Encoder是由$N=6$个独立的层堆叠而成的, 每层有两个子层:
- 第一个层为multi-head self-attention结构
- 第二层为simple, position-wise fully connected feed-forward network, 即基于位置的简单全连接反馈网络
在每个子层又引入了residual connection, 具体的做法为每个子层的输入与输出相加, 这就要求每个子层的输入与输出的维度是完全相等的. 然后再使用layer normalization. 因此每个子层的最终输出为:
$$LayerNorm(x + Sublayer(x))$$
此外, 论文中限定了Embeding层的输出和两个子层的输入输出的维度都是$d_{model}=512$.
Decoder
Decoder也是由$N=6$个独立的层堆叠而成的, 除了与Encoder层中的两个完全相同的子层外, 在两层之间又加入了一个multi-head attention, 这里是对Encoder的输出做attention处理.
与Encoder相同, 每个子层也引入了residual connection, 并且相加之后使用layer normalization得到每个子层最后的输出.
此外, 为了防止序列中元素的位置主导输出结果, 对Decoder层的multi-head self-attention层增加了mask操作, 并且结合对output embedding结果进行右移一位的操作, 保证了每个位置$i$的输出, 只会依赖于$i$位之前(不包括$i$位, 因为右移一位和mask).
Attention
论文中将常用的Attention结构从新的一种角度进行了描述:
Attention作为一种函数, 接受的输入为:
- 一个query
- 一组key-value pairs
即包含三部分, query, keys和values. 三者都是向量.
输出就是对组中所有values的加权之和, 其中的权值是使用compatibility function(如内积), 对组内的每一个keys和query计算得到的.
例如, 对于常见的self-attention来说, 这里值的就是对于序列中的某一个元素对应的向量, 求得经过self-attention之后对应的向量. query指的是这个元素对应的向量(如NLP任务中句子序列中某一个单词对应的embedding向量), key-value pairs就是这个序列的所有元素, 其中的每个元素对应的key和value是完全相同的向量, 对于要比较的那个元素, 与query也是完全相同的. 然后使用当前向量和所有向量做内积得到权值, 最后的数据就是这个权值和对应向量的加权和.
论文中使用了两种Attention方法, 分别为Scaled Dot-Product Attention和Multi-Head Attention Instead.
Scaled Dot-Product Attention
我们假设query
和key
这两个用来比较的向量, 长度都为$d_k$; value
向量的长度为$d_v$. 对query
和所有keys
进行点积得到值, 再对这里得到的每个点积结果除以$\sqrt{d_k}$, 完成scale, 最后应用一个softmax function获得每个value
对应的权值, 加权求得最后的输出向量.
这是对于一个query
的情况. 实际中是直接对一个序列对应的所有querys
直接进行计算, 将所有querys
拼接成一个大的$Q$矩阵, 对应的keys
和values
也拼接成$K$和$V$矩阵, 则Scaled Dot-Product Attention对应的计算公式为:
$$\text{Attention}(Q,K,V)=\text{softmax}(\frac{QK^T}{\sqrt{d_k}})V$$
需要注意的点是: $d_k$较大时, 向量之间的点积结果可能就会非常大, 这回造成softmax函数陷入到梯度很小的区域. 为了应对这种情况, 适应了缩放因子$\sqrt{d_k}$, 将点积结果尽量缩小到梯度敏感的区域内.
Multi-Head Attention
之前的方法都是对$d_{model}$维度的querys
, keys
和values
直接使用一个Attention函数, 得到结果, 在Multi-Head Attention方法中, 我们如下操作:
-
对
querys
,keys
和values
都分别进行$h$次的线性映射(类似于SVM中的线性核), 得到$h$组维度为分别为$d_k$, $d_k$, $d_v$的三种向量.需要注意的是, 这$h$次映射都是不同的映射, 每次线性映射使用的参数是不相同的, 而且这个映射是可学习的, 相当于得到了$h$个不同空间(虽然这些空间的维数是相等的)中的表征.
-
然后并行的对这$h$组维度为分别为$d_k$, $d_k$, $d_v$的
querys
,keys
和values
向量执行Attention函数, 每组都产生一个$d_v$维的输出结果. -
最后将这$h$个维度为$d_k$向量拼接起来.
-
通过线性转换还原成$d_{model}$维度的向量.
公式表示为:
$$\text{MultiHead}(Q,K,V)=\text{Concat}(\text{head}_1, \cdots, \text{head}_h)W^O$$
其中:
$$\text{head}_i=\text{Attention}(QW_i^Q,KW_i^K,VW_i^V)$$
$W_i^Q \in \mathbb{R}^{d_{model} \times d_k}$, $W_i^K \in \mathbb{R}^{d_{model} \times d_k}$, $W_i^V \in \mathbb{R}^{d_{model} \times d_v}$, 以及$W_i^O \in \mathbb{R}^{hd_v \times d_{model}}$都是可学习的线性映射参数. 在论文中超参数的选择为$h=8$, 又由于$d_{model}=512$, 因此$d_k=d_v=d_{model}/h=64$.
因为中间计算的降维, 总体计算的消耗与直接使用Attention函数的消耗相近.
Transformer模型中Attention使用的特殊点
对于Multi-Head Attention, 在Transformer模型中有三个不同点:
-
在encoder-decoder attention层中, 即Encoder和Decoder两者之间的Attention中(对应于Decoder结构中的中间子层部分),
queries
来自于Decoder结构中上一个子层的输出. 这保证了对于Decoder中的每一个位置, 都能捕获input sequence各个位置的信息. -
Encoder中对应的是self-attention, 对应一个位置上的
query
,key
,value
是完全相同的一个向量. 每个位置的输出结果, 都会参考输入的所有位置. -
相似的, Decoder中第一个子层也是self-attention. 因此对于某个位置的元素, 会获取序列中所有序列的信息. 但为了防止leftward information flow(左侧信息泄露), 即防止出现自回归属性, 我们对这种Scaled Dot-Product Attention通过mask进行了限制, 屏蔽从第一个元素到当前元素(包含), 然后再进行Attention操作.
Position-wise Feed-Forward Networks
Encoder和Decoder都含有一个fully connected feed-forward network, 特殊的是, 这个网络分别对每个位置的attention层的输出向量单独地进行作用. 整个过程包含了两次线性变换以及中间夹杂的一次ReLU激活:
$$FFN(x) = \max(0, xW_1 + b_1)W_2 + b_2$$
对于不同位置的线性变换是完全一样的, 即使用相同的参数.
这一层的输入输出都是$d_{model}=512$, 中间隐层的维度为$d_{ff}=2048$.
Embeddings and Softmax
使用已经训练好的embeddings将input token和output token转换成$d_{model}$维度的向量.
在最后Decoder的输出时, 将Decoder的输出通过一层线性变换层和一个softmax层, 转换成预测下一个token的概率向量. 这两个层中的参数也是提前训练好的.
在模型中, 两个embedding layers以及最后的softmax之前的线性变换, 这三者共享使用相同的矩阵权值.
对于embedding层, 里面的权值需要乘以$\sqrt{d_{model}}$之后再使用.
Positional Encoding
因为模型完全没有使用循环(RNN)和卷积(CNN), 而又想使用序列中的顺序信息, 就必须加入一些关于token
的相对位置和绝对位置的信息. 因此我们加入Positional Encoding, 作为Encoder和Decoder的输入. 需要注意的是Positional Encoding产生的向量的维度为$d_{model}$, 与原本的embedding向量维度相同, 从而两者可以被相加使用.
对位置进行embedding的方法很多, 有训练方法和指定方法, 本文中, 采用**频率不同的$\sin$和$\cos$函数:
$$PE_{pos,2i} = \sin(pos/10000^{2i/d_model})$$
$$PE_{pos,2i+i} = \cos(pos/10000^{2i/d_model})$$
其中$pos$代表位置, $i$代表第$i$维. 每个维度对应于不同频率不同的正弦函数. 使用这种方法, 我们认为能够反应相对位置中包含的信息, 这是因为: 对于一个固定的偏移量$k$, $PE_{pos+k}$能表示成$PE_{pos}$的线性函数.
Why Self-Attention
之所以使用Self-Attention而没有使用循环或卷积的结构, 主要出于以下三点的考虑:
- 每层的计算复杂度
- 计算可以并行的程度
- 对于序列问题, 长序列是一个难点. Self-Attention方法对于长短序列都有较好的表现. 这是由于我们认为在模型中, 前向和后向传播的路径越短, 就更容易学习到其中的关系. 对于循环和卷积, 距离当前较远的位置, 在传播过程中都要经过较长的距离. 但对于Self-Attention结构, 无论两个元素在序列中的相对距离如何, 传播的距离总是相等的.
参考资料
来源:oschina
链接:https://my.oschina.net/u/4383224/blog/3702462