文章目录

  • 动量法
    • 1. 梯度下降的问题
    • 2. 动量法
      • 2.1 指数加权移动平均
      • 2.2 由指数加权移动平均理解动量法
    • 3. 从零开始实现
    • 4. 简洁实现
    • 小结

动量法

目标函数有关自变量的梯度代表了目标函数在自变量当前位置下降最快的方向。因此,梯度下降也叫作最陡下降(steepest descent)。在每次迭代中,梯度下降根据自变量当前位置,沿着当前位置的梯度更新自变量。然而,如果自变量的迭代方向仅仅取决于自变量当前位置,这可能会带来一些问题。

1. 梯度下降的问题

让我们考虑一个输入和输出分别为二维向量x=[x1,x2]⊤\boldsymbol{x} = [x_1, x_2]^\topx=[x1​,x2​]⊤和标量的目标函数f(x)=0.1x12+2x22f(\boldsymbol{x})=0.1x_1^2+2x_2^2f(x)=0.1x12​+2x22​。下面实现基于这个目标函数的梯度下降,并演示使用学习率为0.40.40.4时自变量的迭代轨迹。

%matplotlib inline
import sys
import time
sys.path.append("..")
import torch
import numpy as np
import d2lzh_pytorch as d21
import matplotlib.pyplot as plteta = 0.4
print(torch.__version__)
def f_2d(x1, x2): #定义函数f(x)return 0.1 * x1 ** 2 + 2 * x2 ** 2def gd_2d(x1, x2, s1, s2):  # 更新梯度return (x1 - eta * 0.2 * x1, x2 - eta * 4 * x2, 0, 0)
def train_2d(trainer):   #梯度下降x1, x2, s1, s2 = -5, -2, 0, 0 results = [(x1, x2)]for i in range(20):x1, x2, s1, s2 = trainer(x1, x2, s1, s2)results.append((x1, x2))print('epoch %d, x1 %f, x2 %f' % (i + 1, x1, x2))return results
def show_trace_2d(f, results):  # 展示结果plt.plot(*zip(*results), '-o', color='#ff7f0e')x1, x2 = np.meshgrid(np.arange(-5.5, 1.0, 0.1), np.arange(-3.0, 1.0, 0.1))plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')plt.xlabel('x1')plt.ylabel('x2')show_trace_2d(f_2d, train_2d(gd_2d))

输出:

epoch 20, x1 -0.943467, x2 -0.000073

可以看到,同一位置上,目标函数在竖直方向(x2x_2x2​轴方向)比在水平方向(x1x_1x1​轴方向)的斜率的绝对值更大。因此,给定学习率,梯度下降迭代自变量时会使自变量在竖直方向比在水平方向移动幅度更大。那么,我们需要一个较小的学习率从而避免自变量在竖直方向上越过目标函数最优解。然而,这会造成自变量在水平方向上朝最优解移动变慢。

下面我们试着将学习率调得稍大一点,此时自变量在竖直方向不断越过最优解并逐渐发散。

eta = 0.6
show_trace_2d(f_2d, d2l.train_2d(gd_2d))

输出:

epoch 20, x1 -0.387814, x2 -1673.365109

2. 动量法

动量法的提出是为了解决梯度下降的上述问题。由于小批量随机梯度下降比梯度下降更为广义,设时间步ttt的自变量为xt\boldsymbol{x}_txt​,学习率为ηt\eta_tηt​。
在时间步000,动量法创建速度变量v0\boldsymbol{v}_0v0​,并将其元素初始化成0。在时间步t>0t>0t>0,动量法对每次迭代的步骤做如下修改:

vt←γvt−1+ηtgt,xt←xt−1−vt,\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} vt​xt​​←γvt−1​+ηt​gt​,←xt−1​−vt​,​

其中,动量超参数γ\gammaγ满足0≤γ<10 \leq \gamma < 10≤γ<1。当γ=0\gamma=0γ=0时,动量法等价于小批量随机梯度下降。

在解释动量法的数学原理前,让我们先从实验中观察梯度下降在使用动量法后的迭代轨迹。

def momentum_2d(x1, x2, v1, v2):v1 = gamma * v1 + eta * 0.2 * x1v2 = gamma * v2 + eta * 4 * x2return x1 - v1, x2 - v2, v1, v2eta, gamma = 0.4, 0.5
show_trace_2d(f_2d, d2l.train_2d(momentum_2d))

输出:

epoch 20, x1 -0.062843, x2 0.001202

可以看到使用较小的学习率η=0.4\eta=0.4η=0.4和动量超参数γ=0.5\gamma=0.5γ=0.5时,动量法在竖直方向上的移动更加平滑,且在水平方向上更快逼近最优解。下面使用较大的学习率η=0.6\eta=0.6η=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 指数加权移动平均

为了从数学上理解动量法,让我们先解释一下指数加权移动平均(exponentially weighted moving average)。给定超参数0≤γ<10 \leq \gamma < 10≤γ<1,当前时间步ttt的变量yty_tyt​是上一时间步t−1t-1t−1的变量yt−1y_{t-1}yt−1​和当前时间步另一变量xtx_txt​的线性组合:

yt=γyt−1+(1−γ)xt.y_t = \gamma y_{t-1} + (1-\gamma) x_t.yt​=γyt−1​+(1−γ)xt​.

我们可以对yty_tyt​展开:

yt=(1−γ)xt+γyt−1=(1−γ)xt+(1−γ)⋅γxt−1+γ2yt−2=(1−γ)xt+(1−γ)⋅γxt−1+(1−γ)⋅γ2xt−2+γ3yt−3…\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} yt​​=(1−γ)xt​+γyt−1​=(1−γ)xt​+(1−γ)⋅γxt−1​+γ2yt−2​=(1−γ)xt​+(1−γ)⋅γxt−1​+(1−γ)⋅γ2xt−2​+γ3yt−3​…​

令n=1/(1−γ)n = 1/(1-\gamma)n=1/(1−γ),那么 (1−1/n)n=γ1/(1−γ)\left(1-1/n\right)^n = \gamma^{1/(1-\gamma)}(1−1/n)n=γ1/(1−γ)。因为

lim⁡n→∞(1−1n)n=exp⁡(−1)≈0.3679,\lim_{n \rightarrow \infty} \left(1-\frac{1}{n}\right)^n = \exp(-1) \approx 0.3679,n→∞lim​(1−n1​)n=exp(−1)≈0.3679,

所以当γ→1\gamma \rightarrow 1γ→1时,γ1/(1−γ)=exp⁡(−1)\gamma^{1/(1-\gamma)}=\exp(-1)γ1/(1−γ)=exp(−1),如0.9520≈exp⁡(−1)0.95^{20} \approx \exp(-1)0.9520≈exp(−1)。如果把exp⁡(−1)\exp(-1)exp(−1)当作一个比较小的数,我们可以在近似中忽略所有含γ1/(1−γ)\gamma^{1/(1-\gamma)}γ1/(1−γ)和比γ1/(1−γ)\gamma^{1/(1-\gamma)}γ1/(1−γ)更高阶的系数的项。例如,当γ=0.95\gamma=0.95γ=0.95时,

yt≈0.05∑i=0190.95ixt−i.y_t \approx 0.05 \sum_{i=0}^{19} 0.95^i x_{t-i}.yt​≈0.05i=0∑19​0.95ixt−i​.

因此,在实际中,我们常常将yty_tyt​看作是对最近1/(1−γ)1/(1-\gamma)1/(1−γ)个时间步的xtx_txt​值的加权平均。例如,当γ=0.95\gamma = 0.95γ=0.95时,yty_tyt​可以被看作对最近20个时间步的xtx_txt​值的加权平均;当γ=0.9\gamma = 0.9γ=0.9时,yty_tyt​可以看作是对最近10个时间步的xtx_txt​值的加权平均。而且,离当前时间步ttt越近的xtx_txt​值获得的权重越大(越接近1)。

2.2 由指数加权移动平均理解动量法

现在,我们对动量法的速度变量做变形:

vt←γvt−1+(1−γ)(ηt1−γgt).\boldsymbol{v}_t \leftarrow \gamma \boldsymbol{v}_{t-1} + (1 - \gamma) \left(\frac{\eta_t}{1 - \gamma} \boldsymbol{g}_t\right). vt​←γvt−1​+(1−γ)(1−γηt​​gt​).

由指数加权移动平均的形式可得,速度变量vt\boldsymbol{v}_tvt​实际上对序列{ηt−igt−i/(1−γ):i=0,…,1/(1−γ)−1}\{\eta_{t-i}\boldsymbol{g}_{t-i} /(1-\gamma):i=0,\ldots,1/(1-\gamma)-1\}{ηt−i​gt−i​/(1−γ):i=0,…,1/(1−γ)−1}做了指数加权移动平均。换句话说,相比于小批量随机梯度下降,动量法在每个时间步的自变量更新量近似于将最近1/(1−γ)1/(1-\gamma)1/(1−γ)个时间步的普通更新量(即学习率乘以梯度)做了指数加权移动平均后再除以1−γ1-\gamma1−γ。所以,在动量法中,自变量在各个方向上的移动幅度不仅取决当前梯度,还取决于过去的各个梯度在各个方向上是否一致。在本节之前示例的优化问题中,所有梯度在水平方向上为正(向右),而在竖直方向上时正(向上)时负(向下)。这样,我们就可以使用较大的学习率,从而使自变量向最优解更快移动。

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.datap.data -= v.data

定义线性模型和损失函数

def linreg(X, w, b): # 定义线性模型return torch.mm(X, w) + bdef squared_loss(y_hat, y):  # 定义损失函数# 注意这里返回的是向量, 另外, pytorch里的MSELoss并没有除以 2return ((y_hat - y.view(y_hat.size())) ** 2) / 2

下面我们来实现训练模型

def train_ch7(optimizer_fn, states, hyperparams, features, labels,batch_size=10, num_epochs=2):# 初始化模型net, loss = linreg, squared_lossw = torch.nn.Parameter(torch.tensor(np.random.normal(0, 0.01, size=(features.shape[1], 1)), dtype=torch.float32),requires_grad=True)b = torch.nn.Parameter(torch.zeros(1, dtype=torch.float32), requires_grad=True)def eval_loss():return loss(net(features, w, b), labels).mean().item()ls = [eval_loss()]data_iter = torch.utils.data.DataLoader(torch.utils.data.TensorDataset(features, labels), batch_size, shuffle=True)for _ in range(num_epochs):start = time.time()for batch_i, (X, y) in enumerate(data_iter):l = loss(net(X, w, b), y).mean()  # 使用平均损失# 梯度清零if w.grad is not None:w.grad.data.zero_()b.grad.data.zero_()l.backward()optimizer_fn([w, b], states, hyperparams)  # 迭代模型参数if (batch_i + 1) * batch_size % 100 == 0:ls.append(eval_loss())  # 每100个样本记录下当前训练误差# 打印结果和作图print('loss: %f, %f sec per epoch' % (ls[-1], time.time() - start))plt.plot(np.linspace(0, num_epochs, len(ls)), ls)plt.xlabel('epoch')plt.ylabel('loss')

我们先将动量超参数momentum设0.5,这时可以看成是特殊的小批量随机梯度下降:其小批量随机梯度为最近2个时间步的2倍小批量梯度的加权平均。

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不变。

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。此时目标函数值在下降了一段时间后变化更加平滑。

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中,只需要通过参数momentum来指定动量超参数即可使用动量法。

d2l.train_pytorch_ch7(torch.optim.SGD, {'lr': 0.004, 'momentum': 0.9},features, labels)

输出:

loss: 0.253280, 0.060247 sec per epoch

小结

  • 动量法使用了指数加权移动平均的思想。它将过去时间步的梯度做了加权平均,且权重按时间步指数衰减。
  • 动量法使得相邻时间步的自变量更新在方向上更加一致。

pytorch学习笔记(三十五):Momentum相关推荐

  1. pytorch学习笔记(十五):模型构造

    文章目录 1. 继承Module类来构造模型 2. Module的子类 2.1 Sequential类 2.2 ModuleList类 2.3 ModuleDict类 3. 构造复杂的模型 小结 这里 ...

  2. JavaScript学习笔记(十五)

    JavaScript学习笔记(十五) 事件 事件是DOM(文档对象模型)的一部分.事件流就是事件发生顺序,这是IE和其他浏览器在事件支持上的主要差别. 一.事件流 1.冒泡型事件 IE上的解决方案就是 ...

  3. OpenCV学习笔记(十五):图像仿射变换:warpAffine(),getRotationMatrix2D()

    OpenCV学习笔记(十五):图像仿射变换:warpAffine(),getRotationMatrix2D() 一个任意的仿射变换都能表示为乘以一个矩阵(线性变换)接着再加上一个向量(平移)的形式. ...

  4. Mr.J-- jQuery学习笔记(三十二)--jQuery属性操作源码封装

    扫码看专栏 jQuery的优点 jquery是JavaScript库,能够极大地简化JavaScript编程,能够更方便的处理DOM操作和进行Ajax交互 1.轻量级 JQuery非常轻巧 2.强大的 ...

  5. cortex_m3_stm32嵌入式学习笔记(十五):待机唤醒实验(WK_UP外部中断)

    cortex_m3_stm32嵌入式学习笔记(十五):待机唤醒实验(WK_UP外部中断) https://blog.csdn.net/qq_16255321/article/details/43086 ...

  6. tensorflow学习笔记(三十二):conv2d_transpose (解卷积)

    tensorflow学习笔记(三十二):conv2d_transpose ("解卷积") deconv解卷积,实际是叫做conv_transpose, conv_transpose ...

  7. MATLAB学习笔记(十五)

    MATLAB学习笔记(十五) 一.非线性方程求解与函数极值计算 1.1 非线性方程数值求解 1.2 函数极值的计算 1.2.1 无约束最优化问题 1.2.2 有约束最优化问题 一.非线性方程求解与函数 ...

  8. 学习笔记(十五)——镜像的知识点与注意事项

    学习笔记(十五)--镜像的知识点与注意事项 一.基础知识 1.SQL Server镜像只有两种模式:高安全模式和高性能模式.两种模式的主要区别在于在事务提交后的操作. 在高性能模式下,主体服务器不需要 ...

  9. JavaScript学习(三十五)—拖动元素

    JavaScript学习(三十五)-拖动元素 代码如下: <!DOCTYPE html> <html lang="en"><head><m ...

  10. Cty的Linux学习笔记(十五——wget)

    Linux学习笔记--第十五篇 wget命令用于在终端中下载网络文件,格式为"wget  [参数]  下载地址" 参数: -b:后台下载模式 -P:下载到指定目录 -t:最大尝试次 ...

最新文章

  1. Apache启动时报Could not reliably determine the server's fully qualified domain name
  2. PIE SDK热力图
  3. ASP.NET MVC系列:UrlRouting
  4. 电信运营商占IDC市场65%:中国电信占行业半数以上
  5. 李洋疯狂C语言之n个人报数,报到3的退出,最后留在场上的是原来的第几位(约瑟夫环)
  6. linux最小化原则
  7. VS2005 中关于“LC.EXE已退出,代码为 -1”的错误解决方法。
  8. 互联网专用计算机屏保,5款屏保,让你的电脑在闲置时也与众不同。
  9. 移动通信网络规划:信道编码
  10. 装完金蝶电脑无限重启_电脑一直自动重启的原因与解决方法
  11. php 同比增长率上期未0,同比增长率计算时,上期值为0怎么计算?
  12. 常见的主流自动化测试框架,这5种能帮到你很多
  13. PHPWAMP乱码一键解决,PHP乱码通用解决方案/网站乱码的多种原因分析
  14. 线性系统大作业——2.二阶倒立摆建模与控制系统设计(上)
  15. InnerClass annotations are missing corresponding EnclosingMember annotations. Such InnerClas...
  16. springmvc+mybatis+easyui分页
  17. 学习方法:如何在工作内外获得持续的技术成长
  18. 如何把mkv转成mp4?
  19. 关于360杀毒之后IE浏览器打开报错问题的解决!
  20. ActionList详解

热门文章

  1. FZEasyFile的使用
  2. XML与java的应用
  3. 卸载iis express后80端口仍然被占用的解决方法
  4. go语言 goquery爬虫
  5. HTTP协议···(一)
  6. 初学python之生成器
  7. 记录自己的第一次实习
  8. 二维数组转datatable的代码
  9. 【Java从0到架构师】Servlet_JSP
  10. 【数据库系统设计】DBMS的数据库保护