前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深度学习三人行(第4期)---- TF训练DNN之进阶

深度学习三人行(第4期)---- TF训练DNN之进阶

作者头像
智能算法
发布2018-06-04 16:50:05
8860
发布2018-06-04 16:50:05
举报
文章被收录于专栏:智能算法智能算法

上期我们一起学习了

深度学习三人行(第3期)---- TensorFlow从DNN入手

简单的介绍了ANN(人工神经网络),并训练了我们第一个DNN(深度神经网络),但是一个非常浅的DNN,只有两个隐藏层。如果你需要解决一个非常复杂的问题,比如在高分辨率的图像中分辨不上百种不同类型的实体对象,这时候你就需要训练一个更深的DNN来完成,可能是10层,并且每层会包含上百个神经元,并由上成千上百个连接器组成。这时候你将面临如下问题:

  • 你将面临非常诡异的梯度消失或爆炸,这会直接影响DNN的构建并且导致浅层的网络非常难以训练
  • 如此大的神经网络,直接训练的话,极度的慢
  • 拥有大量参数的模型在训练时,很容易出现过拟合现象

解决上面面临的三个问题就是本文要讲的内容。


一. 梯度消失与梯度爆炸

反向传播算法在输出层和输入层之间,传播误差梯度,一旦算法计算完损失函数的梯度,就会利用这些梯度值利用梯度下降方法更新每个参数。但是,随着往浅层的网络层传播,梯度经常会变得越来越小,导致梯度下降算法在低层连接间的权重值几乎没有什么变化,同时训练时也无法收敛到最优解。这就是梯度消失问题。

对于这个问题,常用的做法是选用好的权重初始化策略,使用更优秀的激活函数,批量标准化。

1.1 初始化策略

Xavier等人在论文Understanding the Difficulty of Training Deep Feedforward Neural Networks中提到,信号想在各个神经元中正常流动,需要神经网络在每一层输出方差尽量相等。但现实是由于输入层和输出层的节点数不同,而很难做到这点的,因此文中提出一种初始化策略叫做Xavier初始化来解决这个问题。对于不同的激活函数,其参数初始化方法如下:

ninputs 和noutputs是输入和输出层的连接数,另外对于ReLU激活函数的初始化方案有时候也叫He 初始化。tensorflow中通过variance_scaling_initializer()来进行初始化策略设置的,默认情况下,TF的全连接使用均匀分布初始化。

1.2 激活函数

我们在前面见到的神经网络通常是使用logistic函数作为激活函数的,由于logistic中心部位和两侧的梯度差别太大,如果权重初始化得太大,激活值基本都在sigmoid两侧,两侧梯度几乎为0,传播几层就没有梯度了。即使用很好的初始化算法把激活值控制在一个合理范围内,优化几下有几个神经元就又跑到两侧了,而一旦到两侧,因为梯度过小,就再也无法通过梯度更新来使其恢复。这也正是导致出现梯度消失的原因,因此改选其他激活函数也能解决这个问题。

ReLU激活函数

ReLU激活函数就是一个很好的选择,ReLU(z) = max(0,z)。因为ReLU在正数值上不会出现饱和,并且计算起来很快。但是ReLU激活函数存在一个问题,会出现死掉的ReLu,即训练期间,权重和神经元的输入层结合输出负数,会直接导致神经元只输出0,而且一旦发生就很难再跳转。有时候使用的时候,甚至出现一半的神经元死掉了,特别是使用很大的学习率时。

要想继续使用ReLU,就需要使用它的变种,LeakyReLU(z) = max(αz, z)。

其中超参数α定义了有多少的泄露。即图中z<0部分的坡度,通常这个值设置为0.01。在负数部分设置一个小坡度并能保证LeakyReLU永远不会死,并且有机会再次被唤醒。

另外还有RReLU和PReLU,RReLU是通过在训练期间,每次给α设定一个给定范围的随机值,而在测试集上,使用前面随机α的均值来预测。这样也能够表现很好,并且还有用于减少过拟合风险的正则化作用。PReLU的α是在训练阶段通过学习到的,而不是超参数,而是成为反向传播的一个参数,PReLU在拥有大量的图像数据集上表现非常好,但是小数据集上容易出现过拟合。tensorflow中通过下面定义:

ELU激活函数

另一种激活函数叫做ELU。在实验测试下,ELU相比于ReLU,ELU减少了训练时间并且在测试集上比所有的ReLU变种都要优秀。

分布图如下:

和ReLU函数相比,两者有如下不同点:

  1. 在z < 0处,结果返回负值,并且接近0,这一特性和ReLU的变种很像,能够很好缓和梯度消失问题,并且超参数α控制了z是为负数时返回的结果值,通常设置为1。
  2. ELU在z < 0处,拥有非零梯度值,这很好的避免了神经元在训练中死去的问题
  3. ELU函数处处可导,即使是在z = 0处,这避免了函数在z = 0左右出现跳动,能够很好的加速梯度下降

ELU由于使用指数运算,它的计算速度相比较于ReLU和其变种会慢,虽然收敛速度上有所弥补,但是整体ELU依旧会比ReLU慢。tensorflow中提供了elu()函数,通过设置tf.layers.dense中的activation参数来切换激活函数。

1.3 批量标准化(batch Normalization)

BN是2015年由Sergey提出,是另外一种解决梯度消失或爆炸问题的,通常在训练的时候,前一层的参数发生变化,后一层的分布也会随之改变。BN是在模型中每一层的激活函数前加入标准化操作,首先BN会对输入数据进行零均值方差归一化,该归一化能够加速收敛,甚至特征之间没有相关性,但是简单的归一化神经网络层的输入,可能会改变该层的表征能力,例如,对 sigmoid 函数的输入进行归一化,会使归一化后的输入趋向 s 型函数的线性端。因此还需要引入两个参数来做相应的缩放和平移。BN的算法如下:

其中μB是这个batch数据的均值,σB是标准差,γ是缩放因子,β是平移因子,ε是一个很小的数,防止除数为0,称为平滑因子。很多激活函数都可以使用BN方法而不会造成梯度消失问题,同时模型对初始化权值的要求也降低了。BN类似于一个正则器,它减少了对于其他正则化方法使用的需求。

但是BN会增加模型的复杂度,毕竟每层都需要进行标准化操作。因此如果你想要一个轻量级的神经网络,前面的ELU激活函数加上He初始化,可能会是更好的选择。现在来看一下tensorflow中对BN的使用,完整代码回复关键字可查看。

1.4 梯度裁剪

梯度裁剪主要用于避免梯度爆炸的情况,是通过在反向传播时,将梯度裁剪到一定范围内的值,虽然大家更加喜欢使用BN,但是梯度裁剪也非常的有用,特别时在RNN中,因此有必要知道梯度裁剪已经如何使用。tensorflow是使用clip_by_value()函数来裁剪的:


二、提升训练时间

训练大型神经网络时,还常常会遇到训练时间的问题,通常会采用复用部分网络和参数,来提升训练的时间。

2.1 复用预训练层

从0开始训练大型的DNN网络一般很慢,我们可以找到一个可以完成相似任务的网络,然后利用其部分浅层的网络及参数,对输入的特征进行简单的提取,这就是迁移学习(transfer learning)。这种方法不仅可以加快训练速度,也只需要更少的训练数据。

例如:我们已经训练了一个DNN模型来对100个不同类型的图片进行分类,包括动物,植物,汽车等等。现在我们想再训练一个DNN来对汽车类别中具体的类别分类,如果复用之前训练的DNN的话就能很简单的获取我们想要模型:

2.2 复用tensorflow模型

前面训练模型时,我们用restore将模型进行了保存。

但是我们通常只想复用原模型中的部分内容,一个简单的方法是配置Saver只保存原模型部分变量,例如下面只保存隐藏层的1,2,3层。

上面代码中,首先,我们构建一个新模型,并确定是否是需要复制原模型中1到3的隐藏层,然后通过scope的正则表达式"hidden[123]"匹配出1到3的隐藏层,接下来创建一个字典映射出原模型中变量的名称,接下来创建一个saver保存只包含这些1到3隐藏层的变量,创建另外一个Saver保存整个模型。

最后我们重新开启一个session,并初始化所有变量,restore需要的1到3隐藏层的变量,利用这些变量在新任务上训练模型并保存。

2.3 从其他框架复用

如果已经使用另一个框架训练了模型,你会需要手动导入权重,然后将它分配给合理的变量。下面例子展示了如何使用从另一个框架训练的模型的第一个隐藏中复制权重和偏差。

2.4 冻结浅层

由于第一个DNN的浅层可能已经学会检测图片类别的低级特征,这将在其他图片分类中有用,因此你可以复用这些浅层。通常来说训练一个新的DNN,将模型的权重冻结是一个很好的做法,如果浅层权重固定了,那么深层权重会变得容易训练。为了在训练阶段冻结浅层网络,最简单的方法是给训练的优化器一个除了浅层网络变量的变量列表。

第三行中获取到隐藏层3,4和输出层的层所有的训练变量列表。这样就除去了浅层1,2的变量。接下来将提供的训练变量列表给到优化器minimize函数。这样就是实现浅层1,2的冻结。

2.5 缓存冻结层

因为冻结层无法改变,可以为每个训练实例缓存最顶层冻结层的输出。因为训练多次遍历整个数据集,这会给你带来巨大的速度提升,因为训练实例每次只需要经过冻结层一次。例如,你可以首先运行整个训练集通过较低层

hidden2_outputs = sess.run(hidden2, feed_dict={X: X_train})

在训练阶段,构建批次的训练实例,你可以从隐藏层2构建批量输出,并将数据喂给模型训练。

最后一行运行前面定义的训练操作(隐藏层1,2),并把二个隐藏层的批量输出喂给模型作为整个模型输出中隐藏层1,2的输出,由于我们已经提供了隐藏1,2的输出,因此模型不会再尝试评估它。


三、内容小结

本文讨论了我们再训练更深的DNN模型时,遇到的问题,以及解决方法。训练大型DNN常遇到如下问题:1.梯度消失和爆炸,2.训练效率和速度问题,3.过拟合问题。对于梯度消失问题,我们提出如下解决方案,初始化策略方法:Xavier和He初始化,替换激活函数:ReLU,ReLU变种和ELU,以及BN(批量标准化)。对于训练效率,我们提出了各种复用模型内容的方法,以及下篇会将的各种优化方法。过拟合问题,有很多的正则化方法可用,这也是下篇会讲解的问题。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 智能算法 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档