为什么我们需要显式调用 zero_grad()?

Why do we need to explicitly call zero_grad()?

为什么我们需要明确地将 PyTorch 中的梯度归零?为什么在调用 loss.backward() 时不能将梯度归零?将梯度保持在图形上并要求用户显式将梯度归零可以满足什么场景?

我们明确需要调用zero_grad(),因为在loss.backward()(计算梯度时)之后,我们需要使用optimizer.step()来进行梯度下降。更具体地说,梯度不会自动归零,因为这两个操作 loss.backward()optimizer.step() 是分开的,并且 optimizer.step() 需要刚刚计算的梯度。

另外,有时候,我们需要在一些批次之间累加梯度;为此,我们可以简单地多次调用 backward 并优化一次。

我有一个 PyTorch 中当前设置的用例。

如果使用在每一步都进行预测的递归神经网络 (RNN),则可能需要一个允许及时累积梯度的超参数。不在每个时间步都将梯度归零允许人们以有趣和新颖的方式使用时间反向传播 (BPTT)。

如果您想了解有关 BPTT 或 RNN 的更多信息,请参阅文章 Recurrent Neural Networks Tutorial, Part 3 – Backpropagation Through Time and Vanishing Gradients or The Unreasonable Effectiveness of Recurrent Neural Networks

在调用 .step() 之前将梯度保留在适当的位置,以防您想在多个批次中累积梯度(正如其他人提到的那样)。

它对于 after 调用 .step() 也很有用,以防您想为 SGD 实施动量,并且各种其他方法可能取决于之前的值更新梯度。

PyTorch 中有一个循环:

  • 当我们从输入中得到输出或y_hat时转发,
  • 计算损失 loss = loss_fn(y_hat, y)
  • loss.backward 当我们计算梯度时
  • optimizer.step 当我们更新参数时

或者在代码中:

for mb in range(10): # 10 mini batches
    y_pred = model(x)
    loss = loss_fn(y_pred, y)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

如果我们在 optimizer.step 之后不清除梯度,这是适当的步骤,或者就在下一个 backward() 梯度累积之前。 这是一个显示累积的例子:

import torch
w = torch.rand(5)
w.requires_grad_()
print(w) 
s = w.sum() 
s.backward()
print(w.grad) # tensor([1., 1., 1., 1., 1.])
s.backward()
print(w.grad) # tensor([2., 2., 2., 2., 2.])
s.backward()
print(w.grad) # tensor([3., 3., 3., 3., 3.])
s.backward()
print(w.grad) # tensor([4., 4., 4., 4., 4.])

loss.backward() 没有任何方式指定这个。

torch.autograd.backward(tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None)

从您可以指定的所有选项中,无法手动将梯度归零。就像前面的迷你示例中的这样:

w.grad.zero_()

有一些关于每次用 backward()(显然是以前的梯度)做 zero_grad() 并用 preserve_grads=True 保持梯度的讨论,但这从未实现。