目录
9.4.4 每个参数自适应学习率(Adagrad, RMSProp)
在前面的章节中,我们讨论了神经网络的静态部分:如何建立网络连接、数据和损失函数。本节致力于执行过程,即学习参数和发现良好的超参数的过程。
9.1 梯度检验
理论上,执行梯度检验与比较解析梯度与数值梯度一样简单。然而从实际操作层面上来说,这个过程更加复杂且容易出错。以下是一些值得注意的提示、技巧和问题:
使用中心公式。在评估数值梯度时,有限差分近似的公式如下:
其中h是一个非常小的数字,在实践中大约1E-5左右。在实践中,事实证明,使用中心差分形式的公式更好:
这需要你对损失函数进行两次评估以检查梯度的每一个维度(所以计算资源的耗费也是两倍),但是梯度的近似值会更准确。为了看到这一点,可以使用f(x + h)和f(x - h)的泰勒展开,可以看到第一个公式的误差近似O(h),而第二个公式的误差近似O(h*h)(是二阶近似)。(译者注:泰勒展开相关内容可阅读《高等数学》第十二章第四节:函数展开成幂级数。)
使用相对误差进行比较。比较数值梯度f’n和解析梯度f’a的细节是什么?也就是说,我们如何知道两者是否不匹配?你可能倾向于监测它们的差∣f′a−f′n∣或它的平方,如果梯度大于阈值,则定义梯度检查失败。然而,这是有问题的。例如,考虑它们的差异是1E-4的情况。如果这两个梯度都是大约为1,这似乎是一个非常合适的差异,所以我们会认为这两个梯度匹配。但是如果梯度都在1E-5或更低的量级,那么我们会认为1E-4是一个巨大的差异,梯度实现肯定就是失败的了。因此,考虑相对误差总是更合适的:
上式考虑了差值占两个梯度绝对值的比例。注意通常相对误差公式只包含两个式子中的一个(任意一个均可),但是我更倾向取两个式子的最大值或者取两个式子的和。这样做是为了防止在其中一个式子为0时,公式分母为0(这种情况,在ReLU中是经常发生的)。然而,还必须注意两个式子都为零且通过梯度检查的情况。在实践中:
- 相对误差>1e-2通常意味着梯度可能是错误的
- 1e-2>相对误差> 1e-4应使您感到不舒服
- 1e-4>相对误差:这个值的相对误差对于有不可导点的目标函数是OK的。但如果目标函数中没有kink(使用tanh和softmax),那么相对误差值还是太高
- 1e-7或者更小:好结果,可以高兴一把了。
还要记住,网络越深,相对误差就越大。因此,如果你对10层网络的输入数据进行梯度检查,则1e-2的相对误差可能是正常的,因为错误会在路上累加。相反,对于单个可微函数的1e-2的误差可能指示不正确的梯度。
使用双精度。一个常见的陷阱是使用单精度浮点来计算梯度检查。通常情况下,即使使用正确的梯度实现,也可能会获得较高的相对误差(高达1e-2)。在我的经验中,转换为双精度后,有时我看到我的相对误差从1e-2降到1e-8。
保持浮点的有效范围。建议阅读“每一个计算机科学家应该知道的浮点运算”,该文将阐明你可能犯的错误,促使你写下更加细心的代码。例如,在神经网络中,在一个批量上归一化损失函数是常见的。但是,如果每个数据点的梯度非常小,然后又用数据点的数量去除,就使得数值更小,这将导致更多的数值问题。这就是为什么我喜欢总是打印原始的数值/分析梯度,并确保你比较的数字不是非常小(例如,大约1e-10和更小的绝对值令人担忧)。如果确实过小,可以使用一个常数暂时将损失函数的数值范围扩展到一个更“好”的范围,在这个范围中浮点数变得更加致密。比较理想的是1.0的数量级上,浮点数指数为0。
目标函数的不可导点(kinks,扭结)。在梯度检查中要注意的一个不准确的原因是扭结问题。扭结是指目标函数的不可微部分,由诸如Relu(max(0,x))之类的函数引入,或SVM损失、Maxout 神经元等。考虑在x=-1e6上对ReLU函数进行梯度检查。由于x<0,此时的解析梯度正好为零。然而,数值梯度将突然计算非零梯度,因为f(x+h)可能跨越扭结(例如,如果h>1e - 6),并且引入非零贡献。你可能认为这是一个极端的案例,但事实上这种情况很常见。例如,一个用CIFAR-10训练的SVM中,因为有50,000个样本,根据目标函数每个样本产生9个式子,所以包含有450,000个max(0,x)式子。此外,具有SVM分类器的神经网络由于Relu将包含更多的扭结。
注意,在计算损失的过程中是可以知道不可导点有没有被越过的。在具有 max(x,y) 形式的函数中持续跟踪所有“赢家”的身份,就可以实现这一点。其实就是看在前向传播时,到底x和y谁更大。如果在计算 f(x+h) 和 f(x−h) 的时候,至少有一个“赢家”的身份变了,那就说明不可导点被越过了,数值梯度会不准确。
只使用很少的数据点。对扭结的上述问题的一个解决方案是使用较少的数据点,因为含有不可导点的损失函数(例如:因为使用了ReLU或者边缘损失等函数)的数据点越少,不可导点就越少,所以在计算有限差值近似时越过不可导点的几率就越小。还有,如果你的梯度检查对2-3个数据点都有效,那么基本上对整个批量数据进行梯度检查也是没问题的。所以使用很少量的数据点,能让梯度检查更迅速高效。
小心步长h。h这儿值也不是越小越好,因为当h太小时,你可能开始陷入数值精确问题。当梯度检测不到是,有可能改变h为1e-4或1e-6,可能突然梯度就对了。这篇维基百科文章包含一张图表,它绘制了x轴上h的值和y轴上的数值梯度误差。
梯度检查的特定点操作模式。有一点必须要认识到:梯度检查是在参数空间中的一个特定(往往还是随机的)的单独点进行的。即使是在该点上梯度检查成功了,也不能马上确保全局上梯度的实现都是正确的。还有,一个随机的初始化可能不是参数空间最优代表性的点,这可能导致进入某种病态的情况,即梯度看起来是正确实现了,实际上并没有。例如,SVM使用小数值权重初始化,就会把一些接近于0的得分分配给所有的数据点,而梯度将会在所有的数据点上展现出某种模式。一个不正确实现的梯度也许依然能够产生出这种模式,但是不能泛化到更具代表性的操作模式,比如在一些的得分比另一些得分更大的情况下就不行。因此为了安全起见,最好让网络学习(“预热”)一小段时间,等到损失函数开始下降的之后再进行梯度检查。在第一次迭代就进行梯度检查的危险就在于,此时可能正处在不正常的边界情况,从而掩盖了梯度没有正确实现的事实。
不要让正则化吞没数据。通常损失函数是数据损失和正则化损失的和(例如L2对权重的惩罚)。需要注意的危险是正则化损失可能吞没掉数据损失,在这种情况下梯度主要来源于正则化部分(正则化部分的梯度表达式通常简单很多)。这样就会掩盖掉数据损失梯度的不正确实现。因此,推荐先关掉正则化对数据损失做单独检查,然后对正则化做单独检查。对于正则化的单独检查可以是修改代码,去掉其中数据损失的部分,也可以提高正则化强度,确认其效果在梯度检查中是无法忽略的,这样不正确的实现就会被观察到了。
记得关闭随机失活(dropout)和数据扩张(augmentation)。当执行梯度检查时,记得关闭网络中的任何非确定性效应,例如dropout、随机数据扩充等。否则,它们会在计算数值梯度的时候导致巨大误差。关闭这些操作不好的一点是无法对它们进行梯度检查(例如,可能是dropout没有正确地反向传播)。因此,一个更好的解决方案可能是在评估 f(x+h) 和 f(x−h) 之前以及评估分析梯度时强制增加一个特定的随机种子。
只检查少量的维度。在实际中,梯度可以有上百万的参数,在这种情况下只能检查其中一些维度然后假设其他维度是正确的。注意:确认在所有不同的参数中都抽取一部分来梯度检查。在某些应用中,为了方便,人们将所有的参数放到一个巨大的参数向量中。在这种情况下,例如偏置就可能只占用整个向量中的很小一部分,所以不要随机地从向量中取维度,一定要把这种情况考虑到,确保所有参数都收到了正确的梯度。
9.2 学习前:合理性检查的提示/技巧
在你投入昂贵的优化之前,你可以考虑一些明智的检查:
- 在偶然的性能中寻找正确的损失。确保当你用小的参数初始化时,你会得到预期的损失。最好首先单独检查数据损失(因此将正则化强度设置为零)。例如,对于CIFAR-10数据集,使用Softmax 分类器,我们预期初始损失为2.302,因为我们期望每个类的扩散概率为0.1(因为有10个类),而Softmax损失是正确分类的负对数概率:-ln(0.1)=2.302。对于Weston Watkins SVM,假设所有的边界都被越过(因为所有的分值都近似为零),所以损失值是9(因为对于每个错误分类,边界值是1)。如果没看到这些损失值,那么初始化中就可能有问题。
- 作为第二理智检验,增加正则化强度会导致增加损失
- 过拟合一小部分数据。最后,最重要的是,在对完整数据集进行训练之前,尝试对数据的一小部分(例如20个示例)进行训练,并确保可以实现零损失。对于这个实验,最好将正则化设置为零,否则这将阻止您获得零成本。除非你通过一个小的数据集来进行这个明智的检查,否则不值得继续进行完整的数据集。请注意,可能发生的情况是,您可以覆盖非常小的数据集,但仍有不正确的实现。例如,如果您的数据点的特性是由于一些bug而随机的,那么它可能会使您的小训练集过拟合,但是当您折叠完整的数据集时,您将永远不会注意到任何泛化。
9.3 照看学习过程
在训练神经网络的时候,应该跟踪多个重要数值。这些数值输出的图表是观察训练进程的一扇窗口,是直观理解不同的超参数设置效果的工具,从而知道如何修改超参数以获得更高效的学习过程。
下面的图表中,x轴通常都是表示训练次数(epochs)单位,它测量每一个例子在预期的训练过程中被看了多少次(例如,一个周期意味着每一个例子都被看见过一次)。最好是跟踪训练次数而不是迭代,因为迭代的次数取决于批大小的任意设置。
9.3.1 损失函数
训练过程中跟踪的第一个数值是损失,因为在向前传球过程中对各个批次进行评估的就是损失。下图,显示损失值随着时间的变化,特别曲线形状会给出关于学习率的情况:
左图:描述不同学习率的效果。学习率低,改进将是线性的。高一些的学习率会看起来呈几何指数下降。更高的学习率会更快地降低损失,但他们会陷入更糟的损失值(绿线)。这是因为最优化的“能量”太大,参数在混乱中跳跃,不能收敛到一个很好的点上。
右:一个典型的随时间变化的损失函数值,是在CIFAR-10 数据集上训练一个小网络。这种损失函数看起来是合理的(它可能表示基于其衰减速度的学习速率太小,但很难说),并且也表明批次大小可能有点太低(因为成本有点太吵)。
损失中的“摆动”量与批次大小(batch size)有关。当批量为1时,摆动将相对较高。当批量大小是完整的数据集时,摆动将是最小的,因为每个梯度更新应该单调地改进损失函数(除非学习率被设置得太高)。
有些人喜欢在对数域中绘制他们的损失函数。由于学习进度通常呈指数形,所以结果就表现为一条略微可解释的直线,而不是曲棍球杆形状。此外,如果在同一损失图上绘制多个交叉验证模型,它们之间的差异就会更加明显。
有时损失函数可以看起来滑稽可笑,详见 lossfunctions.tumblr.com.
9.3.2 训练/验证 准确度
训练分类器时跟踪的第二个重要指标是模型预测分别在验证集与训练练集上的准确度。通过下图,你可以有效地观察 你的模型中过拟合的程度:
训练和验证精度之间的差距表明过拟合的数量。上图中显示了两种可能的情况。与训练精度相比,蓝色验证误差曲线显示非常小的验证精度,表明强过拟合(注意,验证精度甚至可能在某点之后开始下降)。当你在实践中看到这一点时,你可能想要增加正则化(更强的L2权重惩罚,更多的dropout等)或者收集更多的数据。另一种可能的情况是验证精度跟踪训练精度匹配得特别好。这种情况表明,您的模型容量不够高:通过增加参数数量使模型变大。
9.3.3 权重更新比率
最后一个您可能想要跟踪的指标是更新幅度与值大小的比率。注意:更新,而不是原始梯度(比如,在普通sgd中就是梯度乘以学习率)。您可能需要独立地评估和跟踪每一组参数的比率。一个粗略的启发是这个比率应该在1e-3左右。如果低于此,那么学习率可能太低。如果它更高,那么学习率可能太高。下面是一个具体的例子:
# 假设参数向量为W,其梯度向量为dW
param_scale = np.linalg.norm(W.ravel())
update = -learning_rate*dW # 简单SGD更新
update_scale = np.linalg.norm(update.ravel())
W += update # 实际更新
print(update_scale / param_scale) # 要得到1e-3左右
相较于跟踪最大和最小值,有些人更喜欢计算和跟踪梯度的范式及其更新。这些度量通常是相关的,并且通常给出大致相同的结果。
9.3.4 每层的激活数据及梯度分布
一个不正确的初始化可能让学习过程变慢,甚至彻底停止。还好,这个问题可以比较简单地诊断出来。其中一个方法是输出网络中所有层的激活数据和梯度分布的柱状图。直观地说,就是如果看到任何奇怪的分布情况,那都不是好兆头。比如,对于使用tanh的神经元,我们应该看到激活数据的值在整个[-1,1]区间中都有分布。如果看到神经元的输出全部是0,或者全都饱和了往-1和1上跑,那肯定就是有问题了。
9.4.5 第一层可视化
最后,当处理图像像素时,可视化第一层特征是有用的和令人满意的:
将神经网络第一层的权重可视化的例子。左图中的特征充满了噪音,这暗示了网络可能出现了问题:网络没有收敛,学习率设置不恰当,正则化惩罚的权重过低。右图的特征不错,平滑,干净而且种类繁多,说明训练过程进行良好。
9.4 参数更新
一旦用反向传播计算解析梯度,梯度就被用来执行参数更新。我们接下来讨论几种执行更新的方法。
优化深度网络是当前非常活跃的研究领域。本节将重点介绍一些公认有效的常用的技巧,这些技巧都是在实践中会遇到的。我们将简要介绍这些技巧的直观概念,但不进行细节分析。对于细节感兴趣的读者,我们提供了一些拓展阅读。
9.4.1 随机梯度下降及各种更新方法
香草更新。最简单的更新形式是沿着负梯度方向改变参数(因为梯度指示增加的方向,但我们通常希望最小化损失函数)。假设参数向量x和梯度dx,则最简单的更新形式如下:
# 普通更新
x += - learning_rate * dx
其中学习速率是一个超参数 —— 一个固定常数。当在整个数据集上进行计算时,只要学习率足够低,总是能在损失函数上得到非负的进展。
动量更新(动量p=mv,为物体质量m与速度v的乘积)是另一种方法,这个方法在深度网络上几乎总能得到更好的收敛速度。该方法可以看成是从物理角度上对于最优化问题得到的启发。损失值可以理解为是山的高度(因此高度势能是U=mgh,所以有U∝h)。用随机数字初始化参数等同于在某个位置给粒子设定初始速度为0。这样最优化过程可以看做是模拟参数向量(即粒子)在地形上滚动的过程。
由于作用在粒子上的力与势能梯度(即F=−∇U)有关,粒子所感受到的力正好是损失函数的(负)梯度。此外,F=ma(F指力,m为质量,a为加速度),因此从这个角度讲,(负)梯度与粒子的加速度成正比。注意,这与上面所示的SGD更新不同,其中梯度直接集成位置。相反,物理观点提出了一种更新,其中梯度只直接影响速度,然后速度再对位置产生影响:
# 动量更新
v = mu * v - learning_rate * dx # 与速度融合
x += v # 与位置融合
这里我们介绍了一个初始化为0的变量 v
和一个额外的超参数(mu)。作为一个不幸的误称,这个超参数(mu)被称为动量(其典型值约为0.9),但其物理意义与摩擦系数更一致。这个变量有效地减慢了系统的速度,降低了系统的动能,否则,粒子将永远不会停在小山的底部。在进行交叉验证时,该参数通常被设置为诸如[0.5,0.9,0.95,0.99 ]的值。类似于学习率的退火时间表(稍后讨论),优化有时可以受益于动量时间表,其中动量在学习的后期阶段增加。一个典型的设置是以大约0.5的动量开始,并在多个周期(epoch)中慢慢提升到0.99。
随着动量更新,参数向量将在任何具有一致梯度的方向上建立速度。
内斯特罗夫动量(Nesterov Momentum)是动量更新的一个稍微不同的版本,它最近越来越受欢迎。它对凸函数有较强的理论收敛性保证,在实际应用中也比标准动量稍好。
内斯特罗夫动量的核心思路是,当参数向量位于某个位置x时,观察上面的动量更新公式可以发现,动量部分(忽视带梯度的第二个部分)会通过 mu * v
稍微改变参数向量。因此,如果要计算梯度,那么可以将未来的近似位置x + mu * v看做是“向前看”,这个点在我们一会儿要停止的位置附近。因此,计算 x + mu * v 的梯度而不是“旧”位置x的梯度就有意义了。
既然我们知道动量将会把我们带到绿色箭头指向的点,我们就不要在原点(红色点)那里计算梯度了。使用Nesterov动量,我们就在这个“向前看”的地方计算梯度。
实现代码如下:
x_ahead = x + mu * v
# 计算dx_ahead(在x_ahead处的梯度,而不是在x处的梯度)
v = mu * v - learning_rate * dx_ahead
x += v
然而,在实践中,人们更喜欢将更新表达为尽可能看起来类似香草SGD或动量更新那样。这是可能的,通过操作上面的变量转换 x_ahead = x + mu * v
,然后用x_ahead
代替x来表示更新。也就是说,实际上我们存储的参数向量总是前一步的版本。x_ahead
(但重命名为x)的方程则变成:
v_prev = v # 存储备份
v = mu * v - learning_rate * dx # 速度更新保持不变
x += -mu * v_prev + (1 + mu) * v # 位置更新变了形式
建议进一步阅读以下资料,以了解这些公式和内斯特罗夫加速动量(NAG)的数学公式之来源:
- Advances in optimizing Recurrent Networks by Yoshua Bengio, 第 3.5 节.
- Ilya Sutskever’s thesis (pdf) 第 7.2 节
9.4.2 学习率退火
在训练深度网络时,随着时间的推移退火学习速率很有用。可以这样理解:如果学习率很高,系统的动能就过大,参数向量就会无规律地跳动,损失函数就不能够稳定到更深更窄的部分去。知道什么时候衰减学习率可能是棘手的:慢慢减小它,可能在很长时间内只能是浪费计算资源地看着它混沌地跳动,实际进展很少。但是它过于激进,系统会冷却得太快,无法达到它所能达到的最佳位置。实施学习速率衰减有三种常见类型:
- 阶跃衰减:每进行几个周期(epoch)就减少一定因子的学习率。典型的值可能是每5个周期减少一半的学习率,或者每20个时期减少0.1倍学习率。这些数字很大程度上取决于问题的类型和模型。在实践中,你可以看到的一个启发是在固定学习速率的训练中观察验证错误,并且每当验证错误停止改进时,将学习率降低一个常数(例如0.5)。
- 指数衰减。数学形式为
,其中α0,k是超参数,t 是迭代次数(但也可以使用周期(epoch))。
- 1/t 衰减 的数学公式为 α=α0/(1+kt) 其中α0,k是超参数,t 是迭代次数
在实践中,我们发现,阶跃衰减稍微好一些,因为它涉及的超参数(衰减系数和以周期为时间单位的步数)比超参数K更能解释。最后,如果你有足够的计算资源,可以让衰减更加缓慢一些,让训练时间更长些。
9.4.3 二阶方法
在深度学习优化领域的第二流行的方法是基于牛顿法的,这这种方法迭代以下更新:
在这里,Hf(x) 就是Hessian矩阵,它是函数的二阶偏导数的平方矩阵。∇f(x) 就是梯度下降法中的梯度向量。直观理解上,Hessian矩阵描述了损失函数的局部曲率,从而使得可以进行更高效的参数更新。具体来说,就是乘以Hessian转置矩阵可以让最优化过程在曲率小的时候大步前进,在曲率大的时候小步前进。需要重点注意的是,在这个公式中是没有学习率这个超参数的,这相较于第一位方法是一个巨大的优势。
然而,上述更新对于大多数深度学习应用来说是不切实际的,因为计算和(反转)Hessian在其空间和时间上是一个非常昂贵的过程。例如,具有一百万个参数的神经网络将具有大小为1000000×1000000的Hessian矩阵,占据大约3725千兆字节的内存。因此,已经开发了大量的拟牛顿方法,寻求近似逆Hessian。其中,最流行的是L- BFGS,它使用随时间变化的梯度中的信息来隐式地形成近似值(全矩阵从未计算)。
然而,即使在我们消除了内存的担忧之后, L-BFGS原始应用的大缺点是,它必须在整个训练集上计算,而训练集可能包含上百万个样本。让L- BFGS在小批量上工作,比小批量SGD更为棘手,这也是一个活跃的研究领域。
实践中,目前将L- BFGS或类似的二阶方法应用于大规模的深度学习和卷积神经网络还不常见。相反,基于(内斯特罗夫)动量的SGD变体更常用,因为它们更简单和更容易缩放。
附加参考文献:
- Large Scale Distributed Deep Networks 谷歌大脑团队的论文, 在大规模分布式优化中比较L- BFGS和SGD变种
- SFO 力求将SGD的优点与L- BFGS的优点结合起来的算法
9.4.4 逐参数自适应学习率方法
到目前为止,我们讨论的所有方法都是对学习率进行全局地操作,并且对所有的参数都是一样的。学习率调参是很耗费计算资源的过程,所以很多工作投入到发明能够自适应地对学习率调参的方法,甚至为每个参数都这么做。很多这些方法依然需要其他的超参数设置,但是其观点是这些方法对于更广范围的超参数比原始的学习率方法有更良好的表现。在本小节我们会介绍一些在实践中可能会遇到的常用适应算法:
Adagrad 是最先提出的一种自适应学习速率方法,作者为 Duchi et al.:
# 假设有梯度和参数向量x
cache += dx**2
x += - learning_rate * dx / (np.sqrt(cache) + eps)
注意,变量 cache
的大小等于梯度的大小,并跟踪每个参数的梯度的平方和。然后,将用来归一化参数更新步长,归一化是逐元素进行的。注意,接收高梯度的权重将降低其有效学习率,而接收小或不频繁更新的权重将使其有效学习率增加。有趣的是,平方根运算是非常重要的,没有它,算法的性能就差得多。平滑项 eps
(通常设置在1e-4至1e-8范围内的某个值)避免除以零。Adagrad的缺点是,在深度学习的情况下,单调学习率通常被证明过于激进,并且过早地停止学习。
RMSprop. RMSprop是一种非常有效但目前未发表的自适应学习率方法。有趣的是,使用这种方法的人都引用了Geoff Hinton的CurSera课程的第6课第29个PPT。RMSprop更新以非常简单的方式调整AdGrad方法,以减少其攻击性、单调递减的学习速率。特别是,它使用移动平均的平方梯度:
cache = decay_rate * cache + (1 - decay_rate) * dx**2
x += - learning_rate * dx / (np.sqrt(cache) + eps)
这里,decay_rate
是一个超参数,典型的值是[0.9,0.99,0.999 ]。请注意,x+= 更新与Adagrad相同,但变量 cache
是一个“渗漏(leaky)”。因此,RMSProp 仍然基于其梯度的大小来调节每个权重的学习速率,这具有有益的均衡效果,但是不像Adagrad ,更新不会单调变小。
Adam. 亚当是最近提出的更新,看起来有点像RMSProp 的动量版。简化的更新代码如下:
m = beta1*m + (1-beta1)*dx
v = beta2*v + (1-beta2)*(dx**2)
x += - learning_rate * m / (np.sqrt(v) + eps)
注意,这个看起来很像RMSProp 更新,除了使用的事“平滑”版本的梯度 m 来,而不是原始的(或者有噪声的)梯度向量 dx。推荐值为 eps = 1e-8
, beta1 = 0.9
, beta2 = 0.999
.。在实践中,Adam目前被推荐为默认的使用算法,并且通常比RMSProp 稍微好一些。然而,通常也值得尝试 SGD+Nesterov 动量作为备选方案。完整的亚当更新还包括一个偏置校正机制,它补位解决了在最初的几个时间步长中,在它们完全“热身”之前,向量 m,v,
偏置都被初始化为零 的问题。使用偏置校正机制,更新后的代码如下:
# t 是迭代计数器,从1到 无穷大
m = beta1*m + (1-beta1)*dx
mt = m / (1-beta1**t)
v = beta2*v + (1-beta2)*(dx**2)
vt = v / (1-beta2**t)
x += - learning_rate * mt / (np.sqrt(vt) + eps)
请注意,更新函数现在增加了迭代参数。建议读者的参考论文的细节,或课程幻灯片。
拓展阅读:
- 随机优化的单元测试提出了一系列的测试作为随机优化的标准化基准
左:损失曲面的轮廓和不同优化算法随时间演化。注意基于动量的方法的“超调”行为,这使得优化看起来像一个滚下山的球。右:展示了一个马鞍状的最优化地形,其中沿不同维度的曲率具有不同的符号(一维曲线向上和向下)。请注意,SGD很难突破对称性,一直卡在顶部。而RMSProp之类的方法能够看到马鞍方向有很低的梯度。因为在RMSProp更新方法中的分母项,算法提高了在该方向的有效学习率,使得RMSProp能够继续前进。图片版权:Alec Radford。
9.5 超参数优化
如上所述可见,训练神经网络可以包括许多超参数设置。神经网络中最常见的超参数有:
- 初始学习率
- 学习速率衰减安排(如衰变常数)
- 正则化强度(L2惩罚,dropout 强度)
但是,正如我们所看到的,有许多相对较不敏感的超参数,例如在每个参数自适应学习方法,动量的设置及其时间表等。本节描述了一些执行超参数搜索的提示和技巧:
实施提示。较大的神经网络通常需要很长的时间来训练,因此执行超参数搜索可能需要很多天/周。记住这一点很重要,因为它影响了代码的设计。一个具体的设计思路是用程序持续地随机设置参数然后进行最优化。在训练过程中,程序会对每个周期(epoch)后验证集的准确率进行监控,然后向文件系统写下一个模型的记录点(记录点中有各种各样的训练统计数据,比如随着时间的损失值变化等),这个文件系统最好是可共享的。在文件名中最好包含验证集的算法表现,这样就能方便地查找和排序了。然后还有一个主程序,它可以启动或者结束计算集群中的程序,有时候也可能根据条件查看程序写下的记录点,输出它们的训练统计数据等。
只使用个验证集优于交叉验证。在大多数情况下,单个验证集就够了,而不需要用多个折叠进行交叉验证。你会听到人们说他们“交叉验证”一个参数,但是很多时候其实他们只使用了一个验证集。
超参数范围。在对数尺度上进行超参数搜索。例如,一个典型的学习率的采样是这样的:learning_rate = 10 ** uniform(-6, 1)
。就是说,我们从标准分布中随机生成了一个数字,然后让它成为10的阶数。正则化强度,也可以采用同样的策略。直观地说,这是因为学习率和正则化强度都对于训练的动态进程有乘的效果。例如:当学习率是0.001的时候,如果对其固定地增加0.01,那么对于学习进程会有很大影响。然而当学习率是10的时候,影响就微乎其微了。这就是因为学习率乘以了计算出的梯度。因此,比起加上或者减少某些值,思考学习率的范围是乘以或者除以某些值更加自然。但是有一些参数(比如随机失活)还是在原始尺度上进行搜索(例如:dropout=uniform(0,1))较为常用。
随机搜索优于网格搜索。正如Bergstra和Bengio在文章Random Search for Hyper-Parameter Optimization中说“随机选择比网格化的选择更加有效”,而且在实践中也更容易实现。
通常情况下,一些超参数比其他参数要重要得多(例如,在这个图中,顶部的超参数与左边的一个)。执行随机搜索而不是网格搜索可以让你更精确地发现重要的数值。
小心边界上的最佳值。有时你可能会在一个很差的范围内搜索一个超参数(例如学习速率)。例如,假设我们使用learning_rate = 10 ** uniform(-6, 1)
。一旦我们接收到结果,重要的是要仔细检查最终学习率不在这个区间的边缘,否则你可能会错过超出区间的更优化的超参数。
从粗到细阶段性搜索。在实践中,在粗略范围(例如,10**[-6, 1])中进行第一次搜索是有帮助的,然后根据最佳结果出现在哪里,再缩小范围。进行粗搜索的时候,让模型训练一个周期就可以了,因为很多超参数的设定会让模型没法学习,或者突然就爆出很大的损失值。第二个阶段就是对一个更小的范围进行搜索,比如,这时可以让模型运行5个周期,而最后一个阶段就在最终的范围内进行仔细搜索,运行很多次周期。
贝叶斯超参数优化是一整个研究领域,主要是研究在超参数空间中更高效的导航算法。其核心的思路是在不同超参数设置下查看算法性能时,要在探索和使用中进行合理的权衡。基于这些模型,发展出很多的库,比较有名的有: Spearmint, SMAC, 和Hyperopt。然而,在卷积神经网络的实际使用中,比起上面介绍的先认真挑选一个范围,然后在该范围内随机搜索的方法,这个方法还是差一些。这里有更详细的讨论。
9.6 评价
9.6.1 模型集成
在实践中,训练多个独立的模型,在测试时间平均他们的预测,可以将神经网络的性能提高几个百分点。随着合奏中模型数量的增加,性能通常单调地改善(虽然提升效果越来越少)。此外,模型之间的差异度越大,提升效果显著地好。合奏方法有几种:
- 相同的模型,不同的初始化。使用交叉验证来确定最佳超参数,然后训练具有最佳集合的超参数但具有不同随机初始化的多个模型。这种方法的危险在于多样性仅仅是源自不一样的初始化。
- 在交叉验证中发现顶级模型。使用交叉验证来确定最佳的超参数,然后选择前几个(例如10)模型来形成集成。这提高了集合的多样性,但也存在包括次优模型的危险。在实践中,这可以更容易执行,因为在交叉验证后不需要额外的重新训练模型。
- 单一模型的不同检查点。如果训练是非常昂贵的,不同的时间(例如,在每一个周期epoch)在单一网络随采用不同的检查点,并使用这些形成一个合奏,一些人已经取得了有限的成功。显然,这是由于缺乏某种多样性,但在实践中仍然可以很好地工作。这种方法的优点是非常便宜。
- 在训练的过程中跑参数的平均值。和上面一点相关的,还有一个也能得到1-2个百分点的提升的小代价方法,这个方法就是在训练过程中,如果损失值相较于前一次权重出现指数下降时,就在内存中对网络的权重进行一个备份。这样你就对前几次循环中的网络状态进行了平均。你会发现这个“平滑”过的版本的权重总是能得到更少的误差。直观的理解就是目标函数是一个碗状的,你的网络在这个周围跳跃,所以对它们平均一下,就更可能跳到中心去。
模型集成的一个缺点是,他们需要更长的时间来评估测试样本。最近Geoff Hinton在“Dark Knowledge”上的工作很有启发:其思路是通过将集成似然估计纳入到修改的目标函数中,从一个好的集成中抽出一个单独模型。
9.7 总结
- 利用小批量数据对实现进行梯度检查,注意各种陷阱
- 进行合理性检查,确保您的初始损失是合理的,在一个很小的数据集上可以达到100%的准确率
- 在训练过程中,监控损失、训练/验证的准确性,还可以跟踪更新的参数量相对于总参数量的比例(一般在1e-3左右),然后如果是对于卷积神经网络,可以将第一层的权重可视化
- 推荐使用的两个更新方法是SGD+Nesterov 动量,或者 Adam
- 在训练期间降低你的学习速度。例如,在固定的周期数之后,或者当验证集准确率达到顶点开始下降时,将学习率减半
- 用随机搜索搜索好的超参数(不是网格搜索)。将搜索很阶段从粗(宽超参数范围,只训练1-5个时期)到精细(狭窄的范围,训练更多的周期)
- 进行模型集成来获得额外的性能提高
9.8 参考文献
- SGD tips and tricks from Leon Bottou
- Efficient BackProp (pdf) from Yann LeCun
- Practical Recommendations for Gradient-Based Training of Deep Architectures from Yoshua Bengio
斯坦福大学计算机视图课程,青星人工智能研究中心 翻译整理
原文地址 CS231n Convolutional Neural Networks for Visual Recognition