Machine Learning Mastery 优化教程(四)
从零开始的 Adadelta 梯度下降
最后更新于 2021 年 10 月 12 日
梯度下降是一种优化算法,它遵循目标函数的负梯度来定位函数的最小值。
梯度下降的一个限制是,它对每个输入变量使用相同的步长(学习率)。AdaGradn 和 RMSProp 是梯度下降的扩展,为目标函数的每个参数增加了自适应学习率。
Adadelta 可以被认为是梯度下降的进一步扩展,它建立在 AdaGrad 和 RMSProp 的基础上,并改变了自定义步长的计算,使得单位一致,进而不再需要初始学习率超参数。
在本教程中,您将发现如何从零开始使用阿达塔优化算法开发梯度下降。
完成本教程后,您将知道:
- 梯度下降是一种优化算法,它使用目标函数的梯度来导航搜索空间。
- 梯度下降可以更新,以使用一个衰减的偏导数平均值(称为 Adadelta)为每个输入变量使用自动自适应步长。
- 如何从零开始实现 Adadelta 优化算法,并将其应用于目标函数并评估结果。
用我的新书机器学习优化启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
Let’s get started.
罗伯特·明克勒拍摄的《从零开始的梯度下降》照片,保留部分权利。
教程概述
本教程分为三个部分;它们是:
- 梯度下降
- Adadelta 算法
- Adadelta 梯度下降
- 二维测试问题
- 带自适应增量的梯度下降优化
- 阿达塔的可视化
梯度下降
梯度下降是一种优化算法。
它在技术上被称为一阶优化算法,因为它明确地利用了目标函数的一阶导数。
一阶方法依赖于梯度信息来帮助指导搜索最小值…
—第 69 页,优化算法,2019。
一阶导数,或简称为“导数,是目标函数在特定点的变化率或斜率,例如对于特定输入。
如果目标函数有多个输入变量,它被称为多元函数,输入变量可以被认为是一个向量。反过来,多元目标函数的导数也可以作为向量,并且通常被称为梯度。
- 梯度:多元目标函数的一阶导数。
对于特定输入,导数或梯度指向目标函数最陡上升的方向。
梯度下降是指一种最小化优化算法,它遵循目标函数梯度下降的负值来定位函数的最小值。
梯度下降算法需要一个正在优化的目标函数和目标函数的导数函数。目标函数 f() 返回给定输入集的得分,导数函数 f'() 给出给定输入集的目标函数的导数。
梯度下降算法要求问题中有一个起点( x ),比如输入空间中随机选择的一个点。
然后计算导数,并在输入空间中采取一个步骤,该步骤预计会导致目标函数的下坡运动,假设我们正在最小化目标函数。
下坡移动是通过首先计算在输入空间中移动多远来实现的,计算方法是步长(称为 alpha 或学习率)乘以梯度。然后从当前点减去这一点,确保我们逆着梯度或目标函数向下移动。
- x = x–步长* f'(x)
给定点处的目标函数越陡,梯度的幅度就越大,反过来,搜索空间中的步长就越大。使用步长超参数来缩放所采取的步长。
- 步长(α):超参数,控制算法每次迭代在搜索空间中逆着梯度移动多远。
如果步长太小,搜索空间中的移动将会很小,并且搜索将会花费很长时间。如果步长过大,搜索可能会绕过搜索空间并跳过 optima。
现在我们已经熟悉了梯度下降优化算法,让我们来看看 Adadelta。
Adadelta 算法
Adadelta(或“ADADELTA”)是梯度下降优化算法的扩展。
该算法由马修·泽勒在 2012 年发表的题为“ ADADELTA:一种自适应学习率方法的论文中进行了描述
Adadelta 旨在加速优化过程,例如减少达到最优所需的函数求值次数,或者提高优化算法的能力,例如产生更好的最终结果。
最好将其理解为 AdaGrad 和 RMSProp 算法的扩展。
AdaGrad 是梯度下降的扩展,它在每次更新时为目标函数的每个参数计算步长(学习率)。步长的计算方法是,首先对搜索过程中所见参数的偏导数求和,然后将初始步长超参数除以偏导数平方和的平方根。
AdaGrad 中一个参数的自定义步长计算如下:
- cust _ step _ size(t+1)= step _ size/(1e-8+sqrt(s(t)))
其中 cust_step_size(t+1) 是搜索过程中给定点输入变量的计算步长, step_size 是初始步长, sqrt() 是平方根运算, s(t) 是迄今为止(包括当前迭代)搜索过程中看到的输入变量的平方偏导数之和。
RMSProp 可以被认为是 AdaGrad 的扩展,因为它在计算每个参数的步长时使用偏导数的衰减平均值或移动平均值,而不是总和。这是通过增加一个新的超参数“ρ”来实现的,它就像偏导数的动量。
一个参数的衰减移动平均平方偏导数的计算如下:
- s(t+1)=(s(t)*ρ)+(f'(x(t))² *(1.0-ρ))
其中 s(t+1) 是算法当前迭代的一个参数的均方偏导数, s(t) 是上一次迭代的衰减移动平均平方偏导数, f'(x(t))² 是当前参数的平方偏导数,rho 是一个超参数,通常具有类似动量的 0.9 的值。
Adadelta 是 RMSProp 的进一步扩展,旨在提高算法的收敛性,并消除对手动指定初始学习率的需求。
本文提出的想法源自 ADAGRAD,目的是改进该方法的两个主要缺点:1)在整个训练过程中学习率不断衰减,以及 2)需要手动选择全局学习率。
——ADADELTA:一种自适应学习率方法,2012。
平方偏导数的衰减移动平均值是为每个参数计算的,就像 RMSProp 一样。关键的区别在于计算参数的步长时,使用的是增量的衰减平均值或参数的变化。
选择分子是为了确保计算的两个部分具有相同的单位。
在独立导出 RMSProp 更新后,作者注意到更新方程中梯度下降、动量和阿达格勒的单位不匹配。为了解决这个问题,他们使用平方更新的指数衰减平均值
—第 78-79 页,优化算法,2019。
首先,自定义步长计算为δ变化的衰减移动平均值的平方根除以平方偏导数的衰减移动平均值的平方根。
- cust _ step _ size(t+1)=(EP+sqrt(δ(t)))/(EP+sqrt(s(t)))
其中 cust_step_size(t+1) 是给定更新的参数的自定义步长, ep 是添加到分子和分母以避免被零除误差的超参数, delta(t) 是参数平方变化的衰减移动平均值(在上一次迭代中计算),而 s(t) 是平方偏导数的衰减移动平均值(在当前迭代中计算)。
ep 超参数设置为小值,如 1e-3 或 1e-8。除了避免被零除的误差,当衰减的移动平均平方变化和衰减的移动平均平方梯度为零时,它也有助于算法的第一步。
接下来,参数的变化被计算为自定义步长乘以偏导数
- change(t+1)= cust _ step _ size(t+1)* f '(x(t))
接下来,更新参数平方变化的衰减平均值。
- δ(t+1)=(δ(t)*ρ)+(change(t+1)² *(1.0-ρ))
其中*δ(t+1)*是要在下一次迭代中使用的变量变化的衰减平均值,变化(t+1) 是在之前的步骤中计算的,ρ是一个类似动量的超参数,其值类似于 0.9。
最后,使用该变化计算变量的新值。
- x(t+1)= x(t)–变化(t+1)
然后对目标函数的每个变量重复该过程,然后重复整个过程以在搜索空间中导航固定次数的算法迭代。
现在我们已经熟悉了 Adadelta 算法,让我们探索如何实现它并评估它的表现。
Adadelta 梯度下降
在本节中,我们将探索如何用 Adadelta 实现梯度下降优化算法。
二维测试问题
首先,让我们定义一个优化函数。
我们将使用一个简单的二维函数,它对每个维度的输入进行平方,并定义从-1.0 到 1.0 的有效输入范围。
下面的 objective()函数实现了这个功能
# objective function
def objective(x, y):
return x**2.0 + y**2.0
我们可以创建数据集的三维图,以获得对响应表面曲率的感觉。
下面列出了绘制目标函数的完整示例。
# 3d plot of the test function
from numpy import arange
from numpy import meshgrid
from matplotlib import pyplot
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# define range for input
r_min, r_max = -1.0, 1.0
# sample input range uniformly at 0.1 increments
xaxis = arange(r_min, r_max, 0.1)
yaxis = arange(r_min, r_max, 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a surface plot with the jet color scheme
figure = pyplot.figure()
axis = figure.gca(projection='3d')
axis.plot_surface(x, y, results, cmap='jet')
# show the plot
pyplot.show()
运行该示例会创建目标函数的三维表面图。
我们可以看到熟悉的碗形,全局最小值在 f(0,0) = 0。
测试目标函数的三维图
我们还可以创建函数的二维图。这将有助于我们以后绘制搜索进度。
以下示例创建了目标函数的等高线图。
# contour plot of the test function
from numpy import asarray
from numpy import arange
from numpy import meshgrid
from matplotlib import pyplot
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# sample input range uniformly at 0.1 increments
xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a filled contour plot with 50 levels and jet color scheme
pyplot.contourf(x, y, results, levels=50, cmap='jet')
# show the plot
pyplot.show()
运行该示例会创建目标函数的二维等高线图。
我们可以看到碗的形状被压缩成带有颜色梯度的轮廓。我们将使用此图来绘制搜索过程中探索的具体点。
测试目标函数的二维等高线图
现在我们有了一个测试目标函数,让我们看看如何实现 Adadelta 优化算法。
带自适应增量的梯度下降优化
我们可以将带有 Adadelta 的梯度下降应用于测试问题。
首先,我们需要一个函数来计算这个函数的导数。
- f(x) = x²
- f'(x) = x * 2
x² 的导数在每个维度上都是 x * 2。导数()函数实现如下。
# derivative of objective function
def derivative(x, y):
return asarray([x * 2.0, y * 2.0])
接下来,我们可以实现梯度下降优化。
首先,我们可以在问题的边界中选择一个随机点作为搜索的起点。
这假设我们有一个定义搜索范围的数组,每个维度有一行,第一列定义维度的最小值,第二列定义维度的最大值。
...
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
接下来,我们需要将每个维度的平方偏导数和平方变化的衰减平均值初始化为 0.0。
...
# list of the average square gradients for each variable
sq_grad_avg = [0.0 for _ in range(bounds.shape[0])]
# list of the average parameter updates
sq_para_avg = [0.0 for _ in range(bounds.shape[0])]
然后,我们可以枚举由“ n_iter ”超参数定义的搜索优化算法的固定迭代次数。
...
# run the gradient descent
for it in range(n_iter):
...
第一步是使用*导数()*函数计算当前解的梯度。
...
# calculate gradient
gradient = derivative(solution[0], solution[1])
然后我们需要计算偏导数的平方,并用“ρ”超参数更新平方偏导数的衰减移动平均值。
...
# update the average of the squared partial derivatives
for i in range(gradient.shape[0]):
# calculate the squared gradient
sg = gradient[i]**2.0
# update the moving average of the squared gradient
sq_grad_avg[i] = (sq_grad_avg[i] * rho) + (sg * (1.0-rho))
然后,我们可以使用平方偏导数和梯度的衰减移动平均值来计算下一个点的步长。我们将一次做一个变量。
...
# build solution
new_solution = list()
for i in range(solution.shape[0]):
...
首先,我们将使用平方变化和平方偏导数的衰减移动平均以及“ep”超参数来计算这个变量在这次迭代中的自定义步长。
...
# calculate the step size for this variable
alpha = (ep + sqrt(sq_para_avg[i])) / (ep + sqrt(sq_grad_avg[i]))
接下来,我们可以使用自定义步长和偏导数来计算变量的变化。
...
# calculate the change
change = alpha * gradient[i]
然后,我们可以使用变化来更新平方变化的衰减移动平均,使用“ρ”超参数。
...
# update the moving average of squared parameter changes
sq_para_avg[i] = (sq_para_avg[i] * rho) + (change**2.0 * (1.0-rho))
最后,我们可以在继续下一个变量之前更改变量并存储结果。
...
# calculate the new position in this variable
value = solution[i] - change
# store this variable
new_solution.append(value)
然后可以使用 objective()函数评估这个新的解决方案,并报告搜索的表现。
...
# evaluate candidate point
solution = asarray(new_solution)
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
就这样。
我们可以将所有这些联系到一个名为 adadelta() 的函数中,该函数采用目标函数和导数函数的名称,一个数组,该数组具有算法迭代总数和ρ的定义域和超参数值的边界,并返回最终解及其求值结果。
ep 超参数也可以作为一个参数,尽管它有一个合理的默认值 1e-3。
下面列出了完整的功能。
# gradient descent algorithm with adadelta
def adadelta(objective, derivative, bounds, n_iter, rho, ep=1e-3):
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of the average square gradients for each variable
sq_grad_avg = [0.0 for _ in range(bounds.shape[0])]
# list of the average parameter updates
sq_para_avg = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate gradient
gradient = derivative(solution[0], solution[1])
# update the average of the squared partial derivatives
for i in range(gradient.shape[0]):
# calculate the squared gradient
sg = gradient[i]**2.0
# update the moving average of the squared gradient
sq_grad_avg[i] = (sq_grad_avg[i] * rho) + (sg * (1.0-rho))
# build a solution one variable at a time
new_solution = list()
for i in range(solution.shape[0]):
# calculate the step size for this variable
alpha = (ep + sqrt(sq_para_avg[i])) / (ep + sqrt(sq_grad_avg[i]))
# calculate the change
change = alpha * gradient[i]
# update the moving average of squared parameter changes
sq_para_avg[i] = (sq_para_avg[i] * rho) + (change**2.0 * (1.0-rho))
# calculate the new position in this variable
value = solution[i] - change
# store this variable
new_solution.append(value)
# evaluate candidate point
solution = asarray(new_solution)
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return [solution, solution_eval]
注:为了可读性,我们特意使用了列表和命令式编码风格,而不是矢量化操作。请随意将该实现调整为使用 NumPy 数组的矢量化实现,以获得更好的表现。
然后我们可以定义我们的超参数,并调用 adadelta() 函数来优化我们的测试目标函数。
在这种情况下,我们将使用该算法的 120 次迭代和 0.99 的 rho 超参数值,该值是在经过一点反复试验后选择的。
...
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 120
# momentum for adadelta
rho = 0.99
# perform the gradient descent search with adadelta
best, score = adadelta(objective, derivative, bounds, n_iter, rho)
print('Done!')
print('f(%s) = %f' % (best, score))
将所有这些联系在一起,下面列出了使用 Adadelta 进行梯度下降优化的完整示例。
# gradient descent optimization with adadelta for a two-dimensional test function
from math import sqrt
from numpy import asarray
from numpy.random import rand
from numpy.random import seed
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# derivative of objective function
def derivative(x, y):
return asarray([x * 2.0, y * 2.0])
# gradient descent algorithm with adadelta
def adadelta(objective, derivative, bounds, n_iter, rho, ep=1e-3):
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of the average square gradients for each variable
sq_grad_avg = [0.0 for _ in range(bounds.shape[0])]
# list of the average parameter updates
sq_para_avg = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate gradient
gradient = derivative(solution[0], solution[1])
# update the average of the squared partial derivatives
for i in range(gradient.shape[0]):
# calculate the squared gradient
sg = gradient[i]**2.0
# update the moving average of the squared gradient
sq_grad_avg[i] = (sq_grad_avg[i] * rho) + (sg * (1.0-rho))
# build a solution one variable at a time
new_solution = list()
for i in range(solution.shape[0]):
# calculate the step size for this variable
alpha = (ep + sqrt(sq_para_avg[i])) / (ep + sqrt(sq_grad_avg[i]))
# calculate the change
change = alpha * gradient[i]
# update the moving average of squared parameter changes
sq_para_avg[i] = (sq_para_avg[i] * rho) + (change**2.0 * (1.0-rho))
# calculate the new position in this variable
value = solution[i] - change
# store this variable
new_solution.append(value)
# evaluate candidate point
solution = asarray(new_solution)
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return [solution, solution_eval]
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 120
# momentum for adadelta
rho = 0.99
# perform the gradient descent search with adadelta
best, score = adadelta(objective, derivative, bounds, n_iter, rho)
print('Done!')
print('f(%s) = %f' % (best, score))
运行该示例将 Adadelta 优化算法应用于我们的测试问题,并报告算法每次迭代的搜索表现。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到,在大约 105 次搜索迭代后,找到了接近最优的解,输入值接近 0.0 和 0.0,评估为 0.0。
...
>100 f([-1.45142626e-07 2.71163181e-03]) = 0.00001
>101 f([-1.24898699e-07 2.56875692e-03]) = 0.00001
>102 f([-1.07454197e-07 2.43328237e-03]) = 0.00001
>103 f([-9.24253035e-08 2.30483111e-03]) = 0.00001
>104 f([-7.94803792e-08 2.18304501e-03]) = 0.00000
>105 f([-6.83329263e-08 2.06758392e-03]) = 0.00000
>106 f([-5.87354975e-08 1.95812477e-03]) = 0.00000
>107 f([-5.04744185e-08 1.85436071e-03]) = 0.00000
>108 f([-4.33652179e-08 1.75600036e-03]) = 0.00000
>109 f([-3.72486699e-08 1.66276699e-03]) = 0.00000
>110 f([-3.19873691e-08 1.57439783e-03]) = 0.00000
>111 f([-2.74627662e-08 1.49064334e-03]) = 0.00000
>112 f([-2.3572602e-08 1.4112666e-03]) = 0.00000
>113 f([-2.02286891e-08 1.33604264e-03]) = 0.00000
>114 f([-1.73549914e-08 1.26475787e-03]) = 0.00000
>115 f([-1.48859650e-08 1.19720951e-03]) = 0.00000
>116 f([-1.27651224e-08 1.13320504e-03]) = 0.00000
>117 f([-1.09437923e-08 1.07256172e-03]) = 0.00000
>118 f([-9.38004754e-09 1.01510604e-03]) = 0.00000
>119 f([-8.03777865e-09 9.60673346e-04]) = 0.00000
Done!
f([-8.03777865e-09 9.60673346e-04]) = 0.000001
阿达塔的可视化
我们可以在域的等高线图上绘制阿达塔搜索的进度。
这可以为算法迭代过程中的搜索进度提供直觉。
我们必须更新 adadelta() 函数,以维护搜索过程中找到的所有解决方案的列表,然后在搜索结束时返回该列表。
下面列出了带有这些更改的功能的更新版本。
# gradient descent algorithm with adadelta
def adadelta(objective, derivative, bounds, n_iter, rho, ep=1e-3):
# track all solutions
solutions = list()
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of the average square gradients for each variable
sq_grad_avg = [0.0 for _ in range(bounds.shape[0])]
# list of the average parameter updates
sq_para_avg = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate gradient
gradient = derivative(solution[0], solution[1])
# update the average of the squared partial derivatives
for i in range(gradient.shape[0]):
# calculate the squared gradient
sg = gradient[i]**2.0
# update the moving average of the squared gradient
sq_grad_avg[i] = (sq_grad_avg[i] * rho) + (sg * (1.0-rho))
# build solution
new_solution = list()
for i in range(solution.shape[0]):
# calculate the step size for this variable
alpha = (ep + sqrt(sq_para_avg[i])) / (ep + sqrt(sq_grad_avg[i]))
# calculate the change
change = alpha * gradient[i]
# update the moving average of squared parameter changes
sq_para_avg[i] = (sq_para_avg[i] * rho) + (change**2.0 * (1.0-rho))
# calculate the new position in this variable
value = solution[i] - change
# store this variable
new_solution.append(value)
# store the new solution
solution = asarray(new_solution)
solutions.append(solution)
# evaluate candidate point
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return solutions
然后,我们可以像以前一样执行搜索,这次检索解决方案列表,而不是最佳最终解决方案。
...
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 120
# rho for adadelta
rho = 0.99
# perform the gradient descent search with adadelta
solutions = adadelta(objective, derivative, bounds, n_iter, rho)
然后,我们可以像以前一样创建目标函数的等高线图。
...
# sample input range uniformly at 0.1 increments
xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a filled contour plot with 50 levels and jet color scheme
pyplot.contourf(x, y, results, levels=50, cmap='jet')
最后,我们可以将搜索过程中找到的每个解决方案绘制成由一条线连接的白点。
...
# plot the sample as black circles
solutions = asarray(solutions)
pyplot.plot(solutions[:, 0], solutions[:, 1], '.-', color='w')
将所有这些结合起来,下面列出了对测试问题执行 Adadelta 优化并将结果绘制在等高线图上的完整示例。
# example of plotting the adadelta 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
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# derivative of objective function
def derivative(x, y):
return asarray([x * 2.0, y * 2.0])
# gradient descent algorithm with adadelta
def adadelta(objective, derivative, bounds, n_iter, rho, ep=1e-3):
# track all solutions
solutions = list()
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of the average square gradients for each variable
sq_grad_avg = [0.0 for _ in range(bounds.shape[0])]
# list of the average parameter updates
sq_para_avg = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate gradient
gradient = derivative(solution[0], solution[1])
# update the average of the squared partial derivatives
for i in range(gradient.shape[0]):
# calculate the squared gradient
sg = gradient[i]**2.0
# update the moving average of the squared gradient
sq_grad_avg[i] = (sq_grad_avg[i] * rho) + (sg * (1.0-rho))
# build solution
new_solution = list()
for i in range(solution.shape[0]):
# calculate the step size for this variable
alpha = (ep + sqrt(sq_para_avg[i])) / (ep + sqrt(sq_grad_avg[i]))
# calculate the change
change = alpha * gradient[i]
# update the moving average of squared parameter changes
sq_para_avg[i] = (sq_para_avg[i] * rho) + (change**2.0 * (1.0-rho))
# calculate the new position in this variable
value = solution[i] - change
# store this variable
new_solution.append(value)
# store the new solution
solution = asarray(new_solution)
solutions.append(solution)
# evaluate candidate point
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return solutions
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 120
# rho for adadelta
rho = 0.99
# perform the gradient descent search with adadelta
solutions = adadelta(objective, derivative, bounds, n_iter, rho)
# sample input range uniformly at 0.1 increments
xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a filled contour plot with 50 levels and jet color scheme
pyplot.contourf(x, y, results, levels=50, cmap='jet')
# plot the sample as black circles
solutions = asarray(solutions)
pyplot.plot(solutions[:, 0], solutions[:, 1], '.-', color='w')
# show the plot
pyplot.show()
运行该示例会像以前一样执行搜索,只是在这种情况下,会创建目标函数的等高线图。
在这种情况下,我们可以看到,搜索过程中找到的每个解决方案都显示一个白点,从 optima 上方开始,逐渐靠近图中心的 optima。
显示 Adadelta 搜索结果的测试目标函数等高线图
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
报纸
- ADADELTA:一种自适应学习率方法,2012。
书
蜜蜂
- num py . random . rand API。
- num py . asar ray API。
- Matplotlib API 。
文章
- 梯度下降,维基百科。
- 随机梯度下降,维基百科。
- 梯度下降优化算法概述,2016。
摘要
在本教程中,您发现了如何从零开始使用 Adadelta 优化算法开发梯度下降。
具体来说,您了解到:
- 梯度下降是一种优化算法,它使用目标函数的梯度来导航搜索空间。
- 梯度下降可以更新,以使用一个衰减的偏导数平均值(称为 Adadelta)为每个输入变量使用自动自适应步长。
- 如何从零开始实现 Adadelta 优化算法,并将其应用于目标函数并评估结果。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
从零开始的 AdaGrad 梯度下降
最后更新于 2021 年 10 月 12 日
梯度下降是一种优化算法,它遵循目标函数的负梯度来定位函数的最小值。
梯度下降的一个限制是,它对每个输入变量使用相同的步长(学习率)。这对于在不同维度上具有不同曲率的目标函数来说可能是一个问题,并且反过来可能需要不同大小的步长来到达新的点。
自适应梯度,简称 AdaGrad ,是梯度下降优化算法的扩展,允许优化算法使用的每个维度中的步长基于搜索过程中看到的变量(偏导数)的梯度自动调整。
在本教程中,您将发现如何从零开始开发带有自适应梯度优化算法的梯度下降。
完成本教程后,您将知道:
- 梯度下降是一种优化算法,它使用目标函数的梯度来导航搜索空间。
- 梯度下降可以更新,为目标函数中的每个输入变量使用自动自适应步长,称为自适应梯度或 AdaGrad。
- 如何从零开始实现 AdaGrad 优化算法,并将其应用于目标函数并评估结果。
用我的新书机器学习优化启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
Let’s get started.
从零开始的梯度下降 图片由毛里蒂斯·韦尔比斯特提供,保留部分权利。
教程概述
本教程分为三个部分;它们是:
- 梯度下降
- 自适应梯度
- 用 AdaGrad 梯度下降
- 二维测试问题
- 基于 AdaGrad 的梯度下降优化
- AdaGrad 的可视化
梯度下降
梯度下降是一种优化算法。
它在技术上被称为一阶优化算法,因为它明确地利用了目标函数的一阶导数。
一阶方法依赖于梯度信息来帮助指导搜索最小值…
—第 69 页,优化算法,2019。
一阶导数,或简称为“导数,是目标函数在特定点的变化率或斜率,例如对于特定输入。
如果目标函数有多个输入变量,它被称为多元函数,输入变量可以被认为是一个向量。反过来,多元目标函数的导数也可以作为向量,通常称为“梯度”
- 梯度:多元目标函数的一阶导数。
对于特定输入,导数或梯度指向目标函数最陡上升的方向。
梯度下降是指一种最小化优化算法,它遵循目标函数梯度下降的负值来定位函数的最小值。
梯度下降算法需要一个正在优化的目标函数和目标函数的导数函数。目标函数 f() 返回给定输入集的得分,导数函数 f'() 给出给定输入集的目标函数的导数。
梯度下降算法要求问题中有一个起点( x ),比如输入空间中随机选择的一个点。
然后计算导数,并在输入空间中采取一个步骤,该步骤预计会导致目标函数的下坡运动,假设我们正在最小化目标函数。
下坡移动是通过首先计算在输入空间中移动多远来实现的,计算方法是步长(称为 alpha 或学习率)乘以梯度。然后从当前点减去这一点,确保我们逆着梯度或目标函数向下移动。
- x = x–步长* f'(x)
给定点处的目标函数越陡,梯度的幅度就越大,反过来,搜索空间中的步长就越大。使用步长超参数来缩放所采取的步长。
- 步长(α):超参数,控制算法每次迭代在搜索空间中逆着梯度移动多远。
如果步长太小,搜索空间中的移动将会很小,并且搜索将会花费很长时间。如果步长过大,搜索可能会绕过搜索空间并跳过 optima。
现在我们已经熟悉了梯度下降优化算法,让我们来看看 AdaGrad。
自适应梯度
自适应梯度算法,简称 AdaGrad,是梯度下降优化算法的扩展。
约翰·杜奇等人在 2011 年的论文《在线学习和随机优化的自适应次梯度方法》中描述了该算法
它旨在加速优化过程,例如减少达到最优所需的函数求值次数,或者提高优化算法的能力,例如产生更好的最终结果。
具有最大损失偏导数的参数的学习率相应地快速下降,而具有小偏导数的参数的学习率下降相对较小。
—第 307 页,深度学习,2016。
梯度下降算法的一个问题是,对于搜索空间中的每个变量或维度,步长(学习率)是相同的。有可能使用针对每个变量定制的步长来实现更好的表现,从而允许在具有一致陡峭梯度的维度上进行更大的移动,而在具有不太陡峭梯度的维度上进行更小的移动。
AdaGrad 旨在专门探索为搜索空间中的每个维度自动定制步长的想法。
自适应次梯度方法,或称 Adagrad,为 x 的每个分量调整一个学习率
—第 77 页,优化算法,2019。
这是通过首先计算给定维度的步长,然后使用计算出的步长在该维度上使用偏导数进行移动来实现的。然后对搜索空间中的每个维度重复这个过程。
Adagrad 减弱了具有持续高梯度的参数的影响,从而增加了不经常更新的参数的影响。
—第 77 页,优化算法,2019。
AdaGrad 适用于搜索空间的曲率在不同维度上不同的目标函数,允许在每个维度上定制步长的情况下进行更有效的优化。
该算法要求您按照标准为所有输入变量设置初始步长,例如 0.1 或 0.001 或类似值。虽然,该算法的好处是它不像梯度下降算法那样对初始学习率敏感。
Adagrad 对学习率参数α的敏感度要低得多。学习率参数通常设置为默认值 0.01。
—第 77 页,优化算法,2019。
然后为每个输入变量维护一个内部变量,该变量是搜索过程中观察到的输入变量的平方偏导数之和。
然后,通过将初始步长值(例如,运行开始时指定的超参数值)除以平方偏导数之和的平方根,平方偏导数之和用于计算变量的步长。
- 客户步长=步长/平方
平方偏导数之和的平方根可能产生 0.0 的值,从而产生除以零的误差。因此,可以在分母上增加一个微小的值来避免这种可能性,例如 1e-8。
- cust _ step _ size = step _ size/(1e-8+sqrt)
其中 cust_step_size 是搜索过程中给定点输入变量的计算步长, step_size 是初始步长, sqrt() 是平方根运算, s 是迄今为止搜索过程中看到的输入变量的平方偏导数之和。
然后使用自定义步长来计算搜索中下一个点或解中的变量值。
- x(t+1)= x(t)–cust _ step _ size * f '(x(t))
然后对每个输入变量重复这个过程,直到在搜索空间中创建一个新的点并可以对其进行评估。
重要的是,当前解的偏导数(搜索的迭代)包含在偏导数的平方根之和中。
我们可以为每个输入变量维护一个偏导数或平方偏导数的数组,但这不是必需的。相反,我们只需保持偏导数的平方和,并在此过程中为该和添加新值。
现在我们已经熟悉了 AdaGrad 算法,让我们探索如何实现它并评估它的表现。
用 AdaGrad 梯度下降
在这一部分,我们将探索如何实现梯度下降优化算法与自适应梯度。
二维测试问题
首先,让我们定义一个优化函数。
我们将使用一个简单的二维函数,它对每个维度的输入进行平方,并定义从-1.0 到 1.0 的有效输入范围。
下面的*目标()*函数实现了这个功能。
# objective function
def objective(x, y):
return x**2.0 + y**2.0
我们可以创建数据集的三维图,以获得对响应表面曲率的感觉。
下面列出了绘制目标函数的完整示例。
# 3d plot of the test function
from numpy import arange
from numpy import meshgrid
from matplotlib import pyplot
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# define range for input
r_min, r_max = -1.0, 1.0
# sample input range uniformly at 0.1 increments
xaxis = arange(r_min, r_max, 0.1)
yaxis = arange(r_min, r_max, 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a surface plot with the jet color scheme
figure = pyplot.figure()
axis = figure.gca(projection='3d')
axis.plot_surface(x, y, results, cmap='jet')
# show the plot
pyplot.show()
运行该示例会创建目标函数的三维表面图。
我们可以看到熟悉的碗形,全局最小值在 f(0,0) = 0。
测试目标函数的三维图
我们还可以创建函数的二维图。这将有助于我们以后绘制搜索进度。
以下示例创建了目标函数的等高线图。
# contour plot of the test function
from numpy import asarray
from numpy import arange
from numpy import meshgrid
from matplotlib import pyplot
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# sample input range uniformly at 0.1 increments
xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a filled contour plot with 50 levels and jet color scheme
pyplot.contourf(x, y, results, levels=50, cmap='jet')
# show the plot
pyplot.show()
运行该示例会创建目标函数的二维等高线图。
我们可以看到碗的形状被压缩成带有颜色梯度的轮廓。我们将使用此图来绘制搜索过程中探索的具体点。
测试目标函数的二维等高线图
现在我们有了一个测试目标函数,让我们看看如何实现 AdaGrad 优化算法。
基于 AdaGrad 的梯度下降优化
我们可以将梯度下降和自适应梯度算法应用于测试问题。
首先,我们需要一个函数来计算这个函数的导数。
- f(x) = x²
- f'(x) = x * 2
x² 的导数在每个维度上都是 x * 2。
*导数()*函数实现如下。
# derivative of objective function
def derivative(x, y):
return asarray([x * 2.0, y * 2.0])
接下来,我们可以用自适应梯度实现梯度下降。
首先,我们可以在问题的边界中选择一个随机点作为搜索的起点。
这假设我们有一个定义搜索范围的数组,每个维度有一行,第一列定义维度的最小值,第二列定义维度的最大值。
...
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
接下来,我们需要将每个维度的平方偏导数之和初始化为 0.0。
...
# list of the sum square gradients for each variable
sq_grad_sums = [0.0 for _ in range(bounds.shape[0])]
然后,我们可以枚举由“ n_iter ”超参数定义的搜索优化算法的固定迭代次数。
...
# run the gradient descent
for it in range(n_iter):
...
第一步是使用*导数()*函数计算当前解的梯度。
...
# calculate gradient
gradient = derivative(solution[0], solution[1])
然后,我们需要计算每个变量偏导数的平方,并将它们加到这些值的总和中。
...
# update the sum of the squared partial derivatives
for i in range(gradient.shape[0]):
sq_grad_sums[i] += gradient[i]**2.0
然后,我们可以使用平方和偏导数和梯度来计算下一个点。
我们将一次处理一个变量,首先计算变量的步长,然后计算变量的新值。这些值在一个数组中建立,直到我们有一个全新的解决方案,该解决方案使用自定义步长从当前点开始以最陡的下降方向下降。
...
# build a solution one variable at a time
new_solution = list()
for i in range(solution.shape[0]):
# calculate the step size for this variable
alpha = step_size / (1e-8 + sqrt(sq_grad_sums[i]))
# calculate the new position in this variable
value = solution[i] - alpha * gradient[i]
# store this variable
new_solution.append(value)
然后,可以使用*客观()*函数来评估这个新的解决方案,并且可以报告搜索的表现。
...
# evaluate candidate point
solution = asarray(new_solution)
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
就这样。
我们可以将所有这些联系到一个名为 adagrad() 的函数中,该函数取目标函数和导函数的名称,一个有定义域边界的数组,以及算法迭代总数和初始学习率的超参数值,并返回最终解及其求值。
下面列出了完整的功能。
# gradient descent algorithm with adagrad
def adagrad(objective, derivative, bounds, n_iter, step_size):
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of the sum square gradients for each variable
sq_grad_sums = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate gradient
gradient = derivative(solution[0], solution[1])
# update the sum of the squared partial derivatives
for i in range(gradient.shape[0]):
sq_grad_sums[i] += gradient[i]**2.0
# build a solution one variable at a time
new_solution = list()
for i in range(solution.shape[0]):
# calculate the step size for this variable
alpha = step_size / (1e-8 + sqrt(sq_grad_sums[i]))
# calculate the new position in this variable
value = solution[i] - alpha * gradient[i]
# store this variable
new_solution.append(value)
# evaluate candidate point
solution = asarray(new_solution)
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return [solution, solution_eval]
注:为了可读性,我们特意使用了列表和命令式编码风格,而不是矢量化操作。请随意将实现调整为使用 NumPy 阵列的矢量化实现,以获得更好的表现。
然后我们可以定义我们的超参数,并调用 adagrad() 函数来优化我们的测试目标函数。
在这种情况下,我们将使用算法的 50 次迭代和 0.1 的初始学习率,这两者都是经过一点点尝试和错误后选择的。
...
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 50
# define the step size
step_size = 0.1
# perform the gradient descent search with adagrad
best, score = adagrad(objective, derivative, bounds, n_iter, step_size)
print('Done!')
print('f(%s) = %f' % (best, score))
将所有这些结合在一起,下面列出了具有自适应梯度的梯度下降优化的完整示例。
# gradient descent optimization with adagrad for a two-dimensional test function
from math import sqrt
from numpy import asarray
from numpy.random import rand
from numpy.random import seed
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# derivative of objective function
def derivative(x, y):
return asarray([x * 2.0, y * 2.0])
# gradient descent algorithm with adagrad
def adagrad(objective, derivative, bounds, n_iter, step_size):
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of the sum square gradients for each variable
sq_grad_sums = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate gradient
gradient = derivative(solution[0], solution[1])
# update the sum of the squared partial derivatives
for i in range(gradient.shape[0]):
sq_grad_sums[i] += gradient[i]**2.0
# build a solution one variable at a time
new_solution = list()
for i in range(solution.shape[0]):
# calculate the step size for this variable
alpha = step_size / (1e-8 + sqrt(sq_grad_sums[i]))
# calculate the new position in this variable
value = solution[i] - alpha * gradient[i]
# store this variable
new_solution.append(value)
# evaluate candidate point
solution = asarray(new_solution)
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return [solution, solution_eval]
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 50
# define the step size
step_size = 0.1
# perform the gradient descent search with adagrad
best, score = adagrad(objective, derivative, bounds, n_iter, step_size)
print('Done!')
print('f(%s) = %f' % (best, score))
运行该示例将 AdaGrad 优化算法应用于我们的测试问题,并报告算法每次迭代的搜索表现。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到,在大约 35 次搜索迭代后,找到了一个接近最优的解,输入值接近 0.0 和 0.0,评估为 0.0。
>0 f([-0.06595599 0.34064899]) = 0.12039
>1 f([-0.02902286 0.27948766]) = 0.07896
>2 f([-0.0129815 0.23463749]) = 0.05522
>3 f([-0.00582483 0.1993997 ]) = 0.03979
>4 f([-0.00261527 0.17071256]) = 0.02915
>5 f([-0.00117437 0.14686138]) = 0.02157
>6 f([-0.00052736 0.12676134]) = 0.01607
>7 f([-0.00023681 0.10966762]) = 0.01203
>8 f([-0.00010634 0.09503809]) = 0.00903
>9 f([-4.77542704e-05 8.24607972e-02]) = 0.00680
>10 f([-2.14444463e-05 7.16123835e-02]) = 0.00513
>11 f([-9.62980437e-06 6.22327049e-02]) = 0.00387
>12 f([-4.32434258e-06 5.41085063e-02]) = 0.00293
>13 f([-1.94188148e-06 4.70624414e-02]) = 0.00221
>14 f([-8.72017797e-07 4.09453989e-02]) = 0.00168
>15 f([-3.91586740e-07 3.56309531e-02]) = 0.00127
>16 f([-1.75845235e-07 3.10112252e-02]) = 0.00096
>17 f([-7.89647442e-08 2.69937139e-02]) = 0.00073
>18 f([-3.54597657e-08 2.34988084e-02]) = 0.00055
>19 f([-1.59234984e-08 2.04577993e-02]) = 0.00042
>20 f([-7.15057749e-09 1.78112581e-02]) = 0.00032
>21 f([-3.21102543e-09 1.55077005e-02]) = 0.00024
>22 f([-1.44193729e-09 1.35024688e-02]) = 0.00018
>23 f([-6.47513760e-10 1.17567908e-02]) = 0.00014
>24 f([-2.90771361e-10 1.02369798e-02]) = 0.00010
>25 f([-1.30573263e-10 8.91375193e-03]) = 0.00008
>26 f([-5.86349941e-11 7.76164047e-03]) = 0.00006
>27 f([-2.63305247e-11 6.75849105e-03]) = 0.00005
>28 f([-1.18239380e-11 5.88502652e-03]) = 0.00003
>29 f([-5.30963626e-12 5.12447017e-03]) = 0.00003
>30 f([-2.38433568e-12 4.46221948e-03]) = 0.00002
>31 f([-1.07070548e-12 3.88556303e-03]) = 0.00002
>32 f([-4.80809073e-13 3.38343471e-03]) = 0.00001
>33 f([-2.15911255e-13 2.94620023e-03]) = 0.00001
>34 f([-9.69567190e-14 2.56547145e-03]) = 0.00001
>35 f([-4.35392094e-14 2.23394494e-03]) = 0.00000
>36 f([-1.95516389e-14 1.94526160e-03]) = 0.00000
>37 f([-8.77982370e-15 1.69388439e-03]) = 0.00000
>38 f([-3.94265180e-15 1.47499203e-03]) = 0.00000
>39 f([-1.77048011e-15 1.28438640e-03]) = 0.00000
>40 f([-7.95048604e-16 1.11841198e-03]) = 0.00000
>41 f([-3.57023093e-16 9.73885702e-04]) = 0.00000
>42 f([-1.60324146e-16 8.48035867e-04]) = 0.00000
>43 f([-7.19948720e-17 7.38448972e-04]) = 0.00000
>44 f([-3.23298874e-17 6.43023418e-04]) = 0.00000
>45 f([-1.45180009e-17 5.59929193e-04]) = 0.00000
>46 f([-6.51942732e-18 4.87572776e-04]) = 0.00000
>47 f([-2.92760228e-18 4.24566574e-04]) = 0.00000
>48 f([-1.31466380e-18 3.69702307e-04]) = 0.00000
>49 f([-5.90360555e-19 3.21927835e-04]) = 0.00000
Done!
f([-5.90360555e-19 3.21927835e-04]) = 0.000000
AdaGrad 的可视化
我们可以在域的等高线图上绘制搜索进度。
这可以为算法迭代过程中的搜索进度提供直觉。
我们必须更新 adagrad() 函数,以维护搜索过程中找到的所有解决方案的列表,然后在搜索结束时返回该列表。
下面列出了带有这些更改的功能的更新版本。
# gradient descent algorithm with adagrad
def adagrad(objective, derivative, bounds, n_iter, step_size):
# track all solutions
solutions = list()
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of the sum square gradients for each variable
sq_grad_sums = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate gradient
gradient = derivative(solution[0], solution[1])
# update the sum of the squared partial derivatives
for i in range(gradient.shape[0]):
sq_grad_sums[i] += gradient[i]**2.0
# build solution
new_solution = list()
for i in range(solution.shape[0]):
# calculate the learning rate for this variable
alpha = step_size / (1e-8 + sqrt(sq_grad_sums[i]))
# calculate the new position in this variable
value = solution[i] - alpha * gradient[i]
new_solution.append(value)
# store the new solution
solution = asarray(new_solution)
solutions.append(solution)
# evaluate candidate point
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return solutions
然后,我们可以像以前一样执行搜索,这次检索解决方案列表,而不是最佳最终解决方案。
...
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 50
# define the step size
step_size = 0.1
# perform the gradient descent search
solutions = adagrad(objective, derivative, bounds, n_iter, step_size)
然后,我们可以像以前一样创建目标函数的等高线图。
...
# sample input range uniformly at 0.1 increments
xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a filled contour plot with 50 levels and jet color scheme
pyplot.contourf(x, y, results, levels=50, cmap='jet')
最后,我们可以将搜索过程中找到的每个解决方案绘制成由一条线连接的白点。
...
# plot the sample as black circles
solutions = asarray(solutions)
pyplot.plot(solutions[:, 0], solutions[:, 1], '.-', color='w')
将所有这些结合起来,下面列出了对测试问题执行 AdaGrad 优化并将结果绘制在等高线图上的完整示例。
# example of plotting the adagrad 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
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# derivative of objective function
def derivative(x, y):
return asarray([x * 2.0, y * 2.0])
# gradient descent algorithm with adagrad
def adagrad(objective, derivative, bounds, n_iter, step_size):
# track all solutions
solutions = list()
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of the sum square gradients for each variable
sq_grad_sums = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate gradient
gradient = derivative(solution[0], solution[1])
# update the sum of the squared partial derivatives
for i in range(gradient.shape[0]):
sq_grad_sums[i] += gradient[i]**2.0
# build solution
new_solution = list()
for i in range(solution.shape[0]):
# calculate the learning rate for this variable
alpha = step_size / (1e-8 + sqrt(sq_grad_sums[i]))
# calculate the new position in this variable
value = solution[i] - alpha * gradient[i]
new_solution.append(value)
# store the new solution
solution = asarray(new_solution)
solutions.append(solution)
# evaluate candidate point
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return solutions
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 50
# define the step size
step_size = 0.1
# perform the gradient descent search
solutions = adagrad(objective, derivative, bounds, n_iter, step_size)
# sample input range uniformly at 0.1 increments
xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a filled contour plot with 50 levels and jet color scheme
pyplot.contourf(x, y, results, levels=50, cmap='jet')
# plot the sample as black circles
solutions = asarray(solutions)
pyplot.plot(solutions[:, 0], solutions[:, 1], '.-', color='w')
# show the plot
pyplot.show()
运行该示例与之前一样执行搜索,只是在这种情况下,创建了目标函数的等高线图,并为搜索过程中找到的每个解显示一个白点,从 optima 上方开始,逐渐靠近图中心的 optima。
显示 AdaGrad 搜索结果的测试目标函数等高线图
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
报纸
- 在线学习和随机优化的自适应次梯度方法,2011。
书
蜜蜂
- num py . random . rand API。
- num py . asar ray API。
- Matplotlib API 。
文章
- 梯度下降,维基百科。
- 随机梯度下降,维基百科。
- 梯度下降优化算法概述,2016。
摘要
在本教程中,您发现了如何从零开始开发带有自适应梯度优化算法的梯度下降。
具体来说,您了解到:
- 梯度下降是一种优化算法,它使用目标函数的梯度来导航搜索空间。
- 梯度下降可以更新,为目标函数中的每个输入变量使用自动自适应步长,称为自适应梯度或 AdaGrad。
- 如何从零开始实现 AdaGrad 优化算法,并将其应用于目标函数并评估结果。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
从零开始的动量梯度下降
最后更新于 2021 年 10 月 12 日
梯度下降是一种优化算法,它遵循目标函数的负梯度,以定位函数的最小值。
梯度下降的一个问题是,它可以在具有大量曲率或噪声梯度的优化问题上围绕搜索空间反弹,并且它可以卡在搜索空间中没有梯度的平坦点上。
动量是梯度下降优化算法的扩展,该算法允许搜索在搜索空间的一个方向上建立惯性,并克服噪声梯度的振荡和在搜索空间的平坦点上滑行。
在本教程中,您将发现动量梯度下降算法。
完成本教程后,您将知道:
- 梯度下降是一种优化算法,它使用目标函数的梯度来导航搜索空间。
- 梯度下降可以通过使用搜索位置过去更新的动量来加速。
- 如何利用动量实现梯度下降优化并对其行为产生直觉?
用我的新书机器学习优化启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
Let’s get started.
从零开始的动量梯度下降 克里斯·巴恩斯摄,版权所有。
教程概述
本教程分为三个部分;它们是:
- 梯度下降
- 动力
- 动量梯度下降
- 一维测试问题
- 梯度下降优化
- 梯度下降优化的可视化
- 动量梯度下降优化
- 动量梯度下降优化的可视化
梯度下降
梯度下降是一种优化算法。
它在技术上被称为一阶优化算法,因为它明确地利用了目标函数的一阶导数。
一阶方法依赖于梯度信息来帮助指导搜索最小值…
—第 69 页,优化算法,2019。
一阶导数,或简称为“导数,是目标函数在特定点的变化率或斜率,例如对于特定输入。
如果目标函数有多个输入变量,它被称为多元函数,输入变量可以被认为是一个向量。反过来,多元目标函数的导数也可以作为向量,通常称为“梯度”
- 梯度:多元目标函数的一阶导数。
对于特定输入,导数或梯度指向目标函数最陡上升的方向。
梯度下降是指一种最小化优化算法,它遵循目标函数梯度下降的负值来定位函数的最小值。
梯度下降算法需要一个正在优化的目标函数和目标函数的导数函数。目标函数 f() 返回给定输入集的得分,导数函数 f'() 给出给定输入集的目标函数的导数。
梯度下降算法要求问题中有一个起点( x ),比如输入空间中随机选择的一个点。
然后计算导数,并在输入空间中采取一个步骤,该步骤预计会导致目标函数的下坡运动,假设我们正在最小化目标函数。
下坡运动是通过首先计算在输入空间中移动多远来实现的,计算方法是步长(称为α或学习率)乘以梯度。然后从当前点减去这一点,确保我们逆着梯度或目标函数向下移动。
- x = x–步长* f'(x)
给定点处的目标函数越陡,梯度的幅度就越大,反过来,搜索空间中的步长也就越大。使用步长超参数来缩放所采取的步长。
- 步长(α):超参数,控制算法每次迭代在搜索空间中逆着梯度移动多远,也称为学习率。
如果步长太小,搜索空间中的移动将会很小,并且搜索将会花费很长时间。如果步长过大,搜索可能会绕过搜索空间并跳过 optima。
现在我们已经熟悉了梯度下降优化算法,让我们来看看动量。
动力
动量是梯度下降优化算法的延伸,通常称为带动量的梯度下降。
它旨在加速优化过程,例如减少达到最优所需的函数求值次数,或者提高优化算法的能力,例如产生更好的最终结果。
梯度下降算法的一个问题是,搜索的进程可以基于梯度在搜索空间中反弹。例如,搜索可以朝着最小值向下进行,但是在这个过程中,它可以根据搜索过程中遇到的特定点(参数集)的梯度向另一个方向移动,甚至上坡。
这可能会减慢搜索的进度,尤其是对于那些优化问题,在这些优化问题中,搜索空间的更广泛的趋势或形状比沿途的特定梯度更有用。
解决这个问题的一种方法是基于在先前更新中遇到的梯度向参数更新方程添加历史。
这种变化是基于物理学中动量的隐喻,一个方向的加速度可以从过去的更新中积累。
动量这个名字来源于一个物理类比,根据牛顿运动定律,负梯度是一种力,使粒子在参数空间中移动。
—第 296 页,深度学习,2016。
动量涉及添加额外的超参数,该超参数控制要包括在更新等式中的历史量(动量),即搜索空间中新点的步长。超参数的值定义在 0.0 到 1.0 的范围内,通常具有接近 1.0 的值,例如 0.8、0.9 或 0.99。0.0 的动量等于没有动量的梯度下降。
首先,让我们将梯度下降更新方程分解为两部分:位置变化的计算和旧位置到新位置的更新。
参数的变化被计算为按步长缩放的点的梯度。
- change_x =步长* f'(x)
通过简单地从当前点减去变化来计算新位置
- x = x–change _ x
动量包括保持位置的变化,并在随后的位置变化计算中使用它。
如果我们考虑随时间的更新,那么当前迭代或时间(t)的更新将加上动量超参数加权的前一时间(t-1)使用的变化,如下所示:
- change_x(t) =步长* f'(x(t-1)) +动量* change_x(t-1)
然后像以前一样更新位置。
- x(t)= x(t-1)-change _ x(t)
位置的变化在搜索的迭代中累积变化的幅度和方向,与动量超参数的大小成比例。
例如,大的动量(例如 0.9)将意味着更新受到先前更新的强烈影响,而适度的动量(0.2)将意味着影响非常小。
动量算法累积过去梯度的指数衰减移动平均值,并继续向它们的方向移动。
—第 296 页,深度学习,2016。
动量具有抑制梯度变化的效果,进而抑制搜索空间中每个新点的步长。
当成本面高度非球形时,动量可以提高速度,因为它抑制了沿高曲率方向的步长,从而沿低曲率方向产生更大的有效学习率。
—第 21 页,神经网络:交易的诀窍,2012 年。
动量在优化问题中最有用,在优化问题中,目标函数具有大量曲率(例如,变化很大),这意味着梯度在搜索空间的相对小的区域上可能变化很大。
动量法旨在加速学习,尤其是在面对高曲率、小但一致的梯度或噪声梯度时。
—第 296 页,深度学习,2016。
当例如从模拟中估计梯度时,这也是有帮助的,并且可能是有噪声的,例如当梯度具有高方差时。
最后,当搜索空间平坦或接近平坦时,动量是有帮助的,例如零梯度。动量允许搜索以与平坦点之前相同的方向前进,并有助于穿过平坦区域。
现在我们已经熟悉了什么是动量,让我们来看一个成功的例子。
动量梯度下降
在本节中,我们将首先实现梯度下降优化算法,然后更新它以使用动量并比较结果。
一维测试问题
首先,让我们定义一个优化函数。
我们将使用一个简单的一维函数对输入进行平方,并定义从-1.0 到 1.0 的有效输入范围。
下面的*目标()*函数实现了这个功能。
# objective function
def objective(x):
return x**2.0
然后,我们可以对该范围内的所有输入进行采样,并计算每个输入的目标函数值。
...
# define range for input
r_min, r_max = -1.0, 1.0
# sample input range uniformly at 0.1 increments
inputs = arange(r_min, r_max+0.1, 0.1)
# compute targets
results = objective(inputs)
最后,我们可以创建输入(x 轴)与目标函数值(y 轴)的线图,以获得我们将搜索的目标函数形状的直觉。
...
# create a line plot of input vs result
pyplot.plot(inputs, results)
# show the plot
pyplot.show()
下面的示例将这些联系在一起,并提供了绘制一维测试函数的示例。
# plot of simple function
from numpy import arange
from matplotlib import pyplot
# objective function
def objective(x):
return x**2.0
# define range for input
r_min, r_max = -1.0, 1.0
# sample input range uniformly at 0.1 increments
inputs = arange(r_min, r_max+0.1, 0.1)
# compute targets
results = objective(inputs)
# create a line plot of input vs result
pyplot.plot(inputs, results)
# show the plot
pyplot.show()
运行该示例会创建函数输入(x 轴)和函数计算输出(y 轴)的线图。
我们可以看到熟悉的 U 形,叫做抛物线。
简单一维函数的线图
梯度下降优化
接下来,我们可以将梯度下降算法应用于该问题。
首先,我们需要一个函数来计算目标函数的导数。
x² 的导数是 x * 2,下面的*导数()*函数实现了这一点。
# derivative of objective function
def derivative(x):
return x * 2.0
我们可以定义一个函数来实现梯度下降优化算法。
该过程包括从搜索空间中随机选择的点开始,然后计算梯度,更新搜索空间中的位置,评估新位置,并报告进度。该过程然后重复固定次数的迭代。最后一个点和它的求值然后从函数返回。
下面的函数*梯度 _ 下降()*实现了这一点,并采用目标函数和梯度函数的名称以及目标函数输入的边界、迭代次数和步长,然后在搜索结束时返回解及其评估。
# gradient descent algorithm
def gradient_descent(objective, derivative, bounds, n_iter, step_size):
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# run the gradient descent
for i in range(n_iter):
# calculate gradient
gradient = derivative(solution)
# take a step
solution = solution - step_size * gradient
# evaluate candidate point
solution_eval = objective(solution)
# report progress
print('>%d f(%s) = %.5f' % (i, solution, solution_eval))
return [solution, solution_eval]
然后,我们可以定义目标函数的边界、步长和算法的迭代次数。
我们将使用 0.1 和 30 次迭代的步长,这两个步长都是经过一些实验后发现的。
伪随机数发生器的种子是固定的,因此我们总是获得相同的随机数序列,在这种情况下,它确保我们每次运行代码时都获得相同的搜索起点(例如,远离 optima 的有趣的东西)。
...
# seed the pseudo random number generator
seed(4)
# define range for input
bounds = asarray([[-1.0, 1.0]])
# define the total iterations
n_iter = 30
# define the maximum step size
step_size = 0.1
# perform the gradient descent search
best, score = gradient_descent(objective, derivative, bounds, n_iter, step_size)
将这些联系在一起,下面列出了将网格搜索应用于我们的一维测试函数的完整示例。
# example of gradient descent for a one-dimensional function
from numpy import asarray
from numpy.random import rand
from numpy.random import seed
# objective function
def objective(x):
return x**2.0
# derivative of objective function
def derivative(x):
return x * 2.0
# gradient descent algorithm
def gradient_descent(objective, derivative, bounds, n_iter, step_size):
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# run the gradient descent
for i in range(n_iter):
# calculate gradient
gradient = derivative(solution)
# take a step
solution = solution - step_size * gradient
# evaluate candidate point
solution_eval = objective(solution)
# report progress
print('>%d f(%s) = %.5f' % (i, solution, solution_eval))
return [solution, solution_eval]
# seed the pseudo random number generator
seed(4)
# define range for input
bounds = asarray([[-1.0, 1.0]])
# define the total iterations
n_iter = 30
# define the step size
step_size = 0.1
# perform the gradient descent search
best, score = gradient_descent(objective, derivative, bounds, n_iter, step_size)
print('Done!')
print('f(%s) = %f' % (best, score))
运行该示例从搜索空间中的随机点开始,然后应用梯度下降算法,报告一路上的表现。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到算法在大约 27 次迭代后找到了一个好的解,函数求值约为 0.0。
注意,此功能的最佳值为 f(0.0) = 0.0。
我们预计,动量梯度下降将加速优化过程,并在更少的迭代中找到类似的评估解决方案。
>0 f([0.74724774]) = 0.55838
>1 f([0.59779819]) = 0.35736
>2 f([0.47823856]) = 0.22871
>3 f([0.38259084]) = 0.14638
>4 f([0.30607268]) = 0.09368
>5 f([0.24485814]) = 0.05996
>6 f([0.19588651]) = 0.03837
>7 f([0.15670921]) = 0.02456
>8 f([0.12536737]) = 0.01572
>9 f([0.10029389]) = 0.01006
>10 f([0.08023512]) = 0.00644
>11 f([0.06418809]) = 0.00412
>12 f([0.05135047]) = 0.00264
>13 f([0.04108038]) = 0.00169
>14 f([0.0328643]) = 0.00108
>15 f([0.02629144]) = 0.00069
>16 f([0.02103315]) = 0.00044
>17 f([0.01682652]) = 0.00028
>18 f([0.01346122]) = 0.00018
>19 f([0.01076897]) = 0.00012
>20 f([0.00861518]) = 0.00007
>21 f([0.00689214]) = 0.00005
>22 f([0.00551372]) = 0.00003
>23 f([0.00441097]) = 0.00002
>24 f([0.00352878]) = 0.00001
>25 f([0.00282302]) = 0.00001
>26 f([0.00225842]) = 0.00001
>27 f([0.00180673]) = 0.00000
>28 f([0.00144539]) = 0.00000
>29 f([0.00115631]) = 0.00000
Done!
f([0.00115631]) = 0.000001
梯度下降优化的可视化
接下来,我们可以在目标函数的图上可视化搜索的进度。
首先,我们可以更新*gradient _ download()*函数,将优化过程中找到的所有解及其得分存储为列表,并在搜索结束时返回,而不是找到的最佳解。
# gradient descent algorithm
def gradient_descent(objective, derivative, bounds, n_iter, step_size):
# track all solutions
solutions, scores = list(), list()
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# run the gradient descent
for i in range(n_iter):
# calculate gradient
gradient = derivative(solution)
# take a step
solution = solution - step_size * gradient
# evaluate candidate point
solution_eval = objective(solution)
# store solution
solutions.append(solution)
scores.append(solution_eval)
# report progress
print('>%d f(%s) = %.5f' % (i, solution, solution_eval))
return [solutions, scores]
这个函数可以被调用,我们可以得到解的列表和搜索过程中找到的分数。
...
# perform the gradient descent search
solutions, scores = gradient_descent(objective, derivative, bounds, n_iter, step_size)
我们可以像以前一样创建目标函数的线图。
...
# sample input range uniformly at 0.1 increments
inputs = arange(bounds[0,0], bounds[0,1]+0.1, 0.1)
# compute targets
results = objective(inputs)
# create a line plot of input vs result
pyplot.plot(inputs, results)
最后,我们可以将找到的每个解决方案绘制成一个红点,并用一条线连接这些点,这样我们就可以看到搜索是如何走下坡路的。
...
# plot the solutions found
pyplot.plot(solutions, scores, '.-', color='red')
将所有这些联系在一起,下面列出了在一维测试函数上绘制梯度下降搜索结果的完整示例。
# example of plotting a gradient descent search on a one-dimensional function
from numpy import asarray
from numpy import arange
from numpy.random import rand
from numpy.random import seed
from matplotlib import pyplot
# objective function
def objective(x):
return x**2.0
# derivative of objective function
def derivative(x):
return x * 2.0
# gradient descent algorithm
def gradient_descent(objective, derivative, bounds, n_iter, step_size):
# track all solutions
solutions, scores = list(), list()
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# run the gradient descent
for i in range(n_iter):
# calculate gradient
gradient = derivative(solution)
# take a step
solution = solution - step_size * gradient
# evaluate candidate point
solution_eval = objective(solution)
# store solution
solutions.append(solution)
scores.append(solution_eval)
# report progress
print('>%d f(%s) = %.5f' % (i, solution, solution_eval))
return [solutions, scores]
# seed the pseudo random number generator
seed(4)
# define range for input
bounds = asarray([[-1.0, 1.0]])
# define the total iterations
n_iter = 30
# define the step size
step_size = 0.1
# perform the gradient descent search
solutions, scores = gradient_descent(objective, derivative, bounds, n_iter, step_size)
# sample input range uniformly at 0.1 increments
inputs = arange(bounds[0,0], bounds[0,1]+0.1, 0.1)
# compute targets
results = objective(inputs)
# create a line plot of input vs result
pyplot.plot(inputs, results)
# plot the solutions found
pyplot.plot(solutions, scores, '.-', color='red')
# show the plot
pyplot.show()
运行该示例会像以前一样对目标函数执行梯度下降搜索,但在这种情况下,搜索过程中找到的每个点都会被绘制出来。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到搜索从功能右侧的一半以上开始,然后向下走到盆地底部。
我们可以看到,在目标函数中曲线较大的部分,导数(梯度)较大,反过来采取较大的步长。同样,当我们越来越接近最优值时,梯度越来越小,反过来,步长也越来越小。
这强调了步长被用作目标函数梯度(曲率)大小的比例因子。
一维目标函数的梯度下降过程图
动量梯度下降优化
接下来,我们可以更新梯度下降优化算法来使用动量。
这可以通过更新*梯度 _ 下降()*函数来获得“动量”参数来实现,该参数定义了搜索过程中使用的动量。
对解决方案所做的更改必须从循环的上一次迭代中记住,初始值为 0.0。
...
# keep track of the change
change = 0.0
然后,我们可以将更新过程分解为首先计算梯度,然后计算解的变化,计算新解的位置,然后将变化保存到下一次迭代中。
...
# calculate gradient
gradient = derivative(solution)
# calculate update
new_change = step_size * gradient + momentum * change
# take a step
solution = solution - new_change
# save the change
change = new_change
带有这些变化的*梯度 _ 下降()*功能的更新版本如下。
# gradient descent algorithm
def gradient_descent(objective, derivative, bounds, n_iter, step_size, momentum):
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# keep track of the change
change = 0.0
# run the gradient descent
for i in range(n_iter):
# calculate gradient
gradient = derivative(solution)
# calculate update
new_change = step_size * gradient + momentum * change
# take a step
solution = solution - new_change
# save the change
change = new_change
# evaluate candidate point
solution_eval = objective(solution)
# report progress
print('>%d f(%s) = %.5f' % (i, solution, solution_eval))
return [solution, solution_eval]
然后我们可以选择一个动量值,并将其传递给*gradient _ download()*函数。
经过一点尝试和错误,动量值 0.3 被发现对这个问题是有效的,给定固定的步长 0.1。
...
# define momentum
momentum = 0.3
# perform the gradient descent search with momentum
best, score = gradient_descent(objective, derivative, bounds, n_iter, step_size, momentum)
将这些联系在一起,下面列出了动量梯度下降优化的完整示例。
# example of gradient descent with momentum for a one-dimensional function
from numpy import asarray
from numpy.random import rand
from numpy.random import seed
# objective function
def objective(x):
return x**2.0
# derivative of objective function
def derivative(x):
return x * 2.0
# gradient descent algorithm
def gradient_descent(objective, derivative, bounds, n_iter, step_size, momentum):
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# keep track of the change
change = 0.0
# run the gradient descent
for i in range(n_iter):
# calculate gradient
gradient = derivative(solution)
# calculate update
new_change = step_size * gradient + momentum * change
# take a step
solution = solution - new_change
# save the change
change = new_change
# evaluate candidate point
solution_eval = objective(solution)
# report progress
print('>%d f(%s) = %.5f' % (i, solution, solution_eval))
return [solution, solution_eval]
# seed the pseudo random number generator
seed(4)
# define range for input
bounds = asarray([[-1.0, 1.0]])
# define the total iterations
n_iter = 30
# define the step size
step_size = 0.1
# define momentum
momentum = 0.3
# perform the gradient descent search with momentum
best, score = gradient_descent(objective, derivative, bounds, n_iter, step_size, momentum)
print('Done!')
print('f(%s) = %f' % (best, score))
运行该示例从搜索空间中的随机点开始,然后应用带有动量的梯度下降算法,报告一路上的表现。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到算法在大约 13 次迭代后找到了一个很好的解,函数求值约为 0.0。
正如预期的那样,这比没有动量的梯度下降更快(迭代更少),使用相同的起点和步长,需要 27 次迭代。
>0 f([0.74724774]) = 0.55838
>1 f([0.54175461]) = 0.29350
>2 f([0.37175575]) = 0.13820
>3 f([0.24640494]) = 0.06072
>4 f([0.15951871]) = 0.02545
>5 f([0.1015491]) = 0.01031
>6 f([0.0638484]) = 0.00408
>7 f([0.03976851]) = 0.00158
>8 f([0.02459084]) = 0.00060
>9 f([0.01511937]) = 0.00023
>10 f([0.00925406]) = 0.00009
>11 f([0.00564365]) = 0.00003
>12 f([0.0034318]) = 0.00001
>13 f([0.00208188]) = 0.00000
>14 f([0.00126053]) = 0.00000
>15 f([0.00076202]) = 0.00000
>16 f([0.00046006]) = 0.00000
>17 f([0.00027746]) = 0.00000
>18 f([0.00016719]) = 0.00000
>19 f([0.00010067]) = 0.00000
>20 f([6.05804744e-05]) = 0.00000
>21 f([3.64373635e-05]) = 0.00000
>22 f([2.19069576e-05]) = 0.00000
>23 f([1.31664443e-05]) = 0.00000
>24 f([7.91100141e-06]) = 0.00000
>25 f([4.75216828e-06]) = 0.00000
>26 f([2.85408468e-06]) = 0.00000
>27 f([1.71384267e-06]) = 0.00000
>28 f([1.02900153e-06]) = 0.00000
>29 f([6.17748881e-07]) = 0.00000
Done!
f([6.17748881e-07]) = 0.000000
动量梯度下降优化的可视化
最后,我们可以用动量可视化梯度下降优化算法的进展。
下面列出了完整的示例。
# example of plotting gradient descent with momentum for a one-dimensional function
from numpy import asarray
from numpy import arange
from numpy.random import rand
from numpy.random import seed
from matplotlib import pyplot
# objective function
def objective(x):
return x**2.0
# derivative of objective function
def derivative(x):
return x * 2.0
# gradient descent algorithm
def gradient_descent(objective, derivative, bounds, n_iter, step_size, momentum):
# track all solutions
solutions, scores = list(), list()
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# keep track of the change
change = 0.0
# run the gradient descent
for i in range(n_iter):
# calculate gradient
gradient = derivative(solution)
# calculate update
new_change = step_size * gradient + momentum * change
# take a step
solution = solution - new_change
# save the change
change = new_change
# evaluate candidate point
solution_eval = objective(solution)
# store solution
solutions.append(solution)
scores.append(solution_eval)
# report progress
print('>%d f(%s) = %.5f' % (i, solution, solution_eval))
return [solutions, scores]
# seed the pseudo random number generator
seed(4)
# define range for input
bounds = asarray([[-1.0, 1.0]])
# define the total iterations
n_iter = 30
# define the step size
step_size = 0.1
# define momentum
momentum = 0.3
# perform the gradient descent search with momentum
solutions, scores = gradient_descent(objective, derivative, bounds, n_iter, step_size, momentum)
# sample input range uniformly at 0.1 increments
inputs = arange(bounds[0,0], bounds[0,1]+0.1, 0.1)
# compute targets
results = objective(inputs)
# create a line plot of input vs result
pyplot.plot(inputs, results)
# plot the solutions found
pyplot.plot(solutions, scores, '.-', color='red')
# show the plot
pyplot.show()
运行该示例执行梯度下降搜索,目标函数上的动量与之前一样,除了在这种情况下,搜索过程中找到的每个点都被绘制出来。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,如果我们将该图与之前为执行梯度下降(没有动量)而创建的图进行比较,我们可以看到搜索确实以更少的步骤到达最优值,在到达盆地底部的路径上用更少的明显红点进行标注。
一维目标函数上动量梯度下降过程图
作为扩展,尝试不同的动量值,例如 0.8,并查看结果图。 让我知道你在下面的评论中发现了什么。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
书
- 优化算法,2019。
- 深度学习,2016 年。
- 神经锻造:前馈人工神经网络中的监督学习,1999。
- 用于模式识别的神经网络,1996。
- 神经网络:交易技巧,2012 年。
蜜蜂
- num py . random . rand API。
- num py . asar ray API。
- Matplotlib API 。
文章
摘要
在本教程中,您发现了动量梯度下降算法。
具体来说,您了解到:
- 梯度下降是一种优化算法,它使用目标函数的梯度来导航搜索空间。
- 梯度下降可以通过使用搜索位置过去更新的动量来加速。
- 如何利用动量实现梯度下降优化并对其行为产生直觉?
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
从零开始的 Nesterov 动量梯度下降
最后更新于 2021 年 10 月 12 日
梯度下降是一种优化算法,它遵循目标函数的负梯度来定位函数的最小值。
梯度下降的一个限制是,如果目标函数返回有噪声的梯度,它会卡在平坦区域或反弹。动量是一种加速搜索进程的方法,可以掠过平坦区域并平滑有弹性的梯度。
在某些情况下,动量的加速会导致搜索错过或超过盆地或山谷底部的最小值。Nesterov 动量是动量的延伸,涉及计算搜索空间中投影位置梯度的衰减移动平均值,而不是实际位置本身。
这具有利用动量加速优势的效果,同时允许搜索在接近最优值时减慢,并降低错过或超过最优值的可能性。
在本教程中,您将发现如何从零开始使用 Nesterov 动量开发梯度下降优化算法。
完成本教程后,您将知道:
- 梯度下降是一种优化算法,它使用目标函数的梯度来导航搜索空间。
- 对梯度下降优化算法进行扩展,增加 Nesterov 动量,可以加快算法的收敛速度。
- 如何从零开始实现 Nesterov 动量优化算法,并将其应用于目标函数和评估结果。
用我的新书机器学习优化启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
Let’s get started.
从零开始的 Nesterov 动量梯度下降 照片由邦妮·莫兰拍摄,版权所有。
教程概述
本教程分为三个部分;它们是:
- 梯度下降
- Nesterov 动量
- Nesterov 动量梯度下降
- 二维测试问题
- 具有 Nesterov 动量的梯度下降优化
- Nesterov 动量的可视化
梯度下降
梯度下降是一种优化算法。
它在技术上被称为一阶优化算法,因为它明确地利用了目标函数的一阶导数。
一阶方法依赖于梯度信息来帮助指导搜索最小值…
—第 69 页,优化算法,2019。
一阶导数,或简称为“导数”,是目标函数在特定点的变化率或斜率,例如对于特定输入。
如果目标函数有多个输入变量,它被称为多元函数,输入变量可以被认为是一个向量。反过来,多元目标函数的导数也可以作为向量,通常称为“梯度”
- 梯度:多元目标函数的一阶导数。
对于特定输入,导数或梯度指向目标函数最陡上升的方向。
梯度下降是指一种最小化优化算法,它遵循目标函数梯度下降的负值来定位函数的最小值。
梯度下降算法需要一个正在优化的目标函数和目标函数的导数函数。目标函数 f()返回给定输入集的得分,导数函数 f '()给出给定输入集的目标函数的导数。
梯度下降算法要求问题中有一个起点(x),例如输入空间中随机选择的一个点。
然后计算导数,并在输入空间中采取一个步骤,该步骤预计会导致目标函数的下坡运动,假设我们正在最小化目标函数。
下坡移动是通过首先计算在输入空间中移动多远来实现的,计算方法是步长(称为 alpha 或学习率)乘以梯度。然后从当前点减去这一点,确保我们逆着梯度或目标函数向下移动。
- x(t+1)= x(t)–步长* f'(x(t))
给定点处的目标函数越陡,梯度的幅度就越大,反过来,搜索空间中的步长就越大。使用步长超参数来缩放所采取的步长。
- 步长(α):超参数,控制算法每次迭代在搜索空间中逆着梯度移动多远。
如果步长过小,搜索空间内的移动会很小,搜索时间会很长。如果步长过大,搜索可能会绕过搜索空间并跳过 optima。
现在我们已经熟悉了梯度下降优化算法,让我们来看看 Nesterov 动量。
Nesterov 动量
Nesterov 动量是梯度下降优化算法的扩展。
Yuri nesterov 在他 1983 年发表的题为“一种求解具有收敛速度 O(1/k² 的凸规划问题的方法”的论文中描述了这种方法(并以其命名)
Ilya Sutskever 等人负责推广 Nesterov 动量在具有随机梯度下降的神经网络的训练中的应用,这在他们 2013 年的论文“关于初始化和动量在深度学习中的重要性中进行了描述。”他们称这种方法为“T4”Nesterov 加速梯度“T5”,简称 NAG。
Nesterov 动量就像更传统的动量一样,只是更新是使用预测更新的偏导数而不是当前变量值的导数来执行的。
虽然 NAG 通常不被认为是一种动量,但它确实与经典动量密切相关,只是在速度矢量的精确更新上有所不同…
——论深度学习中初始化和动量的重要性,2013。
传统的动量涉及保持一个额外的变量,该变量代表对该变量执行的最后一次更新,即过去梯度的指数衰减移动平均值。
动量算法累积过去梯度的指数衰减移动平均值,并继续向它们的方向移动。
—第 296 页,深度学习,2016。
该变量的最后一次更新或最后一次更改随后被添加到由“动量”超参数缩放的变量中,该超参数控制最后一次更改的添加量,例如 90%为 0.9%。
按照两个步骤来考虑这个更新更容易,例如,使用偏导数计算变量的变化,然后计算变量的新值。
- 变化(t+1) =(动量变化(t))–(步长 f'(x(t)))
- x(t+1) = x(t) +变化(t+1)
我们可以把动量想象成一个向下滚动的球,即使在有小山丘的情况下,它也会加速并继续朝着同一个方向前进。
动量可以解释为一个球沿着几乎水平的斜面滚下。当重力使球加速时,球自然聚集动量,就像梯度使动量在这种下降方法中积累一样。
—第 75 页,优化算法,2019。
动量的一个问题是,加速度有时会导致搜索超过盆地底部或谷底的最小值。
Nesterov 动量可以被认为是对动量的一种修正,以克服这个超越最小值的问题。
它包括首先使用上次迭代的变化计算变量的投影位置,并在计算变量的新位置时使用投影位置的导数。
计算投影位置的梯度就像是对已经累积的加速度的校正因子。
利用 Nesterov 动量,在应用当前速度之后评估梯度。因此,我们可以把 Nesterov 动量解释为试图给标准动量法增加一个修正因子。
—第 300 页,深度学习,2016。
Nesterov 动量很容易从四个步骤来思考这个问题:
- 1.投影解决方案的位置。
- 2.计算投影的梯度。
- 3.用偏导数计算变量的变化。
- 4.更新变量。
让我们更详细地了解这些步骤。
首先,使用在算法的最后迭代中计算的变化来计算整个解的投影位置。
- 投影(t+1) = x(t) +(动量*变化(t))
然后我们可以计算这个新位置的梯度。
- 梯度(t+1)= f’(投影(t+1))
现在我们可以使用投影的梯度计算每个变量的新位置,首先通过计算每个变量的变化。
- 变化(t+1) =(动量变化(t))–(步长梯度(t+1))
最后,使用计算出的变化计算每个变量的新值。
- x(t+1) = x(t) +变化(t+1)
在更一般的凸优化领域,已知 Nesterov 动量可以提高优化算法的收敛速度(例如,减少寻找解所需的迭代次数)。
与动量法一样,NAG 是一种一阶优化方法,在某些情况下比梯度下降法具有更好的收敛速度保证。
——论深度学习中初始化和动量的重要性,2013。
尽管该技术在训练神经网络方面是有效的,但是它可能没有加速收敛的相同的一般效果。
不幸的是,在随机梯度的情况下,Nesterov 动量并不能提高收敛速度。
—第 300 页,深度学习,2016。
现在我们已经熟悉了 Nesterov 动量算法,让我们探索如何实现它并评估它的表现。
Nesterov 动量梯度下降
在本节中,我们将探索如何利用 Nesterov 动量实现梯度下降优化算法。
二维测试问题
首先,让我们定义一个优化函数。
我们将使用一个简单的二维函数,它对每个维度的输入进行平方,并定义从-1.0 到 1.0 的有效输入范围。
下面的 objective()函数实现了这个功能。
# objective function
def objective(x, y):
return x**2.0 + y**2.0
我们可以创建数据集的三维图,以获得对响应表面曲率的感觉。
下面列出了绘制目标函数的完整示例。
# 3d plot of the test function
from numpy import arange
from numpy import meshgrid
from matplotlib import pyplot
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# define range for input
r_min, r_max = -1.0, 1.0
# sample input range uniformly at 0.1 increments
xaxis = arange(r_min, r_max, 0.1)
yaxis = arange(r_min, r_max, 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a surface plot with the jet color scheme
figure = pyplot.figure()
axis = figure.gca(projection='3d')
axis.plot_surface(x, y, results, cmap='jet')
# show the plot
pyplot.show()
运行该示例会创建目标函数的三维表面图。
我们可以看到熟悉的碗形,全局最小值在 f(0,0) = 0。
测试目标函数的三维图
我们还可以创建函数的二维图。这将有助于我们以后绘制搜索进度。
以下示例创建了目标函数的等高线图。
# contour plot of the test function
from numpy import asarray
from numpy import arange
from numpy import meshgrid
from matplotlib import pyplot
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# sample input range uniformly at 0.1 increments
xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a filled contour plot with 50 levels and jet color scheme
pyplot.contourf(x, y, results, levels=50, cmap='jet')
# show the plot
pyplot.show()
运行该示例会创建目标函数的二维等高线图。
我们可以看到碗的形状被压缩成带有颜色梯度的轮廓。我们将使用此图来绘制搜索过程中探索的具体点。
测试目标函数的二维等高线图
现在我们有了一个测试目标函数,让我们看看如何实现 Nesterov 动量优化算法。
具有 Nesterov 动量的梯度下降优化
我们可以将带有 Nesterov 动量的梯度下降应用于测试问题。
首先,我们需要一个函数来计算这个函数的导数。
x² 的导数在每个维度上都是 x * 2,下面的*导数()*函数实现了这一点。
# derivative of objective function
def derivative(x, y):
return asarray([x * 2.0, y * 2.0])
接下来,我们可以实现梯度下降优化。
首先,我们可以在问题的边界中选择一个随机点作为搜索的起点。
这假设我们有一个定义搜索范围的数组,每个维度有一行,第一列定义维度的最小值,第二列定义维度的最大值。
...
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
接下来,我们需要从当前位置计算投影点,并计算其导数。
...
# calculate the projected solution
projected = [solution[i] + momentum * change[i] for i in range(solution.shape[0])]
# calculate the gradient for the projection
gradient = derivative(projected[0], projected[1])
然后我们可以创建新的解决方案,一次一个变量。
首先,使用偏导数和学习率以及变量上一次变化的动量来计算变量的变化。该变化被存储用于算法的下一次迭代。然后,该变化用于计算变量的新值。
...
# build a solution one variable at a time
new_solution = list()
for i in range(solution.shape[0]):
# calculate the change
change[i] = (momentum * change[i]) - step_size * gradient[i]
# calculate the new position in this variable
value = solution[i] + change[i]
# store this variable
new_solution.append(value)
对目标函数的每个变量重复这个过程,然后对算法的每次迭代重复这个过程。
然后,可以使用*客观()*函数来评估这个新的解决方案,并且可以报告搜索的表现。
...
# evaluate candidate point
solution = asarray(new_solution)
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
就这样。
我们可以将所有这些联系到一个名为 nesterov() 的函数中,该函数采用目标函数和导数函数的名称,一个具有域边界和超参数值的数组,用于计算算法迭代总数、学习率和动量,并返回最终解及其评估。
下面列出了完整的功能。
# gradient descent algorithm with nesterov momentum
def nesterov(objective, derivative, bounds, n_iter, step_size, momentum):
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of changes made to each variable
change = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate the projected solution
projected = [solution[i] + momentum * change[i] for i in range(solution.shape[0])]
# calculate the gradient for the projection
gradient = derivative(projected[0], projected[1])
# build a solution one variable at a time
new_solution = list()
for i in range(solution.shape[0]):
# calculate the change
change[i] = (momentum * change[i]) - step_size * gradient[i]
# calculate the new position in this variable
value = solution[i] + change[i]
# store this variable
new_solution.append(value)
# evaluate candidate point
solution = asarray(new_solution)
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return [solution, solution_eval]
注意,为了可读性,我们特意使用了列表和命令式编码风格,而不是矢量化操作。请随意将该实现调整为使用 NumPy 数组的矢量化实现,以获得更好的表现。
然后我们可以定义我们的超参数,并调用 nesterov() 函数来优化我们的测试目标函数。
在这种情况下,我们将使用算法的 30 次迭代,学习率为 0.1,动量为 0.3。这些超参数值是经过一点点反复试验后发现的。
...
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 30
# define the step size
step_size = 0.1
# define momentum
momentum = 0.3
# perform the gradient descent search with nesterov momentum
best, score = nesterov(objective, derivative, bounds, n_iter, step_size, momentum)
print('Done!')
print('f(%s) = %f' % (best, score))
将所有这些联系在一起,下面列出了使用 Nesterov 动量进行梯度下降优化的完整示例。
# 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
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# derivative of objective function
def derivative(x, y):
return asarray([x * 2.0, y * 2.0])
# gradient descent algorithm with nesterov momentum
def nesterov(objective, derivative, bounds, n_iter, step_size, momentum):
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of changes made to each variable
change = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate the projected solution
projected = [solution[i] + momentum * change[i] for i in range(solution.shape[0])]
# calculate the gradient for the projection
gradient = derivative(projected[0], projected[1])
# build a solution one variable at a time
new_solution = list()
for i in range(solution.shape[0]):
# calculate the change
change[i] = (momentum * change[i]) - step_size * gradient[i]
# calculate the new position in this variable
value = solution[i] + change[i]
# store this variable
new_solution.append(value)
# evaluate candidate point
solution = asarray(new_solution)
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return [solution, solution_eval]
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 30
# define the step size
step_size = 0.1
# define momentum
momentum = 0.3
# perform the gradient descent search with nesterov momentum
best, score = nesterov(objective, derivative, bounds, n_iter, step_size, momentum)
print('Done!')
print('f(%s) = %f' % (best, score))
运行该示例将带有 Nesterov 动量的优化算法应用于我们的测试问题,并报告算法每次迭代的搜索表现。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到,在大约 15 次搜索迭代后,找到了一个接近最优的解,输入值接近 0.0 和 0.0,评估为 0.0。
>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
Done!
f([-3.82285090e-06 1.01504945e-05]) = 0.000000
Nesterov 动量的可视化
我们可以在该域的等高线图上绘制 Nesterov 动量搜索的进度。
这可以为算法迭代过程中的搜索进度提供直觉。
我们必须更新 nesterov() 函数,以维护搜索过程中找到的所有解决方案的列表,然后在搜索结束时返回该列表。
下面列出了带有这些更改的功能的更新版本。
# gradient descent algorithm with nesterov momentum
def nesterov(objective, derivative, bounds, n_iter, step_size, momentum):
# track all solutions
solutions = list()
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of changes made to each variable
change = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate the projected solution
projected = [solution[i] + momentum * change[i] for i in range(solution.shape[0])]
# calculate the gradient for the projection
gradient = derivative(projected[0], projected[1])
# build a solution one variable at a time
new_solution = list()
for i in range(solution.shape[0]):
# calculate the change
change[i] = (momentum * change[i]) - step_size * gradient[i]
# calculate the new position in this variable
value = solution[i] + change[i]
# store this variable
new_solution.append(value)
# store the new solution
solution = asarray(new_solution)
solutions.append(solution)
# evaluate candidate point
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return solutions
然后,我们可以像以前一样执行搜索,这次检索解决方案列表,而不是最佳最终解决方案。
...
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 50
# define the step size
step_size = 0.01
# define momentum
momentum = 0.8
# perform the gradient descent search with nesterov momentum
solutions = nesterov(objective, derivative, bounds, n_iter, step_size, momentum)
然后,我们可以像以前一样创建目标函数的等高线图。
...
# sample input range uniformly at 0.1 increments
xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a filled contour plot with 50 levels and jet color scheme
pyplot.contourf(x, y, results, levels=50, cmap='jet')
最后,我们可以将搜索过程中找到的每个解决方案绘制成由一条线连接的白点。
...
# plot the sample as black circles
solutions = asarray(solutions)
pyplot.plot(solutions[:, 0], solutions[:, 1], '.-', color='w')
将所有这些联系在一起,下面列出了对测试问题执行 Nesterov 动量优化并将结果绘制在等高线图上的完整示例。
# 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
# objective function
def objective(x, y):
return x**2.0 + y**2.0
# derivative of objective function
def derivative(x, y):
return asarray([x * 2.0, y * 2.0])
# gradient descent algorithm with nesterov momentum
def nesterov(objective, derivative, bounds, n_iter, step_size, momentum):
# track all solutions
solutions = list()
# generate an initial point
solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
# list of changes made to each variable
change = [0.0 for _ in range(bounds.shape[0])]
# run the gradient descent
for it in range(n_iter):
# calculate the projected solution
projected = [solution[i] + momentum * change[i] for i in range(solution.shape[0])]
# calculate the gradient for the projection
gradient = derivative(projected[0], projected[1])
# build a solution one variable at a time
new_solution = list()
for i in range(solution.shape[0]):
# calculate the change
change[i] = (momentum * change[i]) - step_size * gradient[i]
# calculate the new position in this variable
value = solution[i] + change[i]
# store this variable
new_solution.append(value)
# store the new solution
solution = asarray(new_solution)
solutions.append(solution)
# evaluate candidate point
solution_eval = objective(solution[0], solution[1])
# report progress
print('>%d f(%s) = %.5f' % (it, solution, solution_eval))
return solutions
# seed the pseudo random number generator
seed(1)
# define range for input
bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
# define the total iterations
n_iter = 50
# define the step size
step_size = 0.01
# define momentum
momentum = 0.8
# perform the gradient descent search with nesterov momentum
solutions = nesterov(objective, derivative, bounds, n_iter, step_size, momentum)
# sample input range uniformly at 0.1 increments
xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
# create a mesh from the axis
x, y = meshgrid(xaxis, yaxis)
# compute targets
results = objective(x, y)
# create a filled contour plot with 50 levels and jet color scheme
pyplot.contourf(x, y, results, levels=50, cmap='jet')
# plot the sample as black circles
solutions = asarray(solutions)
pyplot.plot(solutions[:, 0], solutions[:, 1], '.-', color='w')
# show the plot
pyplot.show()
运行该示例会像以前一样执行搜索,只是在这种情况下,会创建目标函数的等高线图。
在这种情况下,我们可以看到,搜索过程中找到的每个解决方案都显示一个白点,从 optima 上方开始,逐渐靠近图中心的 optima。
显示 Nesterov 动量搜索结果的测试目标函数等高线图
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
报纸
- 一种求解具有收敛速度的凸规划问题的方法 O(1/k²) ,1983。
- 论深度学习中初始化和动量的重要性,2013。
书
蜜蜂
- num py . random . rand API。
- num py . asar ray API。
- Matplotlib API 。
文章
- 梯度下降,维基百科。
- 随机梯度下降,维基百科。
- 梯度下降优化算法概述,2016。
摘要
在本教程中,您发现了如何利用 Nesterov 动量从零开始开发梯度下降优化。
具体来说,您了解到:
- 梯度下降是一种优化算法,它使用目标函数的梯度来导航搜索空间。
- 对梯度下降优化算法进行扩展,增加 Nesterov 动量,可以加快算法的收敛速度。
- 如何从零开始实现 Nesterov 动量优化算法,并将其应用于目标函数和评估结果。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。