Sklearn 与 TensorFlow 机器学习实用指南——第十一章总结


第10介绍了人工神经网络,并训练了包含两个隐含层的浅的DNN。
如果你需要解决非常复杂的问题,例如检测高分辨率图像中的数百种类型的对象,可能需要训练更深的 DNN,也许有 10 层,每层包含数百个神经元,通过数十万个连接来连接。
这时就会遇到一些问题:

  1. 首先,将面临棘手的梯度消失问题(或相关的梯度爆炸问题),这会影响深度神经网络,并使较低层难以训练。
  2. 其次,对于如此庞大的网络,训练将非常缓慢。
  3. 第三,具有数百万参数的模型将会有严重的过拟合训练集的风险。
    在本章中,我们将依次讨论这些问题,并提出解决问题的技巧。
    这一章内容相当多,也都是干货,非常值得细细咀嚼。本文参考地址

梯度消失/爆炸问题

像第 10 章中所讨论的那样,反向传播算法的工作原理是从输出层到输入层,传播误差的梯度。 一旦该算法已经计算了网络中每个参数的损失函数的梯度,它就使用这些梯度来用梯度下降步骤来更新每个参数。

不幸的是,梯度往往变得越来越小,随着算法进展到较低层。 结果,梯度下降更新使得低层连接权重实际上保持不变,并且训练永远不会收敛到良好的解决方案。 这被称为梯度消失问题。 在某些情况下,可能会发生相反的情况:梯度可能变得越来越大,许多层得到了非常大的权重更新,算法发散。这是梯度爆炸的问题,在循环神经网络中最为常见。

这种不幸经过了很长一段时间,直到 2010 年左右,Xavier Glorot 和 Yoshua Bengio 发表论文发现了一些疑问和改进方法,简言之就是这个问题是由激活函数和初始化方法导致的。
因此在初始化方法方面有一些比较好的策略提出,可以大大加快训练速度。
在这里插入图片描述

非饱和激活函数

Glorot 和 Bengio 在 2010 年的论文中的一个见解是,消失/爆炸的梯度问题部分是由于激活函数的选择不好造成的。事实证明,其他激活函数在深度神经网络中表现得更好,特别是 ReLU 激活函数,主要是因为它对正值不会饱和(也因为它的计算速度很快)。

为了解决这个问题,你可能需要使用 ReLU 函数的一个变体,比如 leaky ReLU。这个函数定义为LeakyReLUα(z)= max(αz,z)(见图 11-2)。超参数α定义了函数“leaks”的程度:它是z < 0时函数的斜率,通常设置为 0.01。

Glorot 和 Bengio还评估了随机化 leaky ReLU(RReLU),其中α在训练期间在给定范围内随机挑选,并在测试期间固定为平均值。它表现相当好,似乎是一个正则项(减少训练集的过拟合风险)。最后,他们还评估了参数 leaky ReLU(PReLU),其中α被授权在训练期间被学习(而不是超参数,它变成可以像任何其他参数一样被反向传播修改的参数)。据说这在大型图像数据集上的表现强于 ReLU,但是对于较小的数据集,其具有过度拟合训练集的风险。
在这里插入图片描述最后,Djork-Arné Clevert 等人在 2015 年的一篇论文中提出了一种称为指数线性单元(exponential linear unit,ELU)的新的激活函数,在他们的实验中表现优于所有的 ReLU 变体:训练时间减少,神经网络在测试集上表现的更好。
在这里插入图片描述
那么具体应该使用哪个激活函数来处理深层神经网络的隐藏层?虽然目标会有所不同,一般情况下ELU > leaky ReLU(及其变体)> ReLU > tanh > sigmoid。 如果你关心运行时性能,那么你可能喜欢 leaky ReLU超过ELU。 如果你不想调整另一个超参数,你可以使用前面提到的默认的α值(leaky ReLU 为 0.01,ELU 为 1)。如果你有充足的时间和计算能力,你可以使用交叉验证来评估其他激活函数,特别是如果你的神经网络过拟合,则为RReLU; 如果你拥有庞大的训练数据集,则为 PReLU。

批量标准化

尽管使用 He初始化和 ELU(或任何 ReLU 变体)可以显著减少训练开始阶段的梯度消失/爆炸问题,但不保证在训练期间问题不会回来。

在 2015 年的一篇论文中,Sergey Ioffe 和 Christian Szegedy 提出了一种称为批量标准化(Batch Normalization,BN)的技术来解决梯度消失/爆炸问题,每层输入的分布在训练期间改变的问题,更普遍的问题是当前一层的参数改变,每层输入的分布会在训练过程中发生变化(他们称之为内部协变量偏移问题)。

该技术包括在每层的激活函数之前在模型中添加操作,简单地对输入进行zero-centering和规范化,然后每层使用两个新参数(一个用于尺度变换,另一个用于偏移)对结果进行尺度变换和偏移。 换句话说,这个操作可以让模型学习到每层输入值的最佳尺度,均值。为了对输入进行归零和归一化,算法需要估计输入的均值和标准差。 它通过评估当前小批量输入的均值和标准差(因此命名为“批量标准化”)来实现。
在这里插入图片描述
作者证明,这项技术大大改善了他们试验的所有深度神经网络。梯度消失问题大大减少了,他们可以使用饱和激活函数,如 tanh 甚至 sigmoid 激活函数,毕竟使用标准化之后特征数据会大概率处于线性区域(激活函数中间那段)。网络对权重初始化也不那么敏感。他们能够使用更大的学习率,显著加快了学习过程。

梯度裁剪

减少梯度爆炸问题的一种常用技术是在反向传播过程中简单地剪切梯度,使它们不超过某个阈值(这对于递归神经网络是非常有用的;参见第 14 章)。 这就是所谓的梯度裁剪。一般来说,人们更喜欢批量标准化,但了解梯度裁剪以及如何实现它仍然是有用的。

复用预训练层

从零开始训练一个非常大的 DNN 通常不是一个好主意,相反,您应该总是尝试找到一个现有的神经网络来完成与您正在尝试解决的任务类似的任务,然后复用这个网络的较低层:这就是所谓的迁移学习。这不仅会大大加快训练速度,还将需要更少的训练数据。

复用 TensorFlow 模型

如果原始模型使用 TensorFlow 进行训练,则可以简单地将其恢复并在新任务上进行训练:

[...] # construct the original model 
with tf.Session() as sess:
    saver.restore(sess, "./my_model_final.ckpt")
    # continue training the model...

冻结较低层

第一个 DNN 的较低层可能已经学会了检测图片中的低级特征,这将在两个图像分类任务中有用,因此您可以按照原样重新使用这些层。 在训练新的 DNN 时,“冻结”权重通常是一个好主意:如果较低层权重是固定的,那么较高层权重将更容易训练(因为他们不需要学习一个移动的目标)。
要在训练期间冻结较低层,最简单的解决方案是给优化器列出要训练的变量,不包括来自较低层的变量:

train_vars = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES,scope="hidden[34]|outputs")
training_op = optimizer.minimize(loss,var_list=train_vars)

第一行获得隐藏层 3 和 4 以及输出层中所有可训练变量的列表。 这留下了隐藏层 1 和 2 中的变量。接下来,我们将这个受限制的可列表变量列表提供给optimizer的minimize()函数。现在,层 1 和层 2 被冻结:在训练过程中不会发生变化(通常称为冻结层)。

缓存冻结层

由于冻结层不会改变,因此可以为每个训练实例缓存最上面的冻结层的输出。 由于训练贯穿整个数据集很多次,这将给你一个巨大的速度提升,因为每个训练实例只需要经过一次冻结层(而不是每个迭代一次)。

调整,删除或替换较高层

原始模型的输出层通常应该被替换,因为对于新的任务来说,它最有可能没有用处,甚至可能没有适合新任务的输出数量。

类似地,原始模型的较高隐藏层不太可能像较低层一样有用,因为对于新任务来说最有用的高层特征可能与对原始任务最有用的高层特征明显不同。

尝试先冻结所有复制的层,然后训练模型并查看它是如何执行的。 然后尝试解冻一个或两个较高隐藏层,让反向传播调整它们,看看性能是否提高。 您拥有的训练数据越多,您可以解冻的层数就越多。

如果仍然无法获得良好的性能,并且您的训练数据很少,请尝试删除顶部的隐藏层,并再次冻结所有剩余的隐藏层。 您可以迭代,直到找到正确的层数重复使用。 如果您有足够的训练数据,您可以尝试替换顶部的隐藏层,而不是丢掉它们,甚至可以添加更多的隐藏层。

Model Zoos

想要找到类似任务训练的神经网络,除了查看自己的模型之外,还可以在Model Zoos中搜索, 许多人为了各种不同的任务而训练机器学习模型,并且善意地向公众发布预训练模型。

TensorFlow 在 https://github.com/tensorflow/models 中有自己的模型动物园。 特别是,它包含了大多数最先进的图像分类网络,如 VGG,Inception 和 ResNet(参见第 13 章,检查model/slim目录),包括代码,预训练模型和 工具来下载流行的图像数据集。

另一个流行的模型动物园是 Caffe 模型动物园。 它还包含许多在各种数据集(例如,ImageNet,Places 数据库,CIFAR10 等)上训练的计算机视觉模型(例如,LeNet,AlexNet,ZFNet,GoogLeNet,VGGNet,开始)。 Saumitro Dasgupta 写了一个转换器,可以在 https://github.com/ethereon/caffe-tensorflow。

无监督的预训练

假设你想要解决一个复杂的任务,你没有太多的标记的训练数据,更不幸的是,你不能找到一个类似的任务训练模型。 首先,你当然应该尝试收集更多的有标签的训练数据,但是如果这太难或太昂贵,你仍然可以进行无监督的训练(如下图)。也就是说,如果你有很多未标记的训练数据,你可以尝试逐层训练层,从最低层开始,然后上升,使用无监督的特征检测算法(如限制波尔兹曼机或自动编码器)。 一旦所有层都以这种方式进行了训练,就可以使用监督式学习(即反向传播)对网络进行微调。
在这里插入图片描述

更快的优化器

训练一个非常大的深度神经网络可能会非常缓慢。
到目前为止,我们已经看到了四种加速训练的方法(并且达到更好的解决方案):

  1. 对连接权重应用良好的初始化策略,
  2. 使用良好的激活函数,
  3. 使用批量规范化,
  4. 重用预训练网络的部分。
    另一个巨大的速度提升来自使用比普通渐变下降优化器更快的优化器。 在本节中,我们将介绍最流行的:动量优化,Nesterov 加速梯度,AdaGrad,RMSProp,最后是 Adam 优化。

动量优化

想象一下,一个保龄球在一个光滑的表面上滚动:它会缓慢地开始,但是它会很快地达到最终的速度(如果有一些摩擦或空气阻力的话)。这是 Boris Polyak 在 1964 年提出的动量优化背后的一个非常简单的想法。相比之下,普通的梯度下降只需要沿着斜坡进行小的有规律的下降步骤,所以需要更多的时间才能到达底部。

梯度下降算法: θ : = θ − η ∇ θ J ( θ ) \theta:=\theta-\eta\nabla_\theta J(\theta) θ:=θηθJ(θ),它不关心早期的梯度是什么。 如果局部梯度很小,则会非常缓慢。

动量优化则关心以前的梯度:在每次迭代时,它将动量矢量m与局部梯度相加,并且通过简单地减去该动量矢量来更新权重(参见公式 11-4)。 换句话说,梯度用作加速度,不用作速度。 为了模拟某种摩擦机制,避免动量过大,该算法引入了一个新的超参数β,简称为动量,它必须设置在 0(高摩擦)和 1(无摩擦)之间。 典型的动量值是 0.9。
在这里插入图片描述
很容易地验证,如果梯度保持不变,则最终速度(即,权重更新的最大大小)等于该梯度乘以学习率η乘以1/(1-β)。 例如,如果β = 0.9,则最终速度等于学习率的梯度乘以 10 倍,因此动量优化比梯度下降快 10 倍! 这使动量优化比梯度下降快得多。

由于动量的原因,优化器可能会超调一些,然后再回来,再次超调,并在稳定在最小值之前多次振荡(有点像步子迈大了,跨过去了然后再往回走)。这就是为什么在系统中有一点摩擦的原因之一:它消除了这些振荡,从而加速了收敛。

在 TensorFlow 中实现动量优化是一件简单的事情:只需用MomentumOptimizer替换GradientDescentOptimizer,增加了一个超参数β ,然而,在实践中动量值通常取0.9 ,几乎总是比梯度下降快。

optimizer = tf.train.MomentumOptimizer
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值