注意
转到末尾 下载完整的示例代码。
torch.autograd 简介#
创建于:2017年3月24日 | 最后更新:2025年10月01日 | 最后验证:2024年11月05日
torch.autograd 是 PyTorch 的自动微分引擎,它为神经网络的训练提供了动力。在本节中,您将对 autograd 如何帮助神经网络进行训练有一个概念性的理解。
背景#
神经网络 (NNs) 是一系列嵌套函数,它们在输入数据上执行。这些函数由*参数*(包括权重和偏置)定义,在 PyTorch 中,这些参数存储在张量中。
训练神经网络分两个步骤完成
前向传播:在前向传播中,神经网络对其正确输出做出最佳猜测。它将输入数据通过每个函数来做出这个猜测。
反向传播:在反向传播中,神经网络会根据其猜测的误差成比例地调整其参数。它通过从输出向后遍历,收集误差相对于函数*参数*(*梯度*)的导数,并使用梯度下降优化参数来完成此操作。有关反向传播的更详细演练,请参阅 3Blue1Brown 的视频。
PyTorch 中的用法#
让我们来看一个训练步骤。在此示例中,我们从 torchvision 加载一个预训练的 resnet18 模型。我们创建一个随机数据张量来表示单个图像,它具有 3 个通道,高度和宽度均为 64,以及相应的 label,初始化为一些随机值。预训练模型中的标签形状为 (1,1000)。
注意
此教程仅在 CPU 上运行,在 GPU 设备上(即使张量已移至 CUDA)也不起作用。
import torch
from torchvision.models import resnet18, ResNet18_Weights
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]
72%|███████▏ | 32.2M/44.7M [00:00<00:00, 320MB/s]
100%|██████████| 44.7M/44.7M [00:00<00:00, 233MB/s]
接下来,我们通过模型的每一层运行输入数据以进行预测。这是*前向传播*。
prediction = model(data) # forward pass
我们使用模型的预测和相应的标签来计算误差(loss)。下一步是通过网络反向传播此误差。当我们对误差张量调用 .backward() 时,将触发反向传播。Autograd 然后计算并存储每个模型参数的梯度,这些梯度存储在参数的 .grad 属性中。
loss = (prediction - labels).sum()
loss.backward() # backward pass
接下来,我们加载一个优化器,在本例中为 SGD,学习率为 0.01,动量为 0.9。我们将模型的所有参数注册到优化器中。
optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
最后,我们调用 .step() 来启动梯度下降。优化器根据存储在 .grad 中的梯度调整每个参数。
optim.step() #gradient descent
此时,您已拥有训练神经网络所需的一切。以下各节详细介绍了 autograd 的工作原理 — 您可以随时跳过它们。
Autograd 中的微分#
让我们看看 autograd 如何收集梯度。我们创建两个张量 a 和 b,并将 requires_grad=True。这会向 autograd 发出信号,表明应跟踪它们上的每个操作。
import torch
a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)
我们从 a 和 b 创建另一个张量 Q。
假设 a 和 b 是神经网络的参数,而 Q 是误差。在神经网络训练中,我们想要误差相对于参数的梯度,即
当我们对 Q 调用 .backward() 时,autograd 会计算这些梯度并将它们存储在相应张量的 .grad 属性中。
由于 Q 是一个向量,因此我们需要在 Q.backward() 中显式传递 gradient 参数。 gradient 是一个与 Q 形状相同的张量,它代表 Q 相对于自身的梯度,即
同样,我们也可以将 Q 聚合为标量并隐式调用 backward,例如 Q.sum().backward()。
external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)
a.grad 和 b.grad 中现在存放着梯度。
tensor([True, True])
tensor([True, True])
可选阅读 - 使用 autograd 进行向量微积分#
从数学上讲,如果您有一个向量值函数 \(\vec{y}=f(\vec{x})\),那么 \(\vec{y}\) 相对于 \(\vec{x}\) 的梯度是一个雅可比矩阵 \(J\)
总的来说,torch.autograd 是一个用于计算向量-雅可比乘积的引擎。也就是说,给定任何向量 \(\vec{v}\),计算乘积 \(J^{T}\cdot \vec{v}\)
如果 \(\vec{v}\) 恰好是一个标量函数 \(l=g\left(\vec{y}\right)\) 的梯度
那么根据链式法则,向量-雅可比乘积就是 \(l\) 相对于 \(\vec{x}\) 的梯度
向量-雅可比乘积的这个特性是我们上面例子中使用的; external_grad 代表 \(\vec{v}\)。
计算图#
从概念上讲,autograd 在一个由 Function 对象组成的有向无环图 (DAG) 中记录数据(张量)和所有已执行的操作(以及生成的新张量)。在该 DAG 中,叶节点是输入张量,根节点是输出张量。通过从根节点到叶节点跟踪此图,您可以使用链式法则自动计算梯度。
在前向传播中,autograd 同时执行两项操作:
执行所需的操作以计算结果张量,以及
在 DAG 中维护该操作的*梯度函数*。
当在 DAG 根节点调用 .backward() 时,反向传播启动。然后,autograd 会
从每个
.grad_fn计算梯度,将它们累积到相应张量的
.grad属性中,并且使用链式法则,一直传播到叶张量。
下面是我们示例中 DAG 的可视化表示。图中,箭头指示前向传播的方向。节点代表前向传播中每个操作的反向函数。蓝色的叶节点代表我们的叶张量 a 和 b。
注意
PyTorch 中的 DAG 是动态的 需要注意的一点是,图是从头开始重新创建的;每次调用 .backward() 后,autograd 都会开始填充一个新图。这正是允许您在模型中使用控制流语句的原因;您可以根据需要,在每次迭代时更改形状、大小和操作。
从 DAG 中排除#
torch.autograd 会跟踪所有 requires_grad 标志设置为 True 的张量的操作。对于不需要梯度的张量,将此属性设置为 False 会将其从梯度计算 DAG 中排除。
即使只有一个输入张量具有 requires_grad=True,操作的输出张量也需要梯度。
x = torch.rand(5, 5)
y = torch.rand(5, 5)
z = torch.rand((5, 5), requires_grad=True)
a = x + y
print(f"Does `a` require gradients?: {a.requires_grad}")
b = x + z
print(f"Does `b` require gradients?: {b.requires_grad}")
Does `a` require gradients?: False
Does `b` require gradients?: True
在神经网络中,不计算梯度的参数通常称为*冻结参数*。如果您提前知道不需要这些参数的梯度,则“冻结”模型的某些部分会很有用(这可以通过减少 autograd 计算来提供一些性能优势)。
在微调中,我们冻结模型的大部分,通常只修改分类器层来对新标签进行预测。让我们通过一个小例子来演示这一点。和以前一样,我们加载一个预训练的 resnet18 模型,并冻结所有参数。
from torch import nn, optim
model = resnet18(weights=ResNet18_Weights.DEFAULT)
# Freeze all the parameters in the network
for param in model.parameters():
param.requires_grad = False
假设我们要在一个具有 10 个标签的新数据集上微调模型。在 resnet 中,分类器是最后一个线性层 model.fc。我们可以简单地用一个新的线性层(默认未冻结)替换它,该层充当我们的分类器。
现在,模型中除 model.fc 的参数外,所有参数都已冻结。唯一计算梯度的参数是 model.fc 的权重和偏置。
# Optimize only the classifier
optimizer = optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
请注意,尽管我们将所有参数都注册到了优化器中,但唯一计算梯度(因此在梯度下降中更新)的参数是分类器的权重和偏置。
相同的排除功能可通过 torch.no_grad() 中的上下文管理器使用。
进一步阅读:#
脚本总运行时间: (0 分钟 0.752 秒)