趋近智
requires_grad)backward()).grad)torch.nn 搭建模型torch.nn.Module 基类torch.nn 损失)torch.optim)torch.utils.data.Datasettorchvision.transforms)torch.utils.data.DataLoader对标量张量(如损失值)调用 .backward() 会触发计算图中所有 requires_grad=True 的张量的梯度。需要了解的一个主要特性是,PyTorch 默认会累积梯度。
如果您在多次调用 .backward() 之间不清除梯度,PyTorch 会将新计算的梯度添加到叶张量(参数)的.grad属性中已有的值上。
我们用一个简单例子来说明这一点:
import torch
# 创建一个需要梯度的张量
x = torch.tensor([2.0], requires_grad=True)
# 执行一些操作
y = x * x
z = y * 3 # z = 3 * x^2
# 第一次反向传播
# dz/dx = 6*x = 6*2 = 12
z.backward(retain_graph=True) # retain_graph=True 允许后续的反向传播调用
print(f"After first backward pass, x.grad: {x.grad}")
# 执行另一个操作(可以相同也可以不同)
# 为简单起见,我们再次使用相同的 z 进行演示
# 注意:在实际应用中,您可能会基于新的输入或模型的不同部分计算新的损失。
z.backward() # 第二次反向传播
# 我们预期新梯度 (12) 会被加到已有的梯度 (12) 上
print(f"After second backward pass, x.grad: {x.grad}")
# 手动清零梯度
x.grad.zero_()
print(f"After zeroing, x.grad: {x.grad}")
运行此代码会产生类似于以下的输出:
After first backward pass, x.grad: tensor([12.])
After second backward pass, x.grad: tensor([24.])
After zeroing, x.grad: tensor([0.])
请注意,第二次调用 z.backward() 如何将新计算的梯度(12)加到之前存储的梯度(12)上,结果为 24。这种累积是刻意为之的,并且有重要的用途。
这种默认行为的主要原因是为了方便梯度累积。当训练大型模型需要大批量数据以实现稳定收敛,但现有 GPU 内存无法一次性容纳如此大的批量时,此方法就很有用。
与其处理一个大批量,不如这样操作:
.backward()。为此迷你批量计算的梯度将添加到模型参数的 .grad 属性中。optimizer.step() 执行一次优化器更新步。此步骤使用所有迷你批量梯度的总和来更新模型权重,从而有效地模拟了更大批量的一次更新步。optimizer.zero_grad() 清除梯度。这使您可以使用模型所需的有效批量大小进行训练,即使它不能一次性完全载入内存,从而牺牲计算时间来提高内存效率。
optimizer.zero_grad() 的必要性在标准的训练循环中,您在每次迭代中处理一个批量、计算损失、计算梯度并更新权重,您通常不希望来自前一个批量的梯度影响当前的更新步。每个批量的梯度计算应该是独立的。
由于 PyTorch 默认累积梯度,如果在计算新批量的梯度之前未能清除它们,将导致不正确的更新。优化器将使用新旧梯度的混合,从而损害训练过程。
这就是为什么在标准的 PyTorch 训练循环中,您几乎总能看到 optimizer.zero_grad() 被调用的原因。它将优化器管理的所有参数的 .grad 属性重置,以确保随后的 .backward() 调用完全基于当前批量的损失来计算梯度。
典型的训练迭代结构如下所示:
# 假设 model, dataloader, loss_fn, 和 optimizer 已定义
# 遍历 epoch...
# 遍历批量...
# 1. 获取数据批量
inputs, labels = data_batch
inputs, labels = inputs.to(device), labels.to(device) # 将数据移动到适当的设备
# 2. 清零梯度
# 重要:在处理新批量之前清除之前的梯度
optimizer.zero_grad()
# 3. 前向传播:计算模型预测
outputs = model(inputs)
# 4. 计算损失
loss = loss_fn(outputs, labels)
# 5. 反向传播:计算梯度
loss.backward()
# 6. 优化器更新步:更新模型权重
optimizer.step()
# ... (记录日志、评估等)
optimizer.zero_grad() 的放置位置很重要。它应该在您计算当前迭代的损失并执行反向传播之前发生,确保当前批量的梯度计算有一个干净的开始。虽然它通常放在循环的开头,但技术上它只需要在 loss.backward() 之前发生。然而,将其放在开头是常见做法,并且能清楚地划分新批量处理的开始。
总而言之,梯度累积是 PyTorch 的一个内置功能,对于模拟更大的批量数据很有用。然而,在标准训练循环中,您必须通过在每次迭代开始时调用 optimizer.zero_grad() 来明确阻止这种累积,以确保模型更新仅基于当前批量的数据是正确的。
这部分内容有帮助吗?
torch.autograd的机制,包括梯度计算、.grad属性以及默认的梯度累加行为。© 2026 ApX Machine Learning用心打造