ChatGPT解决这个技术问题 Extra ChatGPT

为什么我们需要在 PyTorch 中调用 zero_grad()?

为什么需要在训练期间调用 zero_grad()

|  zero_grad(self)
|      Sets gradients of all model parameters to zero.

k
kmario23

PyTorch 中,对于 训练 阶段的每个小批量,我们通常希望在开始执行反向传播之前将梯度显式设置为零(即更新 W< /strong>eightsbiases),因为 PyTorch 在随后的反向传递中累积梯度。这种累积行为在训练 RNN 或当我们想要计算多个 mini-batches 的总损失梯度时很方便。因此,每个 loss.backward() 调用的默认操作都设置为 accumulate (i.e. sum) the gradients

因此,当您开始训练循环时,理想情况下您应该 zero out the gradients 以便正确更新参数。否则,梯度将是您已用于更新模型参数的旧梯度和新计算的梯度的组合。因此,它会指向一些其他方向,而不是指向 minimum(或 maximum,在最大化目标的情况下)的预期方向。

这是一个简单的例子:

import torch
from torch.autograd import Variable
import torch.optim as optim

def linear_model(x, W, b):
    return torch.matmul(x, W) + b

data, targets = ...

W = Variable(torch.randn(4, 3), requires_grad=True)
b = Variable(torch.randn(3), requires_grad=True)

optimizer = optim.Adam([W, b])

for sample, target in zip(data, targets):
    # clear out the gradients of all Variables 
    # in this optimizer (i.e. W, b)
    optimizer.zero_grad()
    output = linear_model(sample, W, b)
    loss = (output - target) ** 2
    loss.backward()
    optimizer.step()

或者,如果你正在做一个普通的梯度下降,那么:

W = Variable(torch.randn(4, 3), requires_grad=True)
b = Variable(torch.randn(3), requires_grad=True)

for sample, target in zip(data, targets):
    # clear out the gradients of Variables 
    # (i.e. W, b)
    W.grad.data.zero_()
    b.grad.data.zero_()

    output = linear_model(sample, W, b)
    loss = (output - target) ** 2
    loss.backward()

    W -= learning_rate * W.grad.data
    b -= learning_rate * b.grad.data

笔记:

当在损失张量上调用 .backward() 时,会发生梯度的累积(即总和)。

从 v1.7.0 开始,Pytorch 提供了将梯度重置为 None optimizer.zero_grad(set_to_none=True) 的选项,而不是用零张量填充它们。文档声称此设置减少了内存需求并略微提高了性能,但如果不小心处理可能会出错。


非常感谢,这真的很有帮助!你碰巧知道张量流是否有这种行为吗?
只是为了确定..如果你不这样做,那么你会遇到梯度爆炸问题,对吧?
@zwep 如果我们累积梯度,这并不意味着它们的幅度会增加:一个例子是梯度的符号是否不断翻转。所以它不能保证你会遇到梯度爆炸问题。此外,即使您正确归零,也会存在爆炸梯度。
当您运行 vanilla 梯度下降时,当您尝试更新权重时,您是否不会收到“需要 grad 的叶变量已用于就地操作”错误?
关于这个的后续问题:所以你是说我们不应该在训练 RNN 模型(例如 LSTM)时调用 optimizer.zero_grad() 吗?
j
jerryIsHere

虽然这个想法可以从选择的答案中得出,但我觉得我想明确地写出来。

能够决定何时调用 optimizer.zero_grad()optimizer.step() 为优化器在训练循环中如何累积和应用梯度提供了更多自由。当模型或输入数据很大并且一个实际的训练批次不适合 gpu 卡时,这一点至关重要。

在这个来自 google-research 的示例中,有两个参数,名为 train_batch_sizegradient_accumulation_steps

train_batch_size 是前向传递的批大小,在 loss.backward() 之后。这受 gpu 内存的限制。

gradient_accumulation_steps 是实际的训练批量大小,其中累积了多次前向传递的损失。这不受 gpu 内存的限制。

从这个示例中,您可以看到 optimizer.zero_grad() 后面可能是 optimizer.step()NOT loss.backward()loss.backward() 在每次迭代中调用(第 216 行),但 optimizer.zero_grad()optimizer.step() 仅在累积的训练批次数等于 gradient_accumulation_steps 时调用(第 227 行在第 219 行的 if 块内)

https://github.com/google-research/xtreme/blob/master/third_party/run_classify.py

也有人在询问 TensorFlow 中的等效方法。我猜 tf.GradientTape 有同样的目的。

(我还是AI库的新手,如果我说的有问题,请纠正我)


感谢这个例子。它帮助了我。
这与训练具有有限 GPU 内存的大型模型有关。您的想法在这篇不错的帖子中得到了扩展:towardsdatascience.com/…
D
Divya Bansal

如果您使用梯度方法减少误差(或损失),zero_grad() 会从上一步开始重新开始循环而不会造成损失。

如果不使用zero_grad(),损失将根据需要增加而不是减少。

例如:

如果您使用 zero_grad(),您将获得以下输出:

model training loss is 1.5
model training loss is 1.4
model training loss is 1.3
model training loss is 1.2

如果您不使用 zero_grad(),您将获得以下输出:

model training loss is 1.4
model training loss is 1.9
model training loss is 2
model training loss is 2.8
model training loss is 3.5

这至少可以说是令人困惑的。什么循环被重新启动?损失增加/减少间接受到影响,当你做.zero_grad()时它会增加,当你不做时它会减少。您显示的输出来自哪里?
亲爱的 dedObed (这个例子是如果你从正确的代码中删除 zero_grad ),我们谈论 .zero_grad() 函数,这个函数只是开始循环而没有最后的结果 ، 如果损失增加正在增加你应该检查你的输入(写您在新主题中的问题并将链接发给我。
我(我想我)对 PyTorch 的理解已经足够好了。我只是指出我认为您回答中的缺陷 - 不清楚,得出快速结论,显示输出谁知道什么。
u
user3505444

您不必调用 grad_zero() 或者可以衰减梯度,例如:

optimizer = some_pytorch_optimizer
# decay the grads :
for group in optimizer.param_groups:
    for p in group['params']:
        if p.grad is not None:
            ''' original code from git:
            if set_to_none:
                p.grad = None
            else:
                if p.grad.grad_fn is not None:
                    p.grad.detach_()
                else:
                    p.grad.requires_grad_(False)
                p.grad.zero_()
                
            '''
            p.grad = p.grad / 2

这样学习就更继续了


R
Rahul Dogra

在前馈传播期间,权重被分配给输入,并且在第一次迭代之后,权重被初始化模型从样本(输入)中学到的东西。当我们开始反向传播时,我们想要更新权重,以使我们的成本函数损失最小。所以我们清除之前的权重,以获得更好的权重。我们在训练中一直这样做,我们在测试中不这样做,因为我们在训练时间得到了最适合我们数据的权重。希望这会更清楚!