自动微分:PyTorch 的核心魔法#

神经网络模块:搭建计算图中我们学会了构建神经网络结构。现在的问题是:如何让网络"学习"?

反向传播算法中,我们学习了反向传播算法的原理——通过链式法则将损失梯度从输出层传回输入层。但如果手动实现这个算法,代码会非常复杂且容易出错。

PyTorch 的自动微分(autograd)就是解决方案:它自动构建计算图并执行反向传播,让我们只需关注前向计算,梯度会自动计算。

直觉理解:自动微分是什么?#

类比:自动记账系统

想象你经营一家连锁餐厅:

  • 手动反向传播:每道菜卖了多少钱,你需要手动追踪每一笔成本(食材、人工、租金),然后计算每个分店应该调整什么——极其繁琐且容易出错。

  • 自动微分:你只需记录每笔交易(前向传播),系统自动生成财务报表(梯度),告诉你每个分店该如何调整。

核心洞察: 自动微分 = 自动构建计算图 + 自动执行反向传播 + 自动存储梯度

从理论到代码#

反向传播算法 理论

PyTorch 实现

作用

计算图

requires_grad=True

标记需要计算梯度的张量

链式法则

.backward()

自动回传梯度

梯度存储

.grad

存储计算得到的梯度

梯度清零

.zero_()

清除旧梯度,避免累积

计算图的自动构建#

什么是动态计算图?#

PyTorch 使用动态计算图(Dynamic Computational Graph),这意味着:

  1. 图在运行时构建:每次前向传播都会重新构建图

  2. 图结构可以变化:支持 Python 控制流(if、for、while)

  3. 内存高效:反向传播后可以释放中间结果

flowchart TD A["x<br/>requires_grad=True<br/>叶子节点"] --> C["乘法 op"] B["w<br/>requires_grad=True<br/>叶子节点"] --> C C --> D["z = x × w<br/>中间节点"] D --> E["加法 op"] F["b<br/>requires_grad=True"] --> E E --> G["y = z + b<br/>输出节点"] style A fill:#e3f2fd style B fill:#e3f2fd style F fill:#e3f2fd style G fill:#e8f5e9

关键区别

  • 叶子节点(Leaf Node):用户创建的张量(requires_grad=True),梯度会保存

  • 中间节点:运算产生的张量,默认不保存梯度(除非设置 retain_graph=True

创建可追踪的张量#

 1import torch
 2
 3# 叶子节点:requires_grad=True 告诉 PyTorch"我要跟踪这个张量"
 4x = torch.tensor([2.0, 3.0], requires_grad=True)  # 输入特征
 5w = torch.tensor([1.0, 2.0], requires_grad=True)  # 权重
 6b = torch.tensor(0.5, requires_grad=True)          # 偏置
 7
 8print(f"x.is_leaf: {x.is_leaf}")  # True:用户创建的叶子节点
 9print(f"w.is_leaf: {w.is_leaf}")  # True
10
11# 中间节点:通过运算产生
12z = x * w              # 逐元素乘法,形状 [2]
13y = z.sum() + b        # 求和后加偏置,标量
14
15print(f"z.is_leaf: {z.is_leaf}")  # False:运算产生
16print(f"y.is_leaf: {y.is_leaf}")  # False

梯度计算:.backward() 的魔力#

标量输出的梯度计算#

 1import torch
 2
 3# 创建叶子节点
 4x = torch.tensor(2.0, requires_grad=True)  # 对应 back-propagation 中的示例
 5y = torch.tensor(3.0, requires_grad=True)
 6
 7# 前向传播:f = (x + y) × y
 8a = x + y      # a = 5
 9f = a * y      # f = 15
10
11print(f"前向结果: f = {f.item()}")  # 15.0
12
13# 反向传播:自动计算梯度
14f.backward()
15
16# 查看梯度
17print(f"∂f/∂x = {x.grad}")  # 3.0(与理论一致!)
18print(f"∂f/∂y = {y.grad}")  # 8.0(与理论一致!)
19
20# 梯度验证(手工计算):
21# ∂f/∂x = ∂f/∂a × ∂a/∂x = y × 1 = 3 ✓
22# ∂f/∂y = ∂f/∂a × ∂a/∂y + ∂f/∂y(直接) = y + a = 3 + 5 = 8 ✓

代码解释

  • requires_grad=True:告诉 PyTorch 跟踪这个张量的所有操作

  • .backward():从输出节点开始,沿计算图反向传播,计算所有叶子节点的梯度

  • .grad:存储计算得到的梯度值

非标量输出的处理#

当输出不是标量时,需要指定权重向量:

import torch

# 输出是向量而非标量
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x * 2  # y = [2, 4, 6]

# 错误:y 是向量,不能直接 backward()
# y.backward()  # RuntimeError!

# 正确:提供一个权重向量(相当于计算 v^T × J)
# 这里我们计算 y 每个元素对 x 的梯度的加权和
v = torch.tensor([1.0, 1.0, 1.0])  # 权重向量
y.backward(v)  # 等价于计算 sum(y) 的梯度

print(f"x.grad = {x.grad}")  # [2, 2, 2]

# 实际应用:通常我们会将损失降为标量
loss = y.sum()  # 标量
loss.backward()  # 可以直接调用

梯度控制技巧#

梯度累积与清零#

关键问题:PyTorch 的 .backward() 默认会累积梯度!

 1import torch
 2
 3x = torch.tensor(2.0, requires_grad=True)
 4
 5# 第一次前向+反向
 6y1 = x ** 2      # y1 = 4
 7y1.backward()
 8print(f"第一次反向后 x.grad = {x.grad}")  # 4.0(dy1/dx = 2x = 4)
 9
10# 第二次前向+反向(不清零)
11y2 = x ** 3      # y2 = 8
12y2.backward()
13print(f"第二次反向后 x.grad = {x.grad}")  # 16.0!(4 + 12,累积了!)
14
15# 正确做法:每次反向前清零
16x.grad.zero_()   # 清零梯度
17y3 = x ** 2
18y3.backward()
19print(f"清零后 x.grad = {x.grad}")  # 4.0

训练循环中的标准模式

optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

for batch in dataloader:
    # 1. 清零旧梯度
    optimizer.zero_grad()  # 或 model.zero_grad()
    
    # 2. 前向传播
    output = model(batch.input)
    loss = criterion(output, batch.target)
    
    # 3. 反向传播
    loss.backward()
    
    # 4. 更新参数
    optimizer.step()

禁用梯度计算#

在某些情况下,我们不需要计算梯度:

 1import torch
 2
 3x = torch.tensor(2.0, requires_grad=True)
 4
 5# 方式1:使用 torch.no_grad() 上下文管理器(推荐)
 6with torch.no_grad():
 7    y = x * 2
 8    print(f"y.requires_grad: {y.requires_grad}")  # False
 9    # y.backward()  # 错误!无法反向传播
10
11# 方式2:使用 .detach() 从计算图中分离
12z = x * 3
13z_detached = z.detach()
14print(f"z_detached.requires_grad: {z_detached.requires_grad}")  # False
15
16# 方式3:设置 requires_grad=False(全局)
17w = torch.tensor(2.0, requires_grad=False)
18output = w * x
19print(f"output.requires_grad: {output.requires_grad}")  # True(x 需要梯度)

何时禁用梯度?

场景

原因

代码

模型推理

不需要更新参数

with torch.no_grad()

特征提取

冻结预训练模型

param.requires_grad = False

数值计算

避免梯度开销

.detach()

保存张量

避免保留计算图

tensor.detach().cpu().numpy()

实际应用:神经网络训练#

完整示例:线性回归#

import torch
import torch.nn as nn

# 生成数据:y = 2x + 1 + 噪声
torch.manual_seed(42)
X = torch.randn(100, 1)          # 100个样本,1个特征
y_true = 2 * X + 1 + 0.1 * torch.randn(100, 1)

# 定义模型:对应 {doc}`../math-fundamentals/gradient-descent` 中的线性模型
model = nn.Linear(1, 1)          # 输入1维,输出1维

# 损失函数和优化器
criterion = nn.MSELoss()         # 均方误差
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

# 训练循环
for epoch in range(100):
    # 1. 清零梯度
    optimizer.zero_grad()
    
    # 2. 前向传播
    y_pred = model(X)            # 计算预测值
    loss = criterion(y_pred, y_true)  # 计算损失
    
    # 3. 反向传播(自动计算梯度)
    loss.backward()
    
    # 4. 更新参数
    optimizer.step()
    
    if (epoch + 1) % 20 == 0:
        print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")

# 查看学习到的参数
print(f"\n学习到的权重: {model.weight.item():.4f}(真实值: 2.0)")
print(f"学习到的偏置: {model.bias.item():.4f}(真实值: 1.0)")

查看计算图#

import torch

x = torch.tensor(2.0, requires_grad=True)
y = x ** 2

# 查看梯度函数(对应计算图中的边)
print(f"y.grad_fn: {y.grad_fn}")          # <PowBackward0 object>
print(f"y.grad_fn.next_functions: {y.grad_fn.next_functions}")

# 查看是否需要梯度
print(f"x.requires_grad: {x.requires_grad}")  # True
print(f"y.requires_grad: {y.requires_grad}")  # True

高级主题#

自定义梯度计算#

偶尔需要自定义梯度计算规则:

import torch
from torch.autograd import Function

class MyReLU(Function):
    """
    自定义 ReLU 激活函数,带梯度裁剪
    对应 {doc}`../math-fundamentals/activation-functions` 中的 ReLU
    """
    
    @staticmethod
    def forward(ctx, input):
        """前向传播:max(0, x)"""
        ctx.save_for_backward(input)  # 保存用于反向传播的张量
        return input.clamp(min=0)
    
    @staticmethod
    def backward(ctx, grad_output):
        """反向传播:梯度裁剪"""
        input, = ctx.saved_tensors
        grad_input = grad_output.clone()
        grad_input[input < 0] = 0  # ReLU 的梯度规则
        return grad_input

# 使用自定义函数
my_relu = MyReLU.apply
x = torch.tensor([-1.0, 2.0, -3.0, 4.0], requires_grad=True)
y = my_relu(x)
y.sum().backward()

print(f"输入: {x}")
print(f"输出: {y}")
print(f"梯度: {x.grad}")  # [0, 1, 0, 1]

梯度检查#

验证梯度计算是否正确:

import torch
from torch.autograd import gradcheck

# 定义需要测试的函数
def func(x):
    return x ** 3 + x ** 2

# 使用双精度浮点数进行数值梯度检查
test_input = torch.randn(3, 4, dtype=torch.double, requires_grad=True)

# 验证梯度
result = gradcheck(func, test_input, eps=1e-6, atol=1e-4)
print(f"梯度检查通过: {result}")  # True 表示梯度计算正确

总结#

核心概念回顾#

概念

解释

代码

计算图

记录张量操作的 DAG

自动构建

叶子节点

用户创建的可训练参数

requires_grad=True

反向传播

从输出回传梯度

.backward()

梯度存储

存储在 .grad 属性中

tensor.grad

梯度清零

避免梯度累积

.zero_grad()

禁用梯度

推理时节省内存

torch.no_grad()

下一步#

掌握了自动微分后,下一节 优化器:用梯度更新参数 我们将学习如何使用这些梯度来更新模型参数——从简单的 SGD 到自适应学习率的 Adam,掌握优化算法的核心原理。

从"计算梯度"到"使用梯度优化",让我们继续深入!

贡献者与修订历史

查看详细修订记录
  • b20ef3e 2026-04-28 - Heyan Zhu: docs: update pytorch practice section with detailed explanations and code examples
  • dcecce4 2026-01-26 - Heyan Zhu: docs: enrich math fundamentals documentation with code captions and TikZ visualizations
  • 0c291d7 2025-12-10 - Heyan Zhu: docs: restructure course materials and add new content