# 7.4 动量法
在7.2节(梯度下降和随机梯度下降)中我们提到,目标函数有关自变量的梯度代表了目标函数在自变量当前位置下降最快的方向。因此,梯度下降也叫作最陡下降(steepest descent)。在每次迭代中,梯度下降根据自变量当前位置,沿着当前位置的梯度更新自变量。然而,如果自变量的迭代方向仅仅取决于自变量当前位置,这可能会带来一些问题。
## 7.4.1 梯度下降的问题
让我们考虑一个输入和输出分别为二维向量`$ \boldsymbol{x} = [x_1, x_2]^\top $`和标量的目标函数`$ f(\boldsymbol{x})=0.1x_1^2+2x_2^2 $`。与7.2节中不同,这里将`$ x_1^2 $`系数从`$ 1 $` 减小到了`$ 0.1 $`。下面实现基于这个目标函数的梯度下降,并演示使用学习率为`$ 0.4 $`时自变量的迭代轨迹。
``` python
%matplotlib inline
import sys
sys.path.append("..")
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):
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
```
<div align=center>
<img width="300" src="images/7.4_output1.png"/>
</div>
可以看到,同一位置上,目标函数在竖直方向(`$ x_2 $`轴方向)比在水平方向(`$ x_1 $`轴方向)的斜率的绝对值更大。因此,给定学习率,梯度下降迭代自变量时会使自变量在竖直方向比在水平方向移动幅度更大。那么,我们需要一个较小的学习率从而避免自变量在竖直方向上越过目标函数最优解。然而,这会造成自变量在水平方向上朝最优解移动变慢。
下面我们试着将学习率调得稍大一点,此时自变量在竖直方向不断越过最优解并逐渐发散。
``` python
eta = 0.6
d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
```
输出:
```
epoch 20, x1 -0.387814, x2 -1673.365109
```
<div align=center>
<img width="300" src="images/7.4_output2.png"/>
</div>
## 7.4.2 动量法
动量法的提出是为了解决梯度下降的上述问题。由于小批量随机梯度下降比梯度下降更为广义,本章后续讨论将沿用7.3节(小批量随机梯度下降)中时间步$t$的小批量随机梯度`$ \boldsymbol{g}_t $`的定义。设时间步`$ t $`的自变量为`$ \boldsymbol{x}_t $`,学习率为`$ \eta_t $`。
在时间步`$ 0 $`,动量法创建速度变量`$ \boldsymbol{v}_0 $`,并将其元素初始化成0。在时间步`$ t>0 $`,动量法对每次迭代的步骤做如下修改:
```[tex]
\begin{aligned}
\boldsymbol{v}_t &\leftarrow \gamma \boldsymbol{v}_{t-1} + \eta_t \boldsymbol{g}_t, \\
\boldsymbol{x}_t &\leftarrow \boldsymbol{x}_{t-1} - \boldsymbol{v}_t,
\end{aligned}
```
其中,动量超参数`$ \gamma $`满足 `$ 0 \leq \gamma < 1 $`。当`$ \gamma=0 $`时,动量法等价于小批量随机梯度下降。
在解释动量法的数学原理前,让我们先从实验中观察梯度下降在使用动量法后的迭代轨迹。
``` python
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
```
<div align=center>
<img width="300" src="images/7.4_output3.png"/>
</div>
可以看到使用较小的学习率`$ \eta=0.4 $`和动量超参数`$ \gamma=0.5 $`时,动量法在竖直方向上的移动更加平滑,且在水平方向上更快逼近最优解。下面使用较大的学习率`$ \eta=0.6 $`,此时自变量也不再发散。
``` python
eta = 0.6
d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
```
输出:
```
epoch 20, x1 0.007188, x2 0.002553
```
<div align=center>
<img width="300" src="images/7.4_output4.png"/>
</div>
### 7.4.2.1 指数加权移动平均
为了从数学上理解动量法,让我们先解释一下指数加权移动平均(exponentially weighted moving average)。给定超参数`$ 0 \leq \gamma < 1 $`,当前时间步`$ t $`的变量`$ y_t $`是上一时间步`$ t-1 $`的变量`$ y_{t-1} $`和当前时间步另一变量`$ x_t $`的线性组合:
```[tex]
y_t = \gamma y_{t-1} + (1-\gamma) x_t.
```
我们可以对`$ y_t $`展开:
```[tex]
\begin{aligned}
y_t &= (1-\gamma) x_t + \gamma y_{t-1}\\
&= (1-\gamma)x_t + (1-\gamma) \cdot \gamma x_{t-1} + \gamma^2y_{t-2}\\
&= (1-\gamma)x_t + (1-\gamma) \cdot \gamma x_{t-1} + (1-\gamma) \cdot \gamma^2x_{t-2} + \gamma^3y_{t-3}\\
&\ldots
\end{aligned}
```
令`$ n = 1/(1-\gamma) $`,那么 `$ \left(1-1/n\right)^n = \gamma^{1/(1-\gamma)} $`。因为
```[tex]
\lim_{n \rightarrow \infty} \left(1-\frac{1}{n}\right)^n = \exp(-1) \approx 0.3679,
```
所以当`$ \gamma \rightarrow 1 $`时,`$ \gamma^{1/(1-\gamma)}=\exp(-1) $`,如`$ 0.95^{20} \approx \exp(-1) $`。如果把`$ \exp(-1) $`当作一个比较小的数,我们可以在近似中忽略所有含`$ \gamma^{1/(1-\gamma)}$`和比`$ \gamma^{1/(1-\gamma)} $`更高阶的系数的项。例如,当`$ \gamma=0.95 $`时,
```[tex]
y_t \approx 0.05 \sum_{i=0}^{19} 0.95^i x_{t-i}.
```
因此,在实际中,我们常常将`$ y_t $`看作是对最近`$ 1/(1-\gamma) $`个时间步的`$ x_t $`值的加权平均。例如,当`$ \gamma = 0.95 $`时,`$ y_t $`可以被看作对最近20个时间步的`$ x_t $`值的加权平均;当`$ \gamma = 0.9 $`时,`$ y_t $`可以看作是对最近10个时间步的`$ x_t $`值的加权平均。而且,离当前时间步`$ t $`越近的`$ x_t $`值获得的权重越大(越接近1)。
### 7.4.2.2 由指数加权移动平均理解动量法
现在,我们对动量法的速度变量做变形:
```[tex]
\boldsymbol{v}_t \leftarrow \gamma \boldsymbol{v}_{t-1} + (1 - \gamma) \left(\frac{\eta_t}{1 - \gamma} \boldsymbol{g}_t\right).
```
由指数加权移动平均的形式可得,速度变量`$ \boldsymbol{v}_t $`实际上对序列`$ \{\eta_{t-i}\boldsymbol{g}_{t-i} /(1-\gamma):i=0,\ldots,1/(1-\gamma)-1\} $`做了指数加权移动平均。换句话说,相比于小批量随机梯度下降,**动量法在每个时间步的自变量更新量近似于将最近`$ 1/(1-\gamma) $`个时间步的普通更新量(即学习率乘以梯度)做了指数加权移动平均后再除以`$ 1-\gamma $`**。所以,在动量法中,自变量在各个方向上的移动幅度不仅取决当前梯度,还取决于过去的各个梯度在各个方向上是否一致。在本节之前示例的优化问题中,所有梯度在水平方向上为正(向右),而在竖直方向上时正(向上)时负(向下)。这样,我们就可以使用较大的学习率,从而使自变量向最优解更快移动。
## 7.4.3 从零开始实现
相对于小批量随机梯度下降,动量法需要对每一个自变量维护一个同它一样形状的速度变量,且超参数里多了动量超参数。实现中,我们将速度变量用更广义的状态变量`states`表示。
``` python
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倍小批量梯度的加权平均。
> 注:个人认为这里不应该是“加权平均”而应该是“加权和”,因为根据7.4.2.2节分析,加权平均最后除以了$1-\gamma$,所以就相当于没有进行平均。
``` python
d2l.train_ch7(sgd_momentum, init_momentum_states(),
{'lr': 0.02, 'momentum': 0.5}, features, labels)
```
输出:
```
loss: 0.245518, 0.042304 sec per epoch
```
<div align=center>
<img width="300" src="images/7.4_output5.png"/>
</div>
将动量超参数`momentum`增大到0.9,这时依然可以看成是特殊的小批量随机梯度下降:其小批量随机梯度为最近10个时间步的10倍小批量梯度的加权平均。我们先保持学习率0.02不变。
> 同理,这里不应该是“加权平均”而应该是“加权和”。
``` python
d2l.train_ch7(sgd_momentum, init_momentum_states(),
{'lr': 0.02, 'momentum': 0.9}, features, labels)
```
输出:
```
loss: 0.252046, 0.095708 sec per epoch
```
<div align=center>
<img width="300" src="images/7.4_output6.png"/>
</div>
可见目标函数值在后期迭代过程中的变化不够平滑。直觉上,10倍小批量梯度比2倍小批量梯度大了5倍,我们可以试着将学习率减小到原来的1/5。此时目标函数值在下降了一段时间后变化更加平滑。
> 这也印证了刚刚的观点。
``` python
d2l.train_ch7(sgd_momentum, init_momentum_states(),
{'lr': 0.004, 'momentum': 0.9}, features, labels)
```
输出:
```
loss: 0.242905, 0.073496 sec per epoch
```
<div align=center>
<img width="300" src="images/7.4_output7.png"/>
</div>
## 7.4.4 简洁实现
在PyTorch中,只需要通过参数`momentum`来指定动量超参数即可使用动量法。
``` python
d2l.train_pytorch_ch7(torch.optim.SGD, {'lr': 0.004, 'momentum': 0.9},
features, labels)
```
输出:
```
loss: 0.253280, 0.060247 sec per epoch
```
<div align=center>
<img width="300" src="images/7.4_output8.png"/>
</div>
## 小结
* 动量法使用了指数加权移动平均的思想。它将过去时间步的梯度做了加权平均,且权重按时间步指数衰减。
* 动量法使得相邻时间步的自变量更新在方向上更加一致。
-----------
> 注:除代码外本节与原书此节基本相同,[原书传送门](https://zh.d2l.ai/chapter_optimization/momentum.html)
- Home
- Introduce
- 1.深度学习简介
- 深度学习简介
- 2.预备知识
- 2.1环境配置
- 2.2数据操作
- 2.3自动求梯度
- 3.深度学习基础
- 3.1 线性回归
- 3.2 线性回归的从零开始实现
- 3.3 线性回归的简洁实现
- 3.4 softmax回归
- 3.5 图像分类数据集(Fashion-MINST)
- 3.6 softmax回归的从零开始实现
- 3.7 softmax回归的简洁实现
- 3.8 多层感知机
- 3.9 多层感知机的从零开始实现
- 3.10 多层感知机的简洁实现
- 3.11 模型选择、反向传播和计算图
- 3.12 权重衰减
- 3.13 丢弃法
- 3.14 正向传播、反向传播和计算图
- 3.15 数值稳定性和模型初始化
- 3.16 实战kaggle比赛:房价预测
- 4 深度学习计算
- 4.1 模型构造
- 4.2 模型参数的访问、初始化和共享
- 4.3 模型参数的延后初始化
- 4.4 自定义层
- 4.5 读取和存储
- 4.6 GPU计算
- 5 卷积神经网络
- 5.1 二维卷积层
- 5.2 填充和步幅
- 5.3 多输入通道和多输出通道
- 5.4 池化层
- 5.5 卷积神经网络(LeNet)
- 5.6 深度卷积神经网络(AlexNet)
- 5.7 使用重复元素的网络(VGG)
- 5.8 网络中的网络(NiN)
- 5.9 含并行连结的网络(GoogLeNet)
- 5.10 批量归一化
- 5.11 残差网络(ResNet)
- 5.12 稠密连接网络(DenseNet)
- 6 循环神经网络
- 6.1 语言模型
- 6.2 循环神经网络
- 6.3 语言模型数据集(周杰伦专辑歌词)
- 6.4 循环神经网络的从零开始实现
- 6.5 循环神经网络的简单实现
- 6.6 通过时间反向传播
- 6.7 门控循环单元(GRU)
- 6.8 长短期记忆(LSTM)
- 6.9 深度循环神经网络
- 6.10 双向循环神经网络
- 7 优化算法
- 7.1 优化与深度学习
- 7.2 梯度下降和随机梯度下降
- 7.3 小批量随机梯度下降
- 7.4 动量法
- 7.5 AdaGrad算法
- 7.6 RMSProp算法
- 7.7 AdaDelta
- 7.8 Adam算法
- 8 计算性能
- 8.1 命令式和符号式混合编程
- 8.2 异步计算
- 8.3 自动并行计算
- 8.4 多GPU计算
- 9 计算机视觉
- 9.1 图像增广
- 9.2 微调
- 9.3 目标检测和边界框
- 9.4 锚框
- 10 自然语言处理
- 10.1 词嵌入(word2vec)
- 10.2 近似训练
- 10.3 word2vec实现
- 10.4 子词嵌入(fastText)
- 10.5 全局向量的词嵌入(Glove)
- 10.6 求近义词和类比词
- 10.7 文本情感分类:使用循环神经网络
- 10.8 文本情感分类:使用卷积网络
- 10.9 编码器--解码器(seq2seq)
- 10.10 束搜索
- 10.11 注意力机制
- 10.12 机器翻译