7.2 梯度下降和随机梯度下降

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

7.2.1 一维梯度下降

我们先以简单的一维梯度下降为例,解释梯度下降算法可能降低目标函数值的原因。假设连续可导的函数

7.2 梯度下降和随机梯度下降 - 图1 的输入和输出都是标量。给定绝对值足够小的数 7.2 梯度下降和随机梯度下降 - 图2 ,根据泰勒展开公式,我们得到以下的近似:

7.2 梯度下降和随机梯度下降 - 图3

这里

7.2 梯度下降和随机梯度下降 - 图4 是函数 7.2 梯度下降和随机梯度下降 - 图57.2 梯度下降和随机梯度下降 - 图6 处的梯度。一维函数的梯度是一个标量,也称导数。

接下来,找到一个常数

7.2 梯度下降和随机梯度下降 - 图7 ,使得 7.2 梯度下降和随机梯度下降 - 图8 足够小,那么可以将 7.2 梯度下降和随机梯度下降 - 图9 替换为 7.2 梯度下降和随机梯度下降 - 图10 并得到

7.2 梯度下降和随机梯度下降 - 图11

如果导数

7.2 梯度下降和随机梯度下降 - 图12 ,那么 7.2 梯度下降和随机梯度下降 - 图13 ,所以

7.2 梯度下降和随机梯度下降 - 图14

这意味着,如果通过

7.2 梯度下降和随机梯度下降 - 图15

来迭代

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

下面我们以目标函数

7.2 梯度下降和随机梯度下降 - 图22 为例来看一看梯度下降是如何工作的。虽然我们知道最小化 7.2 梯度下降和随机梯度下降 - 图23 的解为 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 梯度下降和随机梯度下降 - 图31

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 梯度下降和随机梯度下降 - 图37

如果使用过大的学习率,

7.2 梯度下降和随机梯度下降 - 图38 可能会过大从而使前面提到的一阶泰勒展开公式不再成立:这时我们无法保证迭代 7.2 梯度下降和随机梯度下降 - 图39 会降低 7.2 梯度下降和随机梯度下降 - 图40 的值。

举个例子,当设学习率

7.2 梯度下降和随机梯度下降 - 图41 时,可以看到 7.2 梯度下降和随机梯度下降 - 图42 不断越过(overshoot)最优解 7.2 梯度下降和随机梯度下降 - 图43 并逐渐发散。

  1. show_trace(gd(1.1))

输出:

  1. epoch 10, x: 61.917364224000096

7.2 梯度下降和随机梯度下降 - 图44

7.2.3 多维梯度下降

在了解了一维梯度下降之后,我们再考虑一种更广义的情况:目标函数的输入为向量,输出为标量。假设目标函数

7.2 梯度下降和随机梯度下降 - 图45 的输入是一个 7.2 梯度下降和随机梯度下降 - 图46 维向量 7.2 梯度下降和随机梯度下降 - 图47 。目标函数 7.2 梯度下降和随机梯度下降 - 图48 有关 7.2 梯度下降和随机梯度下降 - 图49 的梯度是一个由 7.2 梯度下降和随机梯度下降 - 图50 个偏导数组成的向量:

7.2 梯度下降和随机梯度下降 - 图51

为表示简洁,我们用

7.2 梯度下降和随机梯度下降 - 图52 代替 7.2 梯度下降和随机梯度下降 - 图53 。梯度中每个偏导数元素 7.2 梯度下降和随机梯度下降 - 图54 代表着 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

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

7.2 梯度下降和随机梯度下降 - 图65

方向导数

7.2 梯度下降和随机梯度下降 - 图66 给出了 7.2 梯度下降和随机梯度下降 - 图677.2 梯度下降和随机梯度下降 - 图68 上沿着所有可能方向的变化率。为了最小化 7.2 梯度下降和随机梯度下降 - 图69 ,我们希望找到 7.2 梯度下降和随机梯度下降 - 图70 能被降低最快的方向。因此,我们可以通过单位向量 7.2 梯度下降和随机梯度下降 - 图71 来最小化方向导数 7.2 梯度下降和随机梯度下降 - 图72

由于

7.2 梯度下降和随机梯度下降 - 图73 , 其中 7.2 梯度下降和随机梯度下降 - 图74 为梯度 7.2 梯度下降和随机梯度下降 - 图75 和单位向量 7.2 梯度下降和随机梯度下降 - 图76 之间的夹角,当 7.2 梯度下降和随机梯度下降 - 图77 时, 7.2 梯度下降和随机梯度下降 - 图78 取得最小值 7.2 梯度下降和随机梯度下降 - 图79 。因此,当 7.2 梯度下降和随机梯度下降 - 图80 在梯度方向 7.2 梯度下降和随机梯度下降 - 图81 的相反方向时,方向导数 7.2 梯度下降和随机梯度下降 - 图82 被最小化。因此,我们可能通过梯度下降算法来不断降低目标函数 7.2 梯度下降和随机梯度下降 - 图83 的值:

7.2 梯度下降和随机梯度下降 - 图84

同样,其中

7.2 梯度下降和随机梯度下降 - 图85 (取正数)称作学习率。

下面我们构造一个输入为二维向量

7.2 梯度下降和随机梯度下降 - 图86 和输出为标量的目标函数 7.2 梯度下降和随机梯度下降 - 图87 。那么,梯度 7.2 梯度下降和随机梯度下降 - 图88 。我们将观察梯度下降从初始位置 7.2 梯度下降和随机梯度下降 - 图89 开始对自变量 7.2 梯度下降和随机梯度下降 - 图90 的迭代轨迹。我们先定义两个辅助函数,第一个函数使用给定的自变量更新函数,从初始位置 7.2 梯度下降和随机梯度下降 - 图91 开始迭代自变量 7.2 梯度下降和随机梯度下降 - 图92 共20次,第二个函数对自变量 7.2 梯度下降和随机梯度下降 - 图93 的迭代轨迹进行可视化。

  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 梯度下降和随机梯度下降 - 图94 时自变量的迭代轨迹。使用梯度下降对自变量 7.2 梯度下降和随机梯度下降 - 图95 迭代20次后,可见最终 7.2 梯度下降和随机梯度下降 - 图96 的值较接近最优解 7.2 梯度下降和随机梯度下降 - 图97

  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 梯度下降和随机梯度下降 - 图98

7.2.4 随机梯度下降

在深度学习里,目标函数通常是训练数据集中有关各个样本的损失函数的平均。设

7.2 梯度下降和随机梯度下降 - 图99 是有关索引为 7.2 梯度下降和随机梯度下降 - 图100 的训练数据样本的损失函数, 7.2 梯度下降和随机梯度下降 - 图101 是训练数据样本数, 7.2 梯度下降和随机梯度下降 - 图102 是模型的参数向量,那么目标函数定义为

7.2 梯度下降和随机梯度下降 - 图103

目标函数在

7.2 梯度下降和随机梯度下降 - 图104 处的梯度计算为

7.2 梯度下降和随机梯度下降 - 图105

如果使用梯度下降,每次自变量迭代的计算开销为

7.2 梯度下降和随机梯度下降 - 图106 ,它随着 7.2 梯度下降和随机梯度下降 - 图107 线性增长。因此,当训练数据样本数很大时,梯度下降每次迭代的计算开销很高。

随机梯度下降(stochastic gradient descent,SGD)减少了每次迭代的计算开销。在随机梯度下降的每次迭代中,我们随机均匀采样的一个样本索引

7.2 梯度下降和随机梯度下降 - 图108 ,并计算梯度 7.2 梯度下降和随机梯度下降 - 图109 来迭代 7.2 梯度下降和随机梯度下降 - 图110

7.2 梯度下降和随机梯度下降 - 图111

这里

7.2 梯度下降和随机梯度下降 - 图112 同样是学习率。可以看到每次迭代的计算开销从梯度下降的 7.2 梯度下降和随机梯度下降 - 图113 降到了常数 7.2 梯度下降和随机梯度下降 - 图114 。值得强调的是,随机梯度 7.2 梯度下降和随机梯度下降 - 图115 是对梯度 7.2 梯度下降和随机梯度下降 - 图116 的无偏估计:

7.2 梯度下降和随机梯度下降 - 图117

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

下面我们通过在梯度中添加均值为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 梯度下降和随机梯度下降 - 图118

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

小结

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

参考文献

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


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