loss.backward() 到底在干什么?一篇讲透计算图与反向传播
整理说明:本文基于 B 站视频【第05讲《计算图与反向传播:梯度如何流动》】公开信息、课程逐字稿与配套资料进行原创整理。不是逐字转写,而是把核心概念、手算流程、代码练习和排错方法整理成科研小白可以照着学的教程。
视频来源:B站 @Ai学术叫叫兽
视频链接:https://www.bilibili.com/video/BV1y5Ko6DEcR/
视频时长:约 19 分 28 秒
很多同学第一次看到 PyTorch 里的这行代码:
loss.backward()第一反应通常是:这是不是一个“魔法按钮”?
明明我们只看到一个 Loss,点一下backward(),模型里成千上万个参数突然就都有了梯度。哪个权重要变大,哪个偏置要变小,每个参数该改多少,好像模型自己都知道了。
但它不是魔法。
反向传播的本质其实很朴素:用计算图记录计算路线,再用链式法则沿着路线反向追责。
这一讲只解决一个核心问题:
模型预测错了以后,最终错误是怎么分摊到前面每一个参数上的?
如果你能把这个问题讲清楚,后面学深层神经网络、CNN、YOLO 训练、Loss 曲线、梯度消失和梯度爆炸,就不会只是在背术语。
文末也给大家整理了复盘清单和资料领取话术。PPT、讲义和动画资料可以无偿送给大家,适合课后反复对着复盘。
01 先建立一个画面:前向做题,反向追责
上一讲我们讲过一个最简单的神经元:
z = w·x + b a = sigmoid(z) L = loss(a, y)它的前向过程很像学生做题:
输入x进来,模型用权重w和偏置b算出线性得分z,再通过 sigmoid 得到预测概率a,最后和真实标签y比较,得到 Loss。
这一步回答的是:
模型怎么做预测?
但训练真正关心的是下一步:
预测错了以后,模型怎么知道每个参数应该怎么改?
这就像考试总分低了,不能只说“考差了”。你还要追问:
| 问题 | 在模型里对应什么 |
|---|---|
| 哪道题扣分最多? | 哪个计算节点对 Loss 影响大 |
| 是概念错,还是计算错? | 是前向输出异常,还是梯度传递异常 |
| 下次该补哪里? | 哪个参数该往什么方向更新 |
| 是小修小补,还是大幅调整? | 梯度大小和学习率共同决定更新步长 |
所以,反向传播可以先理解成一句话:
Loss 是最后的错误结果,计算图是路线图,链式法则是追责方法,梯度是每个参数收到的调整通知。
02 六个关键词:把反向传播拆成小白能懂的语言
反向传播难,很多时候不是因为公式本身难,而是几个词混在一起了。
我们先把 6 个核心概念讲清楚。
| 概念 | 一句话理解 | 小白记法 |
|---|---|---|
| 计算图 | 用节点和箭头表示计算依赖关系 | 计算路线图 |
| 前向传播 | 从输入一路算到预测和 Loss | 模型先做一遍题 |
| 局部导数 | 某个节点输出对输入的敏感程度 | 每一小段有多敏感 |
| 链式法则 | 把路径上的局部导数乘起来 | 一段一段追影响 |
| 反向传播 | 从 Loss 出发反向计算每个参数梯度 | 从结果往回分责任 |
| 梯度 | Loss 对参数的变化率 | 参数该往哪改、改多猛 |
把这几个词连起来,就是本讲最重要的一句话:
计算图记录计算路线,前向传播得到 Loss,局部导数描述每一小段敏感度,链式法则把敏感度串起来,反向传播把梯度传回参数,优化器再根据梯度更新参数。
这句话能顺口说出来,反向传播的主线就稳了。
03 为什么一定要画计算图?
很多初学者会问:既然最后都是求导,为什么不直接背公式?
因为神经网络不是一个短公式,而是一条很长的计算链。
以单神经元为例:
x, w, b → z → a → L每个箭头表示一次依赖:
| 节点 | 它从哪来 | 它到哪去 |
|---|---|---|
x | 输入特征 | 参与计算z |
w | 权重参数 | 参与计算z |
b | 偏置参数 | 参与计算z |
z | w·x + b | 送入 sigmoid |
a | sigmoid(z) | 和标签比较算 Loss |
L | loss(a, y) | 反向传播从这里开始 |
计算图的价值不只是“画得好看”,而是它能回答训练排错里最关键的三个问题:
- 这个变量从哪里来?
- 这个变量到哪里去?
- 如果 Loss 异常,错误信号能不能沿着这条路传回来?
真实训练里,很多问题并不是链式法则错了,而是计算图在某一步断了。
比如你不小心写了:
value=tensor.item()或者把参与求导的张量转成了 NumPy 数组再参与计算,前面的梯度可能就断了。表面看 Loss 还在,实际上参数已经收不到有效梯度。
所以对科研小白来说,计算图不是可有可无的图示,而是训练排错地图。
04 手把手手算一遍:梯度到底怎么传到 w?
我们用一个小到能手算的例子:
z = w x + b a = sigmoid(z) L = (a - y)^2前向传播按顺序算:
- 用
x、w、b算出z - 把
z放进 sigmoid 得到a - 把
a和标签y比较得到L
现在问题来了:
如果 Loss 大了,怎么知道w应该怎么改?
反向传播不会从L一步跳到w,它会沿着计算图一段一段问:
L → a → z → w每一段都问一个“敏感度”:
| 追问 | 数学表达 | 人话解释 |
|---|---|---|
| Loss 对预测有多敏感? | ∂L/∂a | a变一点,Loss 变多少 |
| 预测对线性得分有多敏感? | ∂a/∂z | z变一点,sigmoid 输出变多少 |
| 线性得分对权重有多敏感? | ∂z/∂w | w变一点,z变多少 |
链式法则就是把三段影响乘起来:
∂L/∂w = ∂L/∂a · ∂a/∂z · ∂z/∂w为了让它更落地,我们带一个数字例子:
x = 2 w = 0.5 b = 0 y = 1 z = 0.5 × 2 + 0 = 1 a = sigmoid(1) ≈ 0.731 L = (0.731 - 1)^2 ≈ 0.072各段导数:
∂L/∂a = 2(a - y) ≈ -0.538 ∂a/∂z = a(1-a) ≈ 0.197 ∂z/∂w = x = 2所以:
∂L/∂w ≈ -0.538 × 0.197 × 2 ≈ -0.212这个负号很重要。它表示:在当前位置,w增大一点,Loss 会下降。所以梯度下降更新时:
w ← w - α∂L/∂w如果学习率α = 0.1:
w_new = 0.5 - 0.1 × (-0.212) = 0.5212也就是说,权重会被稍微调大一点。
这就是反向传播最核心的直觉:
最终错误不是凭空分给参数的,而是沿着前向计算走过的路,反方向追回去的。
05 PyTorch 里 backward() 做了什么?
很多人学反向传播,是从 PyTorch 这一套训练代码开始的:
optimizer.zero_grad()pred=model(x)loss=criterion(pred,y)loss.backward()optimizer.step()这一段可以拆成 5 步:
| 代码 | 对应机制 | 小白解释 |
|---|---|---|
optimizer.zero_grad() | 清空旧梯度 | 先把上次的责任通知清掉 |
pred = model(x) | 前向传播 | 模型做题,得到预测 |
loss = criterion(pred, y) | 计算 Loss | 拿预测和答案对分 |
loss.backward() | 反向传播 | 沿计算图反向计算梯度 |
optimizer.step() | 参数更新 | 根据梯度真正改参数 |
注意两个最容易混的点:
第一,backward()只是计算梯度,它不会直接更新参数。
第二,真正改变参数的是optimizer.step()。
你可以把训练想象成一次“批改作业”:
前向传播:学生先做题 计算 Loss:老师打分 反向传播:分析每一步错在哪里 优化器更新:根据分析结果调整学习方式06 科研小白实操:用 20 行代码看见梯度
下面这个最小例子,建议你真的跑一遍。
代码文件我已经放在本文资料包里:
code_snippets/01_manual_scalar_backprop.py code_snippets/02_pytorch_autograd_backward.py如果你只想先看 PyTorch 版,可以运行:
python code_snippets/02_pytorch_autograd_backward.py核心代码如下:
importtorch x=torch.tensor(2.0)y=torch.tensor(1.0)w=torch.tensor(0.5,requires_grad=True)b=torch.tensor(0.0,requires_grad=True)z=w*x+b a=torch.sigmoid(z)loss=(a-y)**2loss.backward()print("z =",z.item())print("a =",a.item())print("loss =",loss.item())print("dL/dw =",w.grad.item())print("dL/db =",b.grad.item())你会看到w.grad和b.grad不再是空的。
这说明 PyTorch 已经根据计算图,帮你把Loss → a → z → w/b这条链路走完了。
建议你做 3 个小实验:
- 把
w = 0.5改成w = -2.0,看看 Loss 和梯度怎么变。 - 把标签
y = 1.0改成y = 0.0,观察梯度方向是否变化。 - 在
loss.backward()后加一行print(w.grad),再运行两次,理解为什么训练循环里要zero_grad()。
小白阶段不要急着改复杂模型。先把这个极简例子跑懂,你以后看 CNN 和 YOLO 的训练循环,会轻松很多。
07 梯度不是误差:这是很多人卡住的地方
这一讲里最容易混淆的一句话是:
误差告诉你错了,梯度告诉你往哪改。
它们不是一回事。
| 对比项 | 误差 / Loss | 梯度 |
|---|---|---|
| 关注什么 | 预测和标签差多少 | 参数变化会怎样影响 Loss |
| 作用 | 衡量当前结果好不好 | 指导参数往哪更新 |
| 例子 | 这次考试扣了 20 分 | 应该重点补哪类题 |
| 代码位置 | loss = criterion(pred, y) | loss.backward()后的param.grad |
如果只知道 Loss 大,你只知道模型错了。
但你不知道:
- 是哪个参数影响更大?
- 应该增大还是减小?
- 每个参数应该改大步还是小步?
这些信息都来自梯度。
所以调模型时,不要只盯着 Loss 曲线,也要学会检查梯度:
forname,pinmodel.named_parameters():ifp.gradisnotNone:print(name,p.grad.abs().mean().item())如果很多层梯度接近 0,可能存在梯度消失、计算图断裂或学习信号太弱。
如果梯度特别大,Loss 还来回震荡,可能存在梯度爆炸或学习率过大。
08 梯度消失和梯度爆炸,为什么会出现?
深层网络里,梯度要经过很多层才能传回前面的参数。
每经过一层,都会乘上一个局部导数。
如果很多局部导数都小于 1:
0.5 × 0.5 × 0.5 × 0.5 × ... → 越乘越小梯度就可能越来越小,这叫梯度消失。
如果很多局部导数都大于 1:
2 × 2 × 2 × 2 × ... → 越乘越大梯度就可能越来越大,这叫梯度爆炸。
这就是为什么后面学深层神经网络时,要讨论:
- 激活函数怎么选
- 参数怎么初始化
- 学习率怎么设置
- 是否需要归一化
- 是否需要残差连接
它们不是“高级装饰”,而是在帮助梯度稳定流动。
09 真实训练排错:按这 5 步检查
很多科研小白一遇到训练异常,就马上想换模型、换优化器、换更大的网络。
先别急。
真实项目里,大量问题都出在数据、shape、标签和计算链路上。
建议按下面 5 步排查:
| 步骤 | 你要问什么 | 常见问题 |
|---|---|---|
| 1. 输入是什么 | 进入模型的是图片、张量还是特征? | 通道顺序错、归一化错、batch 维丢失 |
| 2. 输出是什么 | 输出是概率、logits、Loss 还是指标? | 把 logits 当概率、Loss 对象搞错 |
| 3. shape 对吗 | 预测和标签形状是否匹配? | [B, C]和[B]搞混 |
| 4. 梯度通吗 | param.grad是否存在、是否为 0? | 计算图断裂、忘记requires_grad |
| 5. 参数更新了吗 | optimizer.step()后参数是否变化? | 忘记 step、学习率为 0、梯度被清空 |
最重要的是:
不要一上来就换模型。先确认输入、输出、中间变量、梯度、参数更新这条链路是通的。
10 结合图像任务理解:错误会传回前面的卷积核
放到图像模型里,反向传播就更有画面感了。
比如一个猫狗分类模型,把猫预测成了狗。
Loss 变大以后,错误信号不会只停在最后一层。
它会沿着计算图一路往回传:
分类输出 → 分类层权重 → 高级特征 → 中级特征 → 低级卷积核前面的卷积核并不是天生会看边缘、纹理和形状。
它们一开始通常是随机初始化的。之所以后来能学出边缘、纹理、局部结构,是因为每一次预测错误都会通过反向传播把调整信号传回来。
这也解释了为什么标签质量非常重要。
如果标签错了,Loss 给出的方向就会偏;反向传播会非常认真地把这个错误方向传给参数。
模型不是故意学错,是你给它的学习信号错了。
所以做科研项目时,永远记住三件事:
- 数据是否正确
- 标签是否可靠
- 梯度是否能稳定传回前面的层
11 常见误区:别再这样理解反向传播
| 误区 | 正确理解 |
|---|---|
| 反向传播是模型自己想明白了 | 它是链式法则在计算图上的高效应用 |
| 梯度就是误差本身 | 误差描述错多少,梯度描述往哪改 |
| 有 Loss 就一定能训练 | 有 Loss 只能衡量错误,不代表梯度稳定 |
| 层数越深一定越好 | 深层网络表达力更强,但梯度更难稳定传递 |
backward()会自动更新参数 | backward()算梯度,optimizer.step()才更新参数 |
| Loss 不降就换模型 | 先查数据、shape、标签、梯度和学习率 |
如果你刚入门,最该背的不是一堆公式,而是这句话:
前向传播让模型知道自己错了多少,反向传播让每个参数知道自己该怎么改。
12 随堂自测:3 道题判断你是否真的懂了
建议停 30 秒,自己答一下。
题 1:画出单神经元计算图
请画出:
z = w·x + b a = sigmoid(z) L = loss(a, y)参考答案:
x, w, b → z → a → L前向从左往右算,反向从右往左传梯度。
题 2:为什么链式法则适合多层网络?
因为多层网络本质上是很多函数一层一层嵌套起来。
前面参数不是直接影响 Loss,而是通过很多中间节点间接影响 Loss。链式法则可以把每一小段的局部影响乘起来,得到最前面参数对最终 Loss 的总影响。
题 3:误差和梯度有什么区别?
误差描述预测结果和标签差了多少。
梯度描述某个参数变化会怎样影响误差。
一句话:
误差告诉你错了,梯度告诉你往哪改。
13 课后按这个顺序学,别乱
如果你是科研小白,建议按下面 4 步复盘:
- 先看第 3 页核心定义表:把计算图、前向传播、局部导数、链式法则、反向传播、梯度讲顺。
- 再看第 5 页机制流程图:复述“前向计算 → 保存中间量 → 从 Loss 开始 → 局部反传 → 累积梯度 → 更新参数”。
- 然后手算第 6 页例子:用
z=wx+b、a=sigmoid(z)、L=(a-y)^2推一遍∂L/∂w。 - 最后跑本文代码:观察
loss.backward()后w.grad怎么出现。
如果你能做到这 4 件事,这一讲就过关了。
14 一句话总结
本讲可以压缩成一条训练链路:
计算图记录路线 前向传播得到 Loss 局部导数描述每一小段敏感度 链式法则把敏感度串起来 反向传播把梯度传回参数 优化器根据梯度更新参数再压缩成一句人话:
前向是数据流,反向是梯度流;前向产生错误,反向分摊责任。
下一讲会进入向量化和 Batch:当样本很多、参数很多时,如何用矩阵一次性高效计算。
如果你想系统学习这套“从深度学习到 YOLO26”的课程,可以关注我。第05讲的 PPT、讲义、动画和本文代码练习都可以无偿送给大家。建议收藏本文,课后对着图和代码再走一遍,反向传播就不再是黑盒了。