all_lessons/数学的逻辑/25第 26 课 / 共 44 课

第八部分 · 学习就是优化

往哪儿下坡:多元微积分与梯度

"学习"被我们逼成了一句话:调参数,把损失降到最低。可损失不是一条曲线,而是一片几百万维的地形。站在地形上的某一点,往哪个方向迈才下坡最快?回答这个问题的,是一支箭——梯度。

线性回顾
上一课:第七部分(表示与几何)一路把数据放进了空间——样本是向量,相似是内积,主结构是特征向量与 PCA。最后在高维几何里,我们看清了一件事:与其逐点死磕,不如承认"学习"其实只有一件事可做。
留下的问题:那件事,就是把损失降到最低。机器不会"顿悟",它只会一点点改参数,让损失一次比一次小——这就是优化。可问题来了:参数有成千上万个,损失是这成千上万个参数的一个函数,站在当前这组参数上,该往哪个方向、改多少,损失才掉得最快
本课新增:把第 7 课的导数从"一个变量"推广到"很多变量"——偏导数量"只动一个参数时损失的变化率",把所有偏导排成一支向量,就是梯度 ∇L。它有一个惊人的性质:梯度指向损失上升最快的方向,于是 −∇L 就是下坡最快的方向。这是整个第八部分的指南针。
本课路线
(1) 一元导数会了,可损失有很多个变量;(2) 偏导数 ∂L/∂w——把别的变量按住不动,只对一个求导;(3) 把偏导排成向量 = 梯度 ∇L;(4) 关键命题:梯度指向最陡上升、−∇L 指向最陡下降;(5) 方向导数——任意方向上的坡度;(6) 雅可比与海森,一句话各就各位;(7) 为什么这把"几百万维的导数"还能算得动。

一元导数会了,可损失有很多个变量

第 7 课的导数解决的是"一个输入、一个输出":给 f(x)f′(x) 告诉你 x 动一点点、f 跟着动多少。可机器学习里的损失不长这样。哪怕是最简单的直线拟合 y ≈ w·x + b,损失 L 也同时依赖两个旋钮:斜率 w 和截距 b。一个真正的神经网络,旋钮(参数)动辄上百万个。损失是它们共同的函数:

L = L(w₁, w₂, …, w_d)
读作"损失 L 是 d 个参数 w₁ 到 w_d 的函数"。把这 d 个参数看成空间里的一个点,L 就是这个点的"海拔"——参数空间里的一片地形。

训练的目标,就是在这片地形上找一个尽量低的山谷。难点在于:地形是几百万维的,你看不见全貌,只能站在当前这一点,靠脚底的坡度判断"往哪走会更低"。一元的 f′(x) 不够用了——它只懂"左右",而这里有几百万个方向。

撞墙
多变量的损失 L(w₁,…,w_d),站在当前这组参数上,哪个方向下坡最快、坡度多大?一元导数只能描述一根轴上的变化,面对几百万个方向同时变动,它哑火了。

偏导数:按住别人,只问一个

突破口其实很朴素:一次只动一个变量,其余全部按住不动。这样多变量函数就临时退化成一元函数,第 7 课的导数立刻能用。对 w₁ 这样做,得到的就是 Lw₁偏导数

∂L/∂w₁ = lim(h→0) ( L(w₁+h, w₂, …) − L(w₁, w₂, …) ) / h
读作"L 对 w₁ 的偏导"。那个圆圆的 读"偏"(partial),提醒你:只有 w₁ 在动,其它参数全当常数。式子和第 7 课的导数定义一模一样——只是把"其余变量"冻住了。

它的含义干脆利落:只把第 1 个旋钮拧动一点点,损失变化的速率。如果 ∂L/∂w₁ > 0,增大 w₁ 会让损失上升(所以该减小它);如果 ∂L/∂w₁ < 0,增大 w₁ 反而让损失下降。每个参数都能这样问一次,得到 ∂L/∂w₁, ∂L/∂w₂, …, ∂L/∂w_d 一共 d 个数。

举个能心算的例子:L(w, b) = w² + 3b²。按住 b、只对 w 求导,得 ∂L/∂w = 2w(3b² 是常数,导数为 0);按住 w、只对 b 求导,得 ∂L/∂b = 6b。在点 (w, b) = (1, 1) 处,两个偏导分别是 2 和 6——沿 b 方向比沿 w 方向陡三倍。

梯度:把所有偏导排成一支箭

偏导分头告诉了我们每根轴上的坡度。把它们排成一个向量,就得到这一点的梯度,记作 ∇L(那个倒三角 读"nabla"或"梯度算子"):

∇L = ( ∂L/∂w₁, ∂L/∂w₂, …, ∂L/∂w_d )
读作"L 的梯度,是由 L 对各个参数的偏导排成的向量"。它和参数 w 住在同一个空间——参数有几维,梯度就有几维。

回到上面的例子,L(w,b)=w²+3b²(1,1) 处的梯度是 ∇L = (2, 6)。它不只是两个数堆在一起,而是一支有方向的箭:它指向哪儿,长度多少,都有确切的几何意义——这正是下一节的主角。

核心命题:梯度指向最陡的上坡

这是整个第八部分最重要的一句话,请记牢:

梯度的方向定理
在任意一点,梯度 ∇L 指向函数上升最快的方向,它的长度 |∇L| 就是那个最陡方向上的坡度(每走单位距离,海拔升多少)。因此,−∇L(梯度的反方向)指向下降最快的方向——这正是我们要找的"最快下坡"。

为什么是它?这里给一个不用公式也能信的直觉。想象你站在地形上,环视四周所有方向,问每个方向"沿你走,海拔升多快"。可以证明:任意方向的坡度,等于梯度与那个方向的夹角的余弦关系——朝向越接近梯度,升得越快;正好顺着梯度,升得最快;正好逆着梯度(−∇L),降得最快;而和梯度垂直的方向,海拔一时不变(这正是等高线的方向)。所以梯度就像地形在每一点贴出的一块路牌:"最陡的上坡在这边,长度就是陡度。"

对训练来说,这块路牌价值千金。我们要的是下坡,那就逆着路牌走——朝 −∇L 迈一步,损失下降得最快。下一课(25)解决"梯度怎么算",再下一课(26)解决"逆着它怎么走、走多大步"。本课先把这支箭的几何看透。下面的小实验把它画在等高线上:拖动地形上的点,看梯度箭头永远垂直于等高线、指向上坡,并实时读出两个偏导和梯度长度。

等高线上的梯度箭头:永远垂直等高线、指向最陡上坡
地形是 f(x, y) = x²/4 + y²(一个被压扁的碗,沿 y 方向更陡)。拖动黑点(或用滑块)移动它,黄色箭头是该点的梯度 ∇f(指向上坡),虚线箭头是 −∇f(下坡最快、训练要走的方向)。注意箭头始终垂直于脚下的等高线,且在更陡的 y 方向更长。
∂f/∂x = x/2
1.20
∂f/∂y = 2y
1.80
梯度长度 |∇f|
2.16

方向导数:任意方向上的坡度

偏导只问"沿坐标轴"的坡度,可路牌告诉我们:每个方向都有自己的坡度。沿任意单位方向 u(读作"u,一个长度为 1 的方向向量")的坡度,叫方向导数,它有一个漂亮到不可思议的公式:

沿 u 的坡度 = ∇L · u
读作"梯度与方向 u 的点积"(第 20 课的内积)。它就是把"任意方向的坡度"全部交给了梯度——你只要算一次梯度,任何方向的坡度都是一次点积。

由内积的几何(第 20 课):点积在 u∇L 同向时最大、反向时最小。这就证明了上一节的命题——梯度方向上升最快,−∇L 方向下降最快,垂直方向(点积为 0)海拔不变。环环相扣:表示部分搭好的内积,在这里直接兑现成了"哪个方向最陡"。

雅可比与海森:各就各位

梯度是"标量函数(一个损失)对很多输入"的一阶导数。再往上还有两层,名字唬人,本质都是导数的推广,这里各安一句话,等到要用时(25 课、27 课)自然会回来:

记住一条阶梯就够了:偏导 → 梯度(一阶,方向)→ 海森(二阶,弯曲)。眼下我们只需要一阶的那支箭。

几百万维的导数,凭什么还算得动

到这里你可能心里一紧:神经网络几百万个参数,难道要对每个参数都跑一遍"加 h、算损失、相减、除以 h"?那样光算一次梯度就要把整个网络正向跑几百万遍,根本训练不动。这正是梯度留下的新危机:定义会写,但照定义一个一个数值地算,慢到不可接受。

撞墙
梯度 ∇L = (∂L/∂w₁, …, ∂L/∂w_d) 有 d 个分量。若每个分量都靠"扰动一下、看损失变化"去逼近,代价是 d 次完整前向计算——百万级参数下完全不可行。我们需要一种一次遍历就把全部偏导都算出来的办法。

好消息是,这件事有救,而且救法优雅得惊人:它把第 7 课早就埋下的链式法则,顺着网络的"计算图"反着跑一遍,一次就拿到所有参数的偏导。这个算法,正是深度学习之所以能训练的引擎——下一课见。

常见误解

一句话带走
把第 7 课的导数推广到很多变量:按住其余、只对一个求导得偏导,把所有偏导排成向量就是梯度 ∇L。梯度指向损失上升最快的方向,−∇L 就是下坡最快的方向——它是训练这台学习机器手里唯一、也是足够的指南针。
下一步
指南针有了,可还有一道工程难关:几百万个偏导,怎么在一次计算里全部算出来?逐个数值逼近会慢到训练不动。答案是把第 7 课的链式法则沿着网络的计算图反向跑一遍——前向算值、反向把局部导数沿边相乘,一趟拿齐所有梯度。这正是深度学习能被训练的根本原因。→ 第 26 课《反向传播:神经网络如何算梯度》。