1. 梯度下降中的问题
%matplotlib inline import sys import d2lzh_pytorch as d2l import torch eta = 0.4 # 学习率 def f_2d(x1, x2): return 0.1 * x1 ** 2 + 2 * x2 ** 2 def gd_2d(x1, x2, s1, s2): # 自变量更新x-eta*dx return (x1 - eta * 0.2 * x1, x2 - eta * 4 * x2, 0, 0) d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
输出:
epoch 20, x1 -0.943467, x2 -0.000073
可以看到,同一位置上,目标函数在竖直方向(x2轴方向)比在水平方向(x1轴方向)的斜率的绝对值更大。因此,给定学习率,梯度下降迭代自变量时会使自变量在竖直方向比在水平方向移动幅度更大。那么,我们需要一个较小的学习率从而避免自变量在竖直方向上越过目标函数最优解。然而,这会造成自变量在水平方向上朝最优解移动变慢。
下面我们试着将学习率调得稍大一点,此时自变量在竖直方向不断越过最优解并逐渐发散。
eta = 0.6 d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
输出:
epoch 20, x1 -0.387814, x2 -1673.365109
2. 动量法介绍及原理
我们先观察一下梯度下降在使用动量法后的迭代轨迹。
def momentum_2d(x1, x2, v1, v2): v1 = gamma * v1 + eta * 0.2 * x1 v2 = gamma * v2 + eta * 4 * x2 return x1 - v1, x2 - v2, v1, v2 eta, gamma = 0.4, 0.5 d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
输出:
epoch 20, x1 -0.062843, x2 0.001202
可以看到使用较小的学习率η=0.4和动量超参数γ=0.5时,动量法在竖直方向上的移动更加平滑,且在水平方向上更快逼近最优解。下面使用较大的学习率η=0.6,此时自变量也不再发散。
eta = 0.6 d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
输出:
epoch 20, x1 0.007188, x2 0.002553
2.1 动量法的数学解释—指数加权移动平均
2.2 由指数加权移动平均理解动量法
现在,我们对动量法的速度变量做变形:
3. 从零实现动量法
相对于小批量随机梯度下降,动量法需要对每一个自变量维护一个同它一样形状的速度变量,且超参数里多了动量超参数。实现中,我们将速度变量用更广义的状态变量states
表示。
features, labels = d2l.get_data_ch7() def init_momentum_states(): v_w = torch.zeros((features.shape[1], 1), dtype=torch.float32) v_b = torch.zeros(1, dtype=torch.float32) return (v_w, v_b) def sgd_momentum(params, states, hyperparams): for p, v in zip(params, states): v.data = hyperparams['momentum'] * v.data + hyperparams['lr'] * p.grad.data p.data -= v.data
我们先将动量超参数momentum
设0.5,这时可以看成是特殊的小批量随机梯度下降:其小批量随机梯度为最近2个时间步的2倍小批量梯度的加权平均。
d2l.train_ch7(sgd_momentum, init_momentum_states(), {'lr': 0.02, 'momentum': 0.5}, features, labels)
输出:
loss: 0.245518, 0.042304 sec per epoch
将动量超参数momentum
增大到0.9,这时依然可以看成是特殊的小批量随机梯度下降:其小批量随机梯度为最近10个时间步的10倍小批量梯度的加权平均。我们先保持学习率0.02不变。
d2l.train_ch7(sgd_momentum, init_momentum_states(), {'lr': 0.02, 'momentum': 0.9}, features, labels)
输出:
loss: 0.252046, 0.095708 sec per epoch
可见目标函数值在后期迭代过程中的变化不够平滑。直觉上,10倍小批量梯度比2倍小批量梯度大了5倍,我们可以试着将学习率减小到原来的1/5。此时目标函数值在下降了一段时间后变化更加平滑。
d2l.train_ch7(sgd_momentum, init_momentum_states(), {'lr': 0.004, 'momentum': 0.9}, features, labels)
输出:
loss: 0.242905, 0.073496 sec per epoch
4. 基于Pytorch简洁实现动量法
在PyTorch中,只需要通过参数momentum
来指定动量超参数即可使用动量法。
d2l.train_pytorch_ch7(torch.optim.SGD, {'lr': 0.004, 'momentum': 0.9}, features, labels)
输出:
loss: 0.253280, 0.060247 sec per epoch
总结
- 动量法使用了指数加权移动平均的思想。它将过去时间步的梯度做了加权平均,且权重按时间步指数衰减。
- 动量法使得相邻时间步的自变量更新在方向上更加一致。