torch.autograd 轻松入门

7 阅读8分钟

torch.autograd 轻松入门

torch.autograd 是 PyTorch 的自动微分引擎,为神经网络训练提供核心支持。本节将帮助你从概念层面理解 autograd 如何助力神经网络训练。

背景知识

神经网络(NN)是由嵌套函数组成的集合,这些函数作用于输入数据并产生输出。这些函数由参数(包含权重和偏置)定义,在 PyTorch 中这些参数存储在张量中。

神经网络的训练分为两个步骤:

  1. 前向传播(Forward Propagation):在前向传播阶段,神经网络根据输入数据尽可能做出最接近真实值的输出猜测。它会将输入数据依次通过网络中的每个函数来生成这个猜测结果。

  2. 反向传播(Backward Propagation):在反向传播阶段,神经网络会根据猜测结果的误差成比例地调整自身参数。具体过程是从输出端反向遍历网络,收集误差相对于各函数参数的导数(梯度),并使用梯度下降算法优化这些参数。想要更深入了解反向传播的原理,可以观看 3Blue1Brown 的相关视频

PyTorch 中的使用示例

我们来演示单个训练步骤的完整流程。本示例中,我们从 torchvision 加载预训练的 resnet18 模型,创建一个随机数据张量(模拟单张 3 通道、64x64 尺寸的图像),并初始化对应的标签为随机值(预训练模型的标签形状为 (1,1000))。

注意
本教程仅适用于 CPU 环境,无法在 GPU 设备上运行(即使将张量移至 CUDA 设备也不行)。

import torch
from torchvision.models import resnet18, ResNet18_Weights

# 加载预训练的 resnet18 模型
model = resnet18(weights=ResNet18_Weights.DEFAULT)
# 创建模拟输入数据:(批次大小, 通道数, 高度, 宽度)
data = torch.rand(1, 3, 64, 64)
# 创建模拟标签
labels = torch.rand(1, 1000)

模型权重下载输出示例

Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to /var/lib/ci-user/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth

  0%|          | 0.00/44.7M [00:00<?, ?B/s]
 63%|██████▎   | 28.0M/44.7M [00:00<00:00, 293MB/s]
100%|██████████| 44.7M/44.7M [00:00<00:00, 280MB/s]

接下来,将输入数据通过模型的各层网络以生成预测结果,这就是前向传播过程:

prediction = model(data) # 前向传播

利用模型的预测结果和对应的标签计算误差(损失值)。下一步是将这个误差反向传播通过整个网络——只需在误差张量上调用 .backward() 方法即可触发反向传播。autograd 会自动计算每个模型参数的梯度,并将其存储在参数的 .grad 属性中。

loss = (prediction - labels).sum()
loss.backward() # 反向传播

然后加载优化器(此处使用学习率为 0.01、动量为 0.9 的 SGD),并将模型的所有参数注册到优化器中:

optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)

最后调用 .step() 方法执行梯度下降,优化器会根据存储在 .grad 中的梯度值调整每个参数:

optim.step() # 梯度下降

至此,你已经掌握了训练神经网络所需的核心流程。以下部分详细讲解 autograd 的工作原理,你可以根据需要选择阅读。

Autograd 中的微分计算

我们来具体看看 autograd 是如何收集梯度的。创建两个设置 requires_grad=True 的张量 ab,这会告诉 autograd 需要追踪它们的所有操作。

import torch

a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)

基于 ab 创建另一个张量 Q

Q=3a3b2Q = 3a^3 - b^2

Q = 3*a**3 - b**2

我们可以将 ab 视为神经网络的参数,Q 视为误差值。在神经网络训练中,我们需要计算误差相对于参数的梯度,即:

Qa=9a2\frac{\partial Q}{\partial a} = 9a^2 Qb=2b\frac{\partial Q}{\partial b} = -2b

当在 Q 上调用 .backward() 时,autograd 会计算这些梯度并将其存储在对应张量的 .grad 属性中。

由于 Q 是一个向量,我们需要在 Q.backward() 中显式传入 gradient 参数。该参数是与 Q 形状相同的张量,表示 Q 相对于自身的梯度,即:

dQdQ=1\frac{dQ}{dQ} = 1

等效地,我们也可以将 Q 聚合为标量后隐式调用 backward,例如 Q.sum().backward()

external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)

此时梯度已被存入 a.gradb.grad

# 验证收集到的梯度是否正确
print(9*a**2 == a.grad)
print(-2*b == b.grad)

输出结果

tensor([True, True])
tensor([True, True])

扩展阅读 - 使用 autograd 进行向量微积分

从数学角度来说,如果你有一个向量值函数 y=f(x)\vec{y} = f(\vec{x}),那么 y\vec{y} 相对于 x\vec{x} 的梯度是一个雅可比矩阵 JJ

\frac{\partial y_1}{\partial x_1} & \cdots & \frac{\partial y_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial y_m}{\partial x_1} & \cdots & \frac{\partial y_m}{\partial x_n} \end{pmatrix}$$ 一般来说,torch.autograd 是一个计算**向量-雅可比乘积**的引擎。即对于任意向量 $\vec{v}$,计算乘积 $J^T \cdot \vec{v}$。 如果 $\vec{v}$ 恰好是标量函数 $l = g(\vec{y})$ 的梯度: $$\vec{v} = \left( \frac{\partial l}{\partial y_1} \cdots \frac{\partial l}{\partial y_m} \right)^T$$ 那么根据链式法则,向量-雅可比乘积就是 $l$ 相对于 $\vec{x}$ 的梯度: $$J^T \cdot \vec{v} = \begin{pmatrix} \frac{\partial y_1}{\partial x_1} & \cdots & \frac{\partial y_m}{\partial x_1} \\ \vdots & \ddots & \vdots \\ \frac{\partial y_1}{\partial x_n} & \cdots & \frac{\partial y_m}{\partial x_n} \end{pmatrix} \begin{pmatrix} \frac{\partial l}{\partial y_1} \\ \vdots \\ \frac{\partial l}{\partial y_m} \end{pmatrix} = \begin{pmatrix} \frac{\partial l}{\partial x_1} \\ \vdots \\ \frac{\partial l}{\partial x_n} \end{pmatrix}$$ 上述示例中我们正是利用了向量-雅可比乘积的这一特性,其中 `external_grad` 就代表了向量 $\vec{v}$。 ## 计算图(Computational Graph) 从概念上讲,autograd 会将数据(张量)和所有执行过的操作(以及生成的新张量)记录在一个由 `Function` 对象组成的**有向无环图(DAG)** 中。在这个图中,叶子节点是输入张量,根节点是输出张量。通过从根节点向叶子节点追溯这个图,autograd 可以利用链式法则自动计算梯度。 ### 前向传播过程 在前向传播时,autograd 会同时完成两件事: 1. 执行请求的操作,计算结果张量; 2. 在计算图中保存该操作对应的梯度函数。 ### 反向传播过程 当在计算图的根节点上调用 `.backward()` 时,反向传播开始。autograd 会: 1. 从每个 `.grad_fn` 计算梯度; 2. 将梯度累加至对应张量的 `.grad` 属性中; 3. 利用链式法则,一直传播到叶子张量。 下图是我们示例中计算图的可视化表示。图中的箭头方向为前向传播方向,节点代表前向传播中每个操作对应的反向函数,蓝色的叶子节点代表我们的叶子张量 `a` 和 `b`。 ``` ../../_images/dag_autograd.png ``` > **注意** > PyTorch 中的计算图是动态的:需要重点注意的是,计算图会被从头重新创建——每次调用 `.backward()` 后,autograd 会开始构建一个新的计算图。这一特性使得你可以在模型中使用控制流语句;你可以根据需要在每次迭代中更改张量的形状、大小和执行的操作。 ## 从计算图中排除张量 torch.autograd 会追踪所有 `requires_grad` 标志设为 `True` 的张量上的操作。对于不需要梯度的张量,将该属性设为 `False` 可使其排除在梯度计算的计算图之外。 只要有一个输入张量的 `requires_grad=True`,操作的输出张量就会开启梯度追踪。 ```python x = torch.rand(5, 5) y = torch.rand(5, 5) z = torch.rand((5, 5), requires_grad=True) a = x + y print(f"张量 `a` 是否需要梯度?: {a.requires_grad}") b = x + z print(f"张量 `b` 是否需要梯度?: {b.requires_grad}") ``` ### 输出结果 ``` 张量 `a` 是否需要梯度?: False 张量 `b` 是否需要梯度?: True ``` 在神经网络中,不计算梯度的参数通常被称为**冻结参数(frozen parameters)**。如果你预先知道某些参数不需要计算梯度,将其“冻结”会很有用(这能减少 autograd 的计算量,提升性能)。 在微调(finetuning)模型时,我们通常会冻结模型的大部分参数,只修改分类器层以适应新的标签。我们通过一个小示例来演示这一过程:加载预训练的 resnet18 模型,并冻结所有参数。 ```python from torch import nn, optim model = resnet18(weights=ResNet18_Weights.DEFAULT) # 冻结网络中的所有参数 for param in model.parameters(): param.requires_grad = False ``` 假设我们要在一个包含 10 个类别的新数据集上微调模型。在 resnet 中,分类器是最后一个线性层 `model.fc`。我们可以直接将其替换为一个新的线性层(默认不冻结)作为新的分类器: ```python model.fc = nn.Linear(512, 10) ``` 现在模型中除了 `model.fc` 的参数外,其他所有参数都被冻结了。只有 `model.fc` 的权重和偏置会计算梯度。 ```python # 仅优化分类器层的参数 optimizer = optim.SGD(model.parameters(), lr=1e-2, momentum=0.9) ``` 注意,尽管我们将所有参数都注册到了优化器中,但只有计算梯度的参数(即分类器的权重和偏置)会在梯度下降中被更新。 同样的排除功能也可以通过上下文管理器 `torch.no_grad()` 实现。 ## 更多参考资料 - [原地操作与多线程 Autograd](https://pytorch.org/docs/stable/notes/autograd.html) - [反向模式自动微分的示例实现](https://colab.research.google.com/github/pytorch/tutorials/blob/gh-pages/_downloads/autograd_derivatives.ipynb) - [视频教程:PyTorch Autograd 详解](https://www.youtube.com/watch?v=MswxJw-8PvE) ### 脚本总运行时间 ``` 0 分 0.708 秒 ``` ### 总结 1. torch.autograd 是 PyTorch 的自动微分引擎,核心作用是在反向传播时计算并存储模型参数的梯度,为梯度下降优化提供数据支撑; 2. autograd 通过构建动态计算图(DAG)追踪张量操作,前向传播构建图,反向传播从输出到输入计算梯度,且计算图每次反向传播后会重新创建,支持动态控制流; 3. 可通过 `requires_grad=False` 或 `torch.no_grad()` 冻结参数/排除张量梯度计算,常用于模型微调场景以提升计算效率。