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)$ 想象成「雾里的山坡」,你看不见全貌但能感知脚底坡度。每一步:
- 计算 $\nabla L$ = 坡度传感器 = 「最陡上坡方向」
- 沿 $-\nabla L$ 走一小步,步长 $\eta$
- 重复直到谷底
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.py30 行核心,值得逐行读完
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 对不上」而非「公式写错」。