Transformer解读
最近开始研究Transformer系列的东西,读了Transformer那篇论文Attention is All You Need,也查了一些资料,算是终于大体摸清了是个啥意思,打算之后再跟着哈佛的一个帖子复现一下,这里先记录一下模型整个流程以防之后忘记了。
模型结构总览
图1是Transformer模型的结构示意图(我是直接从论文里截的图,可能会糊,不过无伤大雅就不自己画矢量图啦!),从宏观的结构说,Transformer还是使用了Encoder-Decoder结构(图1左边矮的是Encoder,右边高的是Decoder)。
如果你不知道什么是Encoder-Decoder结构:
Encoder-Decoder结构是机器翻译任务的模型非常喜欢使用的一种结构(机器翻译:就是实现不同语言之间的翻译,Google Translate、有道翻译这些),它大概干了什么事情呢,就是说我们把需要翻译的语句输入进Encoder(编码器),然后这个Encoder就通过一系列操作算出了一个特征向量,然后再把这个特征向量输入进Decoder(解码器),Decoder通过一系列操作算出最终的翻译结果,这个就是Encoder-Decoder结构。(用更白的话来讲,输入和输出的语句“意思”是要一样的,Encoder就是从输入中提取这个“意思”,Decoder就是通过“意思”把话给组织出来。)
Encoder
Encoder有六个独立的layer,每个layer包含两个sub-layer。第一个sub-layer是Multi-head attention mechanism,顾名思义就是用来计算attention的。第二个sub-layer是一个全连接网络。每个sub-layer都使用了残差的思想,即每个sub-layer的输出都是: L a y e r N o r m ( x + S u b l a y e r ( x ) ) LayerNorm(x+Sublayer(x)) LayerNorm(x+Sublayer(x))(残差+Normalization),因此模型里所有的sub-layer以及embedding layer的输出的维数都是512( d m o d e l = 512 d_{model}=512 dmodel=512)。
如果你不知道什么是残差:
我讲一下我理解的残差。在人们刚刚开始研究深度学习的时候,发现模型的性能并不如人们所想的层数叠得越多就越好,反而层数太多了性能就会下降。这就很诡异了,按理来讲你20层能做到的东西,50层必定能做得一样好:让后面30层不工作不就好了吗?于是就有人提出,那这样好了,每层的输入是上一层的计算结果,我想要让后30层不工作,那我让每层计算的东西直接跟上一层的计算结果叠加,这样我后30层只要无脑全部输出0+上一层的结果,就一定能让50层模型至少拥有20层模型的性能,这就是残差的由来。而且残差收敛起来也快,可以粗暴理解为残差的取值范围一定比原始值取值范围小,所以在用梯度下降更新的时候具有优势。
总结一下,layer如果使用残差,假设它的输入为 x x x,那么它的输出为 x + l a y e r ( x ) x+layer(x) x+layer(x), l a y e r layer layer函数为这个layer计算的结果。由于输入和输出要相加,所以它俩必须要可加,因此这个layer的输入维数和输出维数一定是相等的(所以Transfomer里各层维数都是512)。使用残差的好处在于模型可以更深层以及模型更好训练了。
我是在学ResNet的时候看到的残差是什么。(ResNet是一个CNN模型,知不知道ResNet什么样不影响理解残差,放心。)当时ResNet就是利用残差降低了模型“学成”的难度,从而使得整个模型可以叠超级多层。如果你觉得我在胡扯八道或者没看懂的话,可以参考这个讲ResNet的文章或者自己再找找相关资料。
Decoder
Decoder由六个独立layer组成。每个layer有三个sub-layer:
- Masked multi-head attention,作用其实跟Encoder的multi-head attention相同,都是用来计算attention的。为啥会多个mask,是因为Decoder毕竟是在生成过程,它肯定不会有当前时刻后面的时刻的信息,因此需要把这些没有的mask掉。
- Multi-head attention,用来计算Encoder给Decoder输入的特征的attention。
- 全连接网络,跟Encoder一样了。
什么是当前时刻?
相信有这个疑问的大多对RNN(循环神经网络)不是很熟悉【当然也可能是因为我没讲清楚。我们用最直白的理解方式来讲这个事情。各种语句都可以认为是单词或者字符的序列,而我们在让模型处理语句的时候,不是一股脑地把一整句话都塞进去,而是按顺序一个单词一个单词或者一个字符一个字符处理的,最后推测也是一个单词一个单词一个字符一个字符这样推测的。每个时刻模型都只在处理一个单词或者一个模型。比方说输入是I love China. 那第1个时刻就在处理I,第2个时刻就在处理love,第3个时刻在处理China。可以理解这种感觉了吗?
所以为什么说Decoder的第一个sub-layer需要mask,就是因为它计算的是推测结果的attention,而只要Decoder还在运转,就代表结果还没被推测完,后面还有单词或者字符没推测呢,那你不可能拿不存在的东西去计算吧?而Encoder不一样,Encoder的语句我们是完整知道的,所以它不需要mask,Decoder的第二个sub-layer计算的是Encoder计算的结果,也是已知的,所以它也不需要mask。
下面会对几个sub-layer涉及到的方法进行具体的介绍。
Multi-head Attention
这个方法在Transformer里是核心方法,我们可以把这个方法按照名字拆开:Multi-head和Attention
Attention
Attention是啥???为了节省时间,我举个例子解释一下attention是做什么的(看不懂可以直接跳到公式表达那部分,然后去查其他资料理解attention的作用):
比如说我要把I love China.翻译成中文,我们当然知道这个中文是我爱中国,我们按照人类的思维逻辑想想,你是不是对着I翻译我,对着love翻译爱然后对着China翻译中国?在翻译我这个字的时候除了I以外其他的都不重要,即翻译I的时候我们只需要关注I。把这种思路延申进机器学习里,推测每个单词的时候,如果能“注意”到跟这个单词最相关的输入单词就好了。attention机制就是做的这个。
把知乎上讲Transformer的一篇文章里写的Attention机制公式化的表达给放到这里:
有两个序列Source和Target,我想要计算Source在Target的某个元素的Attention权重下的特征向量是什么,我们可以从Target的这个元素生成一个叫Query(Q)的向量,从Source序列生成一系列的<Key, Value>键值对 { K i , V i ∣ i = 1 , 2 , . . . , m } \left\{K_i,V_i|i=1,2,...,m\right\} {Ki,Vi∣i=1,2,...,m},然后进行三步:
-
计算Q和K的相似度,这里用f来表示
f ( Q , K i ) , i = 1 , 2 , . . . , m f(Q,K_i),i=1,2,...,m f(Q,Ki),i=1,2,...,m
-
将得到的相似度进行Softmax操作,归一得到权重
α i = e f ( Q , K i ) s u m j = 1 m f ( Q , K j ) , i = 1 , 2 , . . . , m \alpha_i=\frac{e^{f(Q,K_i)}}{sum_{j=1}^mf(Q,K_j)},i=1,2,...,m αi=sumj=1mf(Q,Kj)ef(Q,Ki),i=1,2,...,m
-
使用计算出来的权重 α i \alpha_i αi与Value进行加权求和,得到Attention向量。
∑ i = 1 m α i V i \sum_{i=1}^m\alpha_iV_i i=1∑mαiVi
有朋友可能会说了,那这QKV都咋算出来的啊,我看见的是拿个矩阵跟Target和Source的向量相乘算出来的,至于这个矩阵具体是多少数值是由模型在训练过程中自己算出来的。
那self-attention是什么?一句话:Target和Source相等就是self-attention。self-attention有什么用呢?举个例子,有一个句子:“我吃的面,我朋友吃的饭,我觉得我那碗很难吃。”问究竟难吃的是什么东西,使用self-attention就能计算出难吃跟这句话里其他字的相关程度,发现面的相关程度高,就推测出难吃的是面。当然这是非常直观且不严谨的解释,只是想说明self-attention是有用的!
前面说了这么多,终于可以进入到最细节的Transformer里的attention是什么样的了
我们在将模型结构概览的时候提到过,所有的layer的输入的维数都是
d
m
o
d
e
l
=
256
d_{model}=256
dmodel=256,首先要通过输入把QKV三个矩阵算出来,其中
Q
∈
R
m
×
d
k
Q\in R^{m \times d_k}
Q∈Rm×dk,
K
∈
R
m
×
d
k
K\in R^{m \times d_k}
K∈Rm×dk,
V
∈
R
m
×
d
v
V\in R^{m \times d_v}
V∈Rm×dv。
d
k
d_k
dk和
d
v
d_v
dv是两个给定的数值,它的具体数值和设定原因我们放到Multi-head里讲,这里只需要知道是两个数就可以了。这个
m
m
m论文中没有仔细说明,论文只是说在实际实现的时候它们不是一个单词一个单词计算attention的,而是一个序列一起进行计算,所以我感觉
m
m
m可以粗暴理解成输入的序列长度?
(原文)In practice, we compute the attention function on a set of queries simultaneously, packed together into a matrix Q.
还记得前面说的,attention机制的第一步是计算Q和K的相似度吗?在Transformer里,这个相似度是使用这样的一个计算公式计算的:
f ( Q , K i ) = Q T K i d k f(Q,K_i)=\frac{Q^TK_i}{\sqrt{d_k}} f(Q,Ki)=dkQTKi
然后再进行Softmax操作归一化得到权重:
α i = e Q T K i d k s u m j = 1 m Q T K j d k , i = 1 , 2 , . . . , m \alpha_i=\frac{e^{\frac{Q^TK_i}{\sqrt{d_k}}}}{sum_{j=1}^m\frac{Q^TK_j}{\sqrt{d_k}}},i=1,2,...,m αi=sumj=1mdkQTKjedkQTKi,i=1,2,...,m
将query、Keys和Values分别整合为矩阵Q,K,V,计算输出矩阵为(Softmax函数就不展开啦):
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q T K d k ) V Attention(Q,K,V)=softmax(\frac{Q^TK}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dkQTK)V
Multi-head
Multi-head比Attention好理解多了。常规而言,我们算Attention就只用一组线性变换把输入映射成一组Q, K, V。但是Transformer里使用多组线性变换把输入映射成多组QKV,计算出不同的attention output,然后把不同组attention的值拼在一起输出。这就是所谓的Multi-head
Positional Encoding
Positional Encoding的作用比较好理解。机器翻译这些任务之所以一直使用RNN一类的模型,就是因为RNN类的模型拥有处理序列化数据的能力,不仅仅是指它能够输入输出序列化的数据,更多的是因为它能够学到序列数据排序的一些规律。但是我们可以看到Attention机制里它学到的更多是不同元素之间的关系,这种关系是某种相关程度。相关程度跟顺序是有着微妙的不同的。因此为了使得以Attention机制为核心的Transformer拥有类似于RNN理解顺序的能力,需要额外地增加某种表达使得模型能够表示顺序,这个表达就是Positional Encoding。在Transformer里采用的是使用sine和cosine来表示:
P E ( p o s , 2 i ) = s i n ( p o s / 1000 0 2 i / d m o d e l ) PE_{(pos,2i)}=sin(pos/10000^{2i/d_{model}}) PE(pos,2i)=sin(pos/100002i/dmodel)
P E ( p o s , 2 i + 1 ) = c o s ( p o s / 1000 0 2 i / d m o d e l ) PE_{(pos,2i+1)}=cos(pos/10000^{2i/d_{model}}) PE(pos,2i+1)=cos(pos/100002i/dmodel)
参考文献
https://blog.youkuaiyun.com/weixin_44538273/article/details/86501056
https://zhuanlan.zhihu.com/p/46990010