专栏 AI 学习笔记 子专栏 AI 学习笔记 31 篇

Day 03|微积分与梯度:偏导、链式法则与梯度下降(AI 学习笔记 · 基础筑基周 · 第 3 篇)

神经网络训练的「灵魂算法」就是梯度下降——它把「让模型变好」这件事转成「在损失函数的山坡上往最陡的方向往下走」。本节把这条链路拆成三个零件:偏导(瞬时斜率)、链式法则(复合函数的求导骨架)、梯度下降(实际更新规则)。


1. 导数与偏导:从一元到多元

1.1 单变量导数

f'(x) = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h}

几何含义:$f$ 在 $x$ 处的瞬时变化率 / 切线斜率。

1.2 偏导:多元函数的「单维切片」

对 $f(x_1, x_2, \ldots, x_n)$,偏导 $\partial f / \partial x_i$ 表示「按住其它变量,只看 $f$ 沿 $x_i$ 方向的瞬时变化率」。

\frac{\partial f}{\partial x_i}(x_1, \ldots, x_n) = \lim_{h \to 0} \frac{f(x_1, \ldots, x_i + h, \ldots, x_n) - f(x_1, \ldots, x_n)}{h}

例:房价 $f(\text{面积}, \text{楼层}) = 2 \cdot \text{面积} + 5 \cdot \text{楼层} + 100$,则 $\partial f / \partial \text{面积} = 2$ (每多 1 平米 + 2 万),$\partial f / \partial \text{楼层} = 5$ (每高一层 + 5 万)。

1.3 梯度:全部偏导打包

\nabla f = \begin{bmatrix} \partial f/\partial x_1 \\ \partial f/\partial x_2 \\ \vdots \\ \partial f/\partial x_n \end{bmatrix}

梯度是一个向量,指向「$f$ 上升最快的方向」;$-\nabla f$ 就是「下降最快的方向」。这是 Day 6 线性回归、Day 15 反向传播、Day 19 Transformer 训练共用的核心对象。

1.4 Jacobian 矩阵:向量到向量

若 $\mathbf{f}: \mathbb{R}^n \to \mathbb{R}^m$,则 Jacobian $J \in \mathbb{R}^{m \times n}$:

J_{ij} = \frac{\partial f_i}{\partial x_j}

神经网络中每一层都有一个 Jacobian,反向传播本质就是「从后往前把 Jacobian 链式乘起来」。


2. 链式法则:反向传播的数学骨架

2.1 一维链式法则

y = f(u), \quad u = g(x) \;\Rightarrow\; \frac{dy}{dx} = \frac{dy}{du} \cdot \frac{du}{dx}

2.2 多变量链式法则

z = f(x, y), \quad x = g(t), \quad y = h(t)
\frac{dz}{dt} = \frac{\partial f}{\partial x} \cdot \frac{dx}{dt} + \frac{\partial f}{\partial y} \cdot \frac{dy}{dt}

这是「全导数」公式:沿每条依赖路径把局部导数乘起来再求和。

2.3 向量版:BP 的真正形态

设神经网络第 $\ell$ 层 $\mathbf{h}^{(\ell)} = \sigma(W^{(\ell)} \mathbf{h}^{(\ell-1)} + b^{(\ell)})$,损失 $L = \ell(\hat{\mathbf{y}}, \mathbf{y})$。反向传播的关键递推:

\frac{\partial L}{\partial \mathbf{h}^{(\ell-1)}} = \left(\frac{\partial \mathbf{h}^{(\ell)}}{\partial \mathbf{h}^{(\ell-1)}}\right)^T \frac{\partial L}{\partial \mathbf{h}^{(\ell)}}

每层只算「自己的局部 Jacobian × 上一层传下来的梯度」,把整条链上的导数变成 O(网络深度) 的递推——而不是 O(深度²) 的暴力计算。这是 PyTorch 能秒级训练百万参数网络的根本原因。

2.4 计算图视角

flowchart LR
    X[x] --> M1[× w1]
    M1 --> A1[+ b1]
    A1 --> S1[σ]
    S1 --> M2[× w2]
    M2 --> A2[+ b2]
    A2 --> L[Loss]
    L -.->|∂L/∂A2| A2
    A2 -.->|∂L/∂M2| M2
    M2 -.->|∂L/∂S1| S1
    S1 -.->|∂L/∂A1| A1
    A1 -.->|∂L/∂M1| M1
    M1 -.->|∂L/∂x| X

前向:从 $x$ 算出 $L$;反向:从 $L$ 沿图把梯度「乘」回每个中间节点,得到每个参数的梯度。


3. 梯度下降:最陡方向下山

3.1 算法

\theta \leftarrow \theta - \eta \cdot \nabla_\theta L(\theta)
  • $\theta$:模型参数(向量)
  • $\nabla_\theta L$:损失对参数的梯度
  • $\eta$:学习率,控制步长

3.2 直觉

把损失函数 $L(\theta)$ 想象成「雾里的山坡」,你看不见全貌但能感知脚底坡度。每一步:

  1. 计算 $\nabla L$ = 坡度传感器 = 「最陡上坡方向」
  2. 沿 $-\nabla L$ 走一小步,步长 $\eta$
  3. 重复直到谷底

3.3 三种粒度

变体 每步用多少样本 优点 缺点
Batch GD 全部 $N$ 梯度准确,收敛稳定 每步慢,内存大
SGD 1 个 快,能跳出局部极小 噪声大,震荡
Mini-batch GD 32~512 个 工程首选,GPU 友好 需要调 batch size

事实标准:深度学习几乎都用 mini-batch,batch size = 32 是入门默认,大模型训练常用 1024~8192。

3.4 学习率 $\eta$ 的影响

$\eta$ 现象
太小 龟速前进,甚至卡在平台
太大 在谷底来回震荡
太大太多 越过山谷,Loss 爆炸发散
调度(先大后小) 前期快速下降,后期精细收敛

常见调度:step decay(每隔 N epoch 乘 0.1)、cosine annealing(余弦曲线衰减)、warmup(前 N 步线性升)。

3.5 三种粒度的实际代价

变体 单步时间 收敛步数 总时间 适用
Batch GD O(N) 少 高 凸 / 小数据
SGD O(1) 多 中 在线学习
Mini-batch O(B) 中 低 深度学习默认

实践中 batch size 32~512 是「GPU 利用率」与「梯度噪声」的最优平衡点。

3.6 收敛判断

  • Loss 平稳下降:还有改进空间
  • Loss 不再下降:可能已收敛 / 学习率太小 / 陷入鞍点
  • Loss 上升:学习率过大 / 数据泄漏 / 模型结构 bug
  • Loss 震荡:batch 太小 / 数据未 shuffle

4. 优化器家族

4.1 SGD with Momentum

v \leftarrow \mu v + \nabla L(\theta) \\
\theta \leftarrow \theta - \eta v

物理类比:小球下山,$\mu$ 是「惯性系数」,沿一致方向累积动量冲过小坑。$\mu$ 通常取 0.9。

4.2 AdaGrad

每个参数自适应学习率:经常更新的参数学习率小,稀疏更新的大学习率。

\theta \leftarrow \theta - \frac{\eta}{\sqrt{G + \epsilon}} \cdot \nabla L(\theta)

$G$ 是梯度平方和。坑:学习率单调递减,后期可能太小,训练停滞。

4.3 RMSProp

修正 AdaGrad:用指数移动平均代替梯度平方和。

v \leftarrow \beta v + (1-\beta)(\nabla L)^2 \\
\theta \leftarrow \theta - \frac{\eta}{\sqrt{v + \epsilon}} \cdot \nabla L(\theta)

4.4 Adam:深度学习事实标准

结合 Momentum(一阶矩) + RMSProp(二阶矩),并做偏差修正:

m \leftarrow \beta_1 m + (1-\beta_1)\nabla L \\
v \leftarrow \beta_2 v + (1-\beta_2)(\nabla L)^2 \\
\hat{m} = m / (1-\beta_1^t), \quad \hat{v} = v / (1-\beta_2^t) \\
\theta \leftarrow \theta - \eta \cdot \hat{m} / (\sqrt{\hat{v}} + \epsilon)

默认 $\beta_1 = 0.9, \beta_2 = 0.999, \epsilon = 10^{-8}$。

4.5 AdamW:更稳的 Adam

把 weight decay 与梯度更新解耦:

\theta \leftarrow \theta - \eta(\hat{m} / (\sqrt{\hat{v}} + \epsilon) + \lambda \theta)

现代 LLM 训练几乎都用 AdamW(PyTorch torch.optim.AdamW),Day 20 会展开。

4.6 优化器对比表

优化器 每步内存 适合场景 调参难度
SGD 1× 凸问题、CV 微调 易
SGD + Momentum 2× CV baseline 易
AdaGrad 2× 稀疏特征(NLP) 中
RMSProp 2× RNN 中
Adam 3× 通用 DL 低(默认就好)
AdamW 3× Transformer / LLM 低

5. 局部极小与鞍点

5.1 凸 vs 非凸

  • 凸函数:梯度下降必收敛到全局最优(线性回归、逻辑回归在 MSE / Cross-Entropy 下是凸)
  • 非凸函数:神经网络损失面,梯度下降可能陷入局部极小或鞍点

5.2 鞍点:比局部极小更常见

高维空间中,真正陷入「局部极小」(所有方向都上坡)的概率几乎为 0;更常见的是鞍点(某些方向上坡,另一些下坡)。SGD 的噪声正好帮模型「跳出」鞍点,这也是小 batch 反而泛化好的原因之一。

5.3 为什么深度学习「能训」

尽管损失面非凸,实践中梯度下降几乎总能找到「足够好」的解。直觉:大模型参数空间维度极高,真正「完全卡死」的解极少;SGD 噪声 + 适当学习率让模型在低损失盆地间穿梭。


6. 数值梯度与自动求导

6.1 数值梯度

def numerical_grad(f, x, eps=1e-5):
    grad = np.zeros_like(x)
    for i in range(len(x)):
        x_plus = x.copy(); x_plus[i] += eps
        x_minus = x.copy(); x_minus[i] -= eps
        grad[i] = (f(x_plus) - f(x_minus)) / (2 * eps)
    return grad

慢(O(n) 次函数调用),有截断误差,但能验证解析梯度是否写对——写完 BP 后用数值梯度做 gradient check 是标准动作。

6.2 符号微分 vs 自动求导

方法 代表 优劣
数值微分 有限差分 实现简单、慢、有误差
符号微分 Mathematica 精确、但表达式爆炸
自动求导(前向) JAX 适合「输入少、输出多」
自动求导(反向) PyTorch / TF 深度学习事实标准

PyTorch 的 loss.backward() 就是反向模式 AD:从 loss 出发沿计算图反向传播,O(网络深度) 完成所有参数梯度计算。

6.3 PyTorch 梯度示例

import torch

x = torch.tensor(2.0, requires_grad=True)
y = x ** 2 + 3 * x + 2
y.backward()           # 反向传播
print(x.grad)          # tensor(7.) = 2x+3 | x=2 = 7

6.4 torch.no_grad() 上下文

model.eval()
with torch.no_grad():           # 关闭梯度计算,节省显存 + 加速
    pred = model(x_test)
    loss = loss_fn(pred, y_test)

推理时必须包 no_grad,否则 PyTorch 会把所有中间激活都存下来等反向,显存爆炸。

6.5 梯度累积:小显存模拟大 batch

optimizer.zero_grad()
for i, batch in enumerate(dataloader):
    loss = model(batch) / K    # 1/K 缩放
    loss.backward()            # 累加梯度,不更新参数
    if (i + 1) % K == 0:
        optimizer.step()       # K 次累积后更新一次
        optimizer.zero_grad()

K 次小 batch 累加 → 等效大 batch,显存只需装一份。这是 LLaMA 类大模型预训练常用技巧。

6.6 计算图模式:Dynamic vs Static

模式 代表 灵活性 性能
动态图 PyTorch 控制流随心写 略慢
静态图 TensorFlow 1.x / TorchScript 需先 trace 部署友好

PyTorch 默认动态图,调试方便;部署时可用 torch.jit.script / torch.compile 静态化拿速度。


7. 常见坑(10 条)

7.1 学习率过大导致 NaN

症状:Loss 突然跳到 nan 原因:更新步长跨过极小值后,损失爆炸式增长,梯度也跟着爆炸 修法:梯度裁剪 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0);学习率 warmup;Adam 而非 SGD

7.2 数值梯度 vs 解析梯度对不上

症状:写完 BP 跑 gradient check,数值梯度和解析梯度差超过 $10^{-5}$ 原因:BP 中矩阵 / 张量转置搞反,或广播漏掉 修法:用 torch.autograd.gradcheck 自动检测;手写时逐元素验证

7.3 局部极小误判为收敛

症状:Loss 不再下降,但验证集表现仍差 原因:陷入鞍点或局部极小,真正的全局极小还在别处 修法:增大学习率让模型跳出来;SGD 替换 Adam 引入噪声;数据 shuffle

7.4 weight decay 用错地方

症状:训练 loss 高、val loss 同步高,模型欠拟合 原因:weight decay 系数过大,把权重压到接近 0 修法:先关掉 weight decay 调 baseline,再小步加入;典型起点 $10^{-4}$

7.5 batch size 与学习率不同步

症状:batch size 翻倍后 loss 不降或发散 原因:batch size 增大 → 梯度方差减小 → 等效学习率偏大 修法:batch size 翻倍时学习率也近似翻倍(linear scaling rule);或用更稳的 Adam

7.6 不 detach 计算图

症状:多次 loss.backward() 后 RuntimeError: Trying to backward through the graph a second time 原因:默认情况下 PyTorch 会保留计算图,二次反向需要重新 forward 修法:loss.backward(retain_graph=False)(默认);累积梯度时用 loss.backward(retain_graph=True)

7.7 梯度消失 / 爆炸

症状:深层网络浅层梯度接近 0(消失)或极大(爆炸) 原因:链式乘法,Jacobian 范数 < 1 反复乘 → 消失;> 1 反复乘 → 爆炸 修法:BatchNorm / LayerNorm、ReLU 替代 Sigmoid、残差连接、合理的初始化(Xavier / He)

7.8 requires_grad=True 的叶子变量被原地改

症状:RuntimeError: leaf variable has been moved into the graph interior 原因:PyTorch 不允许对叶子 requires_grad 变量做 in-place 操作 修法:用 .data 或重新赋值一个新 tensor

7.9 Adam 学习率调度器错配

症状:用 Adam + StepLR,Loss 收敛不理想 原因:Adam 自适应学习率,叠加外部 step decay 容易破坏二阶矩统计 修法:Adam + CosineAnnealingLR 是更稳的组合;若一定要 step,先小步实验

7.10 训练 / 推理模式不切换

症状:val loss 抖动剧烈、Dropout 行为不一致 原因:PyTorch 默认 model.train() 模式启用 Dropout / BN 更新;验证时应 model.eval() 修法:验证循环开头 model.eval(),验证完 model.train()


8. 自检三问

A. 偏导和(单变量)导数有什么区别?为什么在机器学习里几乎都讲「偏导」而不是普通「导数」?

要点:单变量导数是「函数只有一个变量时的瞬时变化率」;偏导是「函数有多个变量时,按住其它变量只让一个变」。机器学习模型 $f(\mathbf{x}; \theta)$ 几乎总是对多个参数(权重 $\mathbf{w}$、偏置 $\mathbf{b}$)求导,所以偏导是主体;完整对象是梯度 $\nabla f = (\partial f / \partial w_1, \ldots, \partial f / \partial w_n)$。单变量导数只在「$f$ 只有一个未知参数」时才够用,这是 Day 6 简单 toy 例子的情形。

B. 链式法则为什么是反向传播(BP)的核心?如果不靠链式法则对每一层复用,深度网络训练的计算复杂度会膨胀到多少?

要点:深度网络是「上百层函数的复合」,BP 用链式法则把复合导数分解为「局部 Jacobian × 上游梯度」的逐层递推,每层只算自己的一次局部导数,总复杂度 O(网络深度)。如果不复用,每算一个参数梯度都要从 loss 重新正向微分一遍,所有 $N$ 个参数 → O(深度 × 参数量) ≈ O(深度²)。这就是 1986 年 Rumelhart 等人提出 BP 后深度学习才「能训」的数学原因——把「指数爆炸」压成「线性叠加」。

C. 梯度下降里「学习率」和「陷入局部极小」分别对应什么直观场景?为什么 Batch / Mini-batch / SGD 本质上都是梯度下降的变体?

要点:学习率 $\eta$ 类比「步子大小」——太大跨过谷底来回震荡,太小龟速前进;陷入局部极小类比「雾里走进一个洼地,周围都上坡但其实附近还有更深的山谷」。Batch / Mini-batch / SGD 本质都是 $\theta \leftarrow \theta - \eta \cdot \widehat{\nabla L}$,区别只在 $\widehat{\nabla L}$ 是「全样本均值 / 小批均值 / 单样本」——后两者是前者的无偏估计但方差大。三者都是「沿估计的最陡下坡方向走一步」的统一框架,只是「方向」估计的精度不同。


9. 推荐资源

视频

  • 3Blue1Brown《微积分的本质》第 3-5 集 —— 链式法则与导数的几何直觉
  • 李宏毅《机器学习》Gradient Descent 章节 —— 配合台大课件看最快入门
  • Andrej Karpathy「Let’s build a neural network」 —— 30 分钟从零手写 BP,看完彻底理解链式法则

教科书

  • 《深度学习》(Goodfellow) —— 第 6 章「前馈网络」、第 8 章「深度模型的优化」是 Day 3 的标准教材
  • 《动手学深度学习》(d2l.ai) —— 第 7 章「优化算法」直接对应本节
  • 《Numerical Optimization》(Nocedal & Wright) —— 进阶时翻,讲凸优化的圣经

论文

  • Ruder 2016《An overview of gradient descent optimization algorithms》 —— SGD/Momentum/AdaGrad/RMSProp/Adam 全景
  • Kingma & Ba 2015《Adam: A Method for Stochastic Optimization》 —— Adam 原文
  • Loshchilov & Hutter 2019《Decoupled Weight Decay Regularization》 —— AdamW 原文

博客 / 课程

  • Lilian Weng《Optimization for Deep Learning》 —— 综述所有主流优化器
  • Distill.pub《How Momentum Tricks Work》 —— Momentum 的物理直觉
  • PyTorch 官方 Tutorial「Autograd」 —— pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html

代码

  • Andrej Karpathy/char-rnn —— 200 行手写 RNN + BP,最干净的链式法则实现
  • d2l-ai/d2l-zh —— 第 7 章配套 Jupyter Notebook
  • PyTorch optim 源码 —— torch/optim/adam.py 30 行核心,值得逐行读完

10. 本节要点

  • 偏导$\partial f/\partial x_i$是「按住其它变量,只看 $x_i$ 变化」的瞬时斜率;梯度 $\nabla f$ 把所有偏导打包成向量,指向「最陡上升方向」。
  • 链式法则是 BP 的骨架:$\frac{dy}{dx} = \frac{dy}{du} \cdot \frac{du}{dx}$,多层复合时变成逐层 Jacobian 乘积,把深度网络训练复杂度压到 O(深度)。
  • 梯度下降$\theta \leftarrow \theta - \eta \nabla L$是「沿最陡下坡走一步」的迭代;Batch / Mini-batch / SGD 三个变体共用同一公式,只是梯度估计的样本数不同。
  • 学习率 $\eta$ 是调参第一参数:太小龟速、太大震荡/发散;常用 warmup + cosine / step decay 调度。
  • 优化器:SGD + Momentum 给球加惯性,AdaGrad / RMSProp 自适应每个参数的学习率,Adam 兼采两者并加偏差修正——是深度学习事实标准。
  • 非凸性:深度学习损失面非凸,但实践中 SGD + 噪声 + 适当学习率几乎总能找到足够好的解,不必过度担心局部极小。
  • PyTorch autograd:loss.backward() 自动反向传播,requires_grad=True 标记叶子变量,torch.no_grad() 上下文关闭梯度计算(用于 inference)。
  • 常见坑:学习率过大 → NaN(梯度裁剪 + warmup 救场);梯度消失/爆炸 → BatchNorm + 残差连接 + ReLU;weight decay 系数过大 → 欠拟合。

11. 下一节:Day 04 · 概率统计基础

主题:概率分布、贝叶斯、期望方差。覆盖:

  • 概率分布:PMF / PDF、常见离散分布(伯努利、二项、泊松)、连续分布(高斯、均匀)
  • 贝叶斯定理 $P(A B) = P(B A) \cdot P(A) / P(B)$,先验 × 似然 ÷ 证据 = 后验;医学检测的基率谬误
  • 期望 $E[X]$ 与方差 $\text{Var}(X)$,作为「分布的中心 + 离散度」
  • 协方差 / 相关系数 $\rho = \text{Cov}(X,Y) / \sigma_X \sigma_Y \in [-1, 1]$
  • 中心极限定理:独立随机变量之和趋近高斯,这是 ML 默认假设高斯噪声的根据

产出物:scipy.stats 抽样验证每种分布形态 + Python 验证贝叶斯定理(医学检测场景)+ 真实数据上算相关系数矩阵。


12. 附录:本节数学速查

12.1 常用求导公式

函数 $f(x)$ 导数 $f’(x)$
$x^n$ $n x^{n-1}$
$e^x$ $e^x$
$\ln x$ $1/x$
$\sin x$ $\cos x$
$\cos x$ $-\sin x$
$\sigma(x) = 1/(1+e^{-x})$ $\sigma(x)(1-\sigma(x))$
$\text{softmax}(z_i)$ 见 §2 Day 8
$\text{ReLU}(x) = \max(0,x)$ $\mathbb{1}[x>0]$
$\tanh(x)$ $1 - \tanh^2(x)$
$\sqrt{x}$ $1/(2\sqrt{x})$
$\log \sigma(x)$ $1 - \sigma(x)$

12.2 常用向量求导

表达式 梯度 $\nabla$
$\mathbf{a}^T \mathbf{x}$ $\mathbf{a}$
$\mathbf{x}^T A \mathbf{x}$ $(A + A^T)\mathbf{x}$
$|\mathbf{x}|_2^2$ $2\mathbf{x}$
$\text{tr}(A\mathbf{x}\mathbf{x}^T)$ $(A + A^T)\mathbf{x}$

12.3 矩阵求导布局

  • 分子布局:导数形状与分子相同
  • 分母布局:导数形状与分母相同

ML / DL 圈默认采用「分子布局」,写 BP 公式时务必把每项的 shape 标清楚——很多 bug 是「shape 对不上」而非「公式写错」。

说明 · 本站内容均为学习笔记与经验总结,所有菜谱与技法请结合实际食材、季节与个人口味灵活调整。涉及生食、营养与健康的内容仅供参考,特殊体质或疾病请咨询专业营养师/医生。