transformer结构是google在2017年的《Attention Is All You Need》论文中提出,在NLP的多个任务上取得了非常好的效果。其最大特点是抛弃了传统的CNN和RNN,整个网络结构完全是由Attention机制组成。
《Attention Is All You Need》论文地址:https://arxiv.org/abs/1706.03762
1. Transformer模型的思想
1.1. RNN的缺陷
在没有Transformer以前,大家做神经机器翻译用的最多的是基于RNN的Encoder-Decoder模型:
Encoder-Decoder模型有很强的能力,但只要是RNN,就会有梯度消失问题,核心原因是有递归的方式,作用在同一个权值矩阵上,使得如果这个矩阵满足条件的话,其最大的特征值要是小于1的话,那就一定会出现梯度消失问题。后来的LSTM和GRU也仅仅能缓解这个问题。
1.2. Transformer为何优于RNN
Transformer中抛弃了传统的CNN和RNN,整个网络结构完全是由Attention机制组成。 作者采用Attention机制的原因是考虑到RNN(或者LSTM,GRU等)的计算限制为是顺序的,也就是说RNN相关算法只能从左向右依次计算或者从右向左依次计算,这种机制带来了两个问题:
- 时间片 t t t的计算依赖 t − 1 t-1 t−1时刻的计算结果,这样限制了模型的并行能力
- 顺序计算的过程中信息会丢失,尽管LSTM等门机制的结构一定程度上缓解了长期依赖的问题,但是对于特别长期的依赖现象,LSTM依旧无能为力。
Transformer的提出解决了上面两个问题:
- 首先它使用了Attention机制,将序列中的任意两个位置之间的距离缩小为一个常量;
- 其次它不是类似RNN的顺序结构,因此具有更好的并行性,符合现有的GPU框架。
2. Transformer模型架构
Transformer模型总体的样子如下图所示:总体来说,和Encoder-Decoder模型有些相似,左边是Encoder部分,右边是Decoder部分。
Encoder :输入是单词的Embedding,再加上位置编码,然后进入一个统一的结构,这个结构可以循环很多次(N次),也就是说有很多层(N层)。每一层又可以分成Attention层和全连接层,再额外加了一些处理,比如Skip Connection,做跳跃连接,然后还加了Normalization层。其实它本身的模型还是很简单的。
Decoder: 第一次输入是前缀信息,之后的就是上一次产出的Embedding,加入位置编码,然后进入一个可以重复很多次的模块。该模块可以分成三块来看,第一块也是Attention层,第二块是cross Attention,不是Self-Attention,第三块是全连接层。也用了跳跃连接和Normalization。
输出: 最后的输出要通过Linear层(全连接层),再通过softmax做预测。
再换用另一种简单的方式来解释Transformer的网络结构。
上图的Decoder的第一个输入,就是output的前缀信息。
3. Encoder模块
3.1. Self-Attention机制
上图的网络简化一下,理论上Encoder和Decoder只有一个模块,那也算是Transformer。
重点是看Encoder和Decoder里面的Attention机制是怎么运作的。
绿色的框(Encoder #1)就是Encoder里的一个独立模块。下面绿色的输入的是两个单词的embedding。这个模块想要做的事情就是想把
x
1
x_{1}
x1转换为另外一个向量
r
1
r_{1}
r1,这两个向量的维度是一样的。然后就一层层往上传。
转化的过程分成几个步骤,第一个步骤就是Self-Attention,第二个步骤就是普通的全连接神经网络。但是注意,Self-Attention框里是所有的输入向量共同参与了这个过程,也就是说, x 1 x_{1} x1和 x 2 x_{2} x2通过某种信息交换和杂糅,得到了中间变量 z 1 z_{1} z1和 z 2 z_{2} z2。而全连接神经网络是割裂开的,和各自独立通过全连接神经网络,得到了 r 1 r_{1} r1和 r 2 r_{2} r2。
x 1 x_{1} x1和 x 2 x_{2} x2互相不知道对方的信息,但因为在第一个步骤Self-Attention中发生了信息交换,所以 r 1 r_{1} r1和 r 2 r_{2} r2各自都有从 x 1 x_{1} x1和 x 2 x_{2} x2得来的信息了 。
如果用直觉的方式来理解Self-Attention,假设左边的句子就是输入
x
1
,
x
2
,
.
.
.
,
x
14
x_{1},x_{2},...,x_{14}
x1,x2,...,x14,然后通过Self-Attention映射为
z
1
,
z
2
,
.
.
.
,
z
14
z_{1},z_{2},...,z_{14}
z1,z2,...,z14,为什么叫Self-Attention呢,就是一个句子内的单词,互相看其他单词对自己的影响力有多大。 比如单词it,它和句子内其他单词最相关的是哪个,如果颜色的深浅来表示影响力的强弱,那显然我们看到对it影响力最强的就是The和Animal这两个单词了。所以Self-Attention就是说,句子内各单词的注意力,应该关注在该句子内其他单词中的哪些单词上。
Attention和self-attention的区别
- 以Encoder-Decoder框架为例,输入Source和输出Target内容是不一样的,比如对于英-中机器翻译来说,Source是英文句子,Target是对应的翻译出的中文句子,Attention发生在Target的元素Query和Source中的所有元素之间。
- Self Attention,指的不是Target和Source之间的Attention机制,而是Source内部元素之间或者Target内部元素之间发生的Attention机制,也可以理解为Target=Source这种特殊情况下的Attention。
- 两者具体计算过程是一样的,只是计算对象发生了变化而已。
下图就是Self-Attention的计算机制。已知输入的单词embedding,即
x
1
x_{1}
x1和
x
2
x_{2}
x2,想转换成
r
1
r_{1}
r1和
r
2
r_{2}
r2。
转换方式如下:
先把
x
1
x_{1}
x1转换成三个不一样的向量,分别叫做
q
1
q_{1}
q1、
k
1
k_{1}
k1、
v
1
v_{1}
v1,然后把
x
2
x_{2}
x2转换成三个不一样的向量,分别叫做
q
2
q_{2}
q2、
k
2
k_{2}
k2、
v
2
v_{2}
v2。那把一个向量变换成另一个向量的最简单的方式就是乘以矩阵进行变换了。所以,需要三个不同的矩阵
W
Q
W^{Q}
WQ、
W
K
W^{K}
WK、
W
v
W^{v}
Wv,即
可以注意到,上述过程中,不同的
x
i
x_{i}
xi分享了同一个
W
Q
W^{Q}
WQ、
W
K
W^{K}
WK、
W
v
W^{v}
Wv,通过这个操作,
x
1
x_{1}
x1和
x
2
x_{2}
x2已经发生了某种程度上的信息交换 。也就是说,单词和单词之间,通过共享权值,已经相互发生了信息的交换。
然后,有了
q
1
q_{1}
q1、
k
1
k_{1}
k1、
v
1
v_{1}
v1和
q
2
q_{2}
q2、
k
2
k_{2}
k2、
v
2
v_{2}
v2,用
v
1
v_{1}
v1和
v
2
v_{2}
v2两个向量的线性组合,来得到
z
1
z_{1}
z1和
z
2
z_{2}
z2,即:
进而得到组合的权重
θ
\theta
θ:
通过上述的整个流程,就可以把输入的
x
1
x_{1}
x1和
x
2
x_{2}
x2转换成了
z
1
z_{1}
z1和
z
2
z_{2}
z2。这就是Self-Attention机制。有了
z
1
z_{1}
z1和
z
2
z_{2}
z2,再通过全连接层,就能输出该Encoder层的输出
r
1
r_{1}
r1和
r
2
r_{2}
r2。
q 、 k 、 v q、k、v q、k、v 向量的思路自于比较早的信息检索领域, q q q就是query, k k k就是key, v v v就是值,(k,v)就是键值对、也就是用query关键词去找到最相关的检索结果。
为了得到query,key,value,一个
x
x
x就得做3次乘法,那n个
x
x
x就得做
3
n
3n
3n次乘法。为了比较高效的实现矩阵乘法,要进行类似matlab中的向量化操作,因为因为GPU中矩阵运算的复杂度是
O
(
1
)
O(1)
O(1)不是
O
(
N
2
)
O(N^{2})
O(N2)。如果我们能把上面的操作变为矩阵操作,那我们就能很好的利用GPU做并行计算。具体的矩阵操作用公式表示为:
为什么这里要用矩阵而不是神经网络呢?因为矩阵运算能用GPU加速,会更快,同时参数量更少,更节省空间。
注意,上式中
d
k
d_{k}
dk的是向量
q
q
q或
k
k
k的维度,这两个向量的维度一定是一样的,因为要做点积。但是
v
v
v的维度和向量
q
q
q或
k
k
k的维度不一定相同。
上式为什么要除以
d
k
\sqrt{d_{k}}
dk呢?因为为了防止维数过高时
Q
K
T
QK^{T}
QKT的值过大导致softmax函数反向传播时发生梯度消失。
那为什么是
d
k
\sqrt{d_{k}}
dk而不是
d
k
d_{k}
dk呢?这就是个经验值,从理论上来说,就是还需要让
Q
K
T
QK^{T}
QKT的值适度增加,但不能过度增加,如果是
d
k
d_{k}
dk的话,可能就不增加了。
3.2. multi-headed Attention
如果用不同的
W
Q
W^{Q}
WQ、
W
K
W^{K}
WK、
W
v
W^{v}
Wv,就能得到不同的
Q
Q
Q、
K
K
K、
V
V
V。multi-headed Attention就是指用了很多个不同的
W
Q
W^{Q}
WQ、
W
K
W^{K}
WK、
W
v
W^{v}
Wv。
这样的好处是可以让Attention有更丰富的层次。有多个
Q
、
K
、
V
Q、K、V
Q、K、V的话,可以分别从多个不同角度来看待Attention。这样的话,输入
x
x
x,对于不同的multi-headed Attention,就会产生不同的
z
z
z。
将多个版本的
x
x
x拼接称为一个长向量,然后用一个全连接网络,即乘以一个矩阵,就能得到一个短的
x
x
x向量。
把multi-headed输出的不同的
z
z
z,组合成最终想要的输出的
z
z
z,这就是multi-headed Attention要做的一个额外的步骤。
multi-headed Attention用公式表示:
现在从直觉上理解为什么需要multi-headed Attention。
下图是有八个Attention,先看右图,这八个Attention用八种不同的颜色表示,从蓝色到灰色。然后我们可以看到一个单词,在这八个Attention上对句子里每个单词的权重,颜色越深,代表权重越大。我们只挑出橙色和绿色(即第二个和第三个色块),看它们分别是怎样的注意力。然后把橙色和绿色的色块拉长就得到了左边这个图。
我们现在看左边,先看橙色部分,单词it连接的权重最重的是animal,这是从某一个侧面来看,那从另一个侧面来看,看绿色部分,it最关注的是tired。橙色的注意力主要表明it是个什么东西,从东西的角度说明它是一种动物,而不是苹果或者香蕉。如果我们从状态这个层面来看,it这个动物现在是在怎么样的一个状态,它的状态是tired,而不是兴奋。所以不同的Self-Attention Head是不同方面的理解。
词向量Embedding输入
Encoder输入的是单词的embedding,通常有两种选择:
- 使用Pre-trained的embeddings并固化,这种情况下实际就是一个Lookup Table。
- 对其进行随机初始化(当然也可以选择Pre-trained的结果),但设为Trainable。这样在training过程中2不断地对embeddings进行改进。 即End2End训练方式。
Transformer选择后者。
3.3. 位置编码
输入的时候,不仅有单词的向量 x x x,还要加上Positional Encoding,即输入模型的整个Embedding是Word Embedding与Positional Embedding直接相加之后的结果。 这是想让网络知道这个单词所在句子中的位置是什么,是想让网络做自注意力的时候,不但要知道注意力要聚焦在哪个单词上面,还想要知道单词之间的互相距离有多远。
为什么要知道单词之间的相对位置呢? 因为Transformer模型没有用RNN也没有卷积,所以为了让模型能利用序列的顺序,必须输入序列中词的位置。所以我们在Encoder模块和Decoder模块的底部添加了位置编码,这些位置编码和输入
x
x
x的向量的维度相同,所以可以直接相加,从而将位置信息注入。
想要知道单词之间的距离,就得知道单词的坐标。有很多不同衡量距离的方式,
这里使用不同频率的
s
i
n
sin
sin和
c
o
s
cos
cos函数:
下面举例说明该公式的用法。
为什么这样做?用图形的方式可以直觉上理解。下图为一个长度为50,维度是128的句子的Positional Encoding(每一行为一个Encoding向量)。下图中一行就是一个单词的Positional Encoding。
上图可以看出,不同位置的Positional Encoding是独特的。 但是计算Positional Encoding的方式不是唯一的,甚至 Positional Encoding也可以是train出来的,并不是必须用作者说的sin cos。 只要能相互计算距离就可以。但是训练出来的不鲁棒,选择正弦曲线版本是因为它可以使模型外推到比训练过程中遇到的序列更长的序列长度。
Positional Encoding的物理意义 是:把50个Positional Encoding两两互相做点积,看相关性。其特点是Encoding向量的点积值对称,随着距离增大而减小。
3.4. skip connection和Layer Normalization
Add & Norm模块接在Encoder端和Decoder端每个子模块的后面,其中Add表示残差连接,Norm表示LayerNorm,残差连接来源于论文Deep Residual Learning for Image Recognition,LayerNorm来源于论文Layer Normalization,因此Encoder端和Decoder端每个子模块实际的输出为:
其中,
S
u
b
l
a
y
e
r
(
x
)
Sublayer(x)
Sublayer(x) 为子模块的输出。
skip connection最早是在计算机视觉的ResNet里面提到的,主要是想解决当网络很深时,误差向后传递会越来越弱,训练就很困难,那如果产生跳跃连接,如果有误差,可以从不同路径传到早期的网络层,这样的话误差就会比较明确地传回来。这就是跳跃层的来历。
跳跃层不是必须的,但在Transformer中,作者建议这样做,在Selft-Attention的前后和每一个Feed Forwar前后都用了跳跃层,如下图中的虚线所示。
如上图所示,同时,还用了Normalize,用的是一种新的Layer Normalize,不是常用的Batch Normalize。是一种正则化的策略,避免网络过拟合。
Layer Normalize就是对每一层
t
t
t的所有向量进行求均值
u
t
u^{t}
ut和方差
σ
t
\sigma^{t}
σt,然后归一化到正态分布后,再学习到合适的均值
b
b
b和方差
g
g
g进行再次缩放,即
下图为一个对不同样本做Layer Normalization的实例。
3.5. Encoder模块汇总
Encoder模块各部分及相关流程如下所示。
4. Decoder模块
Encoder与Decoder有三大主要的不同:
- Decoder SubLayer-1使用的是 “Masked” Multi-Headed Attention机制,防止为了模型看到要预测的数据,防止泄露。
- SubLayer-2是一个Encoder-Decoder Multi-head Attention。
- LinearLayer和SoftmaxLayer作用于SubLayer-3的输出后面,来预测对应的word的probabilities 。
4.1. Mask-Multi-Head-Attention输入端
模型训练阶段:
- Decoder的初始输入:训练集的标签 Y Y Y,并且需要整体右移(Shifted Right)一位
- Shifted Right的原因:T-1时刻需要预测T时刻的输出,所以Decoder的输入需要整体后移一位
举例说明 :我爱中国 → I Love China
位置关系:
0-“I”
1-“Love”
2-“China”
操作:整体右移一位(Shifted Right)
0-</s>【起始符】目的是为了预测下一个Token
1-“I”
2-“Love”
3-“China”
具体步骤
- Time Step 1
- 初始输入: 起始符
</s>
+ Positional Encoding - 中间输入:(
我爱中国
)Encoder Embedding - Decoder:产生预测
I
- 初始输入: 起始符
- Time Step 2
- 初始输入:起始符
</s>
+I
+ Positonal Encoding - 中间输入:(
我爱中国
)Encoder Embedding - Decoder:产生预测
Love
- 初始输入:起始符
- Time Step 3
- 初始输入:起始符
</s>
+I
+Love
+ Positonal Encoding - 中间输入:(
我爱中国
)Encoder Embedding - Decoder:产生预测
China
- 初始输入:起始符
4.2. Msak
mask表示掩码,它对某些值进行掩盖,使其在参数更新时不产生效果。Transformer模型里面涉及两种mask,分别是 padding mask和sequence mask。 其中,padding mask在所有的scaled dot-product attention 里面都需要用到,而sequence mask只有在Decoder的Self-Attention里面用到。
4.2.1. Padding Mask
什么是padding mask呢?因为每个批次输入序列长度是不一样的,也就是说,我们要对输入序列进行对齐。具体来说,就是给在较短的序列后面填充0。但是如果输入的序列太长,则是截取左边的内容,把多余的直接舍弃。因为这些填充的位置,其实是没什么意义的,所以我们的Attention机制不应该把注意力放在这些位置上,所以我们需要进行一些处理。
具体的做法是,把这些位置的值加上一个非常大的负数(负无穷),这样的话,经过softmax,这些位置的概率就会接近0! 而我们的padding mask 实际上是一个张量,每个值都是一个Boolean,值为false的地方就是我们要进行处理的地方。
4.2.2. Sequence mask
sequence mask是为了使得Decoder不能看见未来的信息。也就是对于一个序列,在time_step为t的时刻,我们的解码输出应该只能依赖于t时刻之前的输出,而不能依赖t之后的输出。因此我们需要想一个办法,把t之后的信息给隐藏起来。 那么具体怎么做呢?也很简单:产生一个上三角矩阵,上三角的值全为0。把这个矩阵作用在每一个序列上,就可以达到我们的目的。
sequence mask的目的是防止Decoder “seeing the future”,就像防止考生偷看考试答案一样。这里mask是一个下三角矩阵,对角线以及对角线左下都是1,其余都是0。下面是个10维度的下三角矩阵:
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
对于Decoder的Self-Attention,里面使用到的scaled dot-product attention,同时需要padding mask和sequence mask作为attn_mask,具体实现就是两个mask相加作为attn_mask。
其他情况,attn_mask一律等于padding mask。
举个例子:
假设最大允许的序列长度为10,先令padding mask为
[0 0 0 0 0 0 0 0 0 0]
然后假设当前句子一共有5个单词(加一个起始标识),在输入第三个单词的时候,前面有一个开始标识和两个单词,则此刻的sequence mask为
[1 1 1 0 0 0]
然后padding mask和sequence mask相加,得
[1 1 1 0 0 0 0 0 0 0]
4.3. Encode-Decode注意力层
Attention的预测流程和和普通的Encoder-Decoder的模式是一样的,只是用Self-Attention替换了RNN。
在这一层(Decoder中的第二个注意力层),输入不仅有前一层的输出
x
x
x,还有来自Encoder的输出
m
m
m,然后把Encoder产生的向量
m
m
m作为Decoder的key和value,Decoder的
x
x
x作为query,然后进行Self-Attention。相当于是,Encoder告诉我key和value是什么,我现在要做的就是产生query。即有
4.4. Decoder的输出
从上图可以看出,Decoder和Encoder唯一的区别就是多了一个Encode-Decode注意力层,然后最后一层接了个linear+softmax层,损失函数就是交叉熵损失。
Decoder的最后一个部分是过一个linear layer将decoder的输出扩展到与vocabulary size一样的维度上。经过softmax 后,选择概率最高的一个word作为预测结果。假设我们有一个已经训练好的网络,在做预测时,步骤如下:
- 给Decoder输入Encoder对整个句子embedding的结果和一个特殊的开始符号
</s>
。Decoder 将产生预测,在我们的例子中应该是 ”I
”。 - 给Decoder输入Encoder的embedding结果和
</s> I
,在这一步Decoder应该产生预测am
。 - 给Decoder输入Encoder的embedding结果和
</s> I am
,在这一步Decoder应该产生预测a
。 - 给Decoder输入Encoder的embedding结果和
</s> I am a
,在这一步Decoder应该产生预测student
。 - 给Decoder输入Encoder的embedding结果和
</s> I am a student
, Decoder应该生成句子结尾的标记,Decoder 应该输出</eos>
。 - 然后Decoder生成了
</eos>
,翻译完成。
这里有两个训练小技巧,第一个是label平滑,第二个就是学习率要有个worm up过程,然后再下降。
1、Label Smoothing(regularization)
由传统的
变为
注: K K K表示多分类的类别总数, ϵ \epsilon ϵ是一个较小的超参数
2、Noam Learning Rate Schedule
学习率不按照这样可能就得不到一个好的Transformer。
5.Transformer动态流程图
Encoder通过处理输入序列开启工作。Encoder顶端的输出之后会变转化为一个包含向量 K K K(键向量)和 V V V(值向量)的注意力向量集 ,这是并行化操作。这些向量将被每个Decoder用于自身的“Encoder-Decoder注意力层”,而这些层可以帮助Decoder关注输入序列哪些位置合适:
在完成Encoder阶段后,则开始Decoder阶段。Decoder阶段的每个步骤都会输出一个输出序列(在这个例子里,是英语翻译的句子)的元素。接下来的步骤重复了这个过程,直到到达一个特殊的终止符号,它表示Transformer的解码器已经完成了它的输出。每个步骤的输出在下一个时间步被提供给底端Decoder,并且就像Encoder之前做的那样,这些Decoder会输出它们的Decoder结果 。
6. Transformer特点
6.1. 优点
- 每层计算复杂度比RNN要低。
- 可以进行并行计算。
- 从计算一个序列长度为n的信息要经过的路径长度来看, CNN需要增加卷积层数来扩大视野,RNN需要从1到n逐个进行计算,而Self-attention只需要一步矩阵计算就可以。Self-Attention可以比RNN更好地解决长时依赖问题。当然如果计算量太大,比如序列长度N大于序列维度D这种情况,也可以用窗口限制Self-Attention的计算数量。
- 从作者在附录中给出的例子可以看出,Self-Attention模型更可解释,Attention结果的分布表明了该模型学习到了一些语法和语义信息。
6.2. 缺点
- 有些RNN轻易可以解决的问题Transformer没做到,比如复制String,或者推理时碰到的sequence长度比训练时更长(因为碰到了没见过的position embedding)
- 理论上:transformers不是computationally universal(图灵完备),而RNN图灵完备。这种非RNN式的模型是非图灵完备的的,无法单独完成NLP中推理、决策等计算问题(包括使用transformer的bert模型等等)