[机器学习实战] 阅读第十一章

训练深度神经网络

处理复杂问题时,比如要在高分辨率的图片中检测数百种形状的对象,该怎么办?你可能需要训练一个更深层的DNN,比如说10层,每一层都含有数百个神经元,通过数十万个链接相连。

诡异的梯度消失问题

训练速度太慢

一个有数百万参数的模型会很容易出现过度拟合训练集的风险。

梯度消失 / 爆炸问题

反向传播算法是从输出层反向作用到输入层,在过程中传播误差梯度。一旦算法根据网络的参数计算出成本函数的梯度,就会根据梯度下降步骤利用这些梯度来修正每一个参数。

不幸的是梯度经常会随着算法进展到更低层时变得越来越小。导致的结果是,梯度下降在更低层网络连接权值更新方面基本没有改变,而且训练不会收敛到好的结果。这称为梯度消失问题。在一些例子中会发生相反的现象:梯度会越来越大,导致很多层的权值疯狂增大,使得算法发散。这就是梯度爆炸问题,它经常出现在循环神经网络中。神经网络受制于不稳定梯度;不同层可能会以完全不同的速度学习。

梯度消失问题的研究方面
在Glorot和Bengio的一篇论文中提到的几个假设:
流行的逻辑S激活函数和当时最流行的权重初始化技术的混合,即用均值为0、方差为1的正态分布进行随机初始化。简而言之,利用这种激活函数和初始化方式,它们发现每一层输出方差都比输入方差大很多。在这个网络里,每层都会出现方差增加,直到激活函数最高层饱和。如果逻辑函数的均值变成0.5,这个现象就会变得比较糟(均值为0的双曲正切函数比逻辑函数在深层网络中表现会稍好一些)。

观察逻辑激活函数,当输入变大(正或负),函数在0或1饱和,导数无限靠近0.也就是当反向传播起作用时,实际上并没有梯度通过网络反向作用,同时在反向传播到顶层的过程中几乎没有梯度被稀释,所以基本没有给低层留下什么。

Xavier 初始化 和 He初始化

论文中针对这个问题提出了一个很有效的缓和方法。
需要让信号在两个方向都正确流动:当预测的时候要保持正向,在反向传播梯度时保持反向。我们并不希望信号消亡,同样也不希望它们爆炸或者稀释。为了让信号正确流动,作者提出需要保持每一层的输入和输出的方差一致,并且需要在反向流动过某一层时,前后的方差也要一致。事实上,这是很难保证的,除非一层又相同数量的输入和输出连接,当然他们也提出了一种很好的折中方案:连接权重必须按照公式11-1进行随机初始化,其中ninputs和noutputs是权重被初始化层的输入和输出连接数(也被称为扇入和扇出)。这种初始化的方法称为Xavier初始化,有时也称为Glorot初始化。

公式11-1:Xavier初始化(当使用逻辑激活函数时)

在这里插入图片描述
在这里插入图片描述
利用Xavier初始化方法可以显著提高训练速度。

针对不同的激活函数,有类似的初始化方法。ReLU激活函数的初始化方法(以及它的变种,包括简称的ELU激活)有时称为He初始化
在这里插入图片描述

非饱和激活函数

一种观点认为梯度消失/爆炸问题一部分的原因是选错了激活函数。在这之前很多人有这样一种假设:如果大自然在生物神经元里都使用S激活函数,那么这个函数一定是一个绝佳的选择。但结果却表明其他的激活函数在深度神经网络中表现得更好一些,特别是在ReLU激活函数中,出现这种结果最主要的原因是它并不稀释正值(ReLU(z)=max(0,z),同时也因为计算速度快)。

然而,ReLU函数并不是完美的。它会出现dying ReLU问题:在训练过程中,一些神经元实际上已经死了,即它们只输出0.在一些案例中,你可能会发现你的网络中有一半神经元都死了,特别是当你用了一个比较大的训练速度时。在训练过程中,如果神经元的权重更新到神经元输入的总权重是负值时,这个神经元就会开始输出0。当这种情况发生时,除非ReLU函数的梯度为0并且输入为负,否则这个神经元就不会再重新开始工作。

要解决这个问题,可以使用ReLU函数的变种,比如leaky ReLU(带泄露线性整流函数)。这个函数定义为:LeakyReLUα(z)=max(αz,z)。超参数α表示函数“泄漏”程度:它是函数中z<0时的坡度,一般会设置为0.01。这个小坡度可以保证leaky ReLU不会死;它可以进入一个很长的昏迷期,但最后还是有机会醒过来。

这篇论文对比了几个ReLU激活函数的变种
这是论文的链接:
“Empirical Evaluation of Rectified Activations in Convolution Network”,B.Xu等人(2015)
链接:https://pan.baidu.com/s/1v1V8-iNvFXNL_Tlg2Jr_IQ
提取码:3exx

结论:
其中一个结论就是带泄漏变种总是优于严格的ReLU激活函数。实际上,设置α=0.2(大泄漏)得到的结果会比α=0.01(小泄漏)好。同时,论文中也评估了RReLU(带泄漏随机ReLU),即在训练过程中α是给定一个区间里的一个随机值,再测试过程中固定一个平均值。它的表现也不错,可以作为一个正则(降低了训练集过度拟合的风险)。最后,他们还评估了PReLU(参数线性整流),其中α在训练中可以进行学习(不作为超函数,而是在反向传播中的参数)。这个函数在大的图片数据集的情况下会比ReLU效果更好,但是在小的数据集时会有训练集过度拟合的风险。

一个新的激活函数,称为ELU(加速线性单元),在他们的测试中,它的表现优于ReLU的所有变种:训练时间减小,神经网络在测试集的表现也更好。

Fast and Accurate Deep Network Learning by Exponential Linear Units(ELUs)”,D.Clevert、T.Unterthiner和S.Hochreiter(2015)。
.
链接:https://pan.baidu.com/s/1-uAV1bedEvw15kRMqEpGPw
提取码:krty

示例

对书中用leaky_relu激活函数训练DNN示例代码的分析
https://mp.csdn.net/mdeditor/90675199

公式11-2:ELU激活函数

在这里插入图片描述
在这里插入图片描述

那么在你的深度神经网络的隐藏层到底应该使用哪一种激活
函数呢?尽管你的里程会不一样,通常来说ELU函数>leaky ReLU函
数(和它的变种)>ReLU函数>tanh函数>逻辑函数。如果你更关心运
行时的性能,那你可以选择leaky ReLU函数,而不是ELU函数。如果
你不想改变别的超参数,就只使用建议α的默认值(leaky ReLU函数
是0.01,ELU函数是1)。如果你有多余的时间和计算能力,你可以
使用交叉验证去评估别的激活函数,特别是如果你的网络过度拟合,
你可以使用RReLU函数,又或者是针对大的训练集使用PReLU函
数。

TensorFlow提供了一个elu()函数来构建神经网络,当调用fully_connected()函数时,可以很简单的设置activation_fn参数:

hidden1 = fully_connected(X, n_hidden1, activation_fn=tf.nn.elu)

TensorFlow没有leaky ReLU函数的预定义函数,但是也可以很简单地定义为:

def leaky_relu(z, name=None):
	return tf.maximum(0.01 * z, z, name=name)
hidden1 = fully_connected(X, n_hidden1, activation_fn=leaky_relu)

或以下形式:

def leaky_relu(z, alpha=0.01):
    return np.maximum(alpha*z, z)

批量归一化

尽管使用了He初始化加ELU(或者ReLU的任一种变种)可以很明显地在训练初期降低梯度消失/爆炸问题,但还是不能保证在训练过程中不会再出现这些问题。

Sergey Ioffe和Christian Szegedy提出了一个叫作批量归一化(BN)的技术,用它来解决梯度消失/爆炸问题,而且每一层的输入分散问题在训练过程中更普遍,前层变量的改变(称为内部协变量转变问题)也是一样。

这篇论文
链接:https://pan.baidu.com/s/1rV3afJz-k6jAEQ1XSEqMuA
提取码:vb03

该技术包括在每一层激活函数之前在模型里加一个操作,简单零中心化和归一化输入,之后再通过每层的两个新参数(一个为了缩放,另一个为了移动)缩放和移动结果。换句话说,这个操作让模型学会了最佳规模和每层输入的平均值

为了零中心化和归一化输入,算法需要评估输入的平均值和标准方差。现在对于小批量(因此得名“批量归一化”)是通过评估输入的平均值和标准方差来这么做的。

公式11-3:批量归一化算法

在这里插入图片描述

在测试期间,没有小批量数据来计算经验平均值和标准方差,所以你可简单地用整个训练集的平均值和标准方差来代替。在训练过程中可以用变动平均值有效地计算出来。所以,整体来看,4个参数是为每一批量归一化层来学习的:γ(缩放),β(偏移),μ(平均值)和σ(标准方差)。

梯度消失问题被有效改善,主要原因是他们使用了饱和激活函数(比如tanh)和逻辑激活函数。网络对于权重初始化也没有那么敏感。它们可以使用更高的学习速率,从而有效地加快整个学习过程。批量归一化同时还可以进行正则化,降低其他正则化技术的需求(比如退出)。

但是,批量归一化的确也给模型增加了一些复杂度(尽管因为有第一隐藏层而不用考虑归一化输入数据的需求,但是提供这个需求的却是批量归一化)。另外,还存在一个运行时的代价:神经网络的预测速度变慢,原因是每一层有很多其他需要进行的计算。所以如果你需要快速预测,你可能需要在进行批量归一化之前先检查一下ELU+He初始化的表现如何。

用TensorFlow来实现批量归一化

关于偏函数

from functools import partial #偏函数,辅助函数

Python3学习(18)–偏函数(Partial)
https://blog.csdn.net/appleyk/article/details/77609114

这一部分见解有些不足
https://blog.csdn.net/az9996/article/details/90709999

梯度剪裁

一个流行的减轻梯度爆炸问题的技术是在反向传播过程中简单的剪裁梯度,从而保证不会超过阈值(这个对于循环神经网络非常有效)。这种技术叫做梯度剪裁。一般情况下倾向于批量归一化,但是有必要了解梯度剪裁以及如何使用。

在tensorflow里,优化器的minimize()函数同时负责计算和应用梯度,所以你必须先调用优化器的compute_gradients()方法,然后调用clip_by_value()方法创建一个剪裁梯度的操作,最后调用apply_gradients()方法来应用剪裁后的梯度:

重用预训练图层

从头开始训练一个非常庞大的DNN并不明智。大多时候应该试着去找一个能处理相似问题的已有的神经网络,然后重用它的低层网络,这叫做迁移学习。这不仅能极大地提升训练速度,也很大程度地减少训练数据。

重用 TensorFlow 模型

使用tensorflow训练地模型,可以轻松地还原该模型并在新任务中接着训练它。

如果,只想重用原有模型地一部分,一种简单地解决方案就是配置Saver使之在还原原模型时只还原所有参数的一个子集。

重用其他框架的模型

如果已有模型是用其他框架训练出来的,我们就需要手动加载所有的权重,然后将它们赋给适当的参数。这个过程会变得相当冗长乏味。

冻结底层

第一个DNN的低层可能已经学会了检测图像中的低级特征,这对于两个图像分类任务都是有用的,因此你可以直接重用这些图层。训练新的DNN时,通常来讲“冻结”权重是一个比较好的方法:如果低层权重被固定,那么高层的权重就比较容易训练(因为不需要学习一个运动的目标)。为了在训练中冻结低层,最简单的办法就是给优化器列出需要训练的变量列表,除去低层的变量。

缓存冻结层

因为冻结层不会变化,所以就有可能将每一个训练实例的最高冻结层的输出缓存起来。由于训练会轮询整个训练集很多次,所以你将获得巨大的速度提升,因为你只需要在一个训练实例中遍历一次冻结层(而不是每个全数据集一次)。

调整、丢弃或替换高层

原始模型的输出层经常会被替换,因为对于新的任务基本没有用,甚至没办法提供正确的输出个数。

同样,原始模型的高隐藏层没有低层用处多,因为对于新任务最有效的高级特性可能和原始任务中最有效的那些差别很大。你需要找到正确的层数来重用。

首先尝试冻结所有的复制层,然后训练你的模型观察效果。接着尝试解冻一到两个顶部的隐藏层,用反向传播进行调整来观察是否有改善。训练数据越多,越能解冻更多的层。

如果你一直不能获得好的效果,而且训练数据很少,那么尝试丢弃最高的一层或多层,然后重新冻结剩下的隐藏层。你可以一直迭代直到找到正确的重用层数。如果你有很多训练数据,你可以尝试替换顶部的隐藏层而不是丢弃它们,甚至可以添加一些隐藏层。

模型动物园

使用已经训练好的模型

模型动物园:
TensorFlow中的模型集合:https://github.com/tensorflow/models
Caffe中的模型集合:https://goo.gl/XI02X3

其中也包括很多计算机视觉模型(比如: LeNet、AlexNet、ZFNet、GoogLeNet、VGGNet和inception),也都 在各种数据集(比如ImageNet、Places Database、CIFAR10等)上经 过训练。

无监督的预训练

面对一个没有太多标记训练数据的复杂任务,并且没有找到在近似任务上训练过的模型。

该怎么办!

先尝试努力去收集更多的标记过的训练数据,但是如果这个代价很高或很难,那么还有最后一个方法,运行无监督的预训练。

面对一堆未标记的训练数据,那你可以逐层训练它们,总最底层开始,然后向上,利用一种非监督特性检测算法比如受限玻尔兹曼机(RBM)或者自动编码器。每一层都是基于提前训练好的图层(除去被冻结的训练层)的输出进行训练。一旦所有层都用这个方式训练过之后,就可以用监督学习的方式(即反向传播)来微调网络。

这是一个冗长且无趣的过程,但是通常情况下效果都不错。
直到2010年,无监督的预训练 (通常使用RBM)都是深度网络的基准,只有在梯度消失问题得到 缓解之后,用反向传播来训练DNN才变得越来越普遍。然而,在面 对复杂任务处理、没有相似模型可重用、有极少标记过的训练数据但 是却有很多未标记的训练数据的情况下,无监督的预处理(现在通常 使用自动编码器而不是RBM)仍然是一个非常不错的选择。

辅助任务中的预训练

最后一个选择是在辅助任务中训练第一个神经网络,你可以轻松获得或者生成标记过的训练数据,然后重用该网络的低层来实现你的实际任务。第一给个神经网络的低层会学习可能会被第二个神经网络重用的特征检测器。

另一种方法是训练第一个神经网络,让它输出每一个训练的得分,然后利用成本函数,确保每一个好的实例的得分都比坏的得分至少高一些。我们称其为最大边界学习。

快速优化器

前面已经学习了四种方法来提高训练速度(并且实现一个更好的解决方案):在连接权重上应用一个良好的初始化策略,使用一个良好的激活函数,使用批量归一化,以及重用部分预处理网络。另一种明显提高训练速度的方法是使用快速优化器,而不是常规的梯度下降优化器。

当前,最流行的几种优化器:Momentum(动量优化), NAG(Nesterov梯度加速),AdaGrad,RMSProp,以及Adam优化。

记得在使用TensorFlow时查看该版本最新的优化器有哪些!最好的优化器是哪个!

Momentum优化

Momentum,动量

想象一个保龄球在光滑表面滚下一个平缓的斜坡:最开始会很 慢,但是会迅速恢复动力,直到达到最终速度(假设有一定摩擦力或 空气阻力)。这是Momentum优化的一个很简单的想法,由Boris Polyak在1964年提出。相比之下,常规梯度 下降会沿着斜坡采用常规的小步前进的方式,所以会花比较长的时间 才能到达底部。

之前学习的梯度下降是直接从权重 θ \theta θ中减去成本函数 J ( θ ) J(\theta) J(θ)的梯度 ( ∇ θ J ( θ ) ) (\nabla_\theta J(\theta)) (θJ(θ))乘以学习速率 η \eta η。公式是: θ ← θ − η ∇ θ J ( θ ) \theta\leftarrow\theta-\eta\nabla_\theta J(\theta) θθηθJ(θ),它不关心之前的梯度是多少。如果本地梯度很少,它就会走得很慢。

Momentum优化关注以前的梯度是多少:每一个迭代,会给 momentum矢量 m ⃗ \vec{m} m 加本地梯度(乘以学习速率 η \eta η),同时权重要减去 momentum矢量(见公式11-4)。换句话说,梯度被当作加速度来使 用,而不是速度。为了模拟某种摩擦机制并防止动量(momentum) 增长过大,该算法引入了一个新的超参数 β \beta β,简称为动量,其必须设 置在0(高摩擦)和1(无摩擦)之间。一个标准动量值为0.9

公式11-4:Momentum算法

(1) m ⃗ ← m ⃗ + η ∇ θ J ( θ ) \vec{m}\leftarrow\vec{m}+\eta\nabla_\theta J(\theta) \tag{1} m m +ηθJ(θ)(1)
(2) θ ← θ − m ⃗ \theta\leftarrow\theta-\vec{m}\tag{2} θθm (2)

当梯度保持一个常量,最终速度(即权重变化的最大值)就等于梯度乘以学习速率 η \eta η乘以 1 1 − β \frac{1}{1-\beta} 1β1

由于有动量,优化器可能会超调一点,然后返回,再超调, 来回振荡多次后,最后稳定在最小值。这也是系统中要有一些摩擦的 原因之一:它可以帮助摆脱振荡,从而加速收敛。

在TensorFlow中使用Momentum优化

optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate, momentum=0.9)

Momentum优化的一个缺点是增加了一个超参数来微调。一般情况下,动量值为0.9的表现都比梯度下降好

Nesterov梯度加速

Nesterov Momentum优化,或者Nesterov梯度加速(NAG),使用来衡量成本函数的梯度的,不是在本地而是动量方向稍向前一点的位置 。与Vanilla Momentum优化唯一的不同就是用 θ + β m \theta+\beta m θ+βm来 测量梯度,而不是 θ \theta θ

Nesterov梯度加速算法

(1) m ← β m + η ∇ θ J ( θ + β m ) m\leftarrow\beta m +\eta\nabla_\theta J(\theta+\beta m)\tag{1} mβm+ηθJ(θ+βm)(1)
(2) θ ← θ − m \theta\leftarrow\theta-m\tag{2} θθm(2)

这个小调整有效是因为在通常情况下,动量矢量会指向正确的方 向(即最优方向),所以在该方向相对远的地方使用梯度会比在原有 地方更准确一些,正如图11-6所示(其中 ∇ 1 \nabla_1 1表示在起始点θ用成本函 数测量的梯度, ∇ 2 \nabla_2 2表示在点θ+βm的梯度)。正如你所见,Nesterov 更接近最优值。过一阵之后,这些小的改进叠加在一起,于是NAG 就比常规Momentum优化明显增速很多。再者,注意到当动量把权重推过山谷时, ∇ 1 \nabla_1 1 会跨过山谷并且推向更远,然而 ∇ 2 \nabla_2 2却往谷底的方向 退回了一些。这有助于降低振荡,从而更快收敛

对比Momentum优化,NAG几乎总能提高训练速度。要使用 NGA,在创建Momentum Optimizer时只需简单地设置 use_nesterov=True即可:

optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate, momentum=0.9, use_nesterov=True)

AdaGrad

AdaGrad算法通过沿着最陡尺寸缩小梯 度向量来实现这一点(见公式11-6)。

公式11-6:AdaGrad算法

(1) s ⃗ ← s ⃗ + ∇ θ J ( θ ) ⊗ ∇ θ J ( θ ) \vec{s}\leftarrow\vec{s}+\nabla_\theta J(\theta)\otimes\nabla_\theta J(\theta)\tag{1} s s +θJ(θ)θJ(θ)(1)
(2) θ ← θ − η ∇ θ J ( θ ) ⊘ s ⃗ + ϵ \theta\leftarrow\theta-\eta\nabla_\theta J(\theta)\oslash\sqrt[]{\vec{s}+\epsilon}\tag{2} θθηθJ(θ)s +ϵ (2)

第一步将梯度的平方累积到向量 s ⃗ \vec{s} s 中( 表示矩阵乘法)。这个 向量化表等于给向量 s ⃗ \vec{s} s 中每一个元素 s i s_i si进行 s i ← s i + ( ∂ ∂ θ i J ( θ ) ) 2 s_i\gets s_i+(\frac{\partial}{\partial\theta_iJ(\theta)})^2 sisi+(θiJ(θ))2运算;换句话说,对于参数 θ i \theta_i θi,每个 s i s_i si累积成本函数偏导数的平方。如 果成本函数沿着第i个尺寸陡峭,那么si会在每一个迭代中越来越大。

第二步与梯度下降基本一致,但是有一个最大的区别:梯度向量 按比例 s ⃗ + ϵ \sqrt[]{\vec{s}+\epsilon} s +ϵ 缩小 ⊘ \oslash ( 表示矩阵除法, ε \varepsilon ε是避免除以0的平滑项,通 常设置为 1 0 − 10 10^{-10} 1010)。这个向量化表等于对于所有参数 θ i \theta_i θi进行 θ ← θ − η ∇ θ J ( θ ) ⊘ s ⃗ + ϵ \theta\leftarrow\theta-\eta\nabla_\theta J(\theta)\oslash\sqrt[]{\vec{s}+\epsilon} θθηθJ(θ)s +ϵ 运算(同步)。
简而言之,这个算法衰减了学习速率,但是对于陡度尺寸而言, 它比使用较缓斜率的尺寸要快得多。这称为适应性学习速率。它有助 于将所得到的更新更直接地指向全局最优。另一个附加 的好处是只需对学习速率超参数η做很少的调整。

简而言之,这个算法衰减了学习速率,但是对于陡度尺寸而言, 它比使用较缓斜率的尺寸要快得多。这称为适应性学习速率。它有助 于将所得到的更新更直接地指向全局最优。另一个附加 的好处是只需对学习速率超参数 η \eta η做很少的调整。

AdaGrad对于简单的二次问题一般表现都不错,但是在训练神经 网络时却经常很早就停滞了。学习速率缩小得很多,在到达全局最优 前算法就停止了。所以尽管TensorFlow有AdagradOptimizer,你也不 要用它来训练深度神经网络(但是,对于类似线性回归这样的简单任 务可能是有效的)。

RMSProp

AdaGrad降速太快而且没办法收敛到全局最优,RMSProp算法却通过仅累积最近迭代中的梯度(而非从训练开始的所有梯度)解决 了这个问题。它通过在第一步使用指数衰减来实现这个操作(见公式 11-7)。

公式11-7:RMSProp算法

(1) s ⃗ ← β s ⃗ + ( 1 − β ) ∇ θ J ( θ ) ⊗ ∇ θ J ( θ ) \vec{s}\gets\beta\vec{s}+(1-\beta)\nabla_\theta J(\theta)\otimes\nabla_\theta J(\theta)\tag{1} s βs +(1β)θJ(θ)θJ(θ)(1)
(2) θ ← θ − η ∇ θ J ( θ ) ⊘ s ⃗ + ϵ \theta\gets\theta-\eta\nabla_\theta J(\theta)\oslash\sqrt[]{\vec{s}+\epsilon}\tag{2} θθηθJ(θ)s +ϵ (2)

衰减率β通常设置为0.9。没错,这又是一个新的超参数,但是这个默认值一般表现都比较好,所以你基本不需要做任何微调。

正如你所期待的,TensorFlow有RMSPropOptimizer类:

optimizer = tf.train.RMSPropOptimizer(learning_rate=learning_rate,momentum=0.9, decay=0.9, epsilon=1e-10)

除去非常简单的问题,这个优化器的表现几乎全都优于 AdaGrad。同时表现也基本都优于Momentum优化和NAG。事实上, 在Adam优化出现之前,它是众多研究者所推荐的优化算法。

Adam优化

Adam,代表了自适应力矩估计,集 合了Momentum优化和RMSProp的想法:类似Momentum优化,它会 跟踪过去梯度的指数衰减平均值,同时也类似RMSProp,它会跟踪过 去梯度平方的指数衰减平均值(见公式11-8)。

公式11-8:Adam算法

(1) m ⃗ ← β 1 m ⃗ + ( 1 − β 1 ) ∇ θ J ( θ ) \vec{m}\gets\beta_1\vec{m}+(1-\beta_1)\nabla_\theta J(\theta)\tag{1} m β1m +(1β1)θJ(θ)(1)
(2) s ⃗ ← β 2 s ⃗ + ( 1 − β 2 ) ∇ θ J ( θ ) ⊗ ∇ θ J ( θ ) \vec{s}\gets\beta_2\vec{s}+(1-\beta_2)\nabla_\theta J(\theta)\otimes\nabla_\theta J(\theta)\tag{2} s β2s +(1β2)θJ(θ)θJ(θ)(2)
(3) m ⃗ ← m ⃗ 1 − β 1 T \vec{m}\gets\frac{\vec{m}}{1-\beta_1^T}\tag{3} m 1β1Tm (3)
(4) s ⃗ ← s ⃗ 1 − β 2 T \vec{s}\gets\frac{\vec{s}}{1-\beta_2^T}\tag{4} s 1β2Ts (4)
(5) θ ← θ − η m ⃗ ⊘ s ⃗ + ϵ \theta\gets\theta-\eta\vec{m}\oslash\sqrt[]{\vec{s}+\epsilon}\tag{5} θθηm s +ϵ (5)

T T T表示迭代数(从1开始)
如果只看步骤1、步骤2和步骤5,你会发现Adam同Momentum优 化以及RMSProp非常类似。唯一的不同是步骤1计算的是指数衰减的 平均值而不是指数衰减的总和,但是除了常数因子以外,它们都是相 等的(衰减平均值是 1 − β 1 1-\beta_1 1β1倍的衰减总和)。步骤3和步骤4是一种技术 的细节:因为 m ⃗ \vec{m} m s ⃗ \vec{s} s 初始化都为0,它们会在训练开始时相对0有一点 偏移量,所以这两个步骤在训练开始时有助于提高 m ⃗ \vec{m} m s ⃗ \vec{s} s 。 动量衰减超参数 β 1 \beta_1 β1通常被初始化为0.9,缩放衰减超参数通常被初 始化为0.999。与之前一样,平滑项 ϵ \epsilon ϵ通常会设置为一个很小的数字, 比如 1 0 − 8 10_{-8} 108。这些是TensorFlow的AdamOptimizer类的默认值,你可以使 用:

optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)

实际上,因为Adam是一个自适应学习速率算法(与AdaGrad以 及RMSProp类似),它需要对学习速率超参数η进行微调。你可以一 直使用默认值 η = 0.001 \eta=0.001 η=0.001,这样使得Adam比梯度下降更容易使用。
目前讨论过的所有优化技术都依赖于一阶偏导数 (Jacobians)。优化文献包含基于二阶偏导数(Hessians)的算法。
不幸的是,这些算法都非常难应用到深度神经网络,原因是每层输出 都有 n 2 H e s s i a n s n^2Hessians n2Hessians(其中n是参数个数),而不是每次只有n Jacobians。 因为DNN通常有数万个参数,所以二阶优化算法一般不会在内存里 适用,而且即使运行,计算Hessians也会非常慢。

这些优化算法都是制作密集模型,意味着大部分参数都是非零的。使用稀疏模型可以在运行时得到一个超速模型,占用更少的内存。

在训练模型的过程中摆脱掉小的权重(将其设置为0)

另一种方法是在训练中应用强 ℓ 1 \ell_1 1正则化,因为它可以促使优化器尽可能地将权重归零。

然而,在某些例子中这些技术可能会无效。最后一个方法是应用对偶平均,通常称为FTRL(Fllow The Regularized Leader)。当使用 ℓ 1 \ell_1 1正则化时,这种技术会导出一个非常稀疏的模型。TensorFlow在FTRLOptimizer类中实现了一个名为FTRL-Proximal的FTRL变体。

学习速率调度

学习速率设置的太高,训练可能会产生分歧;
太低,训练可能需要花很长时间才能收敛到最优解。
设置的相对高,可能最开始运行的很快,但是最后一直在最优解附近摆动不停下来(除非你使用适应性学习速率优化算法,比如AdaGrad、RMSProp或者Adam,但即使这样也需要时间来稳定)。

在资源有限的情况下,可能必须在训练正确收敛之前停下来,从而生成次优解。

通过使用各种固定的学习速率,对比学习曲线在几个数据集内多次训练网络,从而得到一个非常良好的学习速率。理想的学习速率学习速度非常快,并且会收敛到良好的解决方案。

学习计划(监督学习速率的策略)

然而相比一个固定的学习速率,一种更好的方式:以一个高学习速率开始,然后一旦它停止快速过程就降低速率,那么你会获得一个比最优固定学习速率更快速的方案。有很多不同的策略在训练过程中监督学习速率。这些策略称为学习计划。

预定分段常数学习速率
例子,最开始设置学习速率 η 0 = 0.1 \eta_0=0.1 η0=0.1,在50个数据集之后设置 η 0 = 0.01 \eta_0=0.01 η0=0.01。尽管这个方法效果不错,但是需要搞清楚正确的学习速率和何时使用。

性能调度
将N个步骤进行测量来验证错误(比如早起停止),然后当错误停止出现时,将学习速率降低到 1 λ \frac{1}{\lambda} λ1

指数调度
将学习速率设置为迭代数t的函数: η ( t ) = η 0 1 0 − t r \eta(t)=\eta_010^{\frac{-t}{r}} η(t)=η010rt。这个效果很好,但是需要微调 η 0 \eta_0 η0 r r r。学习速率每r步下降10。

功率调度
设置学习速率为 η ( t ) = η 0 ( 1 + t r ) − c \eta(t)=\eta_0(1+\frac{t}{r})^{-c} η(t)=η0(1+rt)c。超参数c通常设置为1.这个与指数调度类似,但是学习速率降低的非常慢。

通过正则化避免过度拟合

拥有数亿的参数你可以适应整个动物园。极大的灵活性也意味着它容易过度拟合训练集。

提前停止

当验证集的性能开始下降时停止训练。

用TensorFlow实现提前停止的一种方法是定期对验证集进行模型评估(比如:每50步),同时如果表现好于前一个“优胜者”快照就将此“优胜者”快照保存起来。在保存最后一张“优胜者”快照的时候计算步数,当步数达到某些限制(比如:2000步)时停止训练。然后恢复最后一张“优胜者”快照。

提前停止表现很好,但通常还可以通过结合其它正则化技术来获得更高的性能。

ℓ 1 和 ℓ 2 \ell_1和\ell_2 12正则化

使用 ℓ 1 和 ℓ 2 \ell_1和\ell_2 12正则化来约束一个神经网络的连接权重(但通常不是其偏差)。

用TensorFlow实现的一个方式是简单的将适当的正则项加在你的成本函数中。

dropout

(中途退出),由一个概率来决定
最受欢迎的深度神经网络正则化技术

拥有很高的成功率:即使最先进的神经网络在加了dropout之后也能提高1%~2%的正确性。

这是一个很简单的算法:每一个训练步骤,每一个神经元(包括 输入神经元,不包括输出神经元)都有一个会被暂时“丢弃”的可能性 p,意思是在这次训练步骤中它会被完全忽略,但是到下一步的时候 就会被激活。超参数p被称为丢弃率,通常设置为 50%。在训练之后,神经元不会再被丢弃。原理就是这样。

最开始这种相当暴力的方法可以起作用也是非常令人震惊的。如 果员工每天早上通过扔硬币来决定去不去上班,这样的公司会越来越 好吗?好吧,谁知道呢,说不定会呢!公司很明显是要去适应它的组 织,而不是依赖某一个人去填充咖啡机或者去做其他关键的任务,所 以这些专业知识必须分散到好几个人身上。员工需要学习和许多同事 一些协作,而不仅是其中几个人。公司的适应性会越来越好。如果有 一个人离开,也不会有太大的影响。现在还不清楚这个想法对于公司 是不是真正奏效,但是对于神经网络的确是有用的。训练有dropout 的神经元不能和周围的神经元共适应;它们必须让自己尽可能有用。 它们也不能过分依赖几个输入神经元;它们必须关注每一个输入神经 元。它们最后会变得对于输入的轻微变化不那么敏感。最终,你会获 得一个更好地泛化了的健壮的网络。

再者,理解dropout的强大是因为注意到在每一个训练步骤都会 创建一个独立的神经网络。因为每一个神经元都可能出现或不出现,所以就会有总共 2 N 2^N 2N个可能的网络(其中N是可丢弃神经元的个数)。 这是一个很大的数字,几乎不可能对一个神经网络采样两次。一旦你 已经运行了10000个训练步骤,那你实际上就已经训练了10000个不同 的神经网络(每一个网络都有一个训练实例)。这些神经网络很明显 不是独立的,因为它们共享很多它们的权重,但是它们还是不同的。 所得到的神经网络可以看作是这些较小的神经网络的平均集合

一个解空间内被拆分出了数个不完全相同的子集

这里提到一个很小但是很重要的技术细节。假设p=50,在测试期 间,一个神经元将会被连接到训练期间输入神经元(平均)的两倍。 为了弥补这个情况,我们需要在训练之后给每一个神经元的输入连接 权重乘以0.5。如果不这样做,每一个神经元就会得到一个总输入信 号,大概是之前训练网络的两倍,而且性能不会表现得特别好。更通 俗来讲,我们需要在训练结束后给每一个输入连接权重乘以保持可能 性(1-p)。或者,我们可以在训练过程中给每一个神经元的输出除 以保持可能性(这些可选方案不完全一致,但是它们的效果都一样 好)。

在TensorFlow中实现dropout,可以直接在输入层和每一个隐藏层的输出调用dropout()函数。在训练中,这个函数会随机丢弃一些项(把它们设置为0),并且给剩下的项除以保持可能性。在训练结束后,这个函数什么也不做。

from tensorflow.contrib.layers import dropout
[...] is_training = tf.placeholder(tf.bool, shape=(), name='is_training')
keep_prob = 0.5 X_drop = dropout(X, keep_prob, is_training=is_training)
hidden1 = fully_connected(X_drop, n_hidden1, scope="hidden1") hidden1_drop = dropout(hidden1, keep_prob, is_training=is_training)
hidden2 = fully_connected(hidden1_drop, n_hidden2, scope="hidden2") hidden2_drop = dropout(hidden2, keep_prob, is_training=is_training)
logits = fully_connected(hidden2_drop, n_outputs, activation_fn=None, scope="outputs")

如果发现模型过度拟合,你可以提高dropout速率(即降低 keep_prob超参数)。相反,如果模型不拟合训练集,你需要降低 dropout速率(即提高keep_prob超参数)。同样针对大层可以帮助提 高dropout速率,针对小层可以降低。
dropout确实收敛变慢,但是如果微调合适,它通常都会得到一 个更好的模型。所以这个结果是值得付出多一些时间和代价的。

最大范数正则化

对每一个神经元,包含一个传入连接权重 w ⃗ \vec{w} w 满足 ∥ w ⃗ ∥ 2 ⩽ r \|\vec{w}\|_2\leqslant r w 2r,其中r是最大范数超参数, ∥ ⋅ ∥ 2 \|\cdot\|_2 2 ℓ 2 \ell_2 2范数。

通常这样来满足这个约束,在每一次训练步骤后计算 ∥ w ⃗ ∥ 2 \|\vec{w}\|_2 w 2,同时如果需要会剪裁( m ⃗ ← w ⃗ r ∥ w ⃗ ∥ 2 \vec{m}\gets\vec{w}\frac{r}{\|\vec{w}\|_2} m w w 2r)。

降低r会增加正则化数目,同时帮助减少过度拟合。最大范数正则化可以同时帮助缓解梯度消失/爆炸问题(如果不使用批量归一化)。

TensorFlow没有提供现成的最大范数正则化器,但是实现起来也不难。下面的代码构建了一个节点clip_weights,该节点会沿着第二个轴消减weights变量,从而使每一个行向量的最大范数为1.0:

def max_norm_regularizer(threshold, axes=1, name="max_norm",   collection="max_norm"): 
	def max_norm(weights):
		clipped = tf.clip_by_norm(weights, clip_norm=threshold, axes=axes) 
		clip_weights = tf.assign(weights, clipped, name=name) 
		tf.add_to_collection(collection, clip_weights) 
		return None  # there is no regularization loss term 
	return max_norm

数据扩展

包括从已有实例构建新的训练实例,认为提高训练集大小。因为会减少过度拟合,所以它是一种正则化技术。棘手的是构建一个切实可用的训练实例。

一般偏向在训练过程中快速生成训练实例,而不是浪费存储空间 和网络带宽。TensorFlow提供了多种图片处理操作,比如转置(偏 移)、旋转、调整大小、翻转和裁剪,同时还有调整亮度、对比度、 饱和度和色调(详见API文档)。这样就可以轻松地实现图像数据集 的数据扩充。

实用指南

默认DNN配置
Initialization 初始化He initialization
Activation function 激活函数ELU
Normalization 归一化Batch Normalization
Regularization 正则化dropout
Optimizer 优化器Adam
Learning rate schedule 学习计划None
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值