主题
第 3 章 微积分与最优化
本章目标:用最少的数学,搞懂大模型训练背后的全部微积分原理——导数、梯度、链式法则、梯度下降与 Adam 优化器,并亲手用纯 NumPy 写出一个带反向传播的神经网络。
预计用时:1~2 周(每天 1~2 小时)
学完你将能够:
- 用"斜率"直觉解释导数、偏导数与梯度,并说出它们各自在训练中扮演的角色
- 在计算图上手工推导反向传播,理解 PyTorch autograd 在替你做什么
- 说清 BGD / SGD / mini-batch 的区别,以及学习率为什么"太大震荡、太小太慢"
- 解释 warmup + cosine decay 为什么是大模型训练的标准学习率策略
- 从公式到代码讲清 Adam 的四步更新,知道为什么 LLM 训练几乎都用 AdamW
- 用纯 NumPy 手写一个两层神经网络(含反向传播)并训练它拟合 sin 函数
3.1 为什么大模型工程师要学这一章
第 0 章里,模型是别人训练好的,你只管调用。但从现在开始,我们要钻进"训练"这台发动机内部。
训练一个大模型,本质上只做一件事:不断调整几千亿个参数,让模型的预测误差(损失)越来越小。这句话里藏着两个问题:
- 每个参数该往哪个方向调?调多少?——这是微积分回答的(梯度)。
- 知道方向后,具体怎么一步步走?——这是最优化回答的(梯度下降、Adam)。
好消息是:大模型用到的微积分非常少,核心就是"求导"和"链式法则"两件事,高中数学基础完全够用。本章不需要你做任何手工证明题,只需要建立直觉,并能看懂、能写出代码。
💡 经验法则:学本章时,每看到一个数学符号,都问自己一句"它在训练循环的哪一行代码里出现"。数学是手段,训练才是目的。
3.2 导数与偏导数:变化率的直觉
3.2.1 导数就是"斜率"
想象你在爬山,脚下山路的陡峭程度就是导数。更正式一点:
导数:函数 $f(x)$ 在点 $x$ 处的瞬时变化率,记作 $f'(x)$ 或 $\frac{df}{dx}$。
- $x$:自变量(你可以控制的旋钮,比如一个参数)
- $f(x)$:因变量(旋钮导致的后果,比如损失值)
- $f'(x)$:$x$ 微小变动一点点时,$f$ 会跟着变动多少
几何直觉:导数 = 曲线上该点切线的斜率。斜率为正,函数在上升;斜率为负,函数在下降;斜率为零,说明站在了山顶或谷底(或者一个平台上)。
如果我们不知道函数的求导公式,还可以用"差商"来近似——取一个极小的 $h$,算一算 $x$ 前后各挪 $h$ 时函数值变了多少:
python
"""示例:用数值方法直观感受导数"""
import numpy as np
def f(x):
"""示例函数:f(x) = x^2"""
return x ** 2
def numerical_derivative(func, x, h=1e-5):
"""用"差商"近似导数:[f(x+h) - f(x-h)] / (2h)
h 是一个很小的数,比如 0.00001
"""
return (func(x + h) - func(x - h)) / (2 * h)
for x in [0.0, 1.0, 2.0, 3.0]:
print(f"x = {x:.1f} 时,数值导数 ≈ {numerical_derivative(f, x):.4f},"
f"理论值 2x = {2 * x:.4f}")运行结果:
x = 0.0 时,数值导数 ≈ 0.0000,理论值 2x = 0.0000
x = 1.0 时,数值导数 ≈ 2.0000,理论值 2x = 2.0000
x = 2.0 时,数值导数 ≈ 4.0000,理论值 2x = 4.0000
x = 3.0 时,数值导数 ≈ 6.0000,理论值 2x = 6.0000$f(x) = x^2$ 的导数是 $f'(x) = 2x$,数值近似与理论值完全吻合。这个"数值微分"技巧后面还会用到——它是验证你手推梯度是否正确的金标准。
3.2.2 三条最常用的求导规则
神经网络里翻来覆去就这几个函数,记住三条规则就够:
| 函数类型 | 例子 | 导数 | 一句话记忆 |
|---|---|---|---|
| 幂函数 $x^n$ | $x^2$ | $n \cdot x^{n-1}$(如 $2x$) | 指数搬到前面,指数减一 |
| 指数函数 $e^x$ | $e^x$ | $e^x$(导数是它自己!) | 自然常数 $e$ 的"特权" |
| 对数函数 $\ln x$ | $\ln x$ | $\frac{1}{x}$ | 越大增长越慢 |
外加一条"复合规则"的特例——常数倍与加法:$(c \cdot f)' = c \cdot f'$,$(f + g)' = f' + g'$,即常数可以提出来,和的导数等于导数的和。
→ 这个在大模型里用在哪:$e^x$ 和 $\ln x$ 是 softmax 和交叉熵损失(第 4 章)的核心部件;幂函数求导则是 MSE 损失(平方误差)求梯度的基础,本章后面的所有代码都会用到 $(\cdot)^2$ 的导数 $2(\cdot)$。
3.2.3 偏导数:一次只动一个旋钮
单变量函数只有一个旋钮,而神经网络有几千亿个参数(旋钮)。怎么办?答案朴素得可爱:一次只动一个旋钮,把其他旋钮全部当成固定的常数,这样多变量问题就退化成了单变量求导。这样得到的导数叫偏导数。
记号上有个小区别:偏导数用 $\partial$(读作"partial")而不是 $d$,例如 $\frac{\partial L}{\partial w}$ 表示"损失 $L$ 对参数 $w$ 的偏导数"。
举个例子,$L(a, b) = a^2 + 3ab$:
- 求 $\frac{\partial L}{\partial a}$ 时,把 $b$ 当常数:$\frac{\partial L}{\partial a} = 2a + 3b$($3ab$ 对 $a$ 求导,$3b$ 是常数系数,剩下 $3b$)
- 求 $\frac{\partial L}{\partial b}$ 时,把 $a$ 当常数:$\frac{\partial L}{\partial b} = 3a$($a^2$ 里没有 $b$,导数为 0)
→ 这个在大模型里用在哪:偏导数 $\frac{\partial L}{\partial w_i}$ 的物理含义是"损失函数对第 $i$ 个参数的敏感程度"——这个参数动一小步,损失会变多少。训练循环里对每个参数算一次偏导数,就知道每个参数该往哪调。一个 70 亿参数的模型,每一步就要算 70 亿个偏导数(好在 GPU 擅长这个,回忆第 0 章)。
📝 练习 3.1:用笔算(不是代码)求 $L(w) = w^3 - 2w + e^w$ 在 $w = 1$ 处的导数。再用上面的
numerical_derivative函数写代码验证你的结果。
3.3 梯度:把所有偏导数装进一个向量
3.3.1 梯度是偏导数的"全家桶"
把所有参数对每个参数的偏导数收集起来,排成一个向量,就是梯度(gradient),记作 $\nabla L$(读作"nabla L",$\nabla$ 是梯度算子):
$$\nabla L = \left( \frac{\partial L}{\partial w_1}, \frac{\partial L}{\partial w_2}, \dots, \frac{\partial L}{\partial w_n} \right)$$
如果参数是矩阵 $W$(神经网络里基本都是矩阵),梯度就是和 $W$ 形状完全相同的矩阵 $\frac{\partial L}{\partial W}$,每个位置是对应参数的偏导数。记住这个形状关系,本章综合实战里我们会靠它检查推导是否正确。
3.3.2 梯度指向"上坡最陡"的方向
梯度最重要的几何性质:
梯度方向 = 函数值上升最快的方向;梯度的长度 = 那个方向上的陡峭程度。
还是用下山的类比:你站在山坡上,脚下朝各个方向的坡度不一样。梯度向量就像一个指南针,永远指向"最陡的上坡方向"。那么负梯度方向 $-\nabla L$ 自然就是"最陡的下坡方向"。
→ 这个在大模型里用在哪:训练的目标是让损失 $L$ 变小,而梯度指向 $L$ 上升最快的方向——所以参数更新永远沿负梯度方向走:$w \leftarrow w - \text{lr} \cdot \nabla L$(
lr是学习率,3.5 节细讲)。这一个式子就是大模型训练的"发动机原理",后面所有优化器(SGD、Adam)都只是给这个式子做改装。
📝 练习 3.2:设 $L(w_1, w_2) = w_1^2 + 2w_2^2$。求点 $(1, 1)$ 处的梯度向量,并回答:从该点出发,沿哪个方向走 $L$ 下降最快?(提示:先分别求两个偏导数。)