从零开始,一步一步学习caffe的使用,期间贯穿深度学习和调参的相关知识!
前面我们介绍了卷积神经网络中主流的数据层,卷积层,全连接层,池化层,激活函数层,归一化层,dropout层,softmax层。分析每一层的配置及意义的目的主要是为了便于设计出适合自己的网络。然后根据自己的任务需要定义合适的损失函数。当搭建出自己的网络并确定网络的损失函数后,下一个关键问题便是训练网络,训练网络的前提需要确定优化算法。下面我们针对常见的深度学习优化算法进行梳理:
SGD算法
介绍SGD算法之前,我们先通过一维与多维梯度下降理解其思想,然后介绍随机梯度下降,最后我们介绍工程中最常用的小批量随机梯度下降并给出对应的代码。
一维梯度下降
学习率
上述梯度下降算法中的η(取正数)叫做学习率或步长。需要注意的是,学习率过大可能会造成x迈过(overshoot
)最优解,甚至不断发散而无法收敛,如下图所示。
TODO
学习率的另一种理解方式:
多维梯度下降
随机梯度下降
小批量随机梯度下降
代码
# 小批量随机梯度下降。
def sgd(params, lr, batch_size):
for param in params:
param[:] = param - lr * param.grad / batch_size
总结
简而言之,批量梯度下降针对的是整个数据集,随机梯度下降针对的是当前样本点,而小批量随机梯度下降针对的是一个batch
样本数据。梯度下降计算量大,因为受到过多数据的牵制,收敛曲线对数据不敏感,不容易跳出陷入的局部极小点。随机梯度下降计算量小,但是只是针对当前一个样本数据,目光狭隘,受数据影响过大导致收敛曲线震荡过于严重。小批量梯度下降则是前面两者的中庸。计算量取决于batch
的大小,对批量的数据有一定的敏感性,收敛具有一定的震荡性,使得算法在搜索的过程中具有跳出当前局部极小点的潜力。这样,对于搜索到更好的局部极小点有帮助。
动量法
梯度下降的问题
上图中,红色三角形代表参数x的初始值。带箭头的线段表示每次迭代时参数的更新。由于目标函数在竖直方向(x2轴方向)上比在水平方向(x1轴方向)弯曲得更厉害,梯度下降迭代参数时会使参数在竖直方向比在水平方向移动更猛烈。因此,我们需要一个较小的学习率从而避免参数在竖直方向上overshoot
。这就造成了上图中参数向最优解移动速度的缓慢。
动量法思想
动量法的核心思想是:每次参数的迭代更新都吸收一部分上次更新的余势。使得迭代的速度可以明显的加快,如下图所示:
代码
# 动量法。
def sgd_momentum(params, vs, lr, mom, batch_size):
for param, v in zip(params, vs):
v[:] = mom * v + lr * param.grad / batch_size
param[:] -= v
Adagrad
介绍
思想
自动调整学习率,调整的思路是:在学习率的基础上除以一个S
开方,而S
取决于计算的小批量梯度g
,按元素平方后累加。这种自动更新学习率的方式,对低频出现的参数进行大的更新,而对高频出现的参数进行小的更新。因此,该方法适用于稀疏的数据。
代码
# Adagrad算法
def adagrad(params, sqrs, lr, batch_size):
eps_stable = 1e-7
for param, sqr in zip(params, sqrs):
g = param.grad / batch_size
sqr[:] += nd.square(g)
div = lr * g / nd.sqrt(sqr + eps_stable)
param[:] -= div
RMSProp
介绍
思想
RMSProp
为了解决Adagrad学习率不断单调下降的问题
实现的方式非常简单,RMSProp
只在Adagrad
的基础上修改了变量S
的更新方法:把累加改成了指数加权移动平均。因此,每个元素的学习率在迭代过程中既可能降低又可能升高。
代码
# RMSProp
def rmsprop(params, sqrs, lr, gamma, batch_size):
eps_stable = 1e-8
for param, sqr in zip(params, sqrs):
g = param.grad / batch_size
sqr[:] = gamma * sqr + (1. - gamma) * nd.square(g)
div = lr * g / nd.sqrt(sqr + eps_stable)
param[:] -= div
AdaDelta
介绍
思想
为解决Adagrad学习率不断单调下降的问题与RMSProp
相同采用指数加权移动平均代替累加。在此基础上,AdaDelta
舍弃了学习率参数,完全由输入数据进行决定!
代码
# Adadalta
def adadelta(params, sqrs, deltas, rho, batch_size):
eps_stable = 1e-5
for param, sqr, delta in zip(params, sqrs, deltas):
g = param.grad / batch_size
sqr[:] = rho * sqr + (1. - rho) * nd.square(g)
cur_delta = nd.sqrt(delta + eps_stable) / nd.sqrt(sqr + eps_stable) * g
delta[:] = rho * delta + (1. - rho) * cur_delta * cur_delta
param[:] -= cur_delta
Adam
介绍
思想
结合了RMSProp
与动量法
的思想。同时利用了更新梯度的一阶矩和二阶矩,并紧接着做了偏差修正使得估计无偏
。
代码
# Adam
def adam(params, vs, sqrs, lr, batch_size, t):
beta1 = 0.9
beta2 = 0.999
eps_stable = 1e-8
for param, v, sqr in zip(params, vs, sqrs):
g = param.grad / batch_size
v[:] = beta1 * v + (1. - beta1) * g
sqr[:] = beta2 * sqr + (1. - beta2) * nd.square(g)
v_bias_corr = v / (1. - beta1 ** t)
sqr_bias_corr = sqr / (1. - beta2 ** t)
div = lr * v_bias_corr / (nd.sqrt(sqr_bias_corr) + eps_stable)
param[:] = param - div
需要注意的是:我们梳理优化算法的过程中发现,除了SGD外,还有很多高级的优化算法,他们可以自学习学习率,对我们调整参数来说提供了一定的便利性。但是为什么深度学习调参的过程中还是大量使用SGD算法呢?我总结的原因是SGD算法虽然简单但是灵活可以调整的超参数较多,除了算法本身,我们还有一些手动调整学习率的方法。因此针对不同的问题,采用手调的SGD往往结果要比一些高级的优化算法效果还要好,原因是SGD通过手调参数可以做到不同问题,不同分析应对,因此,我们还是要将SGD算法做深入的了解,并在实战中学习调整参数的方法与经验!
An overview of gradient descent optimization algorithms
SGD算法比较(https://blog.slinuxer.com/2016/09/sgd-comparison)
动手学习深度学习——优化算法(http://zh.gluon.ai/chapter_optimization/index.html)