cs231n 学习笔记(5)- 神经网络-3:Learning and Evaluation

目录

1. Gradient checks

2. Sanity Checks - Before learning

3. Babysitting the learning process

3.1 Loss function

3.2 Train/Val accuracy

3.3 Ratio of weights:updates 权重的更新幅度

3.4 Activation / Gradients distributions per layer 每层的激活值/梯度分布

3.5 First-layer visualization

4. Parameter updates

4.1 SGD and bells and whistles

4.2 Momentum update 动量更新

4.3 Nesterov Momentum

4.4 Annealing the learning rate学习率退火机制

4.5 Second order methods

4.6 Per-paramter adaptive learning rate methods自适应更新learning rate的方法

5. Hyperparameter optimization 

5.1 Implementation

5.2 Prefer one validation fold to cross-validation

5.3 Hyperparameters ranges

5.4 Prefer random search to grid search

5.5 Careful with best values on border

5.6 Stage your search from coarse to fine

5.7 Bayesian Hyperparamter Optimization

6. Evaluation

7. Summary

8. Additional References


前面的章节中介绍了神经网络中的静态部分:如何构建网络、处理数据以及损失函数。本节将介绍动态的部分:如何学习参数并找到好的超参数。

1. Gradient checks

从理论上来讲,梯度检查就是对推导出的梯度和数值计算出的梯度进行比较。但是在实践过程中,并不那么简单。

需要注意的Tips:

  • Use the centered formula 使用中心化形式的梯度计算公式

梯度计算公式:

\small \frac{df(x)}{dx} = \frac{f(x+h)-f(x)}{h}              (不推荐使用) 公式(1.1)

其中h一般是很小的值,数量级别为\small 1e-5

但是在实践中,不建议使用上面的梯度计算公式近形梯度检查。推荐使用中心化形式的梯度计算公式

\small \frac{df(x)}{dx} = \frac{f(x+h)-f(x-h)}{2h}        (推荐使用) 公式(1.2)

这种中心化形式的梯度计算公式,需要计算损失函数2次,带来的好处就是梯度的精度提高了。根据泰勒展开公式,非中心化的误差为\small O(h),中心化形式的误差为\small O(h^2)

  • Use relative error for the comparison 使用相对误差进行梯度比较

如何比较比数值梯度\small {f_n}'和推导梯度\small {f_a}'之间的差异呢?常规的想法是做差\small \left | {f_a}' - {f_n}'\right |或者差的平方,然后与一个阈值作比较,如果小于阈值,则检查通过,否则失败。但是这种方式存在一个问题:假设将阈值设置为\small 1e-4,如果待比较的梯度近似等于1时,这个阈值看起来很合理;但是如果待比较的梯度近似于\small 1e-5或者更小时,那么当前阈值就显得有些过大了,会导致梯度检查失败。

因此,我们使用相对误差来对比数值梯度\small {f_n}'和推导梯度\small {f_a}'之间的差异:

\small \frac{\left | {f_a}' - {f_n}'\right |}{max(\left | {f_a}' \right |,\left | {f_n}' \right |)}       公式(1.3)

公式(1.3)中的分母中的max算子可以替换成add算子。使用max和add算子可以尽量避免分母为0的情况 。但是也要注意两个梯度都为0的情况。在实践中:

1. 相对误差\small >1e-2时,表示梯度很可能计算错误

2. 相对误差介于\small 1e-2\small 1e-4之间,梯度有可能计算错误。

3. 相对误差 \small <1e-4,对于不过光滑的激活来讲可以接受;但是对于光滑的激活函数如:softmax和tanh来讲,还是有点过高

4. 相对误差小于\small 1e-7,一般啦爱将是正确的。

注意:牢记网络越深,相对误差可能越大,也需要根据深度适当相对误差的阈值的范围。所以,如果对10层的网络进行检查时,\small 1e-2可以接受;但是对于一个可导函数时,\small 1e-2就可能太大了。

  • Use double precision 使用双精度浮点数

使用双精度浮点数会使得计算更加准确。

  • Stick around active range of floating point 确保数值范围处于单精度内

建议阅读What Every Computer Scientist Should Know About Floating-Point Arithmetic,会给你的编码带来一些启发,减少错误。例如,神经网络中,通常需要对一个batch计算损失函数,进行归一化。但是,如果每一个点的loss本身就很小,然后对其求和在除以点集数量,那么会得到一个更小的值,从而会带来一些列的问题。因此,常常需要把解析梯度和数值梯度的原始值打印出来,确保它们不是特别小。如果的确比较小,可以进行放大,一般放大的数值量级为1.0。

  • Kinks in the objective目标函数中的不连续点

目标函数中的不连续点可能使得梯度检查失败。不连续点指的是目标函数中不可导的点,例如Relu,SVM Loss和Maxout会引入不连续点。例如:对于Relu \small f(x) = max(0,x),在\small x=-(1e^{-6})点处,由于\small x<0,所以根据分析梯度计算的结果是\small {f}'(x) = 0;但是对于数值梯度而言,如果\small h>1e^{(-6)},那么\small f(x+h)就会跨越不连续点,\small f(x+h)\small f(x-h)就会分布在不连续点的两侧,得到一个非零的梯度。

注意:这不是极端情况。

在计算提都市,我们需要知道是否跨越了不连续点。具体方式:在前项传播时记录\small max(x,y)中的较大点,如果在计算\small f(x+h),f(x-h)时,至少有一个最大值发生变化,那么越过了不可连续点。

  • Use only few datapoints 使用少量数据点

上诉问题的一个解决方案是:使用少量的数据点。使用的数据点越少,碰到不可导的点的越少。并且,只要检查2到3个点的梯度是否正确,那么也几乎可以确定整个batch的梯度检查结果。此外,使用更少的数据点,可以提高梯度检查的速度。

  • Be careful with the step size h 注意h的大小

理论上h越小越好,但是实际上h并不是越小越好。过小的h会导致数值问题。常将h设置为\small 1e-4\small 1e-6,具体可参照wiki page

  • Gradcheck during a "characteristic" mode of operation

注意,梯度检查只是作用在随机选取的一部分特定的检查点上。即使这些点都正确,也不代表在全局上梯度一定被正确的实现。尽可能的选择最具代表的点进行梯度检查,例如:SVM训练后,在刚开始训练时由于所有权重都接近于0,没什么太大的差异,所以不建议此时进行梯度检查;建议在训练一段时间后,loss函数开始下降的时候再使用梯度检查。

  • Don't let the regularization overwhelm the data不要让正则化淹没数据

损失函数由两部分构成:数据损失和正则化损失。需要注意的是,不要让正则化损失远远大于数据损失。因为在这种情况下,梯度主要来自正则项,会掩盖住数据损失梯度的错误。因此,建议在梯度检查时先去掉正则项loss,只检查数据损失;然后再独立的检查正则项。检查正则项损失时,可以去掉数据损失或者增加正则项的权重。

  • Remember to turn off dropout/augmentitions关闭dropout/数据扩展

在做梯度检查时,注意需要去掉网络中的不确定因素,如:dropout和随机数据扩展等。因为不确定性会导致计算数值梯度时出现较大的错误;但是关掉dropout或随机数据扩展后又会导致无法进行梯度检查。为此,我们可以计算\small f(x+h),f(x-h)时调价随机种子,在计算推导梯度时也是一样。

  • Check only few dimensions只检查部分维度

实践中,梯度可能有几百万个参数。在这种情况下,我们只需要检查部分维度,并假设其他维度均正确即可。注意的是,我们需要检查这些被选定维度的每一个参数。

2. Sanity Checks - Before learning

在开始学习的过程前,有一些建议和技巧。

  • Look for correct loss at change performance.

当时用小的初始化参数时,确保实际得到的数据损失与预期得到的损失一致。

最好的办法就是:先将正则项设为0,然后检查数据损失。例如,使用softmax对CIFAR-10数据集进行分类时,我们期望的数据损失为2.302,这是因为我们期望以0.1的概率分类正确,故而损失为:\small -ln(0.1) = 2.302;若使用SVM进行分类,初始时所有的score都为0,则期望的数据损失应该为9。

如果在初始化的时候,实际的数据损失与期望得到的损失相差很大,那么代表初始化存在问题。

  • Increasing regularization strength should increase the loss.

确保增大正则项强度时,整体的loss应该增大

  • Overfit a tiny subset of data

在整体数据集上训练之前,使用一个较小的数据集(例如:只包括20个样本)进行训练,并确保可以实现zero loss。对于这个实验,需要将正则项设为0,这样可以确保能够得到loss为0。确保你的模型可以通过该测试,否则即便用全部的数据集进行学习也没有意义。

但需要注意的是,即便模型能够咋小数据集上实现过拟合,也不代表一定正确。例如:由于数据集时随机的,所以即便能够在小的训练数据集上实现过拟合,但对于大的数据集也可能永远无法拟合。

3. Babysitting the learning process

在训练神经网络的过程中,观察一些标准在其训练过程中的变化很有帮助,可以用来指导超参数的设置以及如何提高学习效率。

在接下来的介绍中,绘图部分的x-axis都代表epoch的次数,用来度量每个样本都被观测的次数。一个epoch代表每个样本至少被观测到一次。观测epoch,而不是迭代次数是因为迭代次数依赖于batch的大小。

3.1 Loss function

Loss值在训练过程中的变化是一个重要度量指标。Loss值随着训练时间的变化的形状,可以反应learning rate的设置情况。如下图所示:

                                

左图给出了使用不同的学习率时可能出现的效果:

  • 低学习率(蓝色):随着训练时间的增加,损失函数的变化近似与线性下降。
  • 高学习率(绿色):高的学习率最开始可能以指数级下降,但是最后可能维持在一个较高的损失值附近。这是因为学习率较大,“能量”过大,导致参数震荡,不能找到最优点。
  • 过高的学习率(黄色):过高的学习率可能导致损失增高。
  • 合适的学习率(红色):loss降低,并最终稳定在一个较低的损失值附近。

右图给出了一个典型的随着训练时间的增加损失函数的变化曲线,它在CIFAR-10上训练得到的:

在这幅图中的损失函数的变化曲线似乎是合理的,但是也有可能学习率较低,导致收敛较慢;另外,由于损失函数的噪声很多,也可能标志着batch size过小。

损失函数的抖动与batch size的大小有关。batch size为1时,抖动会非常明显。当batch size为全部数据集时,在学习率不是太大的情况下,每次梯度更新都会朝着最优的方向移动,因此抖动会非常小。

也有人倾向于绘图时对损失函数求对数。这是由于学习的过程近似于指数函数的形状,如果做了log操作,会使得学习曲线近似于直线。此外,如果使用交叉验证模型,并把结果画到同一副图上,那么它们之间差异会更加明显。

有些loss函数看起来很搞笑:lossfunctions.tumblr.com

3.2 Train/Val accuracy

第二个很重要的评估标准是:观察在训练过程中分类器在训练集和验证集上的准确率。这个标准可以用来帮助分析模型是否过拟合。

                                                     

训练集合验证集上准确性的Gap(差值)标记了模型是否过拟合。上图给出了两种可能的情况:

  • 红色线+蓝色线:红色线代表训练集上的准确性,蓝色线代表验证集上的准确性。可以看到,验证集上的准确性远远低于训练集上的准确性,这代表了模型存在着严重的过拟合(注意:验证集准确性可能在增长之后还出现下降的情况)。碰到这种情况时,通常可以增加正则化力度,或者使用更大的数据集。
  • 红色线+绿色线:验证集上的准确性和训练集变化一致,说明模型的容量可能不够大,需要增加模型的参数以扩大模型的复杂度。

3.3 Ratio of weights:updates 权重的更新幅度

最后,还需要观察权重的更新幅度。注意:这里的更新代表的不是原始梯度,而是参数的更新值 (以SGD为例,更新幅度 = 梯度乘以学习率)。可能需要对每个参数单独计算。

通常,我们希望更新率在1e-3左右,如果更新比例较低,可能表示学习率过低;如果更新比例过大,可能代表学习率过大。下面,给出一个具体的计算过程:

# assume parameter vector W and its gradient vector dW
param_scale = np.linalg.norm(W.ravel())
update = -learning_rate*dW # simple SGD update
update_scale = np.linalg.norm(update.ravel())
W += update # the actual update
print update_scale / param_scale # want ~1e-3

通常计算和比较归一化后的值,而不是最大值或最小值来评估更新比例。因为这些矩阵通常是相关的,而且通常有相似的结果。

3.4 Activation / Gradients distributions per layer 每层的激活值/梯度分布

糟糕的初始化能够减慢学习速度甚至使得学习过程停止。但是这个问题很容易诊断。一种常用的方法是:为每层网络绘制激活值和梯度的直方图。直观的讲,我们不希望看到任何奇怪的分布 —— 比如:对于tanh神经元,我们期望神经元的激活值的分布在[-1,1]区间,而不是全部分布在0,或者全部分布在1或-1的位置。

3.5 First-layer visualization

最后,如果使用图像数据,那么绘制出第一层的特征会非常有帮助。下图给出了第一层中权重的可视化结果。左图中有很多噪声,代表网络没有收敛好,可能是因为设置了错误学习率,也可能是正则化强度过低。右图的图比较平滑、干净、特征多样,说明学习过程很好。

              

4. Parameter updates

在反向传播过程中,需要使用分析梯度法计算梯度,然后利用计算出的梯度对参数进行更新。接下来,将介绍一些参数更新方法。

神经网络的优化是当前研究热点。在本节中,主要介绍已经出版的、常用的优化算法并给出直观的解释。如果想要了解更多,可以通过提供的读物进行学习。

4.1 SGD and bells and whistles

  • Vanilla Updates

最简单的参数更新方式,即:沿着负梯度方向进行跟更新。假设参数向量\small x,以及对应的梯度\small dx,那么更新方式为:

#Vanilla update
x += - learning_rate * dx

其中learning_rate是一个超参数,是一个固定的常数。当在整个数据集上进行评估,并且learning_rate足够小时,能够确保降低loss函数值。

4.2 Momentum update 动量更新

是另一种常用的参数更新方法,在深度网络中能够较好的收敛速度。

基于动量的更新方式可以从物理角度进行解释。损失函数可以看成山的高度(有高度就有势能,因此\small U = mgh, U\propto h)。用随机数初始化参数可以被看做将质点设置在某位置、并使其初始速度为0。然后,优化过程就可以看成参数向量(质点)向谷底滚动的过程。

由于质点受到的力与势能的梯度有关(\small F = -(\bigtriangledown U)),那么力就可以看做损失函数的(负)梯度,有\small F = ma。所以负梯度,与质点的加速度成正比。这个观点与SGD从不同,SGD是直接更新质点的位置,而基于动量的更新是将梯度直接作用于速度,然后通过速度影响位置。

#Momentum update
v = mu * v - learning_rate * dx #integrate velocity 
x += v #integrate position

其中,这里引入了参数v,初始化为0;还引入了超参数mu。说的不恰当一些,mu*v可以看成动量,常常设置为0.9;但是从物理角度来看,mu与摩擦力系数的含义一致。mu,用来减少速度和势能,否则质点即便抵达山谷也不会停止。在交叉验证过程中,mu通常在\small [0.5,0.9,0.95,0.99]之间取值。与学习率的退火机制类似,随时间的变化为动量mu设置不同的值,可以略微改善优化效果,即:在学习的进行,适当提高动量的值。常用的设置方法是,将动量初始化为0.5,然后对着epoch逐渐增大,直到0.99。

注意:使用动量更新机制,参数向量会在任何有梯度的方向更新。

附加:我对基于动量学习的理解:

动量的物理意义:动量 = 物体的质量 * 速度,表示这个物体在它运动方向上保持运动的趋势

对应到优化的过程:上一轮学习过程的参数更新方向就是原始的运动方向,动量mu就是保持上一次更新方向的趋势。下图给出了我对基于动量的学习的理解:

                

在初始时,初始速度为0,故而初始点的移动方向就是梯度下降的方向;

在接下来的过程中,由于已经开始了运动,故而存在动量。在新的迭代过程中,更新方向除了需要考虑损失函数的梯度方向,还需要考虑当前运动方向所带来的影响。故而出现了上述代码片段的第一句。将二者结合起来,共同作为新的更新方向。

4.3 Nesterov Momentum

Nesterov动量和标准的动量有点不同。对于凸优化,从理论上来讲,Nesterov动量比标准动量更容易收敛。实践中Nesterov动量也优于标准动量。

Nesterov动量的核心思想是:对于当前参数向量位于\small x点处,回顾一下上节介绍的标准动量方法,只考虑其中的动量部分\small mu*v。当我们打算计算梯度时,由于动量的影响,我们可以预估下一步节点的一个近似位置\small x+mu*v,这个点作为“lookhead”点 ;然后,计算这个“lookhead”点的梯度,而不是原始位置\small x的梯度,用来更新x。

用图来表示标准动量和Nesterov动量之间的差别:

实现代码如下:

x_head = x + mu * v
#evaluate dx_head (the gradient at x_head instead of x)
v = mu * v - learning_rate * dx_head
x += v

在实践中,人们更喜欢类似标准SGD和momentum更新的表达形式。可以通过参数x_ahead=x+m*v改写实现上面的实现,用x_ahead表示更新,不用x。这样,实际计算中总是存储的前一步的计算结果,公式中x_ahead(改回x)变为:

v_prev = v #back this up
v = mu * v - learning_rate * dx # velocity update stays the same
x += -mu * v_prev + (1 + mu) * v # position update changes form

推荐阅读公式来源和数学推导Nesterov’s Accelerated Momentum(NAG):

Advances in optimizing Recurrent Networks by Yoshua Bengio, Section 3.5. 

Ilya Sutskever’s thesis (pdf) contains a longer exposition of the topic in section 7.2

附加:我对Nesterov's动量的理解:

下图给出了我对基于Nesterov's动量的学习的理解:

                

4.4 Annealing the learning rate学习率退火机制

在训练过程中,要学会对learning rate进行退火(即:减小learning rate)。如果学习率过高,系统拥有的动能太大,会导致loss函数在一个范围内波动,不能达到更深更窄的位置。什么时候减少学习率是有技巧的:如果减少的太慢则会浪费计算资源,loss值会在某一范围内波动,提高很少;如果减少的太快,则会让系统冷却的过快,将不能达到最好的位置。常用的退火策略有以下三种:

  • Step decay

   经过几个epoch后,通过一些因子降低学习率。典型的用法是:每5个epoch学习率就减少一半或没20个epoch学习率变为原来的0.1倍。当然,这些数值的设定取决于具体的问题和具体的模型。一个启发式的做法是在实际训练过程中中观察交叉验证集的错误率,如果错误率停止不再下降,那么令学习率乘以一个常量(例如0.5)。

  • Exponential decay

数学表达式为:\small \alpha = \alpha_0e^{-kt},其中\small a_0\small k表示超参数,\small t表示迭代的次数(也可以代表epoch的次数)。

  • 1/t decay

数学表达式为:\small \alpha = \alpha_0/(1+kt),其中\small a_0\small k表示超参数,\small t表示迭代的次数。

实际上,经常使用step decay策略,因为step decay引入的超参数比后面的两种方法更容易解释。

4.5 Second order methods

还有一类优化算法,主要是基于Newton’s method,它的更新形式为:

\small x \leftarrow x - [H f(x)]^{-1} \nabla f(x)         公式(4.1)

其中,\small Hf(x)Hessian matrix,它是函数二阶偏导数组成的矩阵。\small \nabla f(x)是梯度向量。Hessian matrix描述了损失函数的局部曲率,局部曲率能够帮助我们更有效的更新参数。通过乘以Hessian矩阵的逆矩阵,可以实现在曲率小的地方大步更新,在曲率大的地方小步更新。注意,上述更新没有涉及到超参数learning rate,因此这是比一阶导数有优势的地方。

但是,虽然二阶导数理论上更加有效,但是实际中并不适用于深度学习的应用中。因为Hessian矩阵以及其逆矩阵计算量太大,且占用内存太多。为了近似的求取Hessian矩阵的逆矩阵,很多类牛顿算法被提出,包括:L-BFGS

L-BFGS使用不同时间下的梯度信息近似构造Hessian矩阵(例如:从不计算整体的矩阵),减少了内存过大的问题。但是其缺点是:每次迭代都必须在全部的数据上进行计算,如果样本集很大,那么耗时仍旧很长。具体内容可以参考:

Large Scale Distributed Deep Networks is a paper from the Google Brain team, comparing L-BFGS and SGD variants in large-scale distributed optimization. 
SFO algorithm strives to combine the advantages of SGD with advantages of L-BFGS.

事实上,在深度学习中很少使用L-BFGS等类似的二阶梯度算法进行参数更新,而是使用带动量的SGD方法。

4.6 Per-paramter adaptive learning rate methods自适应更新learning rate的方法

前面介绍的所有方法都是对所有参数的学习率同时使用相同的操作。学习率调优是一个很澳规的过程,许多工作投入到自适应学习率,甚至每个参数适应的学习率。许多自适应方法都需要引入新的超参数,相对于原始的学习率而言,这些新的超参数可以在比较大的变化范围进行变化,但不会想原始学习率那样对学习系统造成很大的影响。

本节,将介绍一些自适应算法

  • Adagrad

Adagrad是由Duchi et al.提出的算法,具体实现如下:

#Assume the gradient dx and the parameter vector x
cache += dx**2
x += -learning_rate * dx / (np.sqrt(cache) + eps)

注意,变量cache与梯度dx具有相同的大小。cache跟踪每一个参数的梯度的平方和,然后用于归一化参数更新步骤。需要注意的是这种更新是element-wise级别的。从代码中可以看出,如果梯度大,那么其有效学习率就会减小;如果梯度小,那么有效学习率就会增加。求平方根的操作非常重要,没有它的算法效果非常差。平滑项eps,常取的范围是1e-4到1e-8,它的作用是避免分母为0。

其缺点是:在深度学习中,单调的学习率变化被证明太激进,会使得学习停止的太早。

  • RMSprop

RMSprop是诶长高效,但是目前还没有被发表的自适应更新学习率的算法。它是Adagrad的改进版,目的是减小Adagrad的激进性(单调减少学习率的方式),尤其是使用平方梯度的滑动平均值。

#Assume the gradient dx and the parameter vector x
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变量的计算方式不同,cache更平缓了。RMSprop也是基于梯幅值对学习率进行更新的,与Adagrad不同的是,它不会单调递减学习率。

  • Adam 

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 Momentum算法。

完整的Adam还包括矫正机制(bias correction mechanism),对前几步中m和v被初始化为0带来的影响,因此,在网络完全的“warm up”之前,偏置设置为0。带有矫正机制的Adam实现如下:

# t is your iteration counter going from 1 to infinity
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)

如果想了解更多细节,参考论文或课外读物。课外读物: 
Unit Tests for Stochastic Optimization proposes a series of tests as a standardized benchmark for stochastic optimization.5. Hyperparameter Optimization.

通过下面两张动画可以直观感受不同优化算法: 

                     

上面图片绘制了loss函数的等高线。注意到,带有动量的算法,一旦走偏,就难以纠正回来,它的路线就像一个小球一直向下滚。

下面图片绘制了带鞍点的情况。鞍点:就是一个点,在不同维度都有着不同的方向,有的开口向上,有的开口向下。可以看到SGD算法很难突破对称性的限制,被困在了高点。而RMSprop这类的算法则可以在鞍点处看到更低的梯度,这是因为其更新过程中的分母项,将会增大这个方向的学习率,帮助RMSProp在这个方向前进。

5. Hyperparameter optimization 

训练神经网络时需要设置很多超参数,最常见的超参数有:

  • 初始的学习率
  • 学习率衰减算法(例如:常数衰减值)
  • 正则化的强度(L2正则化、dropout的强度)

还有一些相对不那么敏感的超参数,例如:在per-parameter adaptive learning methods中,动量及其设置等。本节,将接胡搜啊一些调节超参数的技巧。

5.1 Implementation

训练一个大型神经网络需要很长的时间,调参可能需要几天或者几周的时间。设计代码时,一种结构是:

  • 有一个worker连续的随机设置超参数,之后做优化。

在训练过程中,worker持续记录每完成一个epoch后,在验证集上的性能,并保存到check point文件中,直接在文件名上反应出验证集的性能会更方便。

  • 还需要一个master,用来管理worker程序,还可以监听check point文件,绘制训练过程图等。

5.2 Prefer one validation fold to cross-validation

大多数情况下,使用大小合适的验证集能够让代码更简单,没必要使用多折交叉验证。可能常常听人说他们使用交叉验证来设置超参数,但是绝大多数情况下只是使用了一个单独的验证集。

5.3 Hyperparameters ranges

以对数的尺度来设置超参数。例如,学习率取样范围为 learning_rate = 10 ** uniform(-6,1),即:学习率是以10的指数幂,幂的范围为(-6,1)上的随机数。同样的方式也适用于正则化强度的设置。

这是因为学习率和正则化强度在训练过程中具有乘的影响。例如:如果我们为原始学习率为0.001的学习率加上0.01,那么会有很大的影响;如果原始学习率为10的话,那么加上0.01的影响则几乎可以忽略。这是由于在更新过程中,是学习率*梯度。因此,对于学习率的取值范围,我们需要考虑的是乘以或者除以某个数,而不是加上或者减去某个数。

但是,也有一些超参数是在原始尺度上进行设置(例如:dropout),那么就可以直接在原始尺度上进行搜索:dropout = uniform(0,1)。

5.4 Prefer random search to grid search

正如 Random Search for Hyper-Parameter Optimization所论证的一样,随机搜索能够比网格搜索更快的找到更优的超参数。

                         

根据上述论文中介绍的,一些超参数比其它超参数更重要。对于重要的哪些参数,随机搜索更可能发现更优的超参数。

5.5 Careful with best values on border

边界上的最优值可能不是最优的!

有时我们可能设置了一个比较差的搜索范围。例如,我们选取learning_rate = 10 ** uniform(-6,1)作为学习率的搜素空间。当找到最优值的时候,需要判定最终的学习率没有出现在边界处,否则,意味着我们要扩大搜索范围。

5.6 Stage your search from coarse to fine

实践中,确保由粗到细的搜索。

首先在一个较大的范围搜索,然后逐步缩小搜索范围。在大范围搜索时,只需要一个或更少的epoch即可,因为许多不合适的超参数会导致网络学不到任何东西,或者引起loss值爆炸;缩小范围搜索后,可以在5个左右的epoch上进行训练,进一步缩小搜素范围,可以在更多的epoch上进行训练,直到找到最优值。

5.7 Bayesian Hyperparamter Optimization

贝叶斯超参数优化,是一种致力于更高效的确定超参数空间的算法的研究领域。其核心思想是:在查询不同超参数的性能时,找寻探索-设置不同超参函数之间的平衡。在此基础上也开发出一些库。但是在卷积网络的实践中,很难有搜索方法能够超越随机搜索找出的值。这里有更详细的讨论。

6. Evaluation

Models Ensembles

在实践中,平均能把性能提高几个百分点的一种方法是:独立训练几个模型,在测试阶段把多个模型的结果求平均。随着模型数量的增多,性能能也会单调递增,但越到后面提升越少。此外,如果模型之间的多样性越大,则提高的效果越好。有下面几种集成方式:

  • 同一个模型,不同初始化

使用交叉验证找到最优的超参数,然后使用这些超参数,随机初始化出多个模型进行训练。其缺点是仅仅依靠初始化创建多样性。

  • 交叉验证中的最优模型

使用交叉验证确定最优超参数,然后选择最优惨重中的前几个(例如:10个)模型来做集成,增加集成模型的多样性。这样做的缺点是可能会把次优的模型包含进来。但是其优点是容易实现,并且在交叉验证之后不要再进行训练了。

  • 同一个模型的不同记录点

训练过程中,随着时间的进行,每个epoch都会有一个checkpoint保存一个模型,这样就会有多个模型。使用这些模型进行集成。虽然这样会缺少多样性,但是在实践中也有良好的性能。但是其优点是代价很低。

  • 训练时使用参数的平均值

这个方法和上一个方法有关,也是一种代价很小的,但是可以提高模型一两个百分点,即:在训练时在内存保存参数的拷贝,当loss函数出现指数下降时,记录这个参数;最终使用记录的这些参数的平均值。这个平滑版本的参数在验证集上有更小的误差。一个直观解释是:目标函数是碗状的,参数在碗的周边跳跃,参数的平均更有可能到达碗的更深处。

模型集成的缺点是:测试过程需要更长的时间。Geoff Hinton的论文Dark Konwledge提出集成模型为单个模型的方法,通过修改目标函数,加入似然函数来实现。

7. Summary

训练一个神经网络:

1. 使用一小批数据进行梯度检查。

2. 合理性检查:确保初始化的loss是合理的,并且能够在小数据集实现100%的准确性。

3. 训练过程中,监控 损失、训练集/验证集准确率。参数的更新幅度与参数值的比值应该在1e-3附近;如果是卷积神经网络,记得可视化第一层权重。

4. 推荐使用的更新算法:Adam和SGD+Nesterov momentum

5. 随着训练的深入,建校学习率。例如:每固定epochs后,降低一些学习率;或验证集准确率不再提高后降低学习率。

6. 使用随机搜索进行超参数调参。从粗到细、分阶段搜索。 

7. 使用模型集成得到额外的提高。

8. Additional References

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

猜你喜欢

转载自blog.csdn.net/Emma_Love/article/details/87886550