目录
2. Sanity Checks - Before learning
3. Babysitting the learning process
3.3 Ratio of weights:updates 权重的更新幅度
3.4 Activation / Gradients distributions per layer 每层的激活值/梯度分布
4.1 SGD and bells and whistles
4.4 Annealing the learning rate学习率退火机制
4.6 Per-paramter adaptive learning rate methods自适应更新learning rate的方法
5. Hyperparameter optimization
5.2 Prefer one validation fold to cross-validation
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
前面的章节中介绍了神经网络中的静态部分:如何构建网络、处理数据以及损失函数。本节将介绍动态的部分:如何学习参数并找到好的超参数。
1. Gradient checks
从理论上来讲,梯度检查就是对推导出的梯度和数值计算出的梯度进行比较。但是在实践过程中,并不那么简单。
需要注意的Tips:
- Use the centered formula 使用中心化形式的梯度计算公式
梯度计算公式:
公式(1.1) |
其中h一般是很小的值,数量级别为。
但是在实践中,不建议使用上面的梯度计算公式近形梯度检查。推荐使用中心化形式的梯度计算公式:
公式(1.2) |
这种中心化形式的梯度计算公式,需要计算损失函数2次,带来的好处就是梯度的精度提高了。根据泰勒展开公式,非中心化的误差为,中心化形式的误差为
。
- Use relative error for the comparison 使用相对误差进行梯度比较
如何比较比数值梯度和推导梯度
之间的差异呢?常规的想法是做差
或者差的平方,然后与一个阈值作比较,如果小于阈值,则检查通过,否则失败。但是这种方式存在一个问题:假设将阈值设置为
,如果待比较的梯度近似等于1时,这个阈值看起来很合理;但是如果待比较的梯度近似于
或者更小时,那么当前阈值就显得有些过大了,会导致梯度检查失败。
因此,我们使用相对误差来对比数值梯度和推导梯度
之间的差异:
公式(1.3) |
公式(1.3)中的分母中的max算子可以替换成add算子。使用max和add算子可以尽量避免分母为0的情况 。但是也要注意两个梯度都为0的情况。在实践中:
1. 相对误差时,表示梯度很可能计算错误
2. 相对误差介于和
之间,梯度有可能计算错误。
3. 相对误差 ,对于不过光滑的激活来讲可以接受;但是对于光滑的激活函数如:softmax和tanh来讲,还是有点过高
4. 相对误差小于,一般啦爱将是正确的。
注意:牢记网络越深,相对误差可能越大,也需要根据深度适当相对误差的阈值的范围。所以,如果对10层的网络进行检查时,可以接受;但是对于一个可导函数时,
就可能太大了。
- 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 ,在
点处,由于
,所以根据分析梯度计算的结果是
;但是对于数值梯度而言,如果
,那么
就会跨越不连续点,
与
就会分布在不连续点的两侧,得到一个非零的梯度。
注意:这不是极端情况。
在计算提都市,我们需要知道是否跨越了不连续点。具体方式:在前项传播时记录中的较大点,如果在计算
时,至少有一个最大值发生变化,那么越过了不可连续点。
- Use only few datapoints 使用少量数据点
上诉问题的一个解决方案是:使用少量的数据点。使用的数据点越少,碰到不可导的点的越少。并且,只要检查2到3个点的梯度是否正确,那么也几乎可以确定整个batch的梯度检查结果。此外,使用更少的数据点,可以提高梯度检查的速度。
- Be careful with the step size h 注意h的大小
理论上h越小越好,但是实际上h并不是越小越好。过小的h会导致数值问题。常将h设置为或
,具体可参照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或随机数据扩展后又会导致无法进行梯度检查。为此,我们可以计算时调价随机种子,在计算推导梯度时也是一样。
- Check only few dimensions只检查部分维度
实践中,梯度可能有几百万个参数。在这种情况下,我们只需要检查部分维度,并假设其他维度均正确即可。注意的是,我们需要检查这些被选定维度的每一个参数。
2. Sanity Checks - Before learning
在开始学习的过程前,有一些建议和技巧。
- Look for correct loss at change performance.
当时用小的初始化参数时,确保实际得到的数据损失与预期得到的损失一致。
最好的办法就是:先将正则项设为0,然后检查数据损失。例如,使用softmax对CIFAR-10数据集进行分类时,我们期望的数据损失为2.302,这是因为我们期望以0.1的概率分类正确,故而损失为:;若使用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
最简单的参数更新方式,即:沿着负梯度方向进行跟更新。假设参数向量,以及对应的梯度
,那么更新方式为:
#Vanilla update
x += - learning_rate * dx
其中learning_rate是一个超参数,是一个固定的常数。当在整个数据集上进行评估,并且learning_rate足够小时,能够确保降低loss函数值。
4.2 Momentum update 动量更新
是另一种常用的参数更新方法,在深度网络中能够较好的收敛速度。
基于动量的更新方式可以从物理角度进行解释。损失函数可以看成山的高度(有高度就有势能,因此)。用随机数初始化参数可以被看做将质点设置在某位置、并使其初始速度为0。然后,优化过程就可以看成参数向量(质点)向谷底滚动的过程。
由于质点受到的力与势能的梯度有关(),那么力就可以看做损失函数的(负)梯度,有
。所以负梯度,与质点的加速度成正比。这个观点与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通常在之间取值。与学习率的退火机制类似,随时间的变化为动量mu设置不同的值,可以略微改善优化效果,即:在学习的进行,适当提高动量的值。常用的设置方法是,将动量初始化为0.5,然后对着epoch逐渐增大,直到0.99。
注意:使用动量更新机制,参数向量会在任何有梯度的方向更新。
附加:我对基于动量学习的理解:
动量的物理意义:动量 = 物体的质量 * 速度,表示这个物体在它运动方向上保持运动的趋势。 对应到优化的过程:上一轮学习过程的参数更新方向就是原始的运动方向,动量mu就是保持上一次更新方向的趋势。下图给出了我对基于动量的学习的理解: 在初始时,初始速度为0,故而初始点的移动方向就是梯度下降的方向; 在接下来的过程中,由于已经开始了运动,故而存在动量。在新的迭代过程中,更新方向除了需要考虑损失函数的梯度方向,还需要考虑当前运动方向所带来的影响。故而出现了上述代码片段的第一句。将二者结合起来,共同作为新的更新方向。 |
4.3 Nesterov Momentum
Nesterov动量和标准的动量有点不同。对于凸优化,从理论上来讲,Nesterov动量比标准动量更容易收敛。实践中Nesterov动量也优于标准动量。
Nesterov动量的核心思想是:对于当前参数向量位于点处,回顾一下上节介绍的标准动量方法,只考虑其中的动量部分
。当我们打算计算梯度时,由于动量的影响,我们可以预估下一步节点的一个近似位置
,这个点作为“lookhead”点 ;然后,计算这个“lookhead”点的梯度,而不是原始位置
的梯度,用来更新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
数学表达式为:,其中
,
表示超参数,
表示迭代的次数(也可以代表epoch的次数)。
- 1/t decay
数学表达式为:,其中
,
表示超参数,
表示迭代的次数。
实际上,经常使用step decay策略,因为step decay引入的超参数比后面的两种方法更容易解释。
4.5 Second order methods
还有一类优化算法,主要是基于Newton’s method,它的更新形式为:
公式(4.1) |
其中,是Hessian matrix,它是函数二阶偏导数组成的矩阵。
是梯度向量。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