文章目录
前言
之前读了引用率超高的《Attention is All You Need》这篇论文,结合网上一些对论文的解读囫囵吞枣式捋了一遍方法思路,以为自己已经get得差不多了。然鹅!跟师兄汇报的时候又被灵魂拷问。。。(羞愧.jpg)
是我太年轻惹!完全没有领悟读paper的精髓,它引用率达到七千多次难道会是一篇平平无奇的文章嘛!
于是我又四处扒拉相关的讲解,再读了几遍文章,把看到的凌乱知识点汇总到一起,重新梳理一下从RNN到Transformer的历程。
主要参考:
https://mp.weixin.qq.com/s/RLxWevVWHXgX-UcoxDS70w
(上次参考的对attention is all you need的解读,讲得很好)
https://www.bilibili.com/video/BV1JE411g7XF?p=23
https://www.bilibili.com/video/BV1JE411g7XF?p=20
(李宏毅老师的课程,强烈推荐!上一篇已经讲很好了,李老师的课简直更清晰明了)
1. 从CNN到RNN
我们知道卷积神经网络(CNN)有着强大的作用,只要有足够多的训练数据,机器就可以自己学习一个网络模型,输入指定的x,就可以输出想要的y。
那么为什么需要RNN呢?
举一个简单的例子:
对于一个slot filling的问题,我们想要输入I arrive Taipei on November这个句子,学到Taipei这个单词属于destination这个slot。CNN在进行学习的时候,对一个句子中的每个单词进行编码作为输入,再通过hidden layer输出每个单词对应的slot。但这样的训练模式会产生一个问题:当输入是arrive Taipei和leave Taipei的时候,由于对每个单词的学习过程都是独立的,因此它无法学到当前一个单词是arrive的时候Taipei就是destination,当前一个单词是leave的时候Taipei就成了destination。
CNN只能对每一个输入独立进行学习,无法学习前一个输入和后一个输入之间的联系。这时候就需要RNN了。
1.1 简单的RNN
RNN的改进核心之处在于每一时刻的hidden layer,不仅接收当前时刻的输入,同时接收上一时刻hidden layer的输出。
如上图所示,我们先输入arrive,对应隐藏层的输出是
a
1
a_1
a1,下一时刻输入Taipei时,隐藏层的输出
a
2
a_2
a2取决于
x
2
x_2
x2和
a
1
a_1
a1,后面依次类推。
用公式表示:
a t = f ( U x t + W a t − 1 ) a_t=f(Ux_t+Wa_{t-1}) at=f(Uxt+Wat−1)
y t = g ( V a t ) y_t=g(Va_t) yt=g(Vat)
1.2 双向RNN
用上面的RNN来进行输出时,输出
y
t
y_t
yt的时候网络学习了
t
t
t时刻以前的所有输入,但有时候生成输出时不仅与前面时刻的输入有关,也可能与后面时刻的输入有关。
于是有了下面的双向RNN网络:
下面一条分支从 x 1 x_1 x1依次输入到 x n x_n xn,上面一条分支反过来从 x n x_n xn输入到 x 1 x_1 x1,中间 t t t时刻hidden layer的输入由 x t x_t xt、 a t − 1 a_{t-1} at−1和 a t + 1 a_{t+1} at+1共同决定。
1.3 Long Short-term Memory(LSTM)
上面介绍的是RNN的简单版本,现在常用的RNN实际上指的是它的进阶版——LSTM。
一开始我们直接把每个时刻的hidden layer的输出都保存起来,提供给下一个时刻使用,这个存储和使用的过程都是没有任何限制的。而在LSTM中,我们增加了3个gate用来控制memory cell的存储和调用。
input gate:决定是否要将某些值存进memory cell里面
forget gate:决定是否要清空此时memory cell中存储的值
output gate:决定是否要调用memory cell中存储的值
LSTM实质就是把上面这个结构整个作为一个neuron取代上面RNN网络图里面store的蓝色圈圈的那部分。
下面用公式来表示这个neuron的输入输出过程:
z是想要存进memory cell中的值(整个neuron的输入), z i z_i zi、 z f z_f zf和 z o z_o zo分别代表三个gates,是由当前时刻的输入 x x x通过矩阵变换得到的。 c c c表示当前时刻cell memory中已经存好了的值, c ′ c' c′表示更新后cell memory中的新值, a a a表示整个neuron的输出。
为什么要用LSTM取代RNN?
——LSTM解决了RNN训练过程中梯度消失的问题。
在RNN的训练过程中发现loss不是逐渐收敛的,而是突然降低或者突然升高。通过实验发现,训练过程中loss对矩阵W的变化非常敏感,因此用梯度下降法更新W时,W从平坦的地方跳到陡峭的地方,loss会发生剧烈变化。
看下面这个例子可以有一个更直观的理解:
对于RNN来说,前后两个step的hidden state中间经过了一层sigmoid,所以后向传播的时候梯度会乘上一个sigmoid的导数值。根据求导的链式法则,导致梯度表示成连积的形式从而造成梯度消失。
对于LSTM来说,前后两个step的hidden cell没有经过一个sigmoid层,而是乘了一个sigmoid的函数值 / 激活值(即LSTM的forget gate),所以后向传播的时候梯度也会乘上一个sigmoid的函数值,导致梯度表现为累加的形式,因此避免了梯度消失。
(Mark一下,关于梯度消失的问题还有待进一步学习)
2. 从RNN到Attention
2.1 基于RNN的Attention-based Model
先看一个seq2seq的经典模型在机器翻译中的应用——
由于输入和输出的长度是不同的,因此采用上图encoder-decoder的结构。
- 左边为encoder,采用RNN对输入的四个字进行编码后得到最后一个字的hidden layer的输出作为整个输入的特征表示,记为 C C C。此时 C C C包含了输入的所有信息。
- 右边的decoder同样也采用RNN的结构,这时候将 C C C看成RNN的输入,蓝色框为decoder的hidden layer,依次进行输出,同时每个时刻的输出取决于 C C C和上一时刻的输出。
简化表示:
用公式表示如下:
C = F ( x 1 , x 2 , . . . , x m ) C=F(x_1,x_2,...,x_m) C=F(x1,x2,...,xm)
y i = G ( C , y 1 , y 2 , . . . , y i − 1 ) y_i=G(C,y_1,y_2,...,y_{i-1}) yi=G(C,y1,y2,...,yi−1)
当输入的句子很长的时候,C与前面的输出关联性越来越弱,仅仅利用C作为输入的特征表示来生成输出,效果不够理想。
这时候Attention就发挥作用了!
Attention-based model主要是模拟人脑的注意力机制,我们知道人在获取外界信息的时候大脑是会有一个侧重的关注点的。
我们现在看下面这张图。
打一个比方说,现在你看着这整个网页,页面上所有的内容就称之为Sensory Memory,你的大脑会对所有的内容有一个注意力的分配,最终为下面这张图分配较高的权重,使得你的视线集中到图上,称之为Working Memory。这个过程就是紫色框部分所表示的Attention的学习过程。
那么你的大脑是怎么知道要将注意力集中到哪里的呢?你的大脑中储存很多知识,如何进行数学运算,如何迅速阅读一篇文章等等,称之为Long-term Memory。所以当你看到这个网页时,你将从你的Long-term Memory中检索与之相关的知识,指导你的大脑去进行注意力的分配。这个检索的过程,实际上也是一个为大脑中Long-term Memory中所有内容分配注意力的过程,也就是图中红色框的部分。
这样以来你用大脑中现存的Long-term Memory解决了此时的新问题,再反过来对现在学到的知识进行编码,存到Memory里面,用于未来新问题的出现。
下面我们主要讲紫色框部分这种attention的学习。
还是以机器翻译为例,它的主要流程如下面所示:
- 第一步通过RNN网络学习“机器学习”四个字的hidden layer输出 h 1 , h 2 , h 3 , h 4 h_1,h_2,h_3,h_4 h1,h2,h3,h4;
- 初始化一个 z 0 z_0 z0向量,用 z 0 z_0 z0去和 h 1 , h 2 , h 3 , h 4 h_1,h_2,h_3,h_4 h1,h2,h3,h4进行匹配计算相似度(有很多种匹配方法,计算cosine similarity/dot product等)得到 α 0 1 , α 0 2 , α 0 3 , α 0 4 \alpha_0^1,\alpha_0^2,\alpha_0^3,\alpha_0^4 α01,α02,α03,α04,对这些 α \alpha α进行softmax计算得到 α ^ 0 1 , α ^ 0 2 , α ^ 0 3 , α ^ 0 4 {\hat\alpha}_0^1,\hat\alpha_0^2,\hat\alpha_0^3,\hat\alpha_0^4 α^01,α^02,α^03,α^04;
- 计算 c 0 = ∑ α ^ 0 i h i c_0=\sum \hat\alpha_0^ih^i c0=∑α^0ihi,解码的过程同样也采用一个RNN网络, z 0 z_0 z0相当于一个初始的memory,此时把 c 0 c_0 c0作为这RNN网络的第一个输入,那么 z 1 z_1 z1就是第一个隐藏层的输出,它取决于 z 0 z_0 z0和 c 0 c_0 c0值;
- 同样用 z 1 z_1 z1去和 h 1 , h 2 , h 3 , h 4 h_1,h_2,h_3,h_4 h1,h2,h3,h4匹配得到 α ^ 1 1 , α ^ 1 2 , α ^ 1 3 , α ^ 1 4 {\hat\alpha}_1^1,\hat\alpha_1^2,\hat\alpha_1^3,\hat\alpha_1^4 α^11,α^12,α^13,α^14,计算 c 1 = ∑ α ^ 1 i h i c_1=\sum \hat\alpha_1^ih^i c1=∑α^1ihi,第二个隐藏层的输出 z 2 z_2 z2不仅取决于 z 1 z_1 z1和 c 1 c_1 c1值,同时还取决于已经输出的 y 1 y_1 y1,即“machine”这个单词。
- 继续重复这个步骤,直到输出结束符的时候终止计算。
2.2 Self-Attention Layer
按照2.1中基于RNN的Attention-based Model来解决seq2seq的问题的弊端是:无论是encoder还是decoder,每一时刻都必须等上一个时刻计算完来之后才能进行,因此无法采用并行计算。
于是就有来这篇引用达到7000多次的《Attention is All You Need》。它提出来一个self-attention layer,可以取代RNN,还可以并行计算。
Self-attention layer的计算流程如下:
对输入的每个特征embedding进行矩阵变换,分别乘以
W
q
,
W
k
,
W
v
W_q,W_k,W_v
Wq,Wk,Wv后得到
q
i
,
k
i
,
v
i
q_i,k_i,v_i
qi,ki,vi。对于
q
1
q_1
q1来说,用它与所有的
k
i
k_i
ki进行匹配计算相似度得到
α
1
i
\alpha_1^i
α1i,那么输出的
b
1
=
∑
α
^
1
i
v
i
b_1=\sum \hat\alpha_1^iv_i
b1=∑α^1ivi。然后用同样的方法计算出
b
2
,
b
3
,
b
4
b_2,b_3,b_4
b2,b3,b4。
这里我想了一个便于理解的比喻来解释为什么要把每个 a a a换成 q , k , v q,k,v q,k,v。
我们可以把 a 1 , a 2 , a 3 , a 4 a_1,a_2,a_3,a_4 a1,a2,a3,a4想象成四个要去攻打敌人的英雄,把 v 1 , v 2 , v 3 , v 4 v_1,v_2,v_3,v_4 v1,v2,v3,v4看成每个英雄各自拥有的技能, q 1 , q 2 , q 3 , q 4 q_1,q_2,q_3,q_4 q1,q2,q3,q4是它们学习技能的能力, k 1 , k 2 , k 3 , k 4 k_1,k_2,k_3,k_4 k1,k2,k3,k4是他们把自身技能教给其他英雄的能力。那么对于英雄 a 1 a_1 a1来说,用 q 1 q_1 q1和 k i k_i ki进行匹配得到这个英雄学到每个英雄的技能的概率,再乘以 v i v_i vi就是这个英雄最终能够学习到的所有技能。
经过这样一个学习过程之后,每个英雄都吸收了其他英雄的技能,得到了强化。
另外Transformer采用了一个multi-head self-attention,线性变换得到8组不同的
q
i
,
k
i
,
v
i
q_i,k_i,v_i
qi,ki,vi矩阵,记作
q
i
j
,
k
i
j
,
v
i
j
q_i^j,k_i^j,v_i^j
qij,kij,vij,
(
i
=
1
,
2
,
3
,
4
;
j
=
1
,
2
,
.
.
.
,
8
)
(i=1,2,3,4;j=1,2,...,8)
(i=1,2,3,4;j=1,2,...,8)。对这8组都进行相同的上面self-attention的操作,得到
b
i
1
,
b
i
2
,
.
.
.
,
b
i
8
b_i^1,b_i^2,...,b_i^8
bi1,bi2,...,bi8,然后将所有的
b
b
b连接起来,再乘上一个转换矩阵得到最终的encoder的输出。
这样做的目的是让8组不同的 q , k , v q,k,v q,k,v自主学习关注不同的东西,可能用 q i 1 , k i 1 , v i 1 q_i^1,k_i^1,v_i^1 qi1,ki1,vi1计算得到的 b i 1 b_i^1 bi1侧重于捕捉每个单词与它周围单词的联系, b i 2 b_i^2 bi2侧重于捕捉每个单词和距离它较远单词的联系。论文中给出了一个attention visualization:
可以看到左边侧重于邻近单词的attention,右边侧重于稍远一点单词的attention。沿用上面英雄的例子的话可以理解成,每个英雄的技能 v v v可以分解成8个不同的子技能。
3. 从Attention到Transformer
Transformer的结构中利用self-attention layer来解决seq2seq的问题,模型结构如下:
具体的步骤整理在上一篇论文笔记《【注意力模型】Attention is All You Need》,这里就不再展开讲解了。