本文是对常见优化器的总结。可以按照下面的路径来学习:GD->SGD->移动指数平均->动量SGD->RMSprop->Adam。
Gradient Descent
梯度下降 (Gradient Descent)算法可以用下面的公式表示:
θ=θ−η⋅∇θJ(θ)
其中,θ 表示算法的参数, η是梯度下降的步长(也称为学习率),J 表示损失函数,∇θJ(θ) 表示损失函数对参数 θ 的偏导数。
梯度下降需要对整个训练集进行一次反向传播计算梯度后再进行参数更新,这个操作在深度学习是非常昂贵和不现实的。
Stochastic Gradient Descent
随机梯度下降 (Stochastic Gradient Descent,SGD)在使用梯度更新参数时刚好相反,是另一个极端,每次只使用整个训练集中的一个样本,这样带来的问题是:参数更新间具有很大的方差,即下降的方向会有很大的波动。因此,出现了折中的方法:mini-batch SGD,即采样一个batch来更新梯度,也是深度学习中最常见的优化方法了。
SGD是很多经典网络的优化方法,他们反而没有采用类似Adam等看起来更新颖的优化方法,原因可能是SGD还是非常有效可以找到更好的极值点的。SGD一旦陷入了鞍点(局部极值点),就很难再逃离鞍点了。
SGD with Momentum
所谓动量(Momentum),就是在梯度的更新项可以保留更多的历史梯度信息,如果历史梯度在某个方向上下降很快,那么即使这一次的梯度较小,依然可以以较大的梯度进行这次更新,所谓积累了动量。
动量就是以指数移动平均的方式更新梯度:
vt=γvt−1+∇θJ(θ)θ=θ−η⋅vt
其中,vt 就是动量值。
AdaGrad
SGD with Momentum优化算法是对SGD在梯度上进行了优化,是梯度更新的方差更小。但是对于所有的参数更新,采用相同的学习率。
AdaGrad试图解决稀疏特征的参数更新,通过的是自动调整学习率。
只有在这些不常见的特征出现时,与其相关的参数才会得到有意义的更新。如果采用相同的学习率,常见特征的参数相当迅速地收敛到最佳值,而对于不常见的特征,没有足够的观测保证更新到最佳。一种解决方法是记录特征的出现,允许对每一个参数动态地更新它的学习率。
具体来说,AdaGrad算法使用 st 表示过去累加的梯度的方差,并除以均方差来调整学习率 η 来更新参数:
stθ=st−1+∇θJ(θ)2=θ−st+ϵη⋅∇θJ(θ)
我们可以发现,AdaGrad算法中st可能会越来越来大, st+ϵη越来越小,最后导致该参数的更新几乎停止。
RMSprop
Hinton在某个课程上提出了RMSprop这个改进,针对AdaGrad可能出现的累计梯度方差爆炸的问题,使用移动指数平均来更新累加梯度方差:
stθ←γst−1+(1−γ)∇θJ(θ)2←θ−st+ϵη⋅∇θJ(θ)
ADAM
ADAM不是二阶优化器,二阶优化算法需要使用Hessian矩阵,ADAM中使用的是二阶矩!
最后到了ADAM算法3,是对前面优化算法的有点的集成。ADAM算法的优缺点:
- 1.收敛速度更快
- 2.对于稀疏数据具有优势,因为可以自适应学习率
- 3.可能不收敛,可能错过全局最优解
关键组成部分:
- 1.使用移动指数平均来更新动量和梯度方差(梯度的二阶矩):
vtst←β1vt−1+(1−β1)∇θJ(θ),←β2st−1+(1−β2)∇θJ(θ)2
其中,常见设置是β1=0.9和β2=0.999,也就是说,方差的估计比动量的估计移动得远远更慢。另外,如果设置v0=0,s0=0,在初始阶段,带来的动量和梯度的估计会有非常大的偏差,会引出一个技巧 Bia Correction4。
v^t=1−β1tvt and s^t=1−β2tst
其中, t表示t次幂。
∇θJ(θ)′=s^t+ϵηv^t
其中的注意的是,下面的均方差计算,ϵ并没有放在开根号计算中,与是RMPSprop的不同,据说这样实际的表现更好一丢丢。另外, ′不是导数,而是对梯度的估计的意思。
θ←θ−gt′
代码:
def adam(params, states, hyperparams):
beta1, beta2, eps = 0.9, 0.999, 1e-6
for p, (v, s) in zip(params, states):
v[:] = beta1 * v + (1 - beta1) * p.grad
s[:] = beta2 * s + (1 - beta2) * np.square(p.grad)
v_bias_corr = v / (1 - beta1 ** hyperparams['t'])
s_bias_corr = s / (1 - beta2 ** hyperparams['t'])
p[:] -= hyperparams['lr'] * v_bias_corr / (np.sqrt(s_bias_corr) + eps)