1 RNN存在的问题
RNN存在梯度消失和梯度爆炸的问题。
-
书上以下图的这句话为例,进行说明;为了正确预测出问号处的单词:
- 一方面需要利用RNN将前面的所有信息编码并保存在RNN层的隐藏状态中;
-
另一方面,可以通过反向传播,将待预测单词位置处的梯度在水平方向上传递到过去(如下图所示),这种有意义的梯度,最终更新模型的权重,使得模型能够学习时间方向上的依赖关系,最终正确做出预测;
-
但是如果梯度在反向传播的过程中,梯度越向过去传播,越小,则则靠前的模型权重就得不到更新,从而无法让模型编码有用的信息,无法让模型学习并利用水平方向上长期的依赖关系来进行预测;
-
此外,如果梯度越来越大,会出现梯度爆炸,这会使得模型的学习变得不稳定,也是一个问题;
-
而这两种问题在RNN中都存在;
-
下面仅仅从时间方向上看梯度的传播(如下图所示);纵向来的梯度会和水平方向上传递来的梯度相加;因此仅仅分析水平方向上梯度的变化的结论在考虑纵向时也是适用的;
1.1梯度消失问题
-
y = t a n h ( x ) y=tanh(x) y=tanh(x)函数的导数为: y ′ = 1 − y 2 y'=1-y^2 y′=1−y2;下图是原函数与导数的图像:
-
由图像可知,导数值小于 1.0,并且随着 x 远离 0,它的值在变小;因此导数永远小于1,即局部梯度是小数,那么输出侧的梯度乘上局部梯度就会变小;并且如果 x 远离 0,局部梯度会非常小;那么经过这么一个tanh节点,梯度就会变小一次;而水平方向上有好多个tanh节点,那么传播到非常过去的节点,梯度甚至有可能会消失。
-
RNN 层的激活函数一般使用 tanh 函数,但是如果改为 ReLU 函数, 则有希望抑制梯度消失的问题(当 ReLU 的输入为 x 时,它的输出是 max(0, x))。这是因为,在 ReLU 的情况下,当 x 大于 0 时,反向传播将上游的梯度原样传递到下游,梯度不会“退化”。而且可以在ReLU之前将输入全部变到正数,这样就完全是x了;
1.2梯度爆炸问题
简单起见, 这里我们忽略反向传播图中的 tanh 节点;
加法节点梯度不变,因此就没画出来了;
-
那么简单来看,图中只剩矩阵乘法的节点,如下图所示:
-
前面说过矩阵相乘的梯度传播公式;这里每经过一个矩阵乘法节点,都要乘上一个局部梯度 W h \boldsymbol{W}_h Wh;
- 由于RNN是使用一组权重参数的,因此这里每次乘的权重是一样的;
-
接下来书上初始化了一个梯度为1的矩阵,然后执行了多次局部梯度,并计算了每次反向传播之后梯度的L2范数;代码如下图所示:
- L2 范数定义为每个元素的平方和的平方根,因此当向量中某个元素很大时,整个范数值就会被拉大;
- 这就使得 L2 范数对离群值和噪声更加敏感,因为这些值在平方运算中会被放大
- 所以本书中就计算了这个范数,这样可以直观的去看一下梯度是不是有爆炸的趋势
-
下图是实验结果;可见确实有爆炸的趋势;梯度爆炸会导致数值过大,甚至溢出,影响神经网络的学习;
-
但是有一个问题,如果权重是一个标量,那么如果权重大于1,则肯定梯度会越来越大的;权重是一个矩阵的时候,就又涉及到矩阵的奇异值这个概念了(还需要深入了解);
- 简单而言,矩阵的奇异值表示数据的离散程度;
- 根据这个奇异值(更准确地说是多个奇异值中的最大值)是否大于 1,可以预测梯度大小的变化
- 如果奇异值的最大值大于 1,则可以预测梯度很有可能会呈指数级增加;而如果奇异值的最大值小于 1,则可以判断梯度会呈指数级减小;详见论文;
1.3梯度爆炸的对策
-
梯度爆炸的对策为**梯度裁剪**;
-
裁剪按照下式进行:
- 将神经网络所有的参数整合到一起,合成一个”向量“ g ^ \hat{\boldsymbol{g}} g^;
- t h r e s h o l d threshold threshold为梯度阈值;
- ∥ g ^ ∥ \|\hat{\boldsymbol{g}}\| ∥g^∥为L2范数;
i f ∥ g ^ ∥ ⩾ t h r e s h o l d : g ^ = t h r e s h o l d ∥ g ^ ∥ g ^ (1) \begin{aligned} if\; \|\hat{\boldsymbol{g}}\|&\geqslant threshold:\\ \hat{\boldsymbol{g}}&=\frac{threshold}{\|\hat{\boldsymbol{g}}\|}\hat{\boldsymbol{g}} \end{aligned} \tag{1} if∥g^∥g^⩾threshold:=∥g^∥thresholdg^(1)
-
下面的代码是书上的一个小例子:
def clip_grads(grads, max_norm): total_norm = 0 for grad in grads: total_norm += np.sum(grad**2) # 对梯度矩阵的所有元素求和;因为公式中就是把所有的梯度组合在一起,然后求的范数; total_norm = np.sqrt(total_norm) # 二范数 rate = max_norm / (total_norm + 1e-6) # 公式的变形 if rate < 1: for grad in grads: grad *= rate if __name__ == '__main__': dW1 = np.random.rand(3, 3) * 10 dW2 = np.random.rand(3, 3) * 10 grads = [dW1, dW2] # 对应梯度裁剪公式里面的g max_norm = 5.0 # 范数阈值 clip_grads(grads, max_norm) pass
2梯度消失的对策——LSTM
通过改变RNN的网络结构,改变了存放”记忆“的载体;引入了门结构;即Gated RNN;
这里先看看LSTM;后面再看一下书上的GRU;
-
LSTM这里的改进涉及:增加了一个记忆单元、输入门、输出门、遗忘门;
-
下图为RNN和LSTM结构的简略对比:
- LSTM多了记忆单元 c \boldsymbol{c} c;
- 这个记忆单元仅在 LSTM 层内部接收和传递数据,不像 h \boldsymbol{h} h那样还要传递给上面的层;
- 结合前面说的TRNN层,虽然我们说水平方向上有多个RNN层,但本质上就是一组参数,这些RNN层其实就是一个;后面的TLSTM也是这样;因此,书上说,记忆单元在 LSTM 层内部结束工作,不向其他层输出,对外部不可见。
2.1输出门
概括: t a n h ( c t ) tanh(\boldsymbol{c}_t) tanh(ct)编码了目前时刻为止所需要的信息,用输出门来控制信息中各个元素的重要程度;
-
LSTM的记忆单元 c t \boldsymbol{c}_t ct保存了从过去到时刻 t t t的所有必要信息,基于这个充满必要信息的记忆,向外部的层(和下一时刻的 LSTM)输出隐藏状态 h t \boldsymbol{h}_t ht;如下图所示:
- LSTM输出经过tanh变换后的记忆单元
- c t \boldsymbol{c}_t ct基于 c t − 1 \boldsymbol{c}_{t-1} ct−1、 h t − 1 \boldsymbol{h}_{t-1} ht−1、 x t \boldsymbol{x}_t xt经过”某种计算“得到;
- 隐藏状态 h t = t a n h ( c t ) \boldsymbol{h}_{t}=tanh(\boldsymbol{c}_t) ht=tanh(ct);即对 c t \boldsymbol{c}_t ct各个元素应用tanh函数;
-
门的概念:
- 从字面意义上看,门可以用于控制开合;
- 但LSTM中的门,还可以控制将门打开多少来控制数据的流动;
- 门的开合程度由 0.0 ~1.0 的实数表示(1.0 为全开);重点是,门的开合程度也是(自动) 从数据中学习到的;
-
接下来用到的函数的区别:
- tanh函数:输出是 −1.0 ~ 1.0 的实数;表示某种被编码的“信息”的强弱(程度);
- sigmoid函数:输出是 0.0~1.0 的实数;表示数据流出的比例;如同前面二分类时用它来转化为概率一样;
- 因此,在大多数情况下,门使用 sigmoid 函数作为激活函数,而包含实质信息的数据则使用 tanh 函数作为激活函数;
-
综合2和3,我们可以说:
- 有专门的权重参数用于控制门的开合程度,这些权重参数通过学习被更新
- sigmoid函数用于求门的开合程度,即将权重参数计算之后的结果输出到0-1之间;
-
输出门:对 t a n h ( c t ) tanh(\boldsymbol{c}_t) tanh(ct)施加门,针对 t a n h ( c t ) tanh(\boldsymbol{c}_t) tanh(ct)中的每个元素,调整它们作为下一时刻的隐藏状态的重要程度;
-
由于这个门管理当前时刻的输出,即隐藏状态 h t \boldsymbol{h}_t ht,所以称为输出门
-
输出门的公式如下:
o = σ ( x t W x ( o ) + h t − 1 W h ( o ) + b ( o ) ) (2) \boldsymbol{o}=\sigma\left(\boldsymbol{x}_t \boldsymbol{W}_x^{(\mathrm{o})}+\boldsymbol{h}_{t-1} \boldsymbol{W}_h^{(\mathrm{o})}+\boldsymbol{b}^{(\mathrm{o})}\right) \tag{2} o=σ(xtWx(o)+ht−1Wh(o)+b(o))(2)-
和单纯的RNN的公式如出一辙;输入 x t \boldsymbol{x}_t xt有权重 W x ( o ) \boldsymbol{W}_x^{(\mathrm{o})} Wx(o),上一时刻的隐藏状态 h t − 1 \boldsymbol{h}_{t-1} ht−1有权重 W h ( o ) \boldsymbol{W}_h^{(\mathrm{o})} Wh(o); σ ( ) \sigma() σ()表示sigmoid函数;结果 o \boldsymbol{o} o应该是一个行向量;
-
然后将 o \boldsymbol{o} o和 t a n h ( c t ) tanh(\boldsymbol{c}_t) tanh(ct)的对应元素相乘,作为输出 h t \boldsymbol{h}_t ht,如下式所示;对应元素相乘即哈达玛积(Hadamard product),用 ⊙ \odot ⊙来表示;
h t = o ⊙ tanh ( c t ) (3) \boldsymbol{h}_t=\boldsymbol{o} \odot \tanh \left(\boldsymbol{c}_t\right) \tag{3} ht=o⊙tanh(ct)(3)
-
-
下图是加入输出门之后的LSTM结构图:
-
2.2遗忘门
- 前面说了: c t \boldsymbol{c}_t ct基于 c t − 1 \boldsymbol{c}_{t-1} ct−1、 h t − 1 \boldsymbol{h}_{t-1} ht−1、 x t \boldsymbol{x}_t xt经过”某种计算“得到;
- 遗忘门的作用:对上一个LSTM单元传递来的记忆信息进行取舍,告诉模型需要忘记什么;因此设置一个遗忘门作用在 c t − 1 \boldsymbol{c}_{t-1} ct−1上,将需要忘记的赋予更低的重要性;
-
与输出门类似,遗忘门也是一组权重参数,加上一个sigmoid函数;如下式所示:
f = σ ( x t W x ( f ) + h t − 1 W h ( f ) + b ( f ) ) (4) \boldsymbol{f}=\sigma\left(\boldsymbol{x}_t \boldsymbol{W}_x^{(\mathrm{f})}+\boldsymbol{h}_{t-1} \boldsymbol{W}_h^{(\mathrm{f})}+\boldsymbol{b}^{(\mathrm{f})}\right) \tag{4} f=σ(xtWx(f)+ht−1Wh(f)+b(f))(4)
其中 W x ( f ) \boldsymbol{W}_x^{(\mathrm{f})} Wx(f)、 W h ( f ) \boldsymbol{W}_h^{(\mathrm{f})} Wh(f)、 b ( f ) \boldsymbol{b}^{(\mathrm{f})} b(f)为遗忘门的权重和偏置参数; σ ( ) \sigma() σ()依旧表示sigmoid函数;然后将遗忘门 f \boldsymbol{f} f与 c t − 1 \boldsymbol{c}_{t-1} ct−1对应元素相乘,得到当前时刻的记忆单元 c t \boldsymbol{c}_{t} ct;如下式所示:
c t = f ⊙ c t − 1 (5) \boldsymbol{c}_t=\boldsymbol{f} \odot \boldsymbol{c}_{t-1} \tag{5} ct=f⊙ct−1(5) -
加入遗忘门之后LSTM的结构如下图所示:
2.3输入门
- 除了遗忘一部分信息,还需要让模型记住一些新的信息;
- 新的信息也有重要程度之分,因此引入输入门,判断新增信息的各个元素的价值有多大;
-
将新的信息加入到记忆单元中,因此使用tanh函数,而不用sigmoid函数;计算公式如下:
g = tanh ( x t W x ( g ) + h t − 1 W h ( g ) + b ( g ) ) (6) \boldsymbol{g}=\tanh \left(\boldsymbol{x}_t \boldsymbol{W}_x^{(\mathrm{g})}+\boldsymbol{h}_{t-1} \boldsymbol{W}_h^{(\mathrm{g})}+\boldsymbol{b}^{(\mathrm{g})}\right) \tag{6} g=tanh(xtWx(g)+ht−1Wh(g)+b(g))(6)
相关参数类似前面的输出门和遗忘门的参数;这是计算的新的信息;将此信息加入到上一时刻的记忆单元 c t − 1 \boldsymbol{c}_{t-1} ct−1中,形成新的记忆;
-
为了区分新的信息中元素的价值,构建输入门,用输入门对新的信息加权;输入门公式如下:
i = σ ( x t W x ( i ) + h t − 1 W h ( i ) + b ( i ) ) (7) \boldsymbol{i}=\sigma\left(\boldsymbol{x}_t \boldsymbol{W}_x^{(\mathrm{i})}+\boldsymbol{h}_{t-1} \boldsymbol{W}_h^{(\mathrm{i})}+\boldsymbol{b}^{(\mathrm{i})}\right) \tag{7} i=σ(xtWx(i)+ht−1Wh(i)+b(i))(7)
用这个权值 i \boldsymbol{i} i与新增加的信息 g \boldsymbol{g} g相乘,对应元素相乘,然后再添加到被筛选过的保留下来的上一时刻的记忆单元 c t − 1 \boldsymbol{c}_{t-1} ct−1中。 -
加入新增信息和输入门之后LSTM的结构如下:
2.4总结
-
上一时刻传来的记忆单元有些信息需要被遗忘,因此增加了遗忘门;
-
当前时刻有新的信息需要添加,且增加的信息也有重要程度之分,因此加了输入门;
-
1和2形成了当前时刻的记忆单元;
-
当前时刻输出的隐藏状态原本就是要包含目前为止所有的信息的,因此基于当前时刻的记忆单元,计算当前时刻的隐藏状态;而输出门就是用来调整当前时刻记忆单元作为下一时刻隐藏状态的重要程度的。
-
另外,从公式上看,不管是哪个门,以及新增的记忆单元,都是基于 h t − 1 \boldsymbol{h}_{t-1} ht−1、 x t \boldsymbol{x}_t xt;每个门的权重参数具体数值不同,但形式相同。
2.5 LSTM梯度的流动
LSTM中最关键的就是记忆单元 c \boldsymbol{c} c,隐藏状态 h \boldsymbol{h} h就是基于 c \boldsymbol{c} c计算得到的;因此LSTM的记忆信息主要来自于 c \boldsymbol{c} c;所以梯度的流动我们主要关注一下记忆单元 c \boldsymbol{c} c部分。
-
涉及记忆单元的梯度流动如下图所示:
- 涉及两种计算:加法和乘法
- 加法节点,梯度直接传递,没有任何变化;因此主要看一下乘法节点
-
不会梯度消失或者爆炸的原因:
- RNN里面的乘法节点是矩阵乘法,而且每次都是相同的权重矩阵,因此出现了梯度消失或者梯度爆炸的情况;
- 而这里的LSTM的乘法是对应元素相乘,因此不会像矩阵乘法的梯度那样,每经过一个节点,都要再乘上一个矩阵;
- 而且这里每次的梯度都是不同的门值(因为每次输入 x \boldsymbol{x} x不一样,虽然依然是一套权重参数)
-
另外,这里的乘法节点是遗忘门处的:
- 遗忘门作为权重乘到了上一时刻的记忆单元上,需要被忘记的会被分配较低的权重;
- 那么反向传播时,需要被忘记的对应的梯度就小,需要保持记住的对应的梯度就大;
- 梯度大,意味着能够被学习;且在水平方向上需要保持记住的,它们的梯度就会保持住,不会退化,能够传播到非常开始的时刻;那么这就让模型能够保持长期的记忆、保持长期的依赖关系
-
LSTM是Long Short-Term Memory(长短期记忆)的缩写,意思是可以长(Long)时间维持短期记忆(Short-Term Memory):
- 每经过一个时刻都可以形成新的记忆,这是短期的;
- 但是由于该记住的,对应的梯度不会退化,因此可以长期记住;