转载自了不起的赵队,以做学习记录。
RNN(Recurrent Neural Network)是一类用于处理序列数据
的神经网络。首先需要明确什么是序列数据, 时间序列数据是指在不同时间点上收集到的数据,这类数据反应了某一事物、现象等随时间的变化状态或程度。这是时间序列数据的定义,当然这里也可以不是时间,比如文字序列,但总归序列数据有一个特点:后面的数据跟前面的数据有关系
。
RNN的结构
我门从基础的神经网络中知道,神经网络包含输入层、隐层、输出层,通过激活函数控制输出,层与层之间通过权值连接。激活函数是事先设定好的,那么神经网络模型通过训练学习到的东西就蕴含在权值
中。
基础的神经网络只在层与层之间建立了权连接,RNN最大的不同之处就是在层之间的神经元之间也建立的权连接
:
图中
O
O
O代表输出,
y
y
y代表样本给出的确定值,
L
L
L代表损失函数。这是一个标准的RNN结构图,图中每个箭头代表做一次变换,也就是说箭头连接带有权值。左侧是折叠起来的样子,右侧是展开的样子,左侧中h旁边的箭头代表此结构中的‘循环’体现在隐层。
在展开结构中我们可以观察到,在标准的RNN结构中:
- 隐层的神经元之间也是带有权值的。也就是说,随着序列的不断推进,
前面的隐层将会影响后面的隐层
,我们可以看到,‘损失’
也是随着序列的推进而不断积累的 权值共享
,图中的 w w w全是相同的, U U U和 V V V也是一样- 每一个输入值都只与它本身的那条路线建立权连接,不会和别的神经元连接
以上为RNN的标准结构,然而在实际中这一种结构并不能解决所有问题,例如我们输入一串文字,输出为分类类别,那么输出就不需要一个序列,只需要单个输出,如图:
同样的,我们有时候还需要单输入但是输出为序列的情况,那么就可以使用如下结构:
还有一种结构是输入虽是序列,但不随着序列变化,就可以使用如下结构:
RNN的变体
原始的N vs N RNN要求序列等长,然而我们遇到的大部分问题序列都是不等长的,如机器翻译中,源语言和目标语言的句子往往并没有相同的长度。下面我们介绍RNN最重要的一个变种:N vs M
。这种结构又叫做Encoder-Decoder
模型,也可以称之为Seq2Seq
模型
从名字就能看出,这个结构的原理是先编码后解码。左侧的RNN用来编码得到
c
c
c,得到
c
c
c后再用右侧的RNN进行解码。得到
c
c
c有多种方式,最简单的方法就是把Encoder的最后一个隐状态赋值给
c
c
c,还可以对最后的隐状态做一个变换得到
c
c
c,也可以对所有的隐状态做变换。
除了以上的结构外,RNN还有很多种结构,用于应对不同的需求和解决不同的问题,更多的结构可以前往 RNN 循环 NN 神经网络基本结构类型
但相同的是循环神经网络除了拥有神经网络都有的一些共性元素之外,它总要在一个地方体现出循环
,而根据循环体现方式的不同和输入输出的变化就形成了多种RNN结构。
标准RNN的前向输出流程
上面介绍了RNN有很多变种,但其数学推导过程其实都是大同小异,这里就介绍一下标准结构的RNN的前向传播过程:
x
x
x是输入,
h
h
h是隐层单元,
O
O
O代表输出,
y
y
y代表样本给出的确定值(标签),
L
L
L代表损失函数。
V
V
V、
W
W
W、
U
U
U是权值,同一类型的权连接权值相同。这些元素右上角带的
t
t
t代表
t
t
t时刻的状态,其中需要注意的是,
h
h
h在
t
t
t时刻的表现不仅由此刻的输入决定,还受
t
t
t之前时刻的影响。
那么前向传播算法即如下,对于
t
t
t时刻:
h
t
=
ϕ
(
U
x
t
+
W
h
t
−
1
+
b
)
h^{t} =\phi({Ux^{t}+Wh^{t-1}+b})
ht=ϕ(Uxt+Wht−1+b)
其中
ϕ
(
)
\phi()
ϕ()为激活函数,一般来说会选择tanh函数,b为偏置。
t
t
t时刻的输出:
o
t
=
V
h
t
+
c
o^{t}=Vh^{t}+c
ot=Vht+c
最终模型的预测输出为:
y
^
(
t
)
=
σ
(
o
(
t
)
)
\widehat{y}^{(t)}=\sigma(o^{(t)})
y
(t)=σ(o(t))
其中
σ
\sigma
σ为激活函数,通常RNN用于分类,故这里一般用softmax函数。
RNN的训练方法–BPTT
BPTT(back-propagation through time)
算法是常用的训练RNN的方法,其实本质还是BP算法,只不过RNN处理时间序列数据,所有要基于时间反向传播,故叫随时间反向传播。BPTT的中心思想和BP算法相同,沿着需要优化的参数的负梯度方向不断寻找更优的点直至收敛。
- 综上所述,BPTT算法本质还是BP算法,
BP算法本质还是梯度下降法
,那么求各个参数的梯度
便成了此算法的核心
再次拿出这个结构图观察,需要优化的参数有三个: U U U、 V V V、 W W W。与BP算法不同的是,其中 W W W和 U U U两个参数的寻优过程需要追溯之前的历史数据,参数 V V V相对简单只需要关注目前,我们先求解 V V V的偏导数:
∂ L t ∂ V = ∂ L ( t ) ∂ o ( t ) ⋅ ∂ o ( t ) ∂ V \frac { \partial{L^{t}}}{\partial{V}}=\frac{\partial L^{(t)}}{\partial o^{(t)}}\cdot\frac{\partial o^{(t)}}{\partial V} ∂V∂Lt=∂o(t)∂L(t)⋅∂V∂o(t)
这个式子看起来简单但是求解起来很容易出错,因为其中嵌套着激活函数,是复合函数的求导过程。
RNN的损失也是会随着时间累加的,所以不能只求t时刻的偏导。
L = ∑ t = 1 n L ( t ) L = \sum_{t=1}^{n}L^{(t)} L=t=1∑nL(t)
∂ L ∂ V = ∑ t = 1 n ∂ L ( t ) ∂ o ( t ) ⋅ ∂ 0 ( t ) ∂ V \frac{\partial L}{\partial V}=\sum_{t=1}^{n} \frac{\partial L^{(t)}}{\partial o^{(t)}} \cdot\frac{\partial 0^{(t)}}{\partial V} ∂V∂L=t=1∑n∂o(t)∂L(t)⋅∂V∂0(t)
W W W和 U U U的偏导的求解由于需要涉及到历史数据,其偏导求起来相对复杂,我们先假设只有三个时刻,那么在第三个时刻 L L L对 W W W的偏导数为:
∂ L ( 3 ) ∂ W = ∂ L ( 3 ) ∂ o ( 3 ) ∂ o ( 3 ) ∂ h ( 3 ) ∂ h ( 3 ) ∂ W + ∂ L ( 3 ) ∂ o ( 3 ) ∂ o ( 3 ) ∂ h ( 3 ) ∂ h ( 3 ) ∂ h ( 2 ) ∂ h ( 2 ) ∂ W + ∂ L ( 3 ) ∂ o ( 3 ) ∂ h ( 3 ) ∂ h ( 2 ) ∂ o ( 2 ) ∂ h ( 1 ) ∂ h ( 1 ) ∂ W \frac{\partial L^{(3)}}{\partial W}=\frac {\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial o^{(3)}}{\partial h^{(3)}}\frac{\partial h^{(3)}}{\partial W}+\frac {\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial o^{(3)}}{\partial h^{(3)}}\frac{\partial h^{(3)}}{\partial h^{(2)}}\frac{\partial h^{(2)}}{\partial W}+\frac {\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial h^{(3)}}{\partial h^{(2)}}\frac{\partial o^{(2)}}{\partial h^{(1)}}\frac{\partial h^{(1)}}{\partial W} ∂W∂L(3)=∂o(3)∂L(3)∂h(3)∂o(3)∂W∂h(3)+∂o(3)∂L(3)∂h(3)∂o(3)∂h(2)∂h(3)∂W∂h(2)+∂o(3)∂L(3)∂h(2)∂h(3)∂h(1)∂o(2)∂W∂h(1)
相应的,L在第三个时刻对U的偏导数为:
∂
L
(
3
)
∂
U
=
∂
L
(
3
)
∂
o
(
3
)
∂
o
(
3
)
∂
h
(
3
)
∂
h
(
3
)
∂
U
+
∂
L
(
3
)
∂
o
(
3
)
∂
o
(
3
)
∂
h
(
3
)
∂
h
(
3
)
∂
h
(
2
)
∂
h
(
2
)
∂
U
+
∂
L
(
3
)
∂
o
(
3
)
∂
h
(
3
)
∂
h
(
2
)
∂
o
(
2
)
∂
h
(
1
)
∂
h
(
1
)
∂
U
\frac{\partial L^{(3)}}{\partial U}=\frac {\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial o^{(3)}}{\partial h^{(3)}}\frac{\partial h^{(3)}}{\partial U}+\frac {\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial o^{(3)}}{\partial h^{(3)}}\frac{\partial h^{(3)}}{\partial h^{(2)}}\frac{\partial h^{(2)}}{\partial U}+\frac {\partial L^{(3)}}{\partial o^{(3)}}\frac{\partial h^{(3)}}{\partial h^{(2)}}\frac{\partial o^{(2)}}{\partial h^{(1)}}\frac{\partial h^{(1)}}{\partial U}
∂U∂L(3)=∂o(3)∂L(3)∂h(3)∂o(3)∂U∂h(3)+∂o(3)∂L(3)∂h(3)∂o(3)∂h(2)∂h(3)∂U∂h(2)+∂o(3)∂L(3)∂h(2)∂h(3)∂h(1)∂o(2)∂U∂h(1)
可以观察到,在某个时刻的对W或U的偏导数,需要追溯这个时刻之前所有时刻的信息,这还仅仅是一个时刻的偏导数,上面说过损失也是会累加的,那么整个损失函数对W和U的偏导数将会非常繁琐。虽然如此但好在规律还是有迹可循,我们根据上面两个式子可以写出L在t时刻对W和U偏导数的通式:
∂
L
(
t
)
∂
W
=
∑
k
=
0
t
∂
L
(
t
)
∂
o
t
∂
o
(
t
)
∂
h
(
t
)
(
∏
j
=
k
+
1
t
∂
h
(
j
)
∂
h
(
j
−
1
)
)
∂
h
(
k
)
∂
W
\frac{\partial L^{(t)}}{\partial{W}}=\sum_{k=0}^{t}\frac{\partial L^{(t)}}{\partial o^{t}}\frac{\partial o^{(t)}}{\partial h^{(t)}}(\prod_{j=k+1}^{t}\frac{\partial h^{(j)}}{\partial h^{(j-1)}})\frac{\partial h^{(k)}}{\partial W}
∂W∂L(t)=k=0∑t∂ot∂L(t)∂h(t)∂o(t)(j=k+1∏t∂h(j−1)∂h(j))∂W∂h(k)
∂
L
(
t
)
∂
U
=
∑
k
=
0
t
∂
L
(
t
)
∂
o
t
∂
o
(
t
)
∂
h
(
t
)
(
∏
j
=
k
+
1
t
∂
h
(
j
)
∂
h
(
j
−
1
)
)
∂
h
(
k
)
∂
U
\frac{\partial L^{(t)}}{\partial{U}}=\sum_{k=0}^{t}\frac{\partial L^{(t)}}{\partial o^{t}}\frac{\partial o^{(t)}}{\partial h^{(t)}}(\prod_{j=k+1}^{t}\frac{\partial h^{(j)}}{\partial h^{(j-1)}})\frac{\partial h^{(k)}}{\partial U}
∂U∂L(t)=k=0∑t∂ot∂L(t)∂h(t)∂o(t)(j=k+1∏t∂h(j−1)∂h(j))∂U∂h(k)
整体的偏导公式就是将其按时刻再一一加起来。
前面说过激活函数是嵌套在里面的,如果我们把激活函数放进去,拿出中间累乘的那部分:
∏
j
=
K
+
1
t
∂
h
j
∂
h
j
−
1
=
∏
j
=
k
+
1
t
t
a
n
h
′
⋅
W
s
\prod^{t}_{j=K+1}\frac{\partial h^j}{\partial h^{j-1}}={\prod_{j=k+1}^{t}tanh^{'}\cdot W_s}
j=K+1∏t∂hj−1∂hj=j=k+1∏ttanh′⋅Ws
或是
∏
j
=
K
+
1
t
∂
h
j
∂
h
j
−
1
=
∏
j
=
k
+
1
t
s
i
g
m
o
i
d
′
⋅
W
s
\prod^{t}_{j=K+1}\frac{\partial h^j}{\partial h^{j-1}}={\prod_{j=k+1}^{t}sigmoid^{'} \cdot W_s}
j=K+1∏t∂hj−1∂hj=j=k+1∏tsigmoid′⋅Ws
梯度消失/梯度爆炸
我们会发现累乘会导致激活函数导数的累乘,进而会导致梯度消失
和梯度爆炸
现象的发生。至于为什么,我们先来看看这两个激活函数的图像。
这是sigmoid的函数和导数图:
这是tanh的函数和导数图:
它们两者都把输出压缩在了一个范围之内,它们的导数图像也非常相近,从图中我们能观察到,sigmoid函数的导数范围是(0,0.025],tanh函数的导数范围是(0,1],他们的导数最大都不大于1。
这会导致一个问题,在上面式子累乘的过程中,如果取sigmoid函数作为激活函数的话,那么必然是一堆小数在做乘法,结果就是越乘越小。随着时间序列的不断深入,小数的累乘就会导致梯度越来越小接近于0,这就是“梯度消失”现象。其实RNN的时间序列与深层神经网络很像,在较为深层的神经网络中使用sigmoid做激活函数也会导致反向传播时梯度消失,梯度消失就意味着,消失的那一层的参数再也不更新,那么那一层隐层就变成了单纯的映射层,毫无意义了,所以在深层神经网络中,有时多加神经元数量可能会比多加深度好。
你可能会提出异议,RNN明明与深层神经网络不同,RNN的参数都是共享的,而且某时刻的梯度是此时刻和之前时刻的累加,即使传不到最深处也是有梯度的。这当然是对的,但如果我们根据有限层的梯度来更新更多层的共享的参数一定会出问题的,因为将有限的信息来作为寻优根据必然不会找到所有信息的最优解。
之前提到多用tanh函数最为激活函数,那tanh函数的导数最大也才1啊,而且又不可能所有取值都能取到1,那相当于还是一堆小数在累乘,还是会出现“梯度消失”,那为什么还要用它做激活函数呢?
- 一个原因是
tanh相对于sigmoid来说梯度较大,收敛速度更快且引起梯度消失慢
- 另一个原因是sigmoid的输出不是
零中心对称
。sigmoid的输出均大于0,这就使得输出不是0均值,称为偏移
现象,这将导致后一层的神经元将上一层输出的非0均值的信号最为输入。关于原点对称的输入和中心对称的输出,网络会收敛地更好
RNN的特点本来就是能“追根溯源”利用历史数据,现在告诉我门可利用的历史数据竟然是有限的,这就令人非常难受,解决梯度消失是非常必要的,解决梯度消失的主要方法有:
- 选取更好的激活函数
- 改变传播结构
更好的激活函数:ReLU
关于第一点,一般选用ReLU函数最为激活函数,ReLU的图像为:
ReLU函数的左侧导数为0,右侧导数恒为1,这就避免了梯度消失的发生。
- 但恒为1的导数容易导致梯度爆炸,但设定合适的
阈值
可以解决这个问题 - 还有一点就是如果左侧恒为0的导数有可能导致神经元学死,不过设置合适的
步长
(学习率)可以有效避免这个问题的发生
关于第二点,LSTM结构可以解决这个问题。
总结,Sigmoid的缺点:
1. 导数值范围(0,0.025],反向传播时会导致梯度消失。tanh函数导数值范围更大,相对好一些
2. simoid函数不是0中心对称,tanh函数是,可以使网络收敛地更好