1.51.RNN训练难题–梯度弥散与梯度爆炸
1.51.1.梯度弥散与梯度爆炸
1.51.2.详解机器学习中的梯度消失、爆炸原因及其解决方法
1.51.2.1.前言
1.51.2.2.第一部分:为什么要使用梯度更新规则
1.51.2.3.第二部分:梯度消失、爆炸
1.51.2.3.1.深层网络角度
1.51.2.3.2.激活函数角度
1.51.2.4.第三部分:梯度消失、爆炸的解决方案
1.51.2.4.1.方案1-预训练加微调
1.51.2.4.2.方案2-梯度剪切、正则
1.51.2.4.3.方案3-relu、leakrelu、elu等激活函数
1.51.2.4.4.解决方案4-batchnorm
1.51.2.4.5.解决方案5-残差结构
1.51.2.4.6.解决方案6-LSTM
1.51.2.4.7.参考资料:
1.51.3.RNN网络的梯度推导公式
1.51.3.1.梯度爆炸
1.51.3.1.1.解决办法
1.51.3.1.2.gradient clipping(梯度裁剪)
1.51.3.2.梯度弥散
1.51.4.参考博文
此部分摘自:https://blog.csdn.net/sinat_41144773/article/details/90712065
梯度弥散(梯度消失): 通常神经网络所用的激活函数是sigmoid函数,sigmod函数容易引起梯度弥散。这个函数能将负无穷到正无穷的数映射到0和1之间,并且对这个函数求导的结果是f′(x)=f(x)(1−f(x))f′(x)=f(x)(1−f(x))表示两个0到1之间的数相乘,得到的结果就会变得很小了。神经网络的反向传播是逐层对函数偏导相乘,因此当神经网络层数非常深的时候,最后一层产生的偏差就因为乘了很多的小于1的数而越来越小,最终就会变为0,从而导致层数比较浅的权重没有更新,这就是梯度消失。
梯度爆炸:就是由于初始化权值过大,前面层会比后面层变化的更快,就会导致权值越来越大,梯度爆炸的现象就发生了。
反向传播基于的是链式求导法则。如果导数小于1,那么随着层数的增多,梯度的更新量会以指数形式衰减,结果就是越靠近输出层的网络层参数更新比较正常,而靠近输入层的网络层参数可能基本就不更新。这就是梯度消失。而如果导数值大于1,那么由于链式法则的连乘,梯度更新量是会成指数级增长的。这就是梯度爆炸。
转自:https://blog.csdn.net/qq_25737169/article/details/78847691
本文主要深入介绍深度学习中的梯度消失和梯度爆炸的问题以及解决方案。本文分为三部分,第一部分主要直观的介绍深度学习中为什么使用梯度更新,第二部分主要介绍深度学习中梯度消失及爆炸的原因,第三部分对提出梯度消失及爆炸的解决方案。有基础的同鞋可以跳着阅读。
其中,梯度消失爆炸的解决方案主要包括以下几个部分。
在介绍梯度消失以及爆炸之前,先简单说一说梯度消失的根源—–深度神经网络和反向传播。目前深度学习方法中,深度神经网络的发展造就了我们可以构建更深层的网络完成更复杂的任务,深层网络比如深度卷积网络,LSTM等等,而且最终结果表明,在处理复杂任务上,深度网络比浅层的网络具有更好的效果。但是,目前优化神经网络的方法都是基于反向传播的思想,即根据损失函数计算的误差通过梯度反向传播的方式,指导深度网络权值的更新优化。这样做是有一定原因的,首先,深层网络由许多非线性层堆叠而来,每一层非线性层都可以视为是一个非线性函数f(x)(非线性来自于非线性激活函数),因此整个深度网络可以视为是一个复合的非线性多元函数。
我们最终的目的是希望这个多元函数可以很好的完成输入到输出之间的映射,假设不同的输入,输出的最优解是g ( x ) ,那么,优化深度网络就是为了寻找到合适的权值,满足Loss=L(g(x),F(x))取得极小值点,比如最简单的损失函数
假设损失函数的数据空间是下图这样的,我们最优的权值就是为了寻找下图中的最小值点,对于这种数学寻找最小值问题,采用梯度下降的方法再适合不过了。
梯度消失与梯度爆炸其实是一种情况,看接下来的文章就知道了。两种情况下梯度消失经常出现,一是在深层网络中,二是采用了不合适的损失函数,比如sigmoid。梯度爆炸一般出现在深层网络和权值初始化值太大的情况下,下面分别从这两个角度分析梯度消失和爆炸的原因。
比较简单的深层网络如下:
图中是一个四层的全连接网络,假设每一层网络激活后的输出为fi
注:下图中的隐层标号和第一张全连接图隐层标号刚好相反。
图中的曲线表示权值更新的速度,对于下图两个隐层的网络来说,已经可以发现隐藏层2的权值更新速度要比隐藏层1更新的速度慢
那么对于四个隐层的网络来说,就更明显了,第四隐藏层比第一隐藏层的更新速度慢了两个数量级:
总结:从深层网络角度来讲,不同的层学习的速度差异很大,表现为网络中靠近输出的层学习的情况很好,靠近输入的层学习的很慢,有时甚至训练了很久,前几层的权值和刚开始随机初始化的值差不多。因此,梯度消失、爆炸,其根本原因在于反向传播训练法则,属于先天不足,另外多说一句,Hinton提出capsule的原因就是为了彻底抛弃反向传播,如果真能大范围普及,那真是一个革命。
其实也注意到了,上文中提到计算权值更新信息的时候需要计算前层偏导信息,因此如果激活函数选择不合适,比如使用sigmoid,梯度消失就会很明显了,原因看下图,左图是sigmoid的损失函数图,右边是其导数的图像,如果使用sigmoid作为损失函数,其梯度是不可能超过0.25的,这样经过链式求导之后,很容易发生梯度消失,sigmoid函数数学表达式为:
同理,tanh作为激活函数,它的导数图如下,可以看出,tanh比sigmoid要好一些,但是它的导数仍然是小于1的。tanh数学表达为:
此方法来自Hinton在2006年发表的一篇论文,Hinton为了解决梯度的问题,提出采取无监督逐层训练方法,其基本思想是每次训练一层隐节点,训练时将上一层隐节点的输出作为输入,而本层隐节点的输出作为下一层隐节点的输入,此过程就是逐层“预训练”(pre-training);在预训练完成后,再对整个网络进行“微调”(fine-tunning)。Hinton在训练深度信念网络(Deep Belief Networks中,使用了这个方法,在各层预训练完成后,再利用BP算法对整个网络进行训练。此思想相当于是先寻找局部最优,然后整合起来寻找全局最优,此方法有一定的好处,但是目前应用的不是很多了。
梯度剪切这个方案主要是针对梯度爆炸提出的,其思想是设置一个梯度剪切阈值,然后更新梯度的时候,如果梯度超过这个阈值,那么就将其强制限制在这个范围之内。这可以防止梯度爆炸。
注:在WGAN中也有梯度剪切限制操作,但是和这个是不一样的,WGAN限制梯度更新信息是为了保证lipchitz条件。
另外一种解决梯度爆炸的手段是采用权重正则化(weithts regularization)比较常见的是l1正则,和l2正则,在各个深度框架中都有相应的API可以使用正则化,比如在tensorflow中,若搭建网络的时候已经设置了正则化参数,则调用以下代码可以直接计算出正则损失:
regularization_loss = tf.add_n(tf.losses.get_regularization_losses(scope='my_resnet_50'))
如果没有设置初始化参数,也可以使用以下代码计算l2正则损失:
l2_loss = tf.add_n([tf.nn.l2_loss(var) for var in tf.trainable_variables() if 'weights' in var.name])
正则化是通过对网络权重做正则限制过拟合,仔细看正则项在损失函数的形式:
其中,α 是指正则项系数,因此,如果发生梯度爆炸,权值的范数就会变的非常大,通过正则化项,可以部分限制梯度爆炸的发生。
注:事实上,在深度神经网络中,往往是梯度消失出现的更多一些。
Relu: 思想也很简单,如果激活函数的导数为1,那么就不存在梯度消失爆炸的问题了,每层的网络都可以得到相同的更新速度,relu就这样应运而生。先看一下relu的数学表达式:
其函数图像:
从上图中,我们可以很容易看出,relu函数的导数在正数部分是恒等于1的,因此在深层网络中使用relu激活函数就不会导致梯度消失和爆炸的问题。
relu的主要贡献在于:
同时也存在一些缺点:
尽管relu也有缺点,但是仍然是目前使用最多的激活函数。
leakrelu
leakrelu就是为了解决relu的0区间带来的影响,其数学表达为:leakrelu=max(k∗x,x)其中k是leak系数,一般选择0.01或者0.02,或者通过学习而来
leakrelu解决了0区间带来的影响,而且包含了relu的所有有点
elu
elu激活函数也是为了解决relu的0区间带来的影响,其数学表达式为:
其函数及其导数数学形式为:
但是elu相对leakrelu来说,计算要更耗时间一些。
有关batch norm详细的内容可以参考我的另一篇博客:
http://blog.csdn.net/qq_25737169/article/details/79048516
残差结构说起残差的话,不得不提这篇论文了:Deep Residual Learning for Image Recognition,关于这篇论文的解读,可以参考知乎链接:https://zhuanlan.zhihu.com/p/31852747 这里只简单介绍残差如何解决梯度的问题。
事实上,就是残差网络的出现导致了image net比赛的终结,自动残差提出后,几乎所有的深度网络都离不开残差的身影,相比较之前的几层,几十层的深度网络,在残差网络面前都不值一提,残差可以很轻松的构建几百层,一千多层的网络而不用担心梯度消失过快的问题,原因就在于残差的捷径(shortcut)部分,其中残差单元如下图所示:
相比较与以前网络的直来直去结构,残差中有很多这样的跨层连接结构,这样的结构在反向传播中具有很大的好处,见下式:
LSTM全称是长短期记忆网络(long-short term memory networks),是不那么容易发生梯度消失的,主要原因在于LSTM内部复杂的“门”(gates),如下图,LSTM通过它内部的“门”可以接下来更新的时候“记住”前几次训练的”残留记忆“,因此,经常用于生成文本中。目前也有基于CNN的LSTM,感兴趣的可以尝试一下。
1.《Neural networks and deep learning》
2.《机器学习》周志华
3. https://www.cnblogs.com/willnote/p/6912798.html
4. https://www.zhihu.com/question/38102762
5. http://www.jianshu.com/p/9dc9f41f0b29
参考论文:http://proceedings.mlr.press/v28/pascanu13.pdf
理论详见:(https://blog.csdn.net/jetFlow/article/details/80161354?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160268066819195188324652%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=160268066819195188324652&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v28-3-80161354.pc_first_rank_v2_rank_v28&utm_term=gradient%20clipping%20%E8%A7%A3%E5%86%B3%E4%BB%A3%E7%A0%81tf&spm=1018.2118.3001.4187)
针对梯度爆炸问题,解决方案是引入Gradient Clipping(梯度裁剪)。通过Gradient Clipping,将梯度约束在一个范围内,这样不会使得梯度过大。
在tensorflow 文档中,可以看到Gradient Clipping板块有五个函数。这里,我们仅仅介绍其中两个:
tf.clip_by_norm(t,clip_norm,name=None)
tf.clip_by_global_norm(t_list,clip_norm,use_norm=None,name=None)
tf.clip_by_norm(t,clip_norm,name=None)
参数说明: t: a tensor ; clip_norm: 阈值,即maximum L2-norm。
如果 tensor的L2-norm大于clip_norm,则 t = t * clip_norm / l2norm(t)
否则t = t
tf.clip_by_global_norm(t_list,clip_norm,use_norm=None,name=None)
参数说明: t_list: multiple tensors; clip_norm: a clipping ratio; use_norm: 如果你
已经计算出global_norm,你可以在use_norm进行指定。
tf.clip_by_global_norm(t_list,clip_norm,use_norm=None,name=None)更慢一些,因为它需要
等所有的梯度都求出来才可以进行梯度截断。
通常会使用一种叫”clip gradients “的方法. 它能有效地权重控制在一定范围之内.
算法步骤如下:
lr = 0.01
max_grad_norm = 5
tvars = tf.trainable_variables()
grads, _ = tf.clip_by_global_norm(tf.gradients(loss, tvars), max_grad_norm)
opt = tf.train.GradientDescentOptimizer(lr)
# opt = tf.train.AdamOptimizer(lr)
optimizer = opt.apply_gradients(zip(grads, tvars))
其中pytorch版本的Gradient Clipping代码如下:
loss = criterion(output, y)
model.zero_grad()
loss.backward()
for p in model.parameters():
print(p.grad.norm())
torch.nn.utils.clip_grad_norm_(p, 10)
optimizer.step()
https://blog.csdn.net/weixin_46649052/article/details/109083154
https://blog.csdn.net/qq_25737169/article/details/78847691
https://blog.csdn.net/sinat_41144773/article/details/90712065