基础 · 02

微积分的几何直觉

导数是切线的斜率,梯度是「上山最陡」的方向。一篇文章把梯度下降——所有神经网络训练的引擎——画给你看。

12 min read

为什么 ML 离不开它

机器学习的训练过程,从最朴素的线性回归到 GPT,本质上都是同一件事:

定义一个表示「模型预测有多错」的数 LL,然后调整参数让它变小。

「调整参数让它变小」听起来抽象——但只要你能算出 LL 对每个参数的导数,你就知道每个参数往哪个方向动会让 LL 变小。整个深度学习的训练循环就两步:

  1. 算损失 LL 对所有参数的梯度 L\nabla L
  2. 把参数往 L-\nabla L 方向挪一小步。

这就是梯度下降。理解它需要的微积分,只是导数和链式法则两件事的几何化。这篇我们就把它们画出来。

导数:一点的斜率

导数 f(x0)f'(x_0) 就是函数 ffx0x_0 这个点的切线的斜率。

定义式:

f(x0)=limh0f(x0+h)f(x0)hf'(x_0) = \lim_{h \to 0} \frac{f(x_0 + h) - f(x_0)}{h}

公式里的「极限」「无穷小」让人头大,但几何上就是一句话:把切线斜率代数化

拖动下面的圆点到曲线的不同位置——观察切线(虚线)的倾斜方向如何变化,以及右侧显示的导数值如何从正变负:

-2-112drag the dot ↔

函数

f(x) = 0.3·(x³ − 2x)

位置

x = -1.30
f(x) = 0.12

导数(斜率)

f′(x) = 0.92

正 → 这里在上升

拖动小圆点。彩色那条线就是该点的切线——它的斜率就是导数。

几个值得留意的事实:

  • 导数 大于 0 → 函数在这里上升(往右走 ff 增加)。
  • 导数 小于 0 → 函数在这里下降
  • 导数 等于 0 → 切线水平 → 极大值、极小值或者鞍点。

最后一条是优化的起点:光滑函数的最小值必然满足 f(x)=0f'(x) = 0。但反过来不成立——f(x)=0f'(x) = 0 的地方也可能是极大值或鞍点。在深度学习里,高维 loss landscape 中 L=0\nabla L = 0 的地方绝大多数是鞍点(某些方向上升、某些方向下降),而不是真正的最小值。好消息是:实践中梯度下降几乎总能找到足够好的低点——虽然不一定是全局最小。

还有一个微妙之处:ML 里广泛使用的 ReLU 激活函数在 x=0x = 0不可导(有折角)。实际中我们用次梯度(取左右导数之间的任意值,通常取 0)来绕过这个问题——PyTorch 默认就是这么做的。

梯度:上山最陡的方向

ML 里的损失函数不是 1 维的,而是依赖几千万、几亿个参数。一个 nn 元函数 L(w1,w2,,wn)L(w_1, w_2, \ldots, w_n) 没有「斜率」这种单一的数——但它有 nn偏导数

Lwi\frac{\partial L}{\partial w_i}

几何意义:想象你站在一片高维山坡上。「假装其他参数全冻住,只动 wiw_i——等于用一把刀沿平行于 wiw_i 轴的方向把山坡切开,你看到的那条 2D 切面的斜率,就是偏导数 Lwi\frac{\partial L}{\partial w_i}

把所有方向的偏导数打包成一个向量,就是梯度:

L=(Lw1,  Lw2,  ,  Lwn)\nabla L = \left( \frac{\partial L}{\partial w_1},\; \frac{\partial L}{\partial w_2},\; \ldots,\; \frac{\partial L}{\partial w_n} \right)

这就是这篇最关键的一句话:

梯度 L\nabla L 指向 LL 在当前位置上升最快的方向,大小是上升的速率。

所以反过来,L-\nabla L 就是下降最快的方向——这就是「下山最陡的路」。

为什么是「最陡」?沿任意方向 u\mathbf{u}(单位向量)走一小步,LL 的变化率是 Lu\nabla L \cdot \mathbf{u}上一篇讲的点积)。点积在两个向量方向一致时最大,所以 u=L/L\mathbf{u} = \nabla L / \|\nabla L\| 时上升最快,u=L/L\mathbf{u} = -\nabla L / \|\nabla L\| 时下降最快。

还有一个免费的几何事实:梯度永远垂直于等高线。等高线上 LL 不变(ΔL=0\Delta L = 0),而 Lu=0\nabla L \cdot \mathbf{u} = 0 正好意味着 L\nabla L 和等高线的切方向垂直。这一点在下面的动图里直接能看到——注意轨迹的每一步都垂直于它穿过的等高线。

梯度下降:下山的算法

知道了「最陡的下山方向」,算法就只剩一行:

wwηL(w)w \leftarrow w - \eta \cdot \nabla L(w)

η\eta学习率(learning rate)——一步迈多大。

但「迈多大」是个真问题。试试下面的交互:点击画布换起点,拖动学习率滑块。特别试试这几个值:

  • η\eta 调到很小(~0.02):轨迹像蚂蚁爬,一万步可能还没到谷底。
  • η\eta 调到适中(~0.1):轨迹平滑收敛到中心。
  • η\eta 调大(>0.5):轨迹开始在山谷两侧来回弹射,甚至越冲越远——这就是训练中常见的发散(divergence)

损失函数

L(x, y) = 0.3·x² + 1.5·y²

椭圆形山谷,最小值在原点(紫色圆心)

当前轨迹

收敛到最小值

L(最终) = 0.000

✦ 点击画布换起点
✦ 拖动滑块改学习率
✦ 路径每次重新跑 30 步

紫色椭圆是等高线——同一圈上 L 相同。 负梯度永远垂直于等高线指向「内圈」——这就是下山的路。

还有一个现象比学习率更深刻——注意上面这个椭圆形山谷:轨迹经常是 Z 字形的

为什么?因为梯度永远垂直于等高线。当等高线是被拉长的椭圆时,它的法线(垂直方向)大部分时候并不指向椭圆的中心(极小值点),而是指向山谷的对岸。这就迫使梯度下降像一个醉汉一样,在两壁之间来回撞击(Z 字形),而不是沿着谷底直奔终点。

数学上,这对应损失函数的 Hessian 矩阵条件数很大(最大特征值 / 最小特征值 ≫ 1)——不同方向的曲率差异极大。这是普通梯度下降的根本病灶,也是后来 Momentum、Adam 等优化器要解决的核心问题——优化器篇会详细展开。

链式法则:把误差传回去

神经网络是函数的层层嵌套。一个三层网络的输出大概长这样:

y^=f3(f2(f1(x;w1);w2);w3)\hat{y} = f_3\big(f_2\big(f_1(x;\, w_1);\, w_2\big);\, w_3\big)

想算 LL 对最深一层参数 w1w_1 的偏导,得穿过 f2f_2f3f_3 才能到 w1w_1。链式法则就是这件事的数学:

Lw1=Lf3f3f2f2f1f1w1\frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial f_3} \cdot \frac{\partial f_3}{\partial f_2} \cdot \frac{\partial f_2}{\partial f_1} \cdot \frac{\partial f_1}{\partial w_1}

代数上是乘法链。几何上有一个更直观的读法:每个偏导数是那一层的局部拉伸率——如果 f1f_1w1w_1 方向的变化放大了 2 倍,f2f_2 又放大了 3 倍,那么端到端的放大倍数就是 2×3=62 \times 3 = 6。链式法则就是「逐层拉伸率的连乘」。

把它读作「敏感度的传递」也可以:w1w_1 抖一下 → f1f_1 抖一下 → f2f_2 跟着抖 → f3f_3 跟着抖 → LL 跟着抖。每一段的「抖动放大倍数」就是局部偏导,乘起来就是端到端的敏感度。

这就是**反向传播(backpropagation)**的全部秘密——从输出端往输入端,把局部导数一段一段乘回去。PyTorch、JAX 这些框架的核心就是:自动帮你把这条乘法链算出来。

一个实际后果:如果每层的局部导数都小于 1(比如 sigmoid 的最大导数是 0.25),连乘几十层之后梯度会指数衰减到接近零——深层参数几乎收不到学习信号。这叫梯度消失,是早期深度网络训不动的主要原因。ReLU(导数 = 0 或 1)和残差连接(让梯度有一条"直通车")就是为了解决这个问题。

这个想法在前沿里