在本节中,我们将介绍梯度下降(gradient descent)的工作原理。虽然梯度下降在深度学习中很少被直接使用,但理解梯度的意义以及沿着梯度反方向更新自变量可能降低目标函数值的原因是学习后续优化算法的基础。随后,我们将引出随机梯度下降(stochastic gradient descent)。

7.2.1 一维梯度下降

我们先以简单的一维梯度下降为例,解释梯度下降算法可能降低目标函数值的原因。假设连续可导的函数7.2 梯度下降和随机梯度下降 - 图1的输入和输出都是标量。给定绝对值足够小的数7.2 梯度下降和随机梯度下降 - 图2,根据泰勒展开公式,我们得到以下的近似:

7.2 梯度下降和随机梯度下降 - 图3%20%5Capprox%20f(x)%20%2B%20%5Cepsilon%20f’(x)%20.%0A#card=math&code=f%28x%20%2B%20%5Cepsilon%29%20%5Capprox%20f%28x%29%20%2B%20%5Cepsilon%20f%27%28x%29%20.%0A)

这里7.2 梯度下降和随机梯度下降 - 图4#card=math&code=f%27%28x%29)是函数7.2 梯度下降和随机梯度下降 - 图57.2 梯度下降和随机梯度下降 - 图6处的梯度。一维函数的梯度是一个标量,也称导数。

接下来,找到一个常数7.2 梯度下降和随机梯度下降 - 图7,使得7.2 梯度下降和随机梯度下降 - 图8%5Cright%7C#card=math&code=%5Cleft%7C%5Ceta%20f%27%28x%29%5Cright%7C)足够小,那么可以将7.2 梯度下降和随机梯度下降 - 图9替换为7.2 梯度下降和随机梯度下降 - 图10#card=math&code=-%5Ceta%20f%27%28x%29)并得到

7.2 梯度下降和随机梯度下降 - 图11)%20%5Capprox%20f(x)%20-%20%20%5Ceta%20f’(x)%5E2.%0A#card=math&code=f%28x%20-%20%5Ceta%20f%27%28x%29%29%20%5Capprox%20f%28x%29%20-%20%20%5Ceta%20f%27%28x%29%5E2.%0A)

如果导数7.2 梯度下降和随机梯度下降 - 图12%20%5Cneq%200#card=math&code=f%27%28x%29%20%5Cneq%200),那么7.2 梯度下降和随机梯度下降 - 图13%5E2%3E0#card=math&code=%5Ceta%20f%27%28x%29%5E2%3E0),所以

7.2 梯度下降和随机梯度下降 - 图14)%20%5Clesssim%20f(x).%0A#card=math&code=f%28x%20-%20%5Ceta%20f%27%28x%29%29%20%5Clesssim%20f%28x%29.%0A)

这意味着,如果通过

7.2 梯度下降和随机梯度下降 - 图15%0A#card=math&code=x%20%5Cleftarrow%20x%20-%20%5Ceta%20f%27%28x%29%0A)

来迭代7.2 梯度下降和随机梯度下降 - 图16,函数7.2 梯度下降和随机梯度下降 - 图17#card=math&code=f%28x%29)的值可能会降低。因此在梯度下降中,我们先选取一个初始值7.2 梯度下降和随机梯度下降 - 图18和常数7.2 梯度下降和随机梯度下降 - 图19,然后不断通过上式来迭代7.2 梯度下降和随机梯度下降 - 图20,直到达到停止条件,例如7.2 梯度下降和随机梯度下降 - 图21%5E2#card=math&code=f%27%28x%29%5E2)的值已足够小或迭代次数已达到某个值。

下面我们以目标函数7.2 梯度下降和随机梯度下降 - 图22%3Dx%5E2#card=math&code=f%28x%29%3Dx%5E2)为例来看一看梯度下降是如何工作的。虽然我们知道最小化7.2 梯度下降和随机梯度下降 - 图23#card=math&code=f%28x%29)的解为7.2 梯度下降和随机梯度下降 - 图24,这里依然使用这个简单函数来观察7.2 梯度下降和随机梯度下降 - 图25是如何被迭代的。首先,导入本节实验所需的包或模块。

  1. %matplotlib inline
  2. import numpy as np
  3. import torch
  4. import math
  5. import sys
  6. sys.path.append("..")
  7. import d2lzh_pytorch as d2l

接下来使用7.2 梯度下降和随机梯度下降 - 图26作为初始值,并设7.2 梯度下降和随机梯度下降 - 图27。使用梯度下降对7.2 梯度下降和随机梯度下降 - 图28迭代10次,可见最终7.2 梯度下降和随机梯度下降 - 图29的值较接近最优解。

  1. def gd(eta):
  2. x = 10
  3. results = [x]
  4. for i in range(10):
  5. x -= eta * 2 * x # f(x) = x * x的导数为f'(x) = 2 * x
  6. results.append(x)
  7. print('epoch 10, x:', x)
  8. return results
  9. res = gd(0.2)

输出:

  1. epoch 10, x: 0.06046617599999997

下面将绘制出自变量7.2 梯度下降和随机梯度下降 - 图30的迭代轨迹。

  1. def show_trace(res):
  2. n = max(abs(min(res)), abs(max(res)), 10)
  3. f_line = np.arange(-n, n, 0.1)
  4. d2l.set_figsize()
  5. d2l.plt.plot(f_line, [x * x for x in f_line])
  6. d2l.plt.plot(res, [x * x for x in res], '-o')
  7. d2l.plt.xlabel('x')
  8. d2l.plt.ylabel('f(x)')
  9. show_trace(res)

7.2_output1.svg

7.2.2 学习率

上述梯度下降算法中的正数7.2 梯度下降和随机梯度下降 - 图32通常叫作学习率。这是一个超参数,需要人工设定。如果使用过小的学习率,会导致7.2 梯度下降和随机梯度下降 - 图33更新缓慢从而需要更多的迭代才能得到较好的解。

下面展示使用学习率7.2 梯度下降和随机梯度下降 - 图34时自变量7.2 梯度下降和随机梯度下降 - 图35的迭代轨迹。可见,同样迭代10次后,当学习率过小时,最终7.2 梯度下降和随机梯度下降 - 图36的值依然与最优解存在较大偏差。

  1. show_trace(gd(0.05))

输出:

  1. epoch 10, x: 3.4867844009999995

7.2_output2.svg

如果使用过大的学习率,7.2 梯度下降和随机梯度下降 - 图38%5Cright%7C#card=math&code=%5Cleft%7C%5Ceta%20f%27%28x%29%5Cright%7C)可能会过大从而使前面提到的一阶泰勒展开公式不再成立:这时我们无法保证迭代7.2 梯度下降和随机梯度下降 - 图39会降低7.2 梯度下降和随机梯度下降 - 图40#card=math&code=f%28x%29)的值。

举个例子,当设学习率7.2 梯度下降和随机梯度下降 - 图41时,可以看到7.2 梯度下降和随机梯度下降 - 图42不断越过(overshoot)最优解7.2 梯度下降和随机梯度下降 - 图43并逐渐发散。

  1. show_trace(gd(1.1))

输出:

  1. epoch 10, x: 61.917364224000096

7.2_output3.svg

7.2.3 多维梯度下降

在了解了一维梯度下降之后,我们再考虑一种更广义的情况:目标函数的输入为向量,输出为标量。假设目标函数7.2 梯度下降和随机梯度下降 - 图45的输入是一个7.2 梯度下降和随机梯度下降 - 图46维向量7.2 梯度下降和随机梯度下降 - 图47。目标函数7.2 梯度下降和随机梯度下降 - 图48#card=math&code=f%28%5Cboldsymbol%7Bx%7D%29)有关7.2 梯度下降和随机梯度下降 - 图49的梯度是一个由7.2 梯度下降和随机梯度下降 - 图50个偏导数组成的向量:

7.2 梯度下降和随机梯度下降 - 图51%20%3D%20%5Cbigg%5B%5Cfrac%7B%5Cpartial%20f(%5Cboldsymbol%7Bx%7D)%7D%7B%5Cpartial%20x1%7D%2C%20%5Cfrac%7B%5Cpartial%20f(%5Cboldsymbol%7Bx%7D)%7D%7B%5Cpartial%20x_2%7D%2C%20%5Cldots%2C%20%5Cfrac%7B%5Cpartial%20f(%5Cboldsymbol%7Bx%7D)%7D%7B%5Cpartial%20x_d%7D%5Cbigg%5D%5E%5Ctop.%0A#card=math&code=%5Cnabla%7B%5Cboldsymbol%7Bx%7D%7D%20f%28%5Cboldsymbol%7Bx%7D%29%20%3D%20%5Cbigg%5B%5Cfrac%7B%5Cpartial%20f%28%5Cboldsymbol%7Bx%7D%29%7D%7B%5Cpartial%20x_1%7D%2C%20%5Cfrac%7B%5Cpartial%20f%28%5Cboldsymbol%7Bx%7D%29%7D%7B%5Cpartial%20x_2%7D%2C%20%5Cldots%2C%20%5Cfrac%7B%5Cpartial%20f%28%5Cboldsymbol%7Bx%7D%29%7D%7B%5Cpartial%20x_d%7D%5Cbigg%5D%5E%5Ctop.%0A)

为表示简洁,我们用7.2 梯度下降和随机梯度下降 - 图52#card=math&code=%5Cnabla%20f%28%5Cboldsymbol%7Bx%7D%29)代替7.2 梯度下降和随机梯度下降 - 图53#card=math&code=%5Cnabla_%7B%5Cboldsymbol%7Bx%7D%7D%20f%28%5Cboldsymbol%7Bx%7D%29)。梯度中每个偏导数元素7.2 梯度下降和随机梯度下降 - 图54%2F%5Cpartial%20x_i#card=math&code=%5Cpartial%20f%28%5Cboldsymbol%7Bx%7D%29%2F%5Cpartial%20x_i)代表着7.2 梯度下降和随机梯度下降 - 图557.2 梯度下降和随机梯度下降 - 图56有关输入7.2 梯度下降和随机梯度下降 - 图57的变化率。为了测量7.2 梯度下降和随机梯度下降 - 图58沿着单位向量7.2 梯度下降和随机梯度下降 - 图59(即7.2 梯度下降和随机梯度下降 - 图60)方向上的变化率,在多元微积分中,我们定义7.2 梯度下降和随机梯度下降 - 图617.2 梯度下降和随机梯度下降 - 图62上沿着7.2 梯度下降和随机梯度下降 - 图63方向的方向导数为

7.2 梯度下降和随机梯度下降 - 图64%20%3D%20%5Clim%7Bh%20%5Crightarrow%200%7D%20%20%5Cfrac%7Bf(%5Cboldsymbol%7Bx%7D%20%2B%20h%20%5Cboldsymbol%7Bu%7D)%20-%20f(%5Cboldsymbol%7Bx%7D)%7D%7Bh%7D.%0A#card=math&code=%5Ctext%7BD%7D%7B%5Cboldsymbol%7Bu%7D%7D%20f%28%5Cboldsymbol%7Bx%7D%29%20%3D%20%5Clim_%7Bh%20%5Crightarrow%200%7D%20%20%5Cfrac%7Bf%28%5Cboldsymbol%7Bx%7D%20%2B%20h%20%5Cboldsymbol%7Bu%7D%29%20-%20f%28%5Cboldsymbol%7Bx%7D%29%7D%7Bh%7D.%0A)

依据方向导数性质[1,14.6节定理三],以上方向导数可以改写为

7.2 梯度下降和随机梯度下降 - 图65%20%3D%20%5Cnabla%20f(%5Cboldsymbol%7Bx%7D)%20%5Ccdot%20%5Cboldsymbol%7Bu%7D.%0A#card=math&code=%5Ctext%7BD%7D_%7B%5Cboldsymbol%7Bu%7D%7D%20f%28%5Cboldsymbol%7Bx%7D%29%20%3D%20%5Cnabla%20f%28%5Cboldsymbol%7Bx%7D%29%20%5Ccdot%20%5Cboldsymbol%7Bu%7D.%0A)

方向导数7.2 梯度下降和随机梯度下降 - 图66#card=math&code=%5Ctext%7BD%7D%7B%5Cboldsymbol%7Bu%7D%7D%20f%28%5Cboldsymbol%7Bx%7D%29)给出了7.2 梯度下降和随机梯度下降 - 图677.2 梯度下降和随机梯度下降 - 图68上沿着所有可能方向的变化率。为了最小化7.2 梯度下降和随机梯度下降 - 图69,我们希望找到7.2 梯度下降和随机梯度下降 - 图70能被降低最快的方向。因此,我们可以通过单位向量7.2 梯度下降和随机梯度下降 - 图71来最小化方向导数![](https://g.yuque.com/gr/latex?%5Ctext%7BD%7D%7B%5Cboldsymbol%7Bu%7D%7D%20f(%5Cboldsymbol%7Bx%7D)#card=math&code=%5Ctext%7BD%7D_%7B%5Cboldsymbol%7Bu%7D%7D%20f%28%5Cboldsymbol%7Bx%7D%29)。

由于7.2 梯度下降和随机梯度下降 - 图72%20%3D%20%5C%7C%5Cnabla%20f(%5Cboldsymbol%7Bx%7D)%5C%7C%20%5Ccdot%20%5C%7C%5Cboldsymbol%7Bu%7D%5C%7C%20%20%5Ccdot%20%5Ctext%7Bcos%7D%20(%5Ctheta)%20%3D%20%5C%7C%5Cnabla%20f(%5Cboldsymbol%7Bx%7D)%5C%7C%20%20%5Ccdot%20%5Ctext%7Bcos%7D%20(%5Ctheta)#card=math&code=%5Ctext%7BD%7D%7B%5Cboldsymbol%7Bu%7D%7D%20f%28%5Cboldsymbol%7Bx%7D%29%20%3D%20%5C%7C%5Cnabla%20f%28%5Cboldsymbol%7Bx%7D%29%5C%7C%20%5Ccdot%20%5C%7C%5Cboldsymbol%7Bu%7D%5C%7C%20%20%5Ccdot%20%5Ctext%7Bcos%7D%20%28%5Ctheta%29%20%3D%20%5C%7C%5Cnabla%20f%28%5Cboldsymbol%7Bx%7D%29%5C%7C%20%20%5Ccdot%20%5Ctext%7Bcos%7D%20%28%5Ctheta%29),
其中7.2 梯度下降和随机梯度下降 - 图73为梯度7.2 梯度下降和随机梯度下降 - 图74#card=math&code=%5Cnabla%20f%28%5Cboldsymbol%7Bx%7D%29)和单位向量7.2 梯度下降和随机梯度下降 - 图75之间的夹角,当7.2 梯度下降和随机梯度下降 - 图76时,7.2 梯度下降和随机梯度下降 - 图77#card=math&code=%5Ctext%7Bcos%7D%28%5Ctheta%29)取得最小值7.2 梯度下降和随机梯度下降 - 图78。因此,当7.2 梯度下降和随机梯度下降 - 图79在梯度方向7.2 梯度下降和随机梯度下降 - 图80#card=math&code=%5Cnabla%20f%28%5Cboldsymbol%7Bx%7D%29)的相反方向时,方向导数![](https://g.yuque.com/gr/latex?%5Ctext%7BD%7D
%7B%5Cboldsymbol%7Bu%7D%7D%20f(%5Cboldsymbol%7Bx%7D)#card=math&code=%5Ctext%7BD%7D_%7B%5Cboldsymbol%7Bu%7D%7D%20f%28%5Cboldsymbol%7Bx%7D%29)被最小化。因此,我们可能通过梯度下降算法来不断降低目标函数7.2 梯度下降和随机梯度下降 - 图81的值:

7.2 梯度下降和随机梯度下降 - 图82.%0A#card=math&code=%5Cboldsymbol%7Bx%7D%20%5Cleftarrow%20%5Cboldsymbol%7Bx%7D%20-%20%5Ceta%20%5Cnabla%20f%28%5Cboldsymbol%7Bx%7D%29.%0A)

同样,其中7.2 梯度下降和随机梯度下降 - 图83(取正数)称作学习率。

下面我们构造一个输入为二维向量7.2 梯度下降和随机梯度下降 - 图84和输出为标量的目标函数7.2 梯度下降和随机梯度下降 - 图85%3Dx_1%5E2%2B2x_2%5E2#card=math&code=f%28%5Cboldsymbol%7Bx%7D%29%3Dx_1%5E2%2B2x_2%5E2)。那么,梯度7.2 梯度下降和随机梯度下降 - 图86%20%3D%20%5B2x_1%2C%204x_2%5D%5E%5Ctop#card=math&code=%5Cnabla%20f%28%5Cboldsymbol%7Bx%7D%29%20%3D%20%5B2x_1%2C%204x_2%5D%5E%5Ctop)。我们将观察梯度下降从初始位置7.2 梯度下降和随机梯度下降 - 图87开始对自变量7.2 梯度下降和随机梯度下降 - 图88的迭代轨迹。我们先定义两个辅助函数,第一个函数使用给定的自变量更新函数,从初始位置7.2 梯度下降和随机梯度下降 - 图89开始迭代自变量7.2 梯度下降和随机梯度下降 - 图90共20次,第二个函数对自变量7.2 梯度下降和随机梯度下降 - 图91的迭代轨迹进行可视化。

  1. def train_2d(trainer): # 本函数将保存在d2lzh_pytorch包中方便以后使用
  2. x1, x2, s1, s2 = -5, -2, 0, 0 # s1和s2是自变量状态,本章后续几节会使用
  3. results = [(x1, x2)]
  4. for i in range(20):
  5. x1, x2, s1, s2 = trainer(x1, x2, s1, s2)
  6. results.append((x1, x2))
  7. print('epoch %d, x1 %f, x2 %f' % (i + 1, x1, x2))
  8. return results
  9. def show_trace_2d(f, results): # 本函数将保存在d2lzh_pytorch包中方便以后使用
  10. d2l.plt.plot(*zip(*results), '-o', color='#ff7f0e')
  11. x1, x2 = np.meshgrid(np.arange(-5.5, 1.0, 0.1), np.arange(-3.0, 1.0, 0.1))
  12. d2l.plt.contour(x1, x2, f(x1, x2), colors='#1f77b4')
  13. d2l.plt.xlabel('x1')
  14. d2l.plt.ylabel('x2')

然后,观察学习率为7.2 梯度下降和随机梯度下降 - 图92时自变量的迭代轨迹。使用梯度下降对自变量7.2 梯度下降和随机梯度下降 - 图93迭代20次后,可见最终7.2 梯度下降和随机梯度下降 - 图94的值较接近最优解7.2 梯度下降和随机梯度下降 - 图95

  1. eta = 0.1
  2. def f_2d(x1, x2): # 目标函数
  3. return x1 ** 2 + 2 * x2 ** 2
  4. def gd_2d(x1, x2, s1, s2):
  5. return (x1 - eta * 2 * x1, x2 - eta * 4 * x2, 0, 0)
  6. show_trace_2d(f_2d, train_2d(gd_2d))

输出:

  1. epoch 20, x1 -0.057646, x2 -0.000073

7.2_output4.svg

7.2.4 随机梯度下降

在深度学习里,目标函数通常是训练数据集中有关各个样本的损失函数的平均。设7.2 梯度下降和随机梯度下降 - 图97#card=math&code=f_i%28%5Cboldsymbol%7Bx%7D%29)是有关索引为7.2 梯度下降和随机梯度下降 - 图98的训练数据样本的损失函数,7.2 梯度下降和随机梯度下降 - 图99是训练数据样本数,7.2 梯度下降和随机梯度下降 - 图100是模型的参数向量,那么目标函数定义为

7.2 梯度下降和随机梯度下降 - 图101%20%3D%20%5Cfrac%7B1%7D%7Bn%7D%20%5Csum%7Bi%20%3D%201%7D%5En%20f_i(%5Cboldsymbol%7Bx%7D).%0A#card=math&code=f%28%5Cboldsymbol%7Bx%7D%29%20%3D%20%5Cfrac%7B1%7D%7Bn%7D%20%5Csum%7Bi%20%3D%201%7D%5En%20f_i%28%5Cboldsymbol%7Bx%7D%29.%0A)

目标函数在7.2 梯度下降和随机梯度下降 - 图102处的梯度计算为

7.2 梯度下降和随机梯度下降 - 图103%20%3D%20%5Cfrac%7B1%7D%7Bn%7D%20%5Csum%7Bi%20%3D%201%7D%5En%20%5Cnabla%20f_i(%5Cboldsymbol%7Bx%7D).%0A#card=math&code=%5Cnabla%20f%28%5Cboldsymbol%7Bx%7D%29%20%3D%20%5Cfrac%7B1%7D%7Bn%7D%20%5Csum%7Bi%20%3D%201%7D%5En%20%5Cnabla%20f_i%28%5Cboldsymbol%7Bx%7D%29.%0A)

如果使用梯度下降,每次自变量迭代的计算开销为7.2 梯度下降和随机梯度下降 - 图104#card=math&code=%5Cmathcal%7BO%7D%28n%29),它随着7.2 梯度下降和随机梯度下降 - 图105线性增长。因此,当训练数据样本数很大时,梯度下降每次迭代的计算开销很高。

随机梯度下降(stochastic gradient descent,SGD)减少了每次迭代的计算开销。在随机梯度下降的每次迭代中,我们随机均匀采样的一个样本索引7.2 梯度下降和随机梯度下降 - 图106,并计算梯度7.2 梯度下降和随机梯度下降 - 图107#card=math&code=%5Cnabla%20f_i%28%5Cboldsymbol%7Bx%7D%29)来迭代7.2 梯度下降和随机梯度下降 - 图108

7.2 梯度下降和随机梯度下降 - 图109.%0A#card=math&code=%5Cboldsymbol%7Bx%7D%20%5Cleftarrow%20%5Cboldsymbol%7Bx%7D%20-%20%5Ceta%20%5Cnabla%20f_i%28%5Cboldsymbol%7Bx%7D%29.%0A)

这里7.2 梯度下降和随机梯度下降 - 图110同样是学习率。可以看到每次迭代的计算开销从梯度下降的7.2 梯度下降和随机梯度下降 - 图111#card=math&code=%5Cmathcal%7BO%7D%28n%29)降到了常数7.2 梯度下降和随机梯度下降 - 图112#card=math&code=%5Cmathcal%7BO%7D%281%29)。值得强调的是,随机梯度7.2 梯度下降和随机梯度下降 - 图113#card=math&code=%5Cnabla%20f_i%28%5Cboldsymbol%7Bx%7D%29)是对梯度7.2 梯度下降和随机梯度下降 - 图114#card=math&code=%5Cnabla%20f%28%5Cboldsymbol%7Bx%7D%29)的无偏估计:

7.2 梯度下降和随机梯度下降 - 图115%20%3D%20%5Cfrac%7B1%7D%7Bn%7D%20%5Csum%7Bi%20%3D%201%7D%5En%20%5Cnabla%20f_i(%5Cboldsymbol%7Bx%7D)%20%3D%20%5Cnabla%20f(%5Cboldsymbol%7Bx%7D).%0A#card=math&code=E_i%20%5Cnabla%20f_i%28%5Cboldsymbol%7Bx%7D%29%20%3D%20%5Cfrac%7B1%7D%7Bn%7D%20%5Csum%7Bi%20%3D%201%7D%5En%20%5Cnabla%20f_i%28%5Cboldsymbol%7Bx%7D%29%20%3D%20%5Cnabla%20f%28%5Cboldsymbol%7Bx%7D%29.%0A)

这意味着,平均来说,随机梯度是对梯度的一个良好的估计。

下面我们通过在梯度中添加均值为0的随机噪声来模拟随机梯度下降,以此来比较它与梯度下降的区别。

  1. def sgd_2d(x1, x2, s1, s2):
  2. return (x1 - eta * (2 * x1 + np.random.normal(0.1)),
  3. x2 - eta * (4 * x2 + np.random.normal(0.1)), 0, 0)
  4. show_trace_2d(f_2d, train_2d(sgd_2d))

输出:

  1. epoch 20, x1 -0.047150, x2 -0.075628

7.2_output5.svg

可以看到,随机梯度下降中自变量的迭代轨迹相对于梯度下降中的来说更为曲折。这是由于实验所添加的噪声使模拟的随机梯度的准确度下降。在实际中,这些噪声通常指训练数据集中的无意义的干扰。

小结

  • 使用适当的学习率,沿着梯度反方向更新自变量可能降低目标函数值。梯度下降重复这一更新过程直到得到满足要求的解。
  • 学习率过大或过小都有问题。一个合适的学习率通常是需要通过多次实验找到的。
  • 当训练数据集的样本较多时,梯度下降每次迭代的计算开销较大,因而随机梯度下降通常更受青睐。

参考文献

[1] Stewart, J. (2010). Calculus: early transcendentals. 7th ed. Cengage Learning.


注:本节与原书基本相同,原书传送门