在训练期间为什么需要调用zero_grad()
?
| zero_grad(self)
| Sets gradients of all model parameters to zero.
在训练期间为什么需要调用zero_grad()
?
| zero_grad(self)
| Sets gradients of all model parameters to zero.
PyTorch
中,在训练阶段的每个小批量中,我们通常希望在开始进行反向传播(即更新权重和偏置)之前明确将梯度设置为零,因为PyTorch在后续的反向传递中累积梯度。这种累积行为在训练RNN或者我们想要计算损失在多个小批量上求和的梯度时非常方便。因此,默认操作已设置为在每次loss.backward()
调用时累加(即求和)梯度。将梯度清零
,以便正确地进行参数更新。否则,梯度将是旧梯度和新计算梯度的组合。因此,它将指向与预期方向不同的方向,朝向最小值(或最大值,在最大化目标的情况下)。
这里是一个简单的例子:
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
注意:
loss
张量的.backward()
方法时。None
optimizer.zero_grad(set_to_none=True)
的选项,而不是用零张量填充它们。文档声称这个设置可以减少内存需求并略微提高性能,但如果处理不当可能会出现错误。optimizer.zero_grad()
和optimizer.step()
在训练循环中提供了更多关于梯度如何被优化器累积和应用的自由。当模型或输入数据很大,并且一个训练批次无法适应GPU时,这一点至关重要。train_batch_size
和gradient_accumulation_steps
。
train_batch_size
是前向传播后的批处理大小,遵循loss.backward()
。这受到GPU内存的限制。
gradient_accumulation_steps
是实际的训练批次大小,多次前向传播的损失会累积起来。这不受GPU内存的限制。
optimizer.zero_grad()
后面跟着optimizer.step()
,但不是loss.backward()
。在每次迭代(第216行)中都会调用loss.backward()
,但只有当累积的训练批次数等于gradient_accumulation_steps
时(第219行中的if
块内的第227行),才会调用optimizer.zero_grad()
和optimizer.step()
。loss.backward() # Compute gradients.
optimizer.step() # Tell the optimizer the gradients, then step.
optimizer.zero_grad() # Zero the gradients to start fresh next time.
# INSTEAD OF:
model.zero_grad()
# or
optimizer.zero_grad()
# CONSIDER:
for param in model.parameters():
param.grad = None
...但是其中一位开发者在5年前的评论中提到了this:
主要区别在于包含梯度的张量不会在每次反向传播时重新分配内存。由于内存分配相当昂贵(尤其在GPU上),这样更加高效。
还有其他微妙的差异,比如一些优化器在梯度为0或None时表现不同。我确信还有其他地方也会有类似的行为。
...另一方面,原地操作通常被认为不是必要的,甚至在某些情况下可能是次优的,所以我想对于两种方法的性能会有所不同。
# let us write a training loop
torch.manual_seed(42)
epochs = 200
for epoch in range(epochs):
model_1.train()
y_pred = model_1(X_train)
loss = loss_fn(y_pred,y_train)
optimizer.zero_grad()
loss.backward()
optimizer.step()
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在前向传播期间,权重被分配给输入,在第一次迭代后,权重被初始化为模型从样本(输入)中学到的内容。当我们开始反向传播时,我们希望更新权重以获得最小化成本函数的损失。因此,我们清除先前的权重,以获得更好的权重。这是我们在训练中不断进行的操作,而在测试中我们不执行此操作,因为我们已经在训练时间内获得了最适合我们数据的权重。希望这能更清楚!
你不必反复调用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
这样学习就更加连续了
optimizer.zero_grad()
在output = linear_model(sample, W, b)
之前? - mrgloomzero_grad()
吗? - Alaa M.