跳至内容
3/30第 3 章,共 30 章

下坡:Gradient Descent,以及所有人都跳过的两个步骤

计算学习率的精确上限,再看一次对 3,600 个方向的暴力搜索如何在无人告知的情况下重新发现梯度。

本页内容

上一章以一条山谷结束。

这不是比喻意义上的山谷:它是一条真实的曲线,把损失画在单个参数上,先下降再回升。而曲线下方的那个损失并不是因为整洁才被选中——它是从关于测量噪声的陈述中推导出来的,平方误差是作为结果而不是惯例出现的。

所以我们有了一片带底部的地形,也有理由相信底部就是正确的位置。但我们还没有到达那里的方法。

本章会构建一个方法,而它就是训练本课程其余所有模型的算法——每一个,无一例外,包括那些拥有数千亿参数的模型。它大约二十行就能写完。难点不在这二十行里,而在几乎所有解释都会跳过的两个问题里:

  • 为什么是减号。 更新会减去梯度。每篇教程都会这么写;却很少有人说明为什么梯度是向上的方向,而这正是让减号不只是信仰行为的唯一事实。
  • 步子能迈多大。 “太大会发散,太小会很慢”是真的,也没什么用。有一个精确的数字,它可以从损失中计算出来,本章会算两次——一次针对玩具抛物线,一次针对真实数据。

为了让本章自洽,先重述一下:数据仍是 Chapter 1 传送带上的八个零件,但我们换一个问题。不是接受或拒绝——那个问题稍后会回来——而是根据零件宽度预测重量。

belt.pyPYTHON
import numpy as np

WIDTH  = np.array([18.0, 19.5, 20.2, 21.0, 24.0, 25.5, 23.0, 26.0])
WEIGHT = np.array([47.0, 52.0, 49.0, 55.0, 61.0, 66.0, 70.0, 58.0])

x = WIDTH  - WIDTH.mean()      # 22.15 mm
y = WEIGHT - WEIGHT.mean()     # 57.25 g

这些测量值已经居中,和 Chapter 1 完全一样,而且这个原因在本章结束前会连本带利地回来。模型是一条直线,y^=ax+b\hat{y} = a x + b,损失是上一章推导出的均方误差:

L(a,b)=1n∑i=1n(axi+b−yi)2L(a, b) = \frac{1}{n} \sum_{i=1}^{n} \left(a x_i + b - y_i\right)^2

两个参数。为什么不直接试很多值?我们真的试一下——从 a=0a = 0 到 55,从 b=−5b = -5 到 55,步长 0.010.01:

TEXT
grid 501 x 1001 = 501,501 evaluations in 3.67 s
  best found: a = 2.1000, b = -0.0000, L = 24.592450

半百万次评估,只是把两个数字确定到小数点后两位——而且那一秒是某台机器上的挂钟时间,所以重跑可能在三到六秒之间;可复现的是评估次数和最小值。到本章末尾,Gradient Descent 会在八步内得到四位小数,在三十六步内得到完整 float64 答案。

但速度不是核心论点,接下来这一点决定了整门课程。Grid search 对 PP 个参数、每个参数 kk 个取值,需要 kPk^P 次评估。每个轴一千个取值时:

模型参数grid 评估次数
这条直线210610^{6}
Chapter 5 的 XOR 网络9102710^{27}
一个小型多层网络20,0001060,00010^{60{,}000}

第三行不是一个很大的数字,而是一个失去意义的数字——可观测宇宙中的原子数量大约是 108010^{80}。随着模型变大,搜索不是变慢;它是停止存在。后面的一切,都是因为这张表而存在。

先把 b=0b = 0 固定住,这样就只剩一个参数和一条曲线,也就是上一章留给你的那张图。在曲线上取一点 a=1a = 1,然后问:如果我把 aa 轻推一个小量 hh,损失每单位轻推会移动多少?

L(a+h)−L(a)h\frac{L(a + h) - L(a)}{h}

这个比值就是上升量除以前进量——穿过曲线上两点的直线斜率。当 hh 变小,两点滑向彼此,这条线就变成切线。它的斜率就是导数 L′(a)L'(a):损失相对于 aa 的单位变化而变化的速率。它不是对某个东西的近似,也不是一个无穷小的量。它是普通比值的极限。

值得跑一下,因为数字会说出定义本身没有说的东西:

slope.pyPYTHON
def loss1(a):
    return np.mean((a * x - y) ** 2)

for h in [1.0, 1e-2, 1e-4, 1e-6, 1e-8, 1e-10, 1e-12, 1e-14]:
    q = (loss1(1.0 + h) - loss1(1.0)) / h
    print(f"h = {h:<8.0e}   slope estimate = {q:.10f}   error = {abs(q + 16.385):.3e}")
TEXT
h = 1e+00      slope estimate = -8.9400000000    error = 7.445e+00
h = 1e-02      slope estimate = -16.3105500000   error = 7.445e-02
h = 1e-04      slope estimate = -16.3842555001   error = 7.445e-04
h = 1e-06      slope estimate = -16.3849925556   error = 7.444e-06
h = 1e-08      slope estimate = -16.3850003787   error = 3.787e-07
h = 1e-10      slope estimate = -16.3850444324   error = 4.443e-05
h = 1e-12      slope estimate = -16.3851154866   error = 1.155e-04
h = 1e-14      slope estimate = -17.0530256582   error = 6.680e-01

这里发生了两件事,而且两件都承重。

误差不是含糊地正比于 hh——它精确地是 7.445 h7.445\,h。 把 hh 除以一百,误差也除以一百,每次都精确到四位有效数字。这个常数不是装饰:它是损失的二阶导数的一半,也是两节之后一个想法的首次出现——曲线在某点附近看起来像一条直线,再加上一个与 h2h^2 成正比的修正项。

然后模式断裂了。 低于 h=10−8h = 10^{-8} 时,估计反而变得更差,到了 10−1410^{-14},第二位数字就错了。数学上什么都没发生;是上一章的浮点盒子在起作用。L(a+h)L(a+h) 和 L(a)L(a) 的前十位数字相同,把它们相减会毁掉这些数字,再除以一个极小的数会放大剩下的残骸。存在一个最佳 hh——这里大约是 10−810^{-8},差不多是机器 epsilon 的平方根——再小不是更谨慎,而是更不谨慎。记住这一点;本章末尾有个函数依赖它。

精确斜率来自微积分而不是测量,是 −16.385-16.385。所以我们可以停止测量,开始推导。

新文章直达你的收件箱

AI 新闻、指南和产品更新——有值得你花时间阅读的内容时,我们会发一封简短邮件。

这是整门课程其余部分建立其上的想法,清楚地说一次。

复合两个函数,就是把一个函数喂给另一个函数:(f∘g)(x)=f(g(x))(f \circ g)(x) = f(g(x))。仅此而已。

深度网络不是像一个复合。它就是一个复合。一层是一个函数;堆叠层就是复合它们;“深度”就是这条链中函数的数量。Chapter 5 构建网络时,它构建的是 f4∘f3∘f2∘f1f_4 \circ f_3 \circ f_2 \circ f_1,除此之外没有别的。这意味着,对我们来说,微积分中最重要的一条规则,就是那条对复合求导的规则:

ddxf(g(x))=f′(g(x))⋅g′(x)\frac{d}{dx} f(g(x)) = f'(g(x)) \cdot g'(x)

速率会相乘。 如果 gg 的变化速度是 xx 的三倍,而 ff 的变化速度是 gg 的两倍,那么 ff 的变化速度就是 xx 的六倍。这就是全部内容,也解释了为什么一个信号反向穿过十层时会乘上十个数字——这也是 Chapter 6 会花一节讨论当这些数字都略小于一时会发生什么的原因。

把它用到我们的损失上。写出残差 ri=axi+b−yir_i = a x_i + b - y_i,于是 L=1n∑ri2L = \frac{1}{n}\sum r_i^2。每个 rir_i 都通过内部函数 axia x_i 依赖于 aa,而该内部函数的导数是 xix_i。逐项应用链式法则:

∂L∂a=1n∑i2ri⋅xi,∂L∂b=1n∑i2ri⋅1\frac{\partial L}{\partial a} = \frac{1}{n}\sum_i 2 r_i \cdot x_i, \qquad \frac{\partial L}{\partial b} = \frac{1}{n}\sum_i 2 r_i \cdot 1

这些花体 ∂\partial 符号表示偏导数:对一个变量求导,并把其他所有变量都视为常数。这里没有发生新事情——它仍是前面的同一个极限,只是沿着一个轴取。把这些偏导收集成一个向量,你就得到了梯度:

∇L=(∂L∂a, ∂L∂b)\nabla L = \left( \frac{\partial L}{\partial a},\ \frac{\partial L}{\partial b} \right)

在点 (a,b)=(1,4)(a, b) = (1, 4),这个向量是 (−16.385, 8.0)(-16.385,\ 8.0)。两个数字。问题是它们意味着什么,而这就是所有人都会跳过的第一步。

梯度是一个由沿坐标轴的斜率组成的向量。我们只证明了这一点。把它们组装成一个向量后,会得到某个特定方向,这并不显然——也不应该显然。

所以先定义我们真正想要的东西。选一个单位向量 u\mathbf{u},也就是一个方向。方向导数就是当你沿那个方向走时,损失变化的速率:

DuL=lim⁡h→0L(θ+hu)−L(θ)hD_{\mathbf{u}} L = \lim_{h \to 0} \frac{L(\boldsymbol{\theta} + h\mathbf{u}) - L(\boldsymbol{\theta})}{h}

链式法则把它变成可计算的东西。沿 u\mathbf{u} 行走时,aa 以 u1u_1 的速率变化,bb 以 u2u_2 的速率变化,而这些贡献会相加:

DuL=∂L∂au1+∂L∂bu2=∇L⋅uD_{\mathbf{u}} L = \frac{\partial L}{\partial a} u_1 + \frac{\partial L}{\partial b} u_2 = \nabla L \cdot \mathbf{u}

任意方向上的变化速率,都是梯度与该方向的 dot product。现在是关键结论,一行几何就够了。用两个向量之间的角 ϕ\phi 写出 dot product,

∇L⋅u=∥∇L∥ ∥u∥cos⁡ϕ=∥∇L∥cos⁡ϕ\nabla L \cdot \mathbf{u} = \lVert \nabla L \rVert \, \lVert \mathbf{u} \rVert \cos\phi = \lVert \nabla L \rVert \cos\phi

因为 u\mathbf{u} 的长度是 1。你唯一能控制的是 cos⁡ϕ\cos\phi,它在 ϕ=0\phi = 0 最大,在转半圈也就是 ϕ=180\phi = 180 度时最小。所以:

  • 最陡上升沿着 ∇L\nabla L 本身,且那里的斜率精确等于 ∥∇L∥\lVert \nabla L \rVert。
  • 最陡下降沿着 −∇L-\nabla L,且那里的斜率是 −∥∇L∥-\lVert \nabla L \rVert。
  • 垂直于梯度时,损失完全不变。这就是为什么等高线图中的线会与梯度成直角相交。

这就是减号。它不是惯例,不是某个人选择的符号翻转:最快下降的方向是负梯度,是因为 cos⁡ϕ\cos\phi 在转半圈时最小,除此之外没有别的原因。

既然这是关于所有方向的主张,那就拿所有方向来测试。采样 3,600 个方向,每十分之一度一个,并通过轻推来测量每一个:

directions.pyPYTHON
theta = np.array([1.0, 4.0])
g = grad(theta)
print("gradient      ", g)
print("its length    ", np.linalg.norm(g))
print("its angle     ", np.degrees(np.arctan2(g[1], g[0])) % 360, "degrees")

best = max(
    ((loss(theta + 1e-6 * u) - loss(theta - 1e-6 * u)) / 2e-6, np.degrees(ang))
    for ang, u in (
        (a, np.array([np.cos(a), np.sin(a)])) for a in np.arange(3600) * 2 * np.pi / 3600
    )
)
print("steepest slope", best[0], "at", best[1], "degrees")
TEXT
gradient       [-16.385   8.   ]
its length     18.23371122399386
its angle      153.97598928042032 degrees
steepest slope 18.233709624837502 at 154.0 degrees

一个完全不知道梯度的搜索,在 3,600 个方向上找到了 154.0 度处的最陡上升——也就是梯度自己的方向,误差只在搜索的 0.1 度分辨率内。它在那里找到的斜率 18.2337,也与梯度长度在六位数字上相符。这个定理不是关于梯度含义的故事;它是一个可测量的事实,而这就是测量结果。

现在是第二个被跳过的步骤。我们知道哪个方向向下。但这并不意味着沿那个方向走会降低损失,因为“向下”说的是无穷小轻推,而一步并不是无穷小。

桥梁是线性化。在某点附近,一个光滑函数等于它的切线再加上一个修正项:

L(θ+δ)=L(θ)+∇L⋅δ+O(∥δ∥2)L(\boldsymbol{\theta} + \boldsymbol{\delta}) = L(\boldsymbol{\theta}) + \nabla L \cdot \boldsymbol{\delta} + O(\lVert\boldsymbol{\delta}\rVert^2)

这是一阶 Taylor 展开。被丢弃的 O(∥δ∥2)O(\lVert\boldsymbol{\delta}\rVert^2) 是曲率——也就是让斜率表中的估计恰好错 7.445 h7.445\,h 的同一个项。放入我们打算迈出的步子,δ=−η∇L\boldsymbol{\delta} = -\eta \nabla L:

L(θ−η∇L)≈L(θ)−η∥∇L∥2L(\boldsymbol{\theta} - \eta \nabla L) \approx L(\boldsymbol{\theta}) - \eta \lVert \nabla L \rVert^2

损失会下降 η∥∇L∥2\eta \lVert \nabla L \rVert^2。这里的每一部分都是非负的,所以承诺是真实的——只要 η\eta 足够小,因为被忽略的项会像 η2\eta^2 一样增长,并最终吃掉它。这就是全部理论。下面先是承诺被兑现,然后是承诺被打破:

TEXT
eta = 0.2       promised    66.49364500   delivered   -16.01619240   ratio -0.240868
eta = 0.1       promised    33.24682250   delivered    12.61936315   ratio  0.379566
eta = 0.01      promised     3.32468225   delivered     3.11840766   ratio  0.937957
eta = 0.001     promised     0.33246822   delivered     0.33040548   ratio  0.993796
eta = 0.0001    promised     0.03324682   delivered     0.03322620   ratio  0.999380
eta = 1e-05     promised     0.00332468   delivered     0.00332448   ratio  0.999938

从底部往上读。随着 η\eta 变小,实际下降量会收敛到承诺的下降量——比值 0.99938,然后 0.99994——这就是 Taylor 定理正确的样子。从顶部往下读,在 η=0.2\eta = 0.2 时,实际“下降量”是负十六。这一步沿着下坡走,损失却上升了。

所以更新规则是

θ←θ−η∇L(θ)\boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \eta \nabla L(\boldsymbol{\theta})

而它带着一个没人说明的条件:η\eta 必须足够小。到底相对于什么足够小,就是下一节。

从最简单的山谷开始,f(x)=x2f(x) = x^2,其中 f′(x)=2xf'(x) = 2x。Gradient Descent 的一步是

x←x−η⋅2x=x (1−2η)x \leftarrow x - \eta \cdot 2x = x\,(1 - 2\eta)

位置在每一步都会乘以 (1−2η)(1 - 2\eta)。这是一个等比数列,而等比数列只有一条规则:当乘数的绝对值小于 1 时它会收缩,否则会增长。所以 ∣1−2η∣<1\lvert 1 - 2\eta \rvert < 1,也就是 0<η<10 < \eta < 1。

边界精确地在 η=1\eta = 1。 不是“大约 1”,也不是“1 通常太大”。在 η=1\eta = 1 时,乘数是 −1-1,点会永远在 xx 和 −x-x 之间来回弹跳,既不接近也不逃离。低于它,收敛;高于它,发散。区间在 η=0.5\eta = 0.5 处再次分裂,那里乘数改变符号:低于它时接近是单调的,高于它时点会越过谷底并在两侧交替,而恰好等于 0.50.5 时乘数为 0,一步就落在最小值上。

四种状态,来自四行代数。自己去跨过这些边界:

14 步后结束于 x = -0.0836。

以表格查看数据
步骤xf(x)
0⁨-1.9000⁩⁨3.6100⁩
1⁨-1.5200⁩⁨2.3104⁩
2⁨-1.2160⁩⁨1.4787⁩
3⁨-0.9728⁩⁨0.9463⁩
4⁨-0.7782⁩⁨0.6057⁩
5⁨-0.6226⁩⁨0.3876⁩
6⁨-0.4981⁩⁨0.2481⁩
7⁨-0.3985⁩⁨0.1588⁩
8⁨-0.3188⁩⁨0.1016⁩
9⁨-0.2550⁩⁨0.0650⁩
10⁨-0.2040⁩⁨0.0416⁩
11⁨-0.1632⁩⁨0.0266⁩
12⁨-0.1306⁩⁨0.0170⁩
13⁨-0.1045⁩⁨0.0109⁩
14⁨-0.0836⁩⁨0.0070⁩
梯度下降,交互式

从 x=−1.9x = -1.9 开始,学习率为 0.1,十四步后结束在 −0.0836-0.0836。把学习率推到 0.5,第一步就落在谷底。推到 0.9,它会结束在和 0.1 相同的 −0.0836-0.0836——距离相同,风格相反,因为两者的 ∣1−2η∣\lvert 1 - 2\eta \rvert 都是 0.8——但它是通过在山谷两侧之字形跳动到达那里,而不是沿一侧走下去。

然后是有趣的那个:

14 步后结束于 x = -1.9000。

以表格查看数据
步骤xf(x)
0⁨-1.9000⁩⁨3.6100⁩
1⁨1.9000⁩⁨3.6100⁩
2⁨-1.9000⁩⁨3.6100⁩
3⁨1.9000⁩⁨3.6100⁩
4⁨-1.9000⁩⁨3.6100⁩
5⁨1.9000⁩⁨3.6100⁩
6⁨-1.9000⁩⁨3.6100⁩
7⁨1.9000⁩⁨3.6100⁩
8⁨-1.9000⁩⁨3.6100⁩
9⁨1.9000⁩⁨3.6100⁩
10⁨-1.9000⁩⁨3.6100⁩
11⁨1.9000⁩⁨3.6100⁩
12⁨-1.9000⁩⁨3.6100⁩
13⁨1.9000⁩⁨3.6100⁩
14⁨-1.9000⁩⁨3.6100⁩
梯度下降,交互式

正好在边界上。学习率为 1,十四步后结束在 −1.9-1.9:精确回到起点,除了弹跳什么也没做。只要再高一点,弹跳就会增长而不是保持;在 1.2 时,四步后就冲出图表。过大的学习率不是慢慢收敛。它不会收敛。

现在是一般规则,它也从同一个论证中掉出来。乘数 1−2η1 - 2\eta 实际上是 1−ηf′′1 - \eta f'',而在最小值附近,多参数损失的每个方向都有一个这样的数——也就是二阶导数矩阵的特征值。每个方向都必须同时稳定,所以上限由最大的那个决定:

η<2λmax⁡\eta < \frac{2}{\lambda_{\max}}

对于 f(x)=x2f(x) = x^2,f′′=2f'' = 2,上限是 1,也就是我们刚刚推导出的结果。对于我们的传送带,二阶导数矩阵是 2nA⊤A\frac{2}{n} A^{\top} A,其中 AA 是输入的两列矩阵,它的特征值是 2 和 14.89,所以上限是 2/14.89=0.134322 / 14.89 = 0.13432。这是一个带有五位有效数字的预测。测试一下:

TEXT
lr=0.1343    -> L =      24.5924
lr=0.13431   -> L =      24.5924
lr=0.13432   -> L =       4707.8       BLEW UP
lr=0.13433   -> L =  4.00452e+16       BLEW UP
lr=0.1344    -> L = 1.18229e+107       BLEW UP

一行线性代数和十万次 for 循环迭代,在五位小数上吻合。

这就是 Chapter 1 回来的地方。 上面的一切都使用了居中后的测量值。在原始毫米和克上运行完全相同的代码,特征值会从 2 和 14.89 变成 0.0298 和 998.1。上限从 0.134 崩塌到 0.002004——同样精确,在 lr=0.002003 收敛,在 lr=0.002004 爆炸。

比上限更糟的是特征值之间的比值。条件数衡量山谷离圆形有多远:一条又长又窄的沟会迫使学习率小到能应付陡峭的墙壁,然后沟底也只能以同样的爬行速度前进。我们的条件数从居中后的 7.44 变成原始数据的 33,452。在每个版本能采用的最佳学习率下:

特征条件数最佳学习率达到距离最优值 1% 以内所需步数
居中7.440.118410
原始毫米和克33,4520.002003779,513

同样的数据,同样的代码,最后得到同样的答案——却要多做八千倍的工作,只因为没人减去均值。在 Chapter 1 中,同样的遗漏让感知机的 epoch 数多了六千倍,而那里的诊断是几何的:数据漂浮在离原点很远的位置。这里是同一种几何,只是披上了优化的外衣,也因此输入归一化不是卫生建议,而是算术。1

上面没有任何东西需要库。下面就是整个优化器。

descent.pyPYTHON
def loss(theta):
    a, b = theta
    return np.mean((a * x + b - y) ** 2)


def grad(theta):
    a, b = theta
    residual = a * x + b - y
    return np.array([np.mean(2 * residual * x), np.mean(2 * residual)])


def descend(theta, lr, steps):
    theta = np.array(theta, dtype=float)
    for _ in range(steps):
        theta = theta - lr * grad(theta)   
    return theta


theta = descend([0.0, 0.0], lr=0.05, steps=60)
print(theta, loss(theta))
TEXT
[ 2.10040296e+00 -2.76445533e-15] 24.592448791134984

这八个点的闭式最小二乘答案是 a=2.100403a = 2.100403,b=0b = 0,损失为 24.59244924.592449。这个循环在不知道闭式解存在的情况下找到了八位有效数字——这很重要,因为从 Chapter 5 开始就不会再有闭式解了。

轨迹如下,因为看它移动才是重点:

TEXT
   0 a=0.000000 b=0.000000 L=57.437500
   1 a=1.563750 b=0.000000 L=26.736582
   2 a=1.963288 b=-0.000000 L=24.732418
   5 a=2.098116 b=-0.000000 L=24.592488
  10 a=2.100400 b=-0.000000 L=24.592449
  60 a=2.100403 b=-0.000000 L=24.592449

大部分距离在前两步就走完了,因为当你离谷底最远时梯度最大,靠近时梯度会变小。Gradient Descent 会在最小值附近自动减速。这是一个特性,而在 Chapter 6 中,它也会成为一个问题。

到目前为止的论证有一个漏洞。当 ∇L=0\nabla L = \mathbf{0} 时,步骤会停止,而我们一直把那里叫作“最小值”。梯度为零的点是临界点,而成为最小值只是成为临界点的方式之一:

  • 局部最小值:每个方向都是上坡,但它可能不是所有这类点中最低的;
  • 局部最大值:每个方向都是下坡;
  • 鞍点:某些方向上坡,另一些方向下坡。曲面 f(x,y)=x2−y2f(x,y) = x^2 - y^2 有 ∇f=(2x,−2y)\nabla f = (2x, -2y),它在原点为零,而原点处函数沿 xx 轴是最小值,同时沿 yy 轴是最大值。

Gradient Descent 无法区分这些情况,因为它永远只看梯度,而三者的梯度都为零。

我们的直线只有一个临界点,而且它就是答案——线性模型上的平方误差损失是凸的,是一个单一的碗,在它上面做下降不可能找不到全局最小值。这个性质不会在本课程后面继续保留。神经网络的损失不是凸的,从 Chapter 5 开始,“那个最小值”不再是存在的东西:会有很多个,深浅不同,而你得到哪一个取决于从哪里开始。这句话到此为止,因为理论很大,而实践后果很小。

你可以在一条曲线上看到完整后果。取 f(x)=x44−x22+x10f(x) = \tfrac{x^4}{4} - \tfrac{x^2}{2} + \tfrac{x}{10},它有两个深度不同的山谷:

TEXT
   x =  -1.046681   f(x) =  -0.352386   minimum
   x =   0.101031   f(x) =   0.005026   maximum
   x =   0.945649   f(x) =  -0.152639   minimum

40 步后结束于 x = 0.9456。

以表格查看数据
步骤xf(x)
0⁨0.1100⁩⁨0.0050⁩
1⁨0.1122⁩⁨0.0050⁩
2⁨0.1149⁩⁨0.0049⁩
3⁨0.1182⁩⁨0.0049⁩
4⁨0.1223⁩⁨0.0048⁩
5⁨0.1275⁩⁨0.0047⁩
6⁨0.1338⁩⁨0.0045⁩
7⁨0.1416⁩⁨0.0042⁩
8⁨0.1513⁩⁨0.0038⁩
9⁨0.1633⁩⁨0.0032⁩
10⁨0.1781⁩⁨0.0022⁩
11⁨0.1962⁩⁨0.0007⁩
12⁨0.2183⁩⁨-0.0014⁩
13⁨0.2453⁩⁨-0.0046⁩
14⁨0.2779⁩⁨-0.0093⁩
15⁨0.3170⁩⁨-0.0160⁩
16⁨0.3633⁩⁨-0.0253⁩
17⁨0.4172⁩⁨-0.0377⁩
18⁨0.4783⁩⁨-0.0535⁩
19⁨0.5455⁩⁨-0.0721⁩
20⁨0.6163⁩⁨-0.0922⁩
21⁨0.6869⁩⁨-0.1116⁩
22⁨0.7526⁩⁨-0.1277⁩
23⁨0.8092⁩⁨-0.1393⁩
24⁨0.8540⁩⁨-0.1463⁩
25⁨0.8868⁩⁨-0.1499⁩
26⁨0.9091⁩⁨-0.1516⁩
27⁨0.9236⁩⁨-0.1522⁩
28⁨0.9325⁩⁨-0.1525⁩
29⁨0.9379⁩⁨-0.1526⁩
30⁨0.9411⁩⁨-0.1526⁩
31⁨0.9430⁩⁨-0.1526⁩
32⁨0.9441⁩⁨-0.1526⁩
33⁨0.9448⁩⁨-0.1526⁩
34⁨0.9451⁩⁨-0.1526⁩
35⁨0.9454⁩⁨-0.1526⁩
36⁨0.9455⁩⁨-0.1526⁩
37⁨0.9455⁩⁨-0.1526⁩
38⁨0.9456⁩⁨-0.1526⁩
39⁨0.9456⁩⁨-0.1526⁩
40⁨0.9456⁩⁨-0.1526⁩
梯度下降,交互式

从 x=0.11x = 0.11 出发走四十步,稳定在 0.94560.9456——两个山谷中较浅的那个。现在把起点向左挪一个刻度,到 0.100.10。同样的学习率,同样的四十步,它改为稳定在 −1.0461-1.0461,那里的损失低 0.199747。分水岭是 0.1010310.101031 处的隆起,而两个答案之间的全部差异,只是你碰巧从它的哪一侧开始。

落入浅谷会让损失差 56.7%,而算法无从得知,因为在山谷内部,每个方向都是上坡。Gradient Descent 没有对此的修复,也不会有。实践中存在的,是一个发现:它远没有这张图暗示的那么重要——在真实网络的极高维空间里,大多数临界点最终是鞍点而不是陷阱,2 Chapter 5 会测量一个小网络实际被卡住的频率。

更便宜的步子:随机、小批量、momentum

链接到此部分:更便宜的步子:随机、小批量、momentum

上面的 grad 有一件事应该让你不舒服:它每一步都对整个数据集求和。八个零件不算什么。一百万个样本,就意味着要计算一百万个梯度,才能移动一次参数。

逃脱方式是:梯度是一个平均值,而平均值可以从样本中估计。在随机抽出的一小把样本——一个 minibatch——上计算它,然后据此迈步。估计是有噪声的;它也是无偏的,而数百个便宜的带噪步骤胜过一个昂贵的精确步骤。在十万个合成零件上,如果统计的是每个样本的梯度而不是步骤数:

方法达到距离最优值 0.1% 以内所需步数每样本梯度数
full batch7700,000
minibatch of 321003,200
one example at a time17,58017,580

到达同一位置所需算术少了二百一十九倍。而极端情况——一次一个样本,也就是 Robbins 和 Monro3 最初的随机近似——并不是赢家:它比 32 个样本的 batch 差五倍,因为在会做矩阵乘法的硬件上,32 个样本几乎不比 1 个样本更贵,而噪声会随 batch size 的平方根下降。这个取舍就是为什么你以后读到的每个训练脚本里都会有一个 batch_size。

Momentum 是另一个便宜修复,它正对着那条沟。在条件很差的山谷里,步子会在狭窄方向上来回之字形振荡,同时沿长方向缓慢爬行。Momentum 会保留过去梯度的运行平均,让振荡分量相互抵消,而一致分量累积起来:4

v←βv+∇L(θ),θ←θ−ηv\mathbf{v} \leftarrow \beta \mathbf{v} + \nabla L(\boldsymbol{\theta}), \qquad \boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \eta \mathbf{v}

多两行。在原始未居中的传送带上——条件数 33,452,是我们遇到的最坏情况——使用普通下降法能承受的最佳学习率:

TEXT
momentum beta=0.0   ->    79,513 steps to 1%
momentum beta=0.9   ->     1,609 steps to 1%
momentum beta=0.99  ->       461 steps to 1%

两行代码带来 172 倍提升。Chapter 6 会把它变成 Adam;机制已经在这里了。

本章中的每个梯度都是手工推导的,因此都可能是错的。修复方法就是开头的斜率表:用数值方法测量导数并比较。使用中心差分,L(θ+h)−L(θ−h)2h\frac{L(\theta+h) - L(\theta-h)}{2h},它会抵消主要误差项,并且在相同 hh 下准确得多。

gradcheck.pyPYTHON
def numeric_grad(f, theta, h=1e-5):
    theta = np.asarray(theta, dtype=float)
    out = np.zeros_like(theta)
    for i in range(theta.size):
        bump = np.zeros_like(theta)
        bump[i] = h
        out[i] = (f(theta + bump) - f(theta - bump)) / (2 * h)     
    return out


def gradcheck(f, df, theta, h=1e-5):
    analytic = np.asarray(df(theta), dtype=float)
    numeric = numeric_grad(f, theta, h)
    return np.max(np.abs(analytic - numeric) / np.maximum(1e-8, np.abs(analytic) + np.abs(numeric)))

比较的相对形式很重要:10−410^{-4} 的绝对差,对大小为 10−310^{-3} 的梯度是灾难,对大小为 10610^{6} 的梯度却无关紧要。

TEXT
relative error: 1.8929136036763527e-11
with 2 dropped: 0.33333333331650744

第一行是上面手工推导的梯度。第二行是同一个函数,但某个分量漏掉了因子 2——一个单字符 typo——检查会立刻抓住它。低于大约 10−710^{-7} 就是一致;高于 10−410^{-4} 就是 bug。保留这个函数:Chapter 5 会用它调试一个自动微分引擎,而它也是让错误梯度能被找到的唯一原因。

本章中的一切都建立在一个从未明说的假设上:你能把 ∂L/∂θ\partial L / \partial \theta 写下来。

对于两个参数的一条直线,那只是一行代数。它几乎立刻就不再是一行。让符号代数系统求一个网络的损失对单个第一层权重、在单个样本上的导数,然后数一数答案里的算术量:

网络一个偏导数中的运算数
四个 hidden units,一层40
四个 hidden units,两层301
四个 hidden units,三层1,717

第三行是一个有 57 个参数的网络——小到在 Chapter 6 里只能算脚注——而手写它在一个训练样本上的梯度,大约意味着 97,869 次运算。没有任何记号能拯救这一点。真正拯救它的是一个观察:应用在复合上的链式法则有巨大的结构,相同的中间量会反复出现,而按正确顺序计算它们,就能以大约一次前向传播的代价得到所有导数。这就是 Chapter 5。

但在那之前还有一个更小的问题,而且它马上就在等着。

我们现在有了一台机器,可以在任何可微损失上向下滚。把它指向传送带最初的问题——接受还是拒绝,目标是 1 或 0——在输出上放一个 sigmoid,让它预测概率,并最小化平方误差。它会运行。但当它错得最厉害时,它几乎不会移动,而梯度说明了原因:

输出 zz预测真值平方误差下的梯度cross-entropy 下的梯度
000.500012.5×10−12.5 \times 10^{-1}5.0×10−15.0 \times 10^{-1}
−2-20.119211.850×10−11.850 \times 10^{-1}8.808×10−18.808 \times 10^{-1}
−6-60.002514.921×10−34.921 \times 10^{-3}9.975×10−19.975 \times 10^{-1}
−10-104.54×10−54.54 \times 10^{-5}19.079×10−59.079 \times 10^{-5}1.0001.000

一个自信而灾难性地错误的模型——在答案为 1 时预测 0.0000454——产生的平方误差梯度是 9×10−59 \times 10^{-5}。它完全不知道自己有麻烦。另一列来自一个我们还没有推导的损失,报告的是 1.0:最大紧迫性,且正好出现在应得的位置。

这就引出了下一章开头的问题。上一章说,损失是关于噪声的假设,而平方误差假设的是 Gaussian noise。一个是或否的答案拥有什么噪声模型——当你对它做同样的推导时,会得到什么损失?


这个方法比这些文献都更古老:Cauchy 在 1847 年给 Académie des Sciences 的一则笔记中描述了它,作为一种通过沿平方残差之和下坡行走来求解方程组的方法。也值得和本章一起阅读的有:Sebastian Ruder 的 An overview of gradient descent optimization algorithms(arXiv:1609.04747),它用十四页易读文字覆盖了从 momentum 到 Adam;Nocedal 和 Wright 的 Numerical Optimization(第 2 版,Springer, 2006)第 3 章,其中定理 3.3 用条件数给出了二次函数上最陡下降的收敛率——这就是为什么条件决定步数的理论背景,虽然它处理的是 line search,而不是上面测量的固定步长 2/λmax⁡2/\lambda_{\max} 上限;或者 Deisenroth, Faisal 和 Ong 的 Mathematics for Machine Learning §5.8 和 §7.1,用更少的工具讲同一片领域;Prince 的 Understanding Deep Learning §6.1 和 Goodfellow, Bengio 与 Courville 的 Deep Learning §4.3;Dive into Deep Learning §12.1–12.3,其中的 minibatch 分析有比这里篇幅允许更多的测量;以及 Géron 的 Hands-On Machine Learning(第 3 版)第 4 章,它最实用地把学习率当作需要调的东西,而不是要推导的东西。MIT 6.390 notes 把 Gradient Descent 放在分类之前,和本课程一样,理由也相同。

  1. LeCun, Y., Bottou, L., Orr, G. B. and Müller, K.-R. Efficient BackProp, in Neural Networks: Tricks of the Trade (Springer, 1998), pp. 9–50. 第 4.3 节给出了建议,第 5.1 节给出了上面细节框所用的论证:对输入进行居中和缩放会改变二阶导数矩阵的特征值,因此改变步骤数,而不仅仅是数值上的舒适度。 ↩

  2. Dauphin, Y. N., Pascanu, R., Gulcehre, C., Cho, K., Ganguli, S. and Bengio, Y. Identifying and attacking the saddle point problem in high-dimensional non-convex optimization, arXiv:1406.2572 (2014). 其论点是,在高维中,临界点压倒性地更可能是鞍点而不是局部最小值,因为一个最小值要求成千上万个方向中的每一个都同时向上弯曲。 ↩

  3. Robbins, H. and Monro, S. A Stochastic Approximation Method. Annals of Mathematical Statistics 22(3), pp. 400–407 (1951). 这篇论文确立了这样一个事实:只要步长以正确方式缩小,一个带噪的梯度估计就足够。 ↩

  4. Polyak, B. T. Some methods of speeding up the convergence of iteration methods. USSR Computational Mathematics and Mathematical Physics 4(5), pp. 1–17 (1964). 重球法,也就是上面的 momentum 更新,比 backpropagation 进入这个领域早了二十二年。 ↩

本章视频

用通俗语言讲解本章概念的短视频。

查看全部视频

新文章直达你的收件箱

AI 新闻、指南和产品更新——有值得你花时间阅读的内容时,我们会发一封简短邮件。

更喜欢用消息接收?同样的内容,也在这里:WhatsApp 社群 (在新标签页打开)Telegram 频道 (在新标签页打开)

作者

David Vicente Campos

NeuraLIA Labs 创始人、MyRealFood 联合创始人

我是莱昂大学毕业的计算机工程师。我共同创立了 MyRealFood,并在那里作为 CTO 打造了一款数百万人用来吃得更健康的应用;我还创立了 NeuraLIA Labs,在这里我打造 AI 产品。我在本站写下一路走来所必须理解的内容,就像我希望当初有人向我讲解的那样。

了解作者更多信息

由 NeuraLIA Labs 发布。

课程目录

Abstract desk scene with blank papers, geometric forms, and a glowing network suggesting AI-assisted mathematics.
openai10 分钟阅读

OpenAI 的数学声明暴露了 AI 的验证瓶颈

有关 OpenAI 模型解决重大数学问题的报道指向一个更深层的问题:生成证明越来越便宜,而验证、归属和发表仍以人类速度推进。

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev10 分钟阅读

Jev AI 模型为决策而生,而非写作

TypeSafe AI 的 Jev 正受到关注,因为它把软件智能视为一个概率问题:选择正确分支,附上置信度,并避免在代码只需要决策时还花钱让 LLM 写文本。

让你的 AI 今天就开始工作

所有模型和工具都在一处。免费开始,无需信用卡。