Transformer 是 Google 的团队在 2017 年提出的一种 NLP 经典模型,现在比较火热的 Bert 也是基于 Transformer。Transformer 模型使用了
Self-Attention 机制,不采用 RNN 的顺序结构,使得模型可以
并行化训练 ,而且能够拥有
全局信息 。
1、一切从Self-attention开始
1.1 处理Sequence数据的模型
Transformer是一个Sequence to Sequence model,特别之处在于它大量用到了self-attention。
要处理一个Sequence,最常想到的就是使用RNN ,它的输入是一串vector sequence,输出是另一串vector sequence。
如果假设是一个single directional的RNN,那当输出b4时,默认a1,a2,a3,a4都已经看过了。如果假设是一个bi-directional的RNN,那当输出b任意时,默认a1,a2,a3,a4都已经看过了。RNN非常擅长于处理input是一个sequence的状况。
那RNN有什么样的问题呢?它的问题就在于:RNN很不容易并行化 (hard to parallel)。
为什么说RNN很不容易并行化呢?假设在single directional的RNN的情形下,你今天要算出 b4 ,就必须要先看a1 再看 a2再看 a3 再看 a4,所以这个过程很难平行处理。
那么这时就想到了使用CNN ,它可以实现并行计算,为什么呢?
在CV 中,图片在卷积的过程中,下一层每张特征图的计算都与上一层所有或者部分特征图有关,并且下一层每张特征图的计算都是相互独立的,因此可以将每张特征图的计算认为是GPU中不同的线程核在跑,从而实现了并行,不需要等待。
但是必须要叠很多层filter,才可以看到长时的资讯。
因此,self-attention的出现了集成了上述二者的优点,既能解决长期依赖的问题同时可以实现并行计算。
1.2 Self-attention
如上图,x1-x4是输入序列,首先乘以W得到embedding,即a1-a4,接着这些embedding进入self-attention层,分别乘以三个不同的transformation matrix(Wq,Wk,Wv),得到q,k,v。 接下来使用每个query 去对每个key 做attention ,attention就是匹配这2个向量有多接近,比如我现在要对 q1和 k1 做attention,我就可以把这2个向量做scaled inner product ,得到 α11 。接下来你再拿 q1 和 k2 做attention,得到 α12。这里的 scaled inner product 就是: 式中, d 是 q 跟 k 的维度。因为 q x k 的数值会随着dimension的增大而增大,所以要除以 d开方 的值,相当于归一化的效果。
接下来要做的事如图6所示,把计算得到的所有 α1i 值取 softmax 操作。 取完 softmax操作以后,我们将该结果和所有的 vi 值进行相乘,最后把结果通通加起来得到 b1 ,所以,在产生 b1 的过程中用了整个sequence的资讯 (Considering the whole sequence)。 同样的方法,也可以计算出b2,b3,b4。经过了以上一连串计算,self-attention layer做的事情跟RNN是一样的,只是它可以并行的得到layer输出的结果。现在我们要用矩阵表示上述的计算过程。 首先输入的embedding是 I=[a1,a2,a3,a4] ,然后用 I 乘以transformation matrix Wq 得到 Q=[q1, q2, q3, q4] ,它的每一列代表着一个vector q 。同理,得到k与v。
接下来是 q与 k 的attention过程,我们可以把vector k 横过来变成行向量,与列向量 q 做内积 。这样, α 就成为了 4x4 的矩阵,它由4个行向量拼成的矩阵和4个列向量拼成的矩阵做内积得到。
在得到 A 以后,如上文所述,要得到 b, 就要使用 A 分别与 v 相乘再求和得到,所以 A 要再左乘 v 矩阵。
所以最后的公式如下:
1.3 Multi-head Self-attention
从下图14可以看到 Multi-Head Attention 包含多个 Self-Attention 层,首先将输入 X 分别传递到 2个不同的 Self-Attention 中,计算得到 2 个输出结果。得到2个输出矩阵之后,Multi-Head Attention 将它们拼接在一起 (Concat),然后传入一个Linear层,得到 Multi-Head Attention 最终的输出 Z。可以看到 Multi-Head Attention 输出的矩阵 [公式] 与其输入的矩阵 X 的维度是一样的。
下面一组Multi-head Self-attention的结果,其中绿色部分是一组query和key,红色部分是另外一组query和key,可以发现绿色部分其实更关注global的信息,而红色部分其实更关注local的信息。 这里multihead是如何实现的呢?我们举一个例子,以[10, 32, 512](seq_len, batch_size, feature_dimension,在pytorch中计算nlp时,一般是这样的顺序,与cv中会有不同 )这样的输入为例,在计算q, k, v时,他们的Wq, Wk, Wv的维度是[512, 512], 这样得到q,k,v均为[10, 32, 512]维。 然后就是multi-head的部分了,transformer在计算时并没有将他们分开计算,仍然是矩阵的思想 ,比如说有12个head,那么会将q, k, v中的feature_dimension维进行划分,划分为12,这样就变成了[10, 32, 12, 64] ,也就是说每一个head的feature_dimension维度是64维 , 然后将其reshape为[10, 384,64] ,在最后计算结束后再reshape到原来维度。
因此,使用multihead的好处是:在不增加计算量的基础上,学习更多的特征,例如全局,局部等
1.4 Positional Encoding
以上是multi-head self-attention的原理,但是还有一个问题是:现在的self-attention中没有位置的信息,一个单词向量的“近在咫尺”位置的单词向量和“远在天涯”位置的单词向量效果是一样的,没有表示位置的信息(No position information in self attention)。所以你输入"A打了B"或者"B打了A"的效果其实是一样的,因为并没有考虑位置的信息。 具体的做法是:给每一个位置规定一个表示位置信息的向量 ei,让它与 ai 加在一起之后作为新的 ai 参与后面的运算过程,但是这个向量 ei 是由人工设定的,而不是神经网络学习出来的。每一个位置都有一个不同的 ei 。
这个式子的好处是: (1)每个位置有一个唯一的positional encoding。 (2)使 PE 能够适应比训练集里面所有句子更长的句子,假设训练集里面最长的句子是有 20 个单词,突然来了一个长度为 21 的句子,则使用公式计算的方法可以计算出第 21 位的 Embedding。 (3)可以让模型容易地计算出相对位置,对于固定长度的间距 k ,任意位置的 pos+k位置 都可以被 pos位置 的线性函数表示,利用三角函数的特性即可计算:cos(a+b) = cosa x cosb - sina x sinb。
2、Transformer讲解
2.1 原理分析
2.1.1 Encoder
上图是一个seq2seq的model,左侧为 Encoder block,右侧为 Decoder block。黄色圈中的部分为Multi-Head Attention ,是由多个Self-Attention 组成的,可以看到 Encoder block 包含一个 Multi-Head Attention,而 Decoder block 包含两个 Multi-Head Attention (其中有一个用到 Masked )。Multi-Head Attention 上方还包括一个 Add & Norm 层,Add 表示残差连接 (Residual Connection) 用于防止网络退化,Norm 表示 Layer Normalization,用于对每一层的激活值进行归一化。比如说在Encoder Input处的输入是机器学习,在Decoder Input处的输入是,输出是machine。再下一个时刻在Decoder Input处的输入是machine,输出是learning。不断重复知道输出是句点(.)代表翻译结束。
对于左半部分的Encoder来说,输入X,通过multihead-attention以及layer norm以及FFN层后,得到的是表示该sequence特征的一个张量。所以:
2.1.2 Decoder
Decoder的工作时利用Encoder生成的序列特征,解码后得到另一串序列。
这里要特别注意一下,编码可以并行计算,一次性全部Encoding出来,但解码不是一次把所有序列解出来的,而是像 RNN 一样一个一个解出来的,因为要用上一个位置的输入当作attention的query。
因此,Decoder的 输出 是:对应 i 位置的输出词的概率分布。
输入 是:Encoder的输出以及 i -1 位置Decoder的输出。这里Encoder输出的序列特征被用于Decoder第二层的 self-Attention 的 Query和Key 。
明确了解码过程之后最上面的图就很好懂了,这里主要的不同就是Decoder的第一层attention多加了一个mask,因为训练时的output都是Ground Truth,这样可以确保预测第 i 个位置时不会接触到未来的信息。
因此,Decoder的结构是:
- 包含两个 Multi-Head Attention 层。
- 第一个 Multi-Head Attention 层采用了 Masked 操作。
- 第二个 Multi-Head Attention 层的Key,Value矩阵使用 Encoder 生成序列特征 进行计算,而Query使用上一个 Decoder block 的输出计算。
- 最后有一个 Softmax 层计算下一个翻译单词的概率。
Masked Self-Attention
因为在翻译的过程中是顺序翻译的,即翻译完第 i 个单词,才可以翻译第 i+1 个单词。通过 Masked 操作可以防止第 i 个单词知道第 i+1 个单词之后的信息。下面以 “我有一只猫” 翻译成 “I have a cat” 为例,了解一下 Masked 操作。在 Decoder 的时候,是需要根据之前的翻译,求解当前最有可能的翻译,如下图所示。首先根据输入 “” 预测出第一个单词为 “I”,然后根据输入 " I" 预测下一个单词 “have”。
Decoder 可以在训练的过程中使用 Teacher Forcing 并且并行化训练 ,即将正确的单词序列 ( I have a cat) 和对应输出 (I have a cat ) 传递到 Decoder。那么在预测第 i 个输出时,就要将第 i+1 之后的单词掩盖住。
注意,上面是训练的流程 ,与测试不同,测试是没有真值的,测试的流程如下: 1、输入,解码器输出 I 。 2、输入前面已经解码的和 I,解码器输出have。 3、输入已经解码的,I, have, a, cat,解码器输出解码结束标志位,每次解码都会利用前面已经解码输出的所有单词嵌入信息。
训练时 ,
不采用上述类似RNN的方法一个一个目标单词嵌入向量顺序输入训练 ,想采用类似编码器中的矩阵并行算法 ,一步就把所有目标单词预测出来 。要实现这个功能就可以参考编码器的操作,把目标单词嵌入向量组成矩阵一次输入即可。即:并行化训练。
但是在解码have时候,不能利用到后面单词a和cat的目标单词嵌入向量信息,否则这就是作弊(测试时候不可能能未卜先知)。
它训练的具体操作如下所示: 其中,Mask矩阵是一个下三角矩阵,在Pytorch实现中,是以bool为基本类型的。
因此,第一个Self-Attention层的Query, Key,Value均来自于decoder的输入, 第二个Self-Attention层的Query来自于第一层的输出,Key和Value来自于Encoder的输出。
Decoder的最后是Softmax 预测输出单词。因为 Mask 的存在,使得单词 0 的输出只包含单词 0 的信息。Softmax 根据输出矩阵的每一行预测下一个单词,如下图27所示。
在预测 时,由于没有真实值,因此decoder输出时,是一个单词一个单词进行预测的, 首先输入[BOS]生成I, 然后输入[BOS, I] 生成have,再输入[BOS, I, have]…, 直到生成EOS结束。 因此,Decoder的输入特征的维度分别是:[1,1, 512] (seq, batch, feature), [2,1,512], [3,1,512],…
下图为Transformer的整体结构。
|