在这节课中,主要讲述了神经网络的检查事项(例如梯度检查,合理性检查和学习过程中对损失函数、权重、每层的激活函数与梯度分布等的检查等)和神经网络的参数调优实现方法(例如:随机梯度下降方法,动量方法,学习率退火方法等等)
-----------第一部分:检查事项----------
另外,在进行误差比较时,应该使用下面的相对误差比较法:
公式3比较误差时是计算的两者的差值占两个梯度绝对值较大值(分母取的是两个梯度绝对值的最大值)的比例,分母也可以是两个梯度绝对值的和。这样做可以防止当其中一个梯度等于0时,分母为0的情况(这种情况在ReLU中经常发生),所以还需要注意当两个梯度都为零时并且通过了梯度检查的情况。老师给出了在实践中的几种情形:
在多层神经网络中,误差时逐层累积的。对于一个可微分函数,如果误差为1e-2,通常就是梯度计算出错了。
另外,梯度检查时所用的数值精度也会影响到结果,例如,可能出现使用单精度数的相对误差为1e-2,但使用双精度数时的相对误差为1e-8的情况。 还需要注意保持浮点数的有效范围,在论文《What Every Computer Scientist Should Konw About Floating-Point Artthmetic》描述了多种可能因为浮点数值计算导致的错误。老师建议将原始的解析梯度和数值梯度数据打印出来,以确保用来比较的数值不要太小(通常绝对值小于1e-10是很坏的情况)。但是如果出现确实过小的情形,可以借助一个常数将损失函数的数值范围暂时扩展到一个更“好”的范围,使得浮点数变得更加密集;比较理想的数值范围是在1.0的数量级上,即浮点数指数为0。
还有一种情况是:目标函数存在不可导点(kinks)。
不可导点是指目标函数不存在导数的部分,ReLU、SVM损失函数、Maxout神经元等都存在kinks点。以ReLU函数为例,当x=0时,函数不可导,即是函数的一个kinks点,下图1是ReLU的函数曲线:
在x=e-6处,理论梯度应该是0,但当使用上面公式(2)求梯度时,如果h>e-6,求出的梯度结果并不为0,因为 f(x+h) 越过了不可导点。而在实际应用中,上述情况是很常见。例如,用CIFAR-10训练的SVM中,样本数为50000个,每个样本产生9个 max(0,x) 式子,所以共有 450,000个式子,所以遇到很多的不可导点是正常现象。
针对上面的情形,课中给出的建议是:
(1)使用少量的数据点。因为含有不可导点损失函数的数据点越少,出现的不可导点就越少,在计算有限差值近似时越过不可导点的概率就越小;并且这还可以使得检查过程变得高效。
(2)谨慎设置步长 h 。步长值并不是越小越好,当h过小时,可能会遇到上面说的数值精度问题。如果梯度检查无法进行,可以尝试将 h 调到1e-4或者1e-6。
(3)需要注意梯度检查的时机。最好让神经网络学习一小段时间,等到损失函数开始下降的以后再进行梯度检查。因为如果从一开始就进行梯度检查,此时梯度可能正处于不正常的边界。
另外,梯度检查还需要注意的三点有:
(1)计算数据损失时注意正则化损失的影响,不要让正则化损失掩盖数据损失。
由于损失函数包括数据损失和正则化损失两部分,所以可能存在正则化损失吞没数据损失的风险。建议做法是:先关掉正则化部分,而是对数据损失做单独检查,然后再对正则化损失做单独检查。
对正则化做单独检查的方法有:1. 修改代码,去掉其中数据损失的部分; 2.提高正则化强度,确认其效果在梯度检查中能否忽略。
(2)注意随机失活和数据扩张的不确定影响。
这可能给计算梯度结果带来不确定的误差影响。如果关闭这些操作,则无法对它们进行梯度检查(例如随机失活的反向传播可能存在错误),所以更好的解决方法是在计算 f(x+h) 和 f(x-h) 前强制增加一个特定的随机种子,在计算解析梯度时也采取这个方法。
(3)检查少量的维度。
在实际应用中,神经网络可能有上百万的参数,在这种情况下只能检查部分维度。但需要注意的是,选取的参数应该从所有不同的参数中选取部分检查,避免出现从参数向量中随机选取出的参数可能只是偏置参数的情况。
参数调优的过程是费时费力的,所以在开始之前,下面的技巧是很有必要的:
(1)原文中“Look for correct loss at chance performance”,这里的chance performance指的是不是统计概率中的得分的意思(我不确定,如果有清楚的还望告知!)。 当使用小参数初始化时,确保得到的损失值与期望的损失值是一样的。最好的方式是单独对数据损失进行检查(正则化强度置零)。
(2)当增大正则化强度时,查看损失值是否跟着变大。
(3)在整个数据集进行训练之前,先在一个很小的数据集上进行训练(比如20个数据),并设置正则化强度为0,确保此时的损失值为0。只有这个检查通过,整个数据集的训练才有意义。
在神经网络训练过程中,有许多有用的参数(例如损失函数值,验证集和训练集的准确率,权重的更新比例等)需要监控,这些参数对于不同超参数的设置和调优具有指导意义。
上图2中,x轴表示周期。左面是不同学习率对应的损失函数值曲线;右图是一个典型的损失函数值随时间的变化曲线。从左图中,我们发现,学习率设置过高时,损失值并不单调了,而红色曲线对应的是较好的学习率。
另外,损失函数值的震荡程度还与批尺寸(batch size)有关:当批尺寸为1时,震荡相对会比较大;当批尺寸是整个数据集时,震荡会比较小,因为每个梯度的更新都在单调地优化损失函数(学习率过高除外)。
上图3中,蓝色的验证集曲线表明相比于训练集/验证集的准确率低了很多,两者中间的缝隙程度也能模型过拟合的程度。此时应该增大正则化强度(更大的权重惩罚,更多的随机失活等)或者收集更多的数据。 如果遇到验证集曲线和训练集曲线近乎重合的情况,说明模型容量不够大,此时应该通过增加参数的数量使得模型容量更大些。
这个之前课中也提过,这个参数指的是每次训练后有更新的权重占所有权重的比例。经验性的结论是这个比例应该在1e-3左右,如果小于此值,表明学习率可能设置的过小;如果大于此值,表明学习率可能设置的过大。
上图是将将神经网络的第一层权重可视化的例子。左边的特征充满了噪音,表明网络可能出现了以下问题:网络不收敛,学习率设置不恰当,正则化惩罚的权重过低等。右边的特征比较平滑,干净而且种类多,表明训练过程良好。
-----------第二部分:参数调优----------
优化算法是通过改善训练方式,来最小化(或最大化)损失函数的过程。优化算法分为两大类:
1. 一阶优化算法。为了计算多变量函数的导数,会用梯度取代导数,使用偏导数来计算梯度。
2. 二阶优化算法。 二阶优化算法使用二阶导数(也叫Hessian方法)优化损失函数。课中也提及了其迭代公式,但是由于其计算成本比较高,所以应用的并不广泛,不加说明了。
当可以使用反向传播计算解析梯度后,梯度能被用来进行更新参数的过程。课中提及了几种网络优化算法:梯度下降法,动量更新法,学习率退火法等。
(1)梯度下降法。 参数更新最简单的方式是沿着梯度负方向改变参数。假设参数向量为x ,其梯度为dx,更新形式为:
x += - learning_rate * dx
其中,learning_rate是之前说的学习率。
#批量梯度下降的实现:
while True:
weights_grad = evaluate_gradient(loss_fun, data, weights)
weights += - step_size * weights_grad # perform parameter update
#小批量梯度下降的实现
while True:
data_batch = sample_training_data(data, 256) # sample 256 examples
weights_grad = evaluate_gradient(loss_fun, data_batch, weights)
weights += - step_size * weights_grad # perform parameter update
#随机梯度下降的实现
while True:
data_batch = sample_training_data(data, 1) # use a single example
weights_grad = evaluate_gradient(loss_fun, data_batch, weights)
weights += - step_size * weights_grad # perform parameter update
使用梯度下降的挑战:(2)动量更新法。动量法或说具有动量的 SGD 有助于加速向量向着正确的梯度方向下降,加快收敛速度。
SGD方法中的高方差振荡会使得网络震荡,动量(Momentum)更新方法可以通过优化相关方向的训练和弱化无关方向的振荡,来加速SGD训练过程。动量更新有两种定义方法:一种是吴恩达提出的:定义一个动量,即是梯度的移动平均值。然后用它来更新网络的权重,公式如下:
式中 L 是损失函数,α 是学习率,β为动量项,一般取值0.9。另一种表达动量更新的方式是:
# Momentum update
v = mu * v - learning_rate * dx # integrate velocity,mu即上面的动量项
x += v # integrate position
Nesterov动量:当参数向量位于位置 x 时,由上面的代码可知,动量部分会通过 mu * v 稍微改变参数向量。可以将未来的近似位置x + mu * v 看做是“向前看”,并计算 x + mu * v处的梯度。视图如下:
x_ahead = x + mu * v
# evaluate dx_ahead (the gradient at x_ahead instead of at x)
v = mu * v - learning_rate * dx_ahead
x += v
(3)学习率退火算法
训练深度网络过程中,让学习率随着时间减弱是一种有效地方法。如果学习率很高,系统的动能就很大,参数向量跳动的就回厉害,不能够稳定到损失函数更深更窄的区域。通常,学习率退火有3种方式:
# Assume the gradient dx and parameter vector x
cache += dx**2
x += - learning_rate * dx / (np.sqrt(cache) + eps)
其中,变量
cache 的尺寸和梯度矩阵的尺寸是相同的,它跟踪每个参数的梯度平方和。由于,
cache 放在分母位置,所以在更新参数
x 时,高梯度值的权重的学习率会被减弱,而低梯度值的权重的学习率会被增强。
eps 用于平滑(一般设为1e-4到1e-8),可以防止出现除数为0的情况。
Adagrad的缺点是,在深度学习中单调的学习率通常过于激进并且过早地停止学习。
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]中的一个。与
Adagrad不同的是,学习率不会单调变小。
m = beta1*m + (1-beta1)*dx
v = beta2*v + (1-beta2)*(dx**2)
x += - learning_rate * m / (np.sqrt(v) + eps)
在引述论文中,推荐的参数值为:eps=1e-8, beta1=0.9, beta2=0.999。由于
m,v 两个矩阵初始为0,所以完整的
Adam算法还包含了偏置(bias)的矫正方法。一般,
Adam比
RMSProp要好,老师推荐的更新方法是
SGD+Nesterov动量方法,或
Adam方法。
http://cs231n.github.io/neural-networks-3/
https://zhuanlan.zhihu.com/p/21741716?refer=intelligentunit
https://zhuanlan.zhihu.com/p/21798784?refer=intelligentunit
http://blog.csdn.net/u012526120/article/details/49183279
https://zhuanlan.zhihu.com/p/27449596?utm_source=weibo&utm_medium=social