PyTorch 自动微分是什么?理解 requires_grad 和 backward

从一个最小例子理解 PyTorch 如何记录计算过程、自动求梯度,以及为什么训练前要清空梯度。

学习路线:PyTorch 深度学习基础(43~58) · 第 44 课
第 44 课PyTorch 计算图从参数和输入连接到损失函数,再通过 backward 返回梯度。
Learning Path

PyTorch 深度学习基础(43~58)

在前 20 篇 PyTorch 入门上继续深化,补齐张量操作、自动微分、优化器家族和训练排错能力。

学完本阶段你能做到:独立搭建一个 PyTorch 训练脚本,正确处理 Shape、device、梯度清空、优化器选择和学习率策略,并能根据 loss/accuracy 曲线诊断训练问题。

推荐读法:当前更新主线,建议跟更;每篇都要配合代码自检。

查看完整阶段 · 16 篇
Lesson Guide

这一课怎么学

先看目标,再带着问题读正文。读完后用练习确认自己真的理解了。

本节你会学到

  • 说清楚「PyTorch 自动微分是什么?理解 requires_grad 和 backward」解决的核心问题
  • 知道它在「PyTorch 深度学习基础(43~58)」中的位置
  • 把 反向传播、Tensor、激活函数 这些关键词联系起来

前置知识

  • 建议先读完上一篇:Tensor 进阶:dtype、device、索引和切片
  • 能区分输入、输出、数据和模型目标。

概念回顾

  • 【反向传播】反向传播负责告诉每个参数:这次错了多少,你该往哪个方向改。 前面或后面会反复用到它。
  • 【Tensor】可以先把 Tensor 理解成支持 GPU、梯度和批量计算的多维数组。 前面或后面会反复用到它。
  • 【激活函数】没有激活函数,多层网络叠起来仍然像一条直线;有了它,模型才能表达弯曲复杂的关系。 前面或后面会反复用到它。
  • 【深度学习】深度学习可以理解为更复杂的机器学习方法,它让模型通过多层变换逐步提取特征。 前面或后面会反复用到它。

核心概念

常见误区

  • 不要只记定义,要追问它解决了什么问题。
  • 不要只看 API 名字,要同时关注输入输出 shape 和训练流程。

课后练习

  • 用 3 句话向一个零基础朋友解释「PyTorch 自动微分是什么?理解 requires_grad 和 backward」。
  • 打开概念库里的「反向传播」,补一遍它和本文的关系。
  • 读下一课「计算图与梯度累积:为什么训练前必须清空梯度」前,先写下你认为它会解决的问题。

自检练习与参考答案

1. 如果一段 PyTorch 代码报 device 不一致,第一步应该检查什么?

先检查模型参数、输入 Tensor、标签 Tensor 是否在同一个设备上。常见修正是把它们统一 `.to(device)`。

2. 为什么学习 PyTorch 时不能只看 API 名字?

因为训练是否正确经常取决于 shape、dtype、device 和梯度流,API 名字只能告诉你工具,不会保证数据契约正确。

3. 读完本文后,至少应该能画出哪条训练主线?

`输入数据 → forward → loss → backward → optimizer.step()`,并知道本文主题位于这条链路的哪一环。

下一步建议读「计算图与梯度累积:为什么训练前必须清空梯度」。

前面我们已经写过 PyTorch 的训练流程:前向计算、损失函数、反向传播、优化器更新。

但很多初学者第一次看到:

loss.backward()
optimizer.step()

会觉得像魔法。为什么调用一次 backward(),参数上就有梯度了?

这一课先把 PyTorch 自动微分讲清楚。

第 44 课视频 · PyTorch 自动微分是什么?理解 requires_grad 和 backward

自动微分解决什么问题

训练神经网络,本质上是在回答一个问题:

如果损失变大了,应该怎么调整每个参数?

这个“怎么调整”就需要梯度。

手动推导一个很小的函数还可以,但真实神经网络有成千上万个参数。如果每个参数的梯度都手写,几乎不可能维护。

PyTorch 的 autograd 会自动记录计算过程,并在需要时帮我们反向求梯度。

requires_grad 是什么

先看一个最小例子:

import torch

w = torch.tensor(2.0, requires_grad=True)
x = torch.tensor(3.0)

y = w * x

这里 w.requires_grad=True 的意思是:请 PyTorch 跟踪与 w 有关的计算,将来我要对它求梯度。

x 没有设置 requires_grad=True,说明它只是普通输入,不需要为它保存梯度。

backward 会做什么

继续上面的例子:

y.backward()

print(w.grad)

因为:

y = w * x

x = 3 时,yw 的梯度就是 3。

所以 w.grad 会得到:

tensor(3.)

你可以把 backward() 理解成:从最终结果往前走,把每个需要梯度的 Tensor 对结果的影响算出来。

计算图是什么

当你写:

w = torch.tensor(2.0, requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)
x = torch.tensor(3.0)

y = w * x + b
loss = (y - 10) ** 2

PyTorch 会在背后记录一张计算图:

w, x -> 乘法 -> 加 b -> y -> loss

这张图不是你手动画出来的,而是 PyTorch 在执行运算时动态记录的。

当你调用:

loss.backward()

它就会沿着这张图反向传播,把梯度累积到 w.gradb.grad 里。

loss 通常要是标量

最常见的训练代码里,loss 是一个标量:

loss = loss_fn(pred, y)
loss.backward()

如果你的输出不是标量,直接 backward 可能会报错,因为 PyTorch 不知道你想对哪个方向求导。

初学阶段先记住:训练时通常对一个标量 loss 调用 backward()

梯度会累积

PyTorch 的梯度默认是累积的:

loss.backward()
loss.backward()

如果连续调用两次,grad 会叠加,而不是自动清零。

所以训练循环里通常会写:

optimizer.zero_grad()
loss.backward()
optimizer.step()

顺序很重要:

  1. zero_grad() 清空上一轮梯度;
  2. backward() 计算这一轮梯度;
  3. step() 根据梯度更新参数。

no_grad 用在什么时候

验证或预测阶段不需要更新参数,也就不需要记录计算图:

model.eval()

with torch.no_grad():
    pred = model(x)

torch.no_grad() 可以减少内存占用,也能避免误把预测阶段的计算加入梯度图。

训练阶段需要梯度,验证和推理阶段通常不需要梯度。

一个完整小例子

import torch

w = torch.tensor(2.0, requires_grad=True)
b = torch.tensor(1.0, requires_grad=True)
x = torch.tensor(3.0)
target = torch.tensor(10.0)

pred = w * x + b
loss = (pred - target) ** 2

loss.backward()

print("w.grad:", w.grad)
print("b.grad:", b.grad)

这个例子虽然简单,但它和神经网络训练的核心逻辑是一样的:

参数参与前向计算
  -> 得到预测
  -> 计算 loss
  -> backward 求梯度
  -> optimizer 更新参数

这一课先记住

PyTorch 自动微分可以先抓住四句话:

  1. requires_grad=True 表示这个 Tensor 需要被求梯度;
  2. PyTorch 会动态记录计算图;
  3. backward() 会沿计算图反向计算梯度;
  4. 梯度会累积,所以训练前要 zero_grad()

下一课我们继续看计算图,理解为什么 PyTorch 能把复杂模型里的梯度自动串起来。

Discussion

留言讨论