【发布时间】:2020-09-15 22:15:48
【问题描述】:
我正在尝试理解PyTorch 中梯度累积的内部工作原理。我的问题与这两个有点相关:
Why do we need to call zero_grad() in PyTorch?
Why do we need to explicitly call zero_grad()?
对第二个问题的已接受答案的评论表明,如果 minibatch 太大而无法在单个前向传递中执行梯度更新,则可以使用累积梯度,因此必须将其拆分为多个子批次。
考虑以下玩具示例:
import numpy as np
import torch
class ExampleLinear(torch.nn.Module):
def __init__(self):
super().__init__()
# Initialize the weight at 1
self.weight = torch.nn.Parameter(torch.Tensor([1]).float(),
requires_grad=True)
def forward(self, x):
return self.weight * x
if __name__ == "__main__":
# Example 1
model = ExampleLinear()
# Generate some data
x = torch.from_numpy(np.array([4, 2])).float()
y = 2 * x
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
y_hat = model(x) # forward pass
loss = (y - y_hat) ** 2
loss = loss.mean() # MSE loss
loss.backward() # backward pass
optimizer.step() # weight update
print(model.weight.grad) # tensor([-20.])
print(model.weight) # tensor([1.2000]
这正是人们所期望的结果。现在假设我们要利用梯度累积逐个样本处理数据集:
# Example 2: MSE sample-by-sample
model2 = ExampleLinear()
optimizer = torch.optim.SGD(model2.parameters(), lr=0.01)
# Compute loss sample-by-sample, then average it over all samples
loss = []
for k in range(len(y)):
y_hat = model2(x[k])
loss.append((y[k] - y_hat) ** 2)
loss = sum(loss) / len(y)
loss.backward() # backward pass
optimizer.step() # weight update
print(model2.weight.grad) # tensor([-20.])
print(model2.weight) # tensor([1.2000]
再次如预期的那样,在调用.backward() 方法时计算梯度。
最后是我的问题:“幕后”到底发生了什么?
我的理解是计算图是动态更新的,从 <PowBackward> 到 <AddBackward> <DivBackward> 对 loss 变量的操作,并且除了每个前向传递使用的数据的任何信息都不会保留在任何地方loss 张量,可以更新直到向后传递。
上述段落中的推理是否有任何警告?最后,在使用梯度累积时是否有任何最佳实践可以遵循(即我在示例 2 中使用的方法是否会适得其反)?
【问题讨论】:
-
您的
model2描述中有一个小误解。 没有梯度累积与描述中的描述不同。您只调用一次loss.backward()并且 not 为每个小批量(这里只有 1 个样本)。梯度计算,也就是累积,是在 PyTorch 中用 C++ 编写的。正确的梯度累积示例,请看gradient accumulation gist -
@kmario23 是的,我的错。显然,在第二个例子中,梯度在我脑海中累积只是因为平均(梯度)=梯度(平均),没有为图形分配“物理”值。
标签: python deep-learning pytorch gradient-descent