参数的更新
神经网络的学习目的是找到使损失函数的值尽可能小的参数,解决这个问题的过程称为最优化(optimization)
梯度下降法在每次迭代时,需要使用所有的训练数据,这给求解大规模数据的优化问题带来了挑战。
1.SGD
使用参数的梯度,沿梯度方向更新参数,并重复这个步骤多次,从而逐渐靠
近最优参数,这个过程称为随机梯度下降法(stochastic gradient descent),
简称SGD。SGD是一个简单的方法,不过比起胡乱地搜索参数空间,也算是“聪
明”的方法。
随机梯度下降用单个训练样本的损失来近似平均损失。随机梯度下降法用单个训练数据即可对模型参数进行一次更新,大大加快了收敛速率。该方法也非常适用于数据源源不断到来的在线更新场景。
SGD的策略是:朝着当前所在位置的坡度最大的方向前进。
用右边的值更新左边的值。
SGD是朝着梯度方向只前进一定距离的方法。
class SGD:
def __init__(self,lr=0.01):
self.lr = lr
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr*grads[key]
参数 params 和 grads (与之前的神经网络的实现一样)是字典型变量,按 params['W1'] 、 grads['W1'] 的形式,分别保存了权重参数和它们的梯度。
SGD的缺点
如果函数的形状非均向,比如呈延伸状,搜索的路径就会非常低效。SGD低效的根本原因是,梯度的方向并没有指向最小值的方向。
1.1 小批量梯度下降法
为了降低随机梯度的方差,从而使得迭代算法更加稳定,也为了充分利用高度优化的矩阵运算操作,在实际应用中我们会同时处理若干训练数据,该方法被称为小批量梯度下降法(Mini-Batch Gradient Descent)。
注意:
(1)如何选取参数批量数据的个数m?在不同的应用中,最优的m通常会不一样,需要通过调参选取。一般m取2的幂次时能充分利用矩阵运算操作,所以可以在2的幂次中挑选最优的取值,例如32、64、128、256等。
(2)如何挑选m个训练数据?为了避免数据的特定顺序给算法收敛带来的影
响,一般会在每次遍历训练数据之前,先对所有的数据进行随机排序,然后在每
次迭代时按顺序挑选m个训练数据直至遍历完所有的数据。
(3)如何选取学习速率α?为了加快收敛速率,同时提高求解精度,通常会
采用衰减学习速率的方案:一开始算法采用较大的学习速率,当误差曲线进入平
台期后,减小学习速率做更精细的调整。最优的学习速率方案也通常需要调参才
能得到。
随机梯度下降无法准确察觉出梯度的微笑变化,就很可能绕弯路或者停下来。
解决之道:惯性保持和环境感知
2.Momentum
想象一下纸团在山谷和鞍点处的运动轨迹,在山谷中纸团受重力作用沿山道滚下,两边是不规则的山壁,纸团不可避免地撞在山壁,由于质量小受山壁弹力的干扰大,从一侧山壁反弹回来撞向另一侧山壁,结果来回震荡地滚下;如果当纸团来到鞍点的一片平坦之地时,还是由于质量小,速度很快减为零。纸团的情况和随机梯度下降法遇到的问题简直如出一辙。直观地,如果换成一个铁球,当沿山谷滚下时,不容易受到途中旁力的干扰,轨迹会更稳更直;当来到鞍点中心处,在惯性作用下继续前行,从而有机会冲出这片平坦的陷阱。因此,有了动量方法,模型参数的迭代公式为
(6.3)
(6.4)
当前梯度相当于加速度,当前的v要依赖于上一时刻的v和加速度。这里新出现了一个变量v,对应物理上的速度。式(6.3)表示了物体在梯度方向上受力,在这个力的作用下,物体的速度增加这一物理法则。
class Momentum:
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] = self.momentum*self.v[key] - self.lr*grads[key]
params[key] += self.v[key]
沿山谷滚下的铁球,会受到沿坡道向下的力和与左右山壁碰撞的弹力。向下的力
稳定不变,产生的动量不断累积,速度越来越快;左右的弹力总是在不停切换,
动量累积的结果是相互抵消,自然减弱了球的来回震荡。
3.AdaGrad
惯性的获得基于历史信息,除了从历史信息获得惯性,还能对周围环境进行感知。
我们希望更新频率低的参数可以拥有较大的更新步幅,而更新频率高的参数的步幅可以减小。
AdaGrad采用了学习率衰减的思想,为参数的每个元素适当地调整学习率。
AdaGrad方法采用“历史梯度平方和”来衡量不同参数的梯度的稀疏性,取值越小表明越稀疏。
其中 表示(t+1)时刻的参数向量 的第i个参数,表示k时刻的梯度向量的第i个维度(方向)。另外,分母中求和的形式实现了退火过程,这是很多优化技术中常见的策略,意味着随着时间推移,是以前所有梯度的平方和,也就是说,以前的梯度大的(变动大的),它的学习速率 越来越小,从而保证了算法的最终收敛。
AdaGrad会记录过去所有梯度的平方和。因此,学习越深入,更新的幅度就越小。实际上,如果无止境地学习,更新量就会变为0,完全不再更新。为了改善这个问题,可以使用RMSProp 方法。RMSProp方法并不是将过去所有的梯度一视同仁地相加,而是逐渐地遗忘过去的梯度,在做加法运算时将新梯度的信息更多地反映出来。这种操作从专业上讲,称为“指数移动平均”,呈指数函数式地减小过去的梯度的尺度。
h保存了以前的所有梯度值的平方和。
class AdaGrad:
def __init__(self,lr=0.01):
self.lr = lr
self.h = None
def update(self,params,grads):
if self.h is None:
self.h = {}
for key,val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] += grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
4. Adam
Momentum参照小球在碗中滚动的物理规则进行移动,AdaGrad为参数的每个元素适当地调整更新步伐。Adam 方法将两者结合。
一方面,Adam记录梯度的一阶矩(first moment),即过往梯度与当前梯度的平均,这体现了惯性保持;另一方面,Adam还记录梯度的二阶矩(second moment),即过往梯度平方与当前梯度平方的平均,这类似AdaGrad方法,体现了环境感知能力,为不同参数产生自适应的学习速率。一阶矩和二阶矩采用类似于滑动窗口内求平均的思想进行融合,即当前梯度和近一段时间内梯度的平均值,时间久远的梯度对当前平均值的贡献呈指数衰减。
其中β 1 ,β 2 为衰减系数,m t 是一阶矩,v t 是二阶矩。
如何理解一阶矩和二阶矩呢?一阶矩相当于估计 :由于当下梯度 是随机
采样得到的估计结果,因此更关注它在统计意义上的期望;二阶矩相当于估计
,这点与AdaGrad方法不同,不是 从开始到现在的加和,而是它的期望。它
们的物理意义是,当||m t ||大且v t 大时,梯度大且稳定,这表明遇到一个明显的大
坡,前进方向明确;当||m t ||趋于零且v t 大时,梯度不稳定,表明可能遇到一个峡
谷,容易引起反弹震荡;当||m t ||大且v t 趋于零时,这种情况不可能出现;当||m t ||趋于零且v t 趋于零时,梯度趋于零,可能到达局部最低点,也可能走到一片坡度极缓的平地,此时要避免陷入平原(plateau)。另外,Adam方法还考虑了m t ,v t 在零初始值情况下的偏置矫正。具体来说,Adam的更新公式为
其中,,
参考书籍:《百面机器学习》、《深度学习入门 基于Python的理论与实现》