为啥我们需要显式调用 zero_grad()? [复制]

Posted

技术标签:

【中文标题】为啥我们需要显式调用 zero_grad()? [复制]【英文标题】:Why do we need to explicitly call zero_grad()? [duplicate]为什么我们需要显式调用 zero_grad()? [复制] 【发布时间】:2017-11-27 16:05:15 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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 留住毕业生,但这从未实现。

【讨论】:

【参考方案2】:

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

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

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

【讨论】:

这个答案需要更清楚。也许举个例子会有所帮助。 这里是示例的解释 + # 它将更新的变量的梯度(这是可学习的 + # 模型的权重)。这是因为默认情况下,每当调用 .backward() + # 时,梯度都会在缓冲区中累积(即不被覆盖)。查看 torch.autograd.backward 的文档以获取更多详细信息。 optimizer.zero_grad()【参考方案3】:

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

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

【讨论】:

实际上,您提到了“minibatch”(或“batch subpartition”)与“batch” - 这是累积在前馈网络(不仅仅是 RNN)中有用的情况。当我们想要更大的批次进行优化,但有物理限制(例如 GPU 内存)来增加样本数量时。 你能举一个我们需要累积梯度的例子吗? @RedFloyd 以防你想有效地训练大批量(假设为 64),但你的 GPU 只能同时容纳 32 个示例。您需要通过 2 步累积梯度,并且每 2 步只调用一次optimizer.step()【参考方案4】:

在调用.step() 之前保留渐变非常有用,以防您想在多个批次中累积渐变(正如其他人提到的那样)。

如果您想为 SGD 实现动量,调用 .step() 之后也很有用,并且各种其他方法可能取决于先前更新的梯度值。

【讨论】:

以上是关于为啥我们需要显式调用 zero_grad()? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

(为啥)我们需要在 RDD 上调用缓存还是持久化

为啥我们不需要在 pl sql 的 for 循环中打开和获取显式游标?

为啥需要显式定义静态变量?

为啥我不需要显式借出一个借来的可变变量?

为啥要显式调用 asyncio.StreamWriter.drain?

为啥我必须显式调用我的库?