以下内容为联合李沐老师的课程和教材增补的学习条记,以及对课后练习的一些思考,自留回顾,也供同学之人交换参考。
本节课程地址:72 优化算法【动手学深度学习v2】_哔哩哔哩_bilibili
本节教材地址:11.6. 动量法 — 动手学深度学习 2.0.0 documentation
本节开源代码:...>d2l-zh>pytorch>chapter_optimization>momentum.ipynb
动量法
在 11.4节 一节中,我们详述了如何执行随机梯度降落,即在只有嘈杂的梯度可用的情况下执行优化时会发生什么。 对于嘈杂的梯度,我们在选择学习率需要格外谨慎。 如果衰减速率太快,收敛就会停滞。 相反,如果太宽松,我们大概无法收敛到最优解。
基础
本节将探讨更有效的优化算法,尤其是针对实验中常见的某些类型的优化题目。
泄漏平均值
上一节中我们讨论了小批量随机梯度降落作为加速盘算的本领。 它也有很好的副作用,即平均梯度减小了方差。 小批量随机梯度降落可以通过以下方式盘算:
为了保持记法简单,在这里我们使用 作为样本 的随机梯度降落,使用时间 时更新的权重 。 如果我们能够从方差减少的影响中受益,甚至凌驾小批量上的梯度平均值,那很不错。 完成这项使命的一种选择是用泄漏平均值(leaky average)取代梯度盘算:
此中 。 这有效地将瞬时梯度替换为多个“已往”梯度的平均值。 被称为动量(momentum), 它累加了已往的梯度。 为了更具体地表明,让我们递归地将 扩展到
此中,较大的 相当于长期平均值,而较小的 相对于梯度法只是略有修正。 新的梯度替换不再指向特定实例降落最陡的方向,而是指向已往梯度的加权平均值的方向。 这使我们能够实现对单批量盘算平均值的大部分好处,而不产生实际盘算其梯度的代价。
上述推理构成了"加速"梯度方法的基础,比方具有动量的梯度。 在优化题目条件不佳的情况下(比方,有些方向的盼望比其他方向慢得多,类似狭窄的峡谷),"加速"梯度还额外享受更有效的好处。 此外,它们答应我们对随后的梯度盘算平均值,以获得更稳固的降落方向。 诚然,即使是对于无噪声凸题目,加速率这方面也是动量如此起效的关键原因之一。
正如人们所盼望的,由于其功效,动量是深度学习及其后优化中一个深入研究的主题。 比方,请参阅文章,观看深入分析和互动动画。 动量是由(Polyak, 1964)提出的。 (Nesterov, 2018)在凸优化的背景下举行了具体的理论讨论。 长期以来,深度学习的动量一直被认为是有益的。 有关实例的具体信息,请参阅 (Sutskeveret al., 2013)的讨论。
条件不佳的题目
为了更好地了解动量法的几何属性,我们复习一下梯度降落,只管它的目标函数显着不那么令人愉快。 追念我们在 11.3节 中使用了 ,即中度扭曲的椭球目标。 我们通过向 方向舒展它来进一步扭曲这个函数
与之前一样, 在 有最小值, 该函数在 的方向上非常平坦。 让我们看看在这个新函数上执行梯度降落时会发生什么。
- %matplotlib inline
- import torch
- from d2l import torch as d2l
- 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
从构造来看, 方向的梯度比水平 方向的梯度大得多,变化也快得多。 因此,我们陷入两难:如果选择较小的学习率,我们会确保解不会在 方向发散,但要承受在 方向的缓慢收敛。相反,如果学习率较高,我们在 方向上盼望很快,但在 方向将会发散。 下面的例子说明了即使学习率从0.4略微进步到0.6,也会发生变化。 方向上的收敛有所改善,但整体来看解的质量更差了。
- eta = 0.6
- d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
复制代码 输出效果:
epoch 20, x1: -0.387814, x2: -1673.365109
动量法
动量法(momentum)使我们能够办理上面形貌的梯度降落题目。 观察上面的优化轨迹,我们大概会直觉到盘算已往的平均梯度效果会很好。 毕竟,在 方向上,这将聚合非常对齐的梯度,从而增长我们在每一步中覆盖的间隔。 相反,在梯度振荡的 方向,由于相互抵消了对方的振荡,聚合梯度将减小步长大小。 使用 而不是梯度 可以天生以下更新等式:
请注意,对于 ,我们恢复通例的梯度降落。 在深入研究它的数学属性之前,让我们快速看一下算法在实验中的体现如何。
- def momentum_2d(x1, x2, v1, v2):
- v1 = beta * v1 + 0.2 * x1
- v2 = beta * v2 + 4 * x2
- return x1 - eta * v1, x2 - eta * v2, v1, v2
- eta, beta = 0.6, 0.5
- d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
复制代码 输出效果:
epoch 20, x1: 0.007188, x2: 0.002553
正如所见,只管学习率与我们从前使用的类似,动量法仍然很好地收敛了。 让我们看看当降低动量参数时会发生什么。 将其减半至 会导致一条险些没有收敛的轨迹。 只管如此,它比没有动量时解将会发散要好得多。
- eta, beta = 0.6, 0.25
- d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
复制代码 输出效果:
epoch 20, x1: -0.126340, x2: -0.186632
请注意,我们可以将动量法与随机梯度降落,特殊是小批量随机梯度降落联合起来。 唯一的变化是,在这种情况下,我们将梯度 替换为 。 为了方便起见,我们在时间 初始化 。
有效样本权重
追念一下 。 极限条件下, 。 换句话说,差别于在梯度降落大概随机梯度降落中取步长 ,我们选取步长 ,同时处置惩罚潜在体现大概会更好的降落方向。 这是集两种好处于一身的做法。 为了说明 的差别选择的权重效果如何,请参考下面的图表。
- # 不同 beta 值的指数衰减曲线
- d2l.set_figsize()
- betas = [0.95, 0.9, 0.6, 0]
- for beta in betas:
- x = torch.arange(40).detach().numpy()
- d2l.plt.plot(x, beta ** x, label=f'beta = {beta:.2f}')
- d2l.plt.xlabel('time')
- d2l.plt.legend()
复制代码
实际实验
让我们来看看动量法在实验中是如何运作的。 为此,我们需要一个更加可扩展的实现。
从零开始实现
相比于小批量随机梯度降落,动量方法需要维护一组辅助变量,即速率。 它与梯度以及优化题目的变量具有类似的形状。 在下面的实现中,我们称这些变量为states。
- def init_momentum_states(feature_dim):
- v_w = torch.zeros((feature_dim, 1))
- v_b = torch.zeros(1)
- return (v_w, v_b)
- def sgd_momentum(params, states, hyperparams):
- for p, v in zip(params, states):
- with torch.no_grad():
- v[:] = hyperparams['momentum'] * v + p.grad
- p[:] -= hyperparams['lr'] * v
- p.grad.data.zero_()
复制代码 让我们看看它在实验中是如何运作的。
- def train_momentum(lr, momentum, num_epochs=2):
- d2l.train_ch11(sgd_momentum, init_momentum_states(feature_dim),
- {'lr': lr, 'momentum': momentum}, data_iter,
- feature_dim, num_epochs)
- data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
- train_momentum(0.02, 0.5)
复制代码 输出效果:
loss: 0.244, 0.009 sec/epoch
当我们将动量超参数momentum增长到0.9时,它相当于有效样本数目增长到 。 我们将学习率略微降至0.01,以确保可控。
- train_momentum(0.01, 0.9)
复制代码 输出效果:
loss: 0.248, 0.009 sec/epoch
降低学习率进一步办理了任何非平滑优化题目的困难,将其设置为0.005会产生良好的收敛性能。
- train_momentum(0.005, 0.9)
复制代码 输出效果:
loss: 0.245, 0.009 sec/epoch
简便实现
由于深度学习框架中的优化求解器早已构建了动量法,设置匹配参数会产生非常类似的轨迹。
- trainer = torch.optim.SGD
- d2l.train_concise_ch11(trainer, {'lr': 0.005, 'momentum': 0.9}, data_iter)
复制代码 输出效果:
loss: 0.246, 0.010 sec/epoch
理论分析
的2D示例好像相当牵强。 下面我们将看到,它在实际生存中非常具有代表性,至少最小化凸二次目标函数的情况下是如此。
二次凸函数
思量这个函数
这是一个平凡的二次函数。 对于正定矩阵 ,即对于具有正特征值的矩阵,有最小化器为 ,最小值为 。 因此我们可以将 重写为
梯度由 给出。 也就是说,它是由 和最小化器之间的间隔乘以 所得出的。 因此,动量法照旧 的线性组合。
由于 是正定的,因此可以通过 分解为正交(旋转)矩阵 和正特征值的对角矩阵 。 这使我们能够将变量从 更改为 ,以获得一个非常简化的表达式:
这里 。 由于 只是一个正交矩阵,因此不会真正意义上扰动梯度。 以 表示的梯度降落酿成
这个表达式中的紧张毕竟是梯度降落在差别的特征空间之间不会混淆。 也就是说,如果用 的特征体系来表示,优化题目是以逐坐标顺序的方式举行的。 这在动量法中也适用。
在这样做的过程中,我们只是证明了以下定理:带有和带有不凸二次函数动量的梯度降落,可以分解为朝二次矩阵特征向量方向坐标顺序的优化。
标量函数
鉴于上述效果,让我们看看当我们最小化函数 时会发生什么。 对于梯度降落我们有
每 时,这种优化以指数速率收敛,由于在 步之后我们可以得到 。 这表现了在我们将学习率 进步到 之前,收敛率最初是如何进步的。 凌驾该数值之后,梯度开始发散,对于 而言,优化题目将会发散。
- lambdas = [0.1, 1, 10, 19]
- eta = 0.1
- d2l.set_figsize((6, 4))
- for lam in lambdas:
- t = torch.arange(20).detach().numpy()
- d2l.plt.plot(t, (1 - eta * lam) ** t, label=f'lambda = {lam:.2f}')
- d2l.plt.xlabel('time')
- d2l.plt.legend()
复制代码
为了分析动量的收敛情况,我们起首用两个标量重写更新方程:一个用于 ,另一个用于动量 。这产生了:
我们用 来表示 管理的收敛体现。 在 t 步之后,最初的值 变为 。 因此,收敛速率是由 R 的特征值决定的。 请参阅文章 (Goh, 2017)了解出色动画。 请参阅 (Flammarion and Bach, 2015)了解具体分析。 简而言之,当 时动量收敛。 与梯度降落的 相比,这是更大范围的可行参数。 另外,一样平常而言较大值的 是可取的。
小结
- 动量法用已往梯度的平均值来替换梯度,这大大加快了收敛速率。
- 对于无噪声梯度降落和嘈杂随机梯度降落,动量法都是可取的。
- 动量法可以防止在随机梯度降落的优化过程停滞的题目。
- 由于对已往的数据举行了指数降权,有效梯度数为 。
- 在凸二次题目中,可以对动量法举行明确而具体的分析。
- 动量法的实现非常简单,但它需要我们存储额外的状态向量(动量 )。
练习
- 使用动量超参数和学习率的其他组合,观察和分析差别的实验效果。
解:
组合差别动量超参数和学习率后发现,高学习率和低动量参数、低学习率和高动量参数的组合,收敛速率快且loss低,分析原因如下:
- 高学习率可以加快收敛,而低动量参数减少了由于噪声或震荡带来的倒霉影响。
- 高动量参数可以平滑震荡,而低学习率可以防止由于步长过大导致的倒霉影响。
代码如下:
- train_momentum(0.002, 0.9)
复制代码 输出效果:
loss: 0.243, 0.009 sec/epoch
- train_momentum(0.02, 0.2)
复制代码 输出效果:
loss: 0.243, 0.009 sec/epoch
2. 试试梯度降落和动量法来办理一个二次题目,此中有多个特征值,即 ,比方 。绘制出 的值在初始化 时如何降落。
解:
代码如下:
- # 定义目标函数
- def f(x, lambdas):
- return 0.5 * torch.sum(lambdas * x**2)
- # 动量法的更新规则
- def momentum(x, v, lambdas, eta, beta):
- with torch.no_grad():
- v = beta * v + lambdas * x
- x -= x - eta * v
- return x, v
- # 初始化参数
- feature_dim = 5
- x = torch.ones(feature_dim) # 初始化x
- v = torch.zeros(feature_dim) # 初始化动量v
- lambdas = torch.tensor([2**(-i) for i in range(feature_dim)]) # 特征值
- eta, beta, num_epochs = 0.1, 0.9, 10 # 学习率
- # 动量法
- x_values = [x.clone().detach().numpy()]
- for _ in range(num_epochs):
- x, v = momentum(x, v, lambdas, eta, beta)
- x_values.append(x.clone().detach().numpy())
- # 绘制结果
- d2l.set_figsize()
- for i in range(len(x_values[-1])):
- d2l.plt.plot([x_values[j][i] for j in range(num_epochs + 1)],
- label=f'x{i+1}={x_values[-1][i]:.3f}')
- d2l.plt.xlabel('epochs')
- d2l.plt.ylabel('x_values')
- d2l.plt.legend()
复制代码
3. 推导 的最小值和最小化器。
解:
关于 的梯度为:
最小值为梯度为0的点,即令上式为0,可解得最小化器为:
将最小化器代入 可得最小值:
由于 ,且 是对称的,故 ,则上式可以简化为:
4. 当我们执行带动量法的随机梯度降落时会有什么变化?当我们使用带动量法的小批量随机梯度降落时会发生什么?试验参数如何?
解:
类似lr下,对于sgd,带动量法后由于增长了盘算量,盘算用时略增长了一些,loss上相差无几。
类似lr下,对于小批量sgd,带动量法后盘算用时和loss都差别不大。
代码如下:
- def sgd(params, states, hyperparams):
- for p in params:
- p.data.sub_(hyperparams['lr'] * p.grad)
- p.grad.data.zero_()
- def train_sgd(lr, batch_size, num_epochs=2):
- data_iter, feature_dim = d2l.get_data_ch11(batch_size)
- return d2l.train_ch11(
- sgd, None, {'lr': lr}, data_iter, feature_dim, num_epochs)
- # sgd
- sgd = train_sgd(0.005, 1)
复制代码 输出效果:
loss: 0.244, 0.055 sec/epoch
- # minibatchsgd
- minisgd = train_sgd(0.4, 100)
复制代码 输出效果:
loss: 0.246, 0.002 sec/epoch
- def train_momentum(lr, momentum, batch_size, num_epochs=2):
- data_iter, feature_dim = d2l.get_data_ch11(batch_size)
- return d2l.train_ch11(
- sgd_momentum, init_momentum_states(feature_dim), {'lr': lr, 'momentum': momentum}, data_iter, feature_dim, num_epochs)
- # sgd with momentum
- sgd_mom = train_momentum(0.005, 0.4, 1)
复制代码 输出效果:
loss: 0.243, 0.068 sec/epoch
- # minibatchsgd with momentum
- minisgd_mom = train_momentum(0.4, 0.4, 100)
复制代码 输出效果:
loss: 0.247, 0.002 sec/epoch
- d2l.set_figsize([6, 3])
- d2l.plot(*list(map(list, zip(sgd, minisgd, sgd_mom, minisgd_mom))),
- 'time (sec)', 'loss', xlim=[1e-2, 1],
- legend=['sgd', 'minisgd', 'sgd with momentum', 'minisgd with momentum'])
- d2l.plt.gca().set_xscale('log')
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |