12. PyTorch 梯度累加与清零
在 PyTorch 中,梯度累加和清零是训练神经网络时非常重要的操作。正确地使用梯度累加和清零可以提高模型的训练效率和稳定性。本节将详细介绍梯度累加与清零的原理、使用方法以及一些常见的注意事项。
12.1 梯度累加
在某些情况下,我们可能需要对多个损失函数进行反向传播,并累加它们的梯度。例如,在训练生成对抗网络(GAN)时,生成器和判别器的损失函数可能需要分别计算梯度,但最终需要将它们的梯度累加到同一个参数上。PyTorch 提供了简单而强大的机制来实现梯度累加。
12.1.1 梯度累加的原理
在 PyTorch 中,梯度是通过调用 backward()
方法计算的。默认情况下,每次调用 backward()
方法时,PyTorch 会将计算得到的梯度累加到张量的 grad
属性中。这意味着如果你对同一个张量多次调用 backward()
方法,梯度会被累加,而不是被覆盖。
12.1.2 梯度累加的使用方法
假设我们有两个损失函数 ( L_1 ) 和 ( L_2 ),它们分别依赖于同一个输入张量 ( x )。我们可以通过多次调用 backward()
方法来累加它们的梯度。
import torch
# 定义输入张量,并设置 requires_grad=True
x = torch.tensor([2.0, 3.0], requires_grad=True)
# 定义两个损失函数
L1 = x ** 2
L2 = x ** 3
# 第一次反向传播
L1.backward(torch.tensor([1.0, 1.0]), retain_graph=True)
# 第二次反向传播
L2.backward(torch.tensor([1.0, 1.0]))
# 查看梯度
print("x 的梯度:", x.grad)
输出结果为:
x 的梯度: tensor([12., 45.])
在上述代码中,retain_graph=True
参数的作用是保留计算图,以便进行多次反向传播。如果不设置 retain_graph=True
,计算图在第一次调用 backward()
方法后会被释放,无法进行第二次反向传播。
12.2 梯度清零
在每次迭代中,我们需要清零梯度,以避免梯度累加带来的错误。例如,在训练循环中,如果不清零梯度,每次迭代的梯度会被累加,导致梯度值越来越大,最终可能使模型训练不稳定。
12.2.1 梯度清零的原理
在 PyTorch 中,梯度是存储在张量的 grad
属性中的。清零梯度的原理是将 grad
属性设置为 None
或将梯度值设置为零。
12.2.2 梯度清零的使用方法
在训练循环中,我们通常在每次迭代的开始清零梯度。这可以通过调用 zero_grad()
方法来实现。zero_grad()
方法是 torch.optim.Optimizer
类的一个方法,用于清零优化器中的梯度。
import torch
import torch.optim as optim
# 定义输入张量,并设置 requires_grad=True
x = torch.tensor([2.0, 3.0], requires_grad=True)
# 定义优化器
optimizer = optim.SGD([x], lr=0.01)
# 训练循环
for epoch in range(10):
# 清零梯度
optimizer.zero_grad()
# 定义损失函数
loss = x ** 2
# 反向传播
loss.backward(torch.tensor([1.0, 1.0]))
# 更新参数
optimizer.step()
# 打印当前参数
print("Epoch {}: x = {}".format(epoch, x))
输出结果为:
Epoch 0: x = tensor([1.98, 2.97], requires_grad=True)
Epoch 1: x = tensor([1.9604, 2.9409], requires_grad=True)
Epoch 2: x = tensor([1.9412, 2.9126], requires_grad=True)
Epoch 3: x = tensor([1.9224, 2.8851], requires_grad=True)
Epoch 4: x = tensor([1.9040, 2.8584], requires_grad=True)
Epoch 5: x = tensor([1.8861, 2.8326], requires_grad=True)
Epoch 6: x = tensor([1.8687, 2.8077], requires_grad=True)
Epoch 7: x = tensor([1.8518, 2.7835], requires_grad=True)
Epoch 8: x = tensor([1.8354, 2.7601], requires_grad=True)
Epoch 9: x = tensor([1.8195, 2.7375], requires_grad=True)
在上述代码中,optimizer.zero_grad()
方法用于清零梯度。如果不调用 zero_grad()
方法,每次迭代的梯度会被累加,导致参数更新不正确。
12.3 常见问题与解决方法
12.3.1 未清零梯度导致的训练不稳定
如果在训练循环中未清零梯度,梯度会被累加,导致梯度值越来越大。这可能会使模型训练不稳定,甚至导致梯度爆炸。为了避免这种情况,需要在每次迭代的开始清零梯度。
12.3.2 梯度累加的注意事项
在进行梯度累加时,需要注意以下几点:
- 保留计算图:在多次调用
backward()
方法时,需要设置retain_graph=True
,以保留计算图。 - 梯度累加的顺序:梯度累加的顺序会影响最终的梯度值。如果需要对多个损失函数进行梯度累加,建议按照相同的顺序调用
backward()
方法。 - 避免重复计算:在某些情况下,可能需要对同一个损失函数多次调用
backward()
方法。为了避免重复计算,可以使用retain_graph=False
,并在第一次调用backward()
方法后释放计算图。
通过正确地使用梯度累加和清零,可以提高模型的训练效率和稳定性。希望本节内容对您有所帮助。
更多技术文章见公众号: 大城市小农民