梯度下降是一种优化算法,它沿着目标函数的负梯度方向移动,以找到函数的最小值。
梯度下降的一个局限性在于它可能会陷入平坦区域或在目标函数返回的梯度有噪声时来回振荡。动量是一种加速搜索过程的方法,可以跳过平坦区域并平滑振荡的梯度。
在某些情况下,动量的加速可能会导致搜索错过或越过盆地或山谷底部的最小值。Nesterov动量是动量的一种扩展,它涉及到计算搜索空间中投影位置的梯度的衰减移动平均值,而不是实际位置本身的梯度。
这样做的好处是,在利用动量加速优势的同时,允许搜索在接近最优值时减速,并降低错过或越过它的可能性。
在本教程中,您将学习如何从零开始开发结合Nesterov动量的梯度下降优化算法。
完成本教程后,您将了解:
- 梯度下降是一种优化算法,它利用目标函数的梯度来导航搜索空间。
- 通过扩展算法并添加Nesterov动量,可以加速梯度下降优化算法的收敛。
- 如何从零开始实现Nesterov动量优化算法,并将其应用于目标函数并评估结果。
开始您的项目,请阅读我的新书《机器学习优化》,其中包含分步教程和所有示例的Python源代码文件。
让我们开始吧。
从头开始的 Nesterov 动量梯度下降
图片来源:Bonnie Moreland,部分权利保留。
教程概述
本教程分为三个部分;它们是:
- 梯度下降
- Nesterov动量
- 梯度下降结合Nesterov动量
- 二维测试问题
- 带Nesterov动量的梯度下降优化
- Nesterov动量可视化
梯度下降
梯度下降是一种优化算法。
它在技术上被称为一阶优化算法,因为它显式地使用了目标函数的一阶导数。
一阶方法依赖梯度信息来帮助指导寻找最小值……
——第69页,《优化算法》,2019年。
一阶导数,或简称为“导数”,是指目标函数在特定点(例如,特定输入)的变化率或斜率。
如果目标函数有多个输入变量,则称其为多元函数,并且可以将输入变量视为一个向量。反过来,多元目标函数的导数也可以视为一个向量,通常称为“梯度”。
- 梯度:多元目标函数的一阶导数。
导数或梯度指向特定输入处目标函数最陡峭上升的方向。
梯度下降指一种最小化优化算法,它沿着目标函数的负梯度方向“下坡”移动,以找到函数的最小值。
梯度下降算法需要一个正在优化的目标函数以及目标函数的导数函数。目标函数f()为给定的一组输入返回一个分数,而导数函数f'()为给定的一组输入提供目标函数的导数。
梯度下降算法需要问题中的一个起始点(x),例如输入空间中随机选择的一个点。
然后计算导数,并在输入空间中迈出一步,预计会导致目标函数下坡移动(假设我们正在最小化目标函数)。
向下移动是通过首先计算在输入空间中移动的距离来实现的,该距离计算为步长(称为alpha或学习率)乘以梯度。然后将其从当前点减去,确保我们沿梯度反方向移动,即沿着目标函数向下移动。
- x(t+1) = x(t) – 步长 * f'(x(t))
给定点处目标函数越陡峭,梯度的幅值越大,反之,在搜索空间中迈出的步长也越大。所迈步长的大小由步长超参数进行缩放。
- 步长(alpha):控制算法每次迭代中在搜索空间中沿梯度反方向移动的距离的超参数。
如果步长太小,搜索空间中的移动就会很小,搜索需要很长时间。如果步长太大,搜索可能会在搜索空间中来回振荡并跳过最优值。
现在我们熟悉了梯度下降优化算法,让我们来看看Nesterov动量。
想要开始学习优化算法吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
Nesterov动量
Nesterov动量是梯度下降优化算法的扩展。
该方法由Yurii Nesterov在其1983年的论文《具有收敛率O(1/k^2)的凸规划问题求解方法》中描述(并以其命名)。
Ilya Sutskever等人通过其2013年的论文“深度学习中初始化和动量的重要性”将Nesterov动量应用于随机梯度下降训练神经网络,从而使其广为人知。他们称该方法为“Nesterov加速梯度”,简称NAG。
Nesterov动量与更传统的动量类似,只是更新是使用投影更新的偏导数进行的,而不是使用当前变量值的导数。
虽然NAG通常不被认为是动量的一种类型,但它确实与经典动量密切相关,只是在速度向量的精确更新上有所不同……
— 深度学习中初始化和动量的重要性,2013年。
传统的动量涉及维护一个额外的变量,该变量代表变量的最后一次更新,即过去梯度的指数衰减移动平均值。
动量算法会累积过去梯度的指数衰减的移动平均值,并继续沿着它们的变动方向移动。
— 第296页,深度学习,2016年。
这个最后的更新或变量的最后一次变化会被加到变量上,并通过“动量”超参数进行缩放,该超参数控制要添加的 last change 的量,例如0.9表示90%。
更简单地说,可以将此更新视为两个步骤:例如,使用偏导数计算变量的变化,然后计算变量的新值。
- change(t+1) = (momentum * change(t)) – (step_size * f'(x(t)))
- x(t+1) = x(t) + change(t+1)
我们可以将动量想象成一个滚下山坡的球,即使遇到小山丘,它也会加速并继续沿同一方向前进。
动量可以被解释为一个在近乎水平的斜坡上滚动的球。球会自然地积聚动量,就像重力使其加速一样,正如梯度在下降过程中使动量累积一样。
— 第75页,优化算法,2019年。
动量的一个问题是,加速有时会导致搜索错过或越过盆地底部或山谷底部的最小值。
Nesterov动量可以被视为对动量的一种修改,以解决这种越过最小值的 overshoot 问题。
它涉及到首先使用上一迭代的更改来计算变量的投影位置,并使用投影位置的导数来计算变量的新位置。
计算投影位置的梯度就像是累积加速的校正因子。
使用Nesterov动量时,梯度是在应用当前速度之后计算的。因此,可以将Nesterov动量解释为尝试为标准的动量方法添加一个校正因子。
— 第300页,深度学习,2016年。
Nesterov动量可以很容易地通过以下四个步骤来理解:
- 1. 投影解的位置。
- 2. 计算投影的梯度。
- 3. 使用偏导数计算变量的更改。
- 4. 更新变量。
让我们详细介绍这些步骤。
首先,使用算法上次迭代计算的更改来计算整个解的投影位置。
- projection(t+1) = x(t) + (momentum * change(t))
然后我们可以计算这个新位置的梯度。
- gradient(t+1) = f'(projection(t+1))
现在,我们可以通过首先计算每个变量的更改来使用投影的梯度计算每个变量的新位置。
- change(t+1) = (momentum * change(t)) – (step_size * gradient(t+1))
最后,使用计算出的更改来计算每个变量的新值。
- x(t+1) = x(t) + change(t+1)
在更普遍的凸优化领域,Nesterov动量因其能够提高优化算法的收敛速率(例如,减少找到解所需的迭代次数)而闻名。
与动量类似,NAG是一种一阶优化方法,在某些情况下比梯度下降具有更好的收敛速率保证。
— 深度学习中初始化和动量的重要性,2013年。
虽然该技术在训练神经网络方面非常有效,但它可能不会对加速收敛产生相同的普遍效果。
不幸的是,在随机梯度情况下,Nesterov动量并不能提高收敛速率。
— 第300页,深度学习,2016年。
现在我们已经熟悉了Nesterov动量算法,让我们探讨如何实现它并评估其性能。
梯度下降结合Nesterov动量
在本节中,我们将探讨如何实现结合Nesterov动量的梯度下降优化算法。
二维测试问题
首先,让我们定义一个优化函数。
我们将使用一个简单的二维函数,它将每个维度的输入平方,并将有效输入范围定义为-1.0到1.0。
下面的objective()函数实现了这个功能。
1 2 3 |
# 目标函数 def objective(x, y): return x**2.0 + y**2.0 |
我们可以创建一个数据集的三维图来感受响应曲面的曲率。
下面列出了绘制目标函数的完整示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# 绘制测试函数的三维图 from numpy import arange from numpy import meshgrid from matplotlib import pyplot # 目标函数 def objective(x, y): return x**2.0 + y**2.0 # 定义输入范围 r_min, r_max = -1.0, 1.0 # 以 0.1 为增量均匀采样输入范围 xaxis = arange(r_min, r_max, 0.1) yaxis = arange(r_min, r_max, 0.1) # 从坐标轴创建网格 x, y = meshgrid(xaxis, yaxis) # 计算目标值 results = objective(x, y) # 使用 jet 配色方案创建曲面图 figure = pyplot.figure() axis = figure.gca(projection='3d') axis.plot_surface(x, y, results, cmap='jet') # 显示绘图 pyplot.show() |
运行示例将创建目标函数的三维曲面图。
我们可以看到熟悉的碗形,全局最小值在 f(0, 0) = 0。

测试目标函数的三维图
我们还可以创建函数的二维图。这将在以后我们想要绘制搜索进度时提供帮助。
以下示例创建了目标函数的等高线图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 绘制测试函数的等高线图 from numpy import asarray from numpy import arange from numpy import meshgrid from matplotlib import pyplot # 目标函数 def objective(x, y): return x**2.0 + y**2.0 # 定义输入范围 bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]]) # 以 0.1 为增量均匀采样输入范围 xaxis = arange(bounds[0,0], bounds[0,1], 0.1) yaxis = arange(bounds[1,0], bounds[1,1], 0.1) # 从坐标轴创建网格 x, y = meshgrid(xaxis, yaxis) # 计算目标值 results = objective(x, y) # 使用50个级别和jet颜色方案创建填充等高线图 pyplot.contourf(x, y, results, levels=50, cmap='jet') # 显示绘图 pyplot.show() |
运行示例将创建目标函数的二维等高线图。
我们可以看到碗状被压缩成用颜色梯度显示的等高线。我们将使用这个图来绘制搜索过程中探索的特定点。

测试目标函数的二维等高线图
现在我们有了一个测试目标函数,让我们看看如何实现Nesterov动量优化算法。
带Nesterov动量的梯度下降优化
我们可以将梯度下降与Nesterov动量应用于测试问题。
首先,我们需要一个函数来计算此函数的导数。
x^2 的导数在每个维度上都是 x * 2,而下面的 derivative() 函数实现了这一点。
1 2 3 |
# 目标函数的导数 def derivative(x, y): return asarray([x * 2.0, y * 2.0]) |
接下来,我们可以实现梯度下降优化。
首先,我们可以在问题的边界内选择一个随机点作为搜索的起点。
这假设我们有一个数组,它定义了搜索的边界,每行一个维度,第一列定义最小值,第二列定义最大值。
1 2 3 |
... # 生成初始点 solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0]) |
接下来,我们需要计算当前位置的投影点并计算其导数。
1 2 3 4 5 |
... # 计算投影解 projected = [solution[i] + momentum * change[i] for i in range(solution.shape[0])] # 计算投影的梯度 gradient = derivative(projected[0], projected[1]) |
然后,我们可以一次创建一个新的解决方案(一个变量一个变量地)。
首先,使用偏导数和学习率以及上一次变量更改的动量来计算变量的更改。此更改将存储以供算法下次迭代使用。然后,使用更改来计算变量的新值。
1 2 3 4 5 6 7 8 9 10 |
... # 逐个变量构建解 new_solution = list() for i in range(solution.shape[0]): # 计算更改 change[i] = (momentum * change[i]) - step_size * gradient[i] # 计算此变量的新位置 value = solution[i] + change[i] # 存储此变量 new_solution.append(value) |
这对于目标函数的每个变量都会重复,然后对于算法的每个迭代都会重复。
然后可以使用 *objective()* 函数来评估这个新解,并报告搜索的性能。
1 2 3 4 5 6 |
... # 评估候选点 solution = asarray(new_solution) solution_eval = objective(solution[0], solution[1]) # 报告进展 print('>%d f(%s) = %.5f' % (it, solution, solution_eval)) |
就是这样。
我们可以将所有这些内容整合到一个名为nesterov()的函数中,该函数接受目标函数和导数函数的名称、包含域边界的数组以及算法总迭代次数、学习率和动量的超参数值,并返回最终解及其评估。
完整的函数如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# 带有Nesterov动量的梯度下降算法 def nesterov(objective, derivative, bounds, n_iter, step_size, momentum): # 生成初始点 solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0]) # 变量更改列表 change = [0.0 for _ in range(bounds.shape[0])] # 运行梯度下降 for it in range(n_iter): # 计算投影解 projected = [solution[i] + momentum * change[i] for i in range(solution.shape[0])] # 计算投影的梯度 gradient = derivative(projected[0], projected[1]) # 逐个变量构建解 new_solution = list() for i in range(solution.shape[0]): # 计算更改 change[i] = (momentum * change[i]) - step_size * gradient[i] # 计算此变量的新位置 value = solution[i] + change[i] # 存储此变量 new_solution.append(value) # 评估候选点 solution = asarray(new_solution) solution_eval = objective(solution[0], solution[1]) # 报告进度 print('>%d f(%s) = %.5f' % (it, solution, solution_eval)) return [solution, solution_eval] |
注意,为了提高可读性,我们故意使用了列表和命令式编码风格,而不是向量化操作。您可以随意将实现改编为使用NumPy数组的向量化实现,以获得更好的性能。
然后,我们可以定义我们的超参数并调用nesterov()函数来优化我们的测试目标函数。
在这种情况下,我们将使用30次迭代,学习率为0.1,动量为0.3。这些超参数值是在经过一些试错后找到的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
... # 初始化伪随机数生成器 seed(1) # 定义输入范围 bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]]) # 定义总迭代次数 n_iter = 30 # 定义步长 step_size = 0.1 # 定义动量 momentum = 0.3 # 使用nesterov动量执行梯度下降搜索 best, score = nesterov(objective, derivative, bounds, n_iter, step_size, momentum) print('Done!') print('f(%s) = %f' % (best, score)) |
将所有这些结合起来,下面列出了带有 Nesterov Momentum 的梯度下降优化的完整示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# gradient descent optimization with nesterov momentum for a two-dimensional test function from math import sqrt from numpy import asarray from numpy.random import rand from numpy.random import seed # 目标函数 def objective(x, y): return x**2.0 + y**2.0 # 目标函数的导数 def derivative(x, y): return asarray([x * 2.0, y * 2.0]) # 带有Nesterov动量的梯度下降算法 def nesterov(objective, derivative, bounds, n_iter, step_size, momentum): # 生成初始点 solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0]) # 变量更改列表 change = [0.0 for _ in range(bounds.shape[0])] # 运行梯度下降 for it in range(n_iter): # 计算投影解 projected = [solution[i] + momentum * change[i] for i in range(solution.shape[0])] # 计算投影的梯度 gradient = derivative(projected[0], projected[1]) # 逐个变量构建解 new_solution = list() for i in range(solution.shape[0]): # 计算更改 change[i] = (momentum * change[i]) - step_size * gradient[i] # 计算此变量的新位置 value = solution[i] + change[i] # 存储此变量 new_solution.append(value) # 评估候选点 solution = asarray(new_solution) solution_eval = objective(solution[0], solution[1]) # 报告进度 print('>%d f(%s) = %.5f' % (it, solution, solution_eval)) return [solution, solution_eval] # 初始化伪随机数生成器 seed(1) # 定义输入范围 bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]]) # 定义总迭代次数 n_iter = 30 # 定义步长 step_size = 0.1 # 定义动量 momentum = 0.3 # 使用nesterov动量执行梯度下降搜索 best, score = nesterov(objective, derivative, bounds, n_iter, step_size, momentum) print('Done!') print('f(%s) = %f' % (best, score)) |
运行该示例会将带有 Nesterov Momentum 的优化算法应用于我们的测试问题,并报告算法每次迭代的搜索性能。
注意:您的结果可能因算法或评估程序的随机性、或数值精度的差异而有所不同。请考虑运行几次示例并比较平均结果。
在这种情况下,我们可以看到经过大约 15 次搜索迭代后,找到了接近最优的解,输入值接近 0.0 和 0.0,评价值为 0.0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
>0 f([-0.13276479 0.35251919]) = 0.14190 >1 f([-0.09824595 0.2608642 ]) = 0.07770 >2 f([-0.07031223 0.18669416]) = 0.03980 >3 f([-0.0495457 0.13155452]) = 0.01976 >4 f([-0.03465259 0.0920101 ]) = 0.00967 >5 f([-0.02414772 0.06411742]) = 0.00469 >6 f([-0.01679701 0.04459969]) = 0.00227 >7 f([-0.01167344 0.0309955 ]) = 0.00110 >8 f([-0.00810909 0.02153139]) = 0.00053 >9 f([-0.00563183 0.01495373]) = 0.00026 >10 f([-0.00391092 0.01038434]) = 0.00012 >11 f([-0.00271572 0.00721082]) = 0.00006 >12 f([-0.00188573 0.00500701]) = 0.00003 >13 f([-0.00130938 0.0034767 ]) = 0.00001 >14 f([-0.00090918 0.00241408]) = 0.00001 >15 f([-0.0006313 0.00167624]) = 0.00000 >16 f([-0.00043835 0.00116391]) = 0.00000 >17 f([-0.00030437 0.00080817]) = 0.00000 >18 f([-0.00021134 0.00056116]) = 0.00000 >19 f([-0.00014675 0.00038964]) = 0.00000 >20 f([-0.00010189 0.00027055]) = 0.00000 >21 f([-7.07505806e-05 1.87858067e-04]) = 0.00000 >22 f([-4.91260884e-05 1.30440372e-04]) = 0.00000 >23 f([-3.41109926e-05 9.05720503e-05]) = 0.00000 >24 f([-2.36851711e-05 6.28892431e-05]) = 0.00000 >25 f([-1.64459397e-05 4.36675208e-05]) = 0.00000 >26 f([-1.14193362e-05 3.03208033e-05]) = 0.00000 >27 f([-7.92908415e-06 2.10534304e-05]) = 0.00000 >28 f([-5.50560682e-06 1.46185748e-05]) = 0.00000 >29 f([-3.82285090e-06 1.01504945e-05]) = 0.00000 完成! f([-3.82285090e-06 1.01504945e-05]) = 0.000000 |
Nesterov动量可视化
我们可以绘制 Nesterov Momentum 搜索在域的等高图上的进展。
这可以提供对算法迭代过程中搜索进展的直观感受。
我们必须更新 nesterov() 函数,以维护一个包含搜索期间找到的所有解的列表,然后在搜索结束时返回该列表。
包含这些更改的更新版本函数如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# 带有Nesterov动量的梯度下降算法 def nesterov(objective, derivative, bounds, n_iter, step_size, momentum): # 跟踪所有解决方案 solutions = list() # 生成初始点 solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0]) # 变量更改列表 change = [0.0 for _ in range(bounds.shape[0])] # 运行梯度下降 for it in range(n_iter): # 计算投影解 projected = [solution[i] + momentum * change[i] for i in range(solution.shape[0])] # 计算投影的梯度 gradient = derivative(projected[0], projected[1]) # 逐个变量构建解 new_solution = list() for i in range(solution.shape[0]): # 计算更改 change[i] = (momentum * change[i]) - step_size * gradient[i] # 计算此变量的新位置 value = solution[i] + change[i] # 存储此变量 new_solution.append(value) # 存储新解 solution = asarray(new_solution) solutions.append(solution) # 评估候选点 solution_eval = objective(solution[0], solution[1]) # 报告进度 print('>%d f(%s) = %.5f' % (it, solution, solution_eval)) return solutions |
然后我们可以像以前一样执行搜索,这次检索解决方案列表而不是最终的最佳解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
... # 初始化伪随机数生成器 seed(1) # 定义输入范围 bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]]) # 定义总迭代次数 n_iter = 50 # 定义步长 step_size = 0.01 # 定义动量 momentum = 0.8 # 使用nesterov动量执行梯度下降搜索 solutions = nesterov(objective, derivative, bounds, n_iter, step_size, momentum) |
然后我们可以像以前一样创建目标函数的等高线图。
1 2 3 4 5 6 7 8 9 10 |
... # 以 0.1 为增量均匀采样输入范围 xaxis = arange(bounds[0,0], bounds[0,1], 0.1) yaxis = arange(bounds[1,0], bounds[1,1], 0.1) # 从坐标轴创建网格 x, y = meshgrid(xaxis, yaxis) # 计算目标值 results = objective(x, y) # 使用50个级别和jet颜色方案创建填充等高线图 pyplot.contourf(x, y, results, levels=50, cmap='jet') |
最后,我们可以将搜索过程中找到的每个解决方案绘制成一个由线连接的白点。
1 2 3 4 |
... # 将样本绘制为黑色圆圈 solutions = asarray(solutions) pyplot.plot(solutions[:, 0], solutions[:, 1], '.-', color='w') |
将这些全部整合在一起,下面列出了在测试问题上执行 Nesterov Momentum 优化并将结果绘制在等高图上的完整示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# example of plotting the nesterov momentum search on a contour plot of the test function from math import sqrt from numpy import asarray from numpy import arange from numpy.random import rand from numpy.random import seed from numpy import meshgrid from matplotlib import pyplot from mpl_toolkits.mplot3d import Axes3D # 目标函数 def objective(x, y): return x**2.0 + y**2.0 # 目标函数的导数 def derivative(x, y): return asarray([x * 2.0, y * 2.0]) # 带有Nesterov动量的梯度下降算法 def nesterov(objective, derivative, bounds, n_iter, step_size, momentum): # 跟踪所有解决方案 solutions = list() # 生成初始点 solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0]) # 变量更改列表 change = [0.0 for _ in range(bounds.shape[0])] # 运行梯度下降 for it in range(n_iter): # 计算投影解 projected = [solution[i] + momentum * change[i] for i in range(solution.shape[0])] # 计算投影的梯度 gradient = derivative(projected[0], projected[1]) # 逐个变量构建解 new_solution = list() for i in range(solution.shape[0]): # 计算更改 change[i] = (momentum * change[i]) - step_size * gradient[i] # 计算此变量的新位置 value = solution[i] + change[i] # 存储此变量 new_solution.append(value) # 存储新解 solution = asarray(new_solution) solutions.append(solution) # 评估候选点 solution_eval = objective(solution[0], solution[1]) # 报告进度 print('>%d f(%s) = %.5f' % (it, solution, solution_eval)) return solutions # 初始化伪随机数生成器 seed(1) # 定义输入范围 bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]]) # 定义总迭代次数 n_iter = 50 # 定义步长 step_size = 0.01 # 定义动量 momentum = 0.8 # 使用nesterov动量执行梯度下降搜索 solutions = nesterov(objective, derivative, bounds, n_iter, step_size, momentum) # 以 0.1 为增量均匀采样输入范围 xaxis = arange(bounds[0,0], bounds[0,1], 0.1) yaxis = arange(bounds[1,0], bounds[1,1], 0.1) # 从坐标轴创建网格 x, y = meshgrid(xaxis, yaxis) # 计算目标值 results = objective(x, y) # 使用50个级别和jet颜色方案创建填充等高线图 pyplot.contourf(x, y, results, levels=50, cmap='jet') # 将样本绘制为黑色圆圈 solutions = asarray(solutions) pyplot.plot(solutions[:, 0], solutions[:, 1], '.-', color='w') # 显示绘图 pyplot.show() |
运行示例像以前一样执行搜索,但在此情况下,创建了目标函数的等高线图。
在这种情况下,我们可以看到搜索过程中找到的每个解决方案都显示为一个白点,从最优值上方开始,并逐渐靠近图中中心的最优值。

带有 Nesterov Momentum 搜索结果的测试目标函数的等高图
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
论文
- 一种解决凸规划问题的方法,收敛速度为 O(1/k^2), 1983.
- 论初始化和动量在深度学习中的重要性, 2013.
书籍
API
文章
- 梯度下降,维基百科.
- 随机梯度下降,维基百科.
- 梯度下降优化算法综述, 2016.
总结
在本教程中,您了解了如何从头开始开发带有 Nesterov Momentum 的梯度下降优化。
具体来说,你学到了:
- 梯度下降是一种优化算法,它利用目标函数的梯度来导航搜索空间。
- 通过扩展算法并添加Nesterov动量,可以加速梯度下降优化算法的收敛。
- 如何从零开始实现Nesterov动量优化算法,并将其应用于目标函数并评估结果。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
嗨 Jason,感谢您的精彩教程!我有一个疑问,在神经网络的情况下,我们无法将导数定义为直接函数,而是通过计算图通过 .grad() 获取,您能否举例说明使用此算法?在拥有数百万参数的神经网络情况下,如何定义边界?
好建议,谢谢。也许将来我可以扩展这个教程。
存在 bug,首先 random 需要用 numpy 导入,其次 momentum 没有声明。
很抱歉听到您遇到了麻烦。
Random 是从 numpy 导入的,momentum 在最后的完整示例中定义了。
也许您跳过了一些行?
尊敬的Jason博士,
三点
(1) 我看到 Tensorflow 和 Pytorch 都实现了“Nesterov Momentum”算法,例如 https://tensorflowcn.cn/api_docs/python/tf/keras/optimizers/SGD。
在 Tensorflow 的实现中,目标函数不是一个参数。
相反,优化算法是模型的参数,而不是像您的示例中那样,函数是优化算法的参数。
参考:https://tflearn.tensorflowcn.cn/optimizers/
(2) (1) 的原因是您可以使用计时模块来比较特定算法的计时,使用梯度下降算法与“Nestorov Momentum”的计时。
灵感来源:https://stackoverflow.com/questions/7370801/how-to-measure-elapsed-time-in-python 中的第一个答案
(3) (1) 和 (2) 的另一个原因是,您可以查看更快的梯度下降,例如“Nesterov Momentum”,尽管速度更快,但是否能产生有效的交叉验证分数和学习曲线。
谢谢你,
悉尼的Anthony
我建议根据对目标函数的先验知识、如何有效使用优化器的先验知识或最终结果的性能来选择优化器。挂钟时间可能不是一种有效的方法。
尊敬的Jason博士,
尊敬的Jason博士,
谢谢你的回复。
我还有关于您的“从头开始”实现的“Nestorov Momentum”以及 Keras 和 Pytorch 中“Nestorov Momentum”实现的另一个问题。
例如
tf.keras.optimizers.SGD(
learning_rate=0.01, momentum=0.0, nesterov=True, name=”SGD”, **kwargs
)
问题请:Keras 的 Nesterov 实现是否会像您“从头开始”实现的 Nesterov 一样执行?或者 Keras 的 Nesterov 实现是否就是您“从头开始”版本的完整实现?
谢谢你,
悉尼的Anthony
不,库的实现可能会利用一些技巧使其实现更有效,并进行检查以确保执行在数值上是稳定的。
这就是为什么我建议仅将算法从头开始编码作为学习练习。
尊敬的Jason博士,
谢谢你,
悉尼的Anthony