Python 理解PyTorch中的累积梯度
我试图理解Python 理解PyTorch中的累积梯度,python,deep-learning,pytorch,gradient-descent,Python,Deep Learning,Pytorch,Gradient Descent,我试图理解PyTorch中梯度累积的内部工作原理。我的问题与这两个有点相关: 对第二个问题的公认答案的评论表明,如果一个小批次太大,无法在单个正向过程中执行梯度更新,因此必须将其拆分为多个子批次,则可以使用累积梯度 考虑以下玩具示例: import numpy as np import torch class ExampleLinear(torch.nn.Module): def __init__(self): super().__init__()
PyTorch
中梯度累积的内部工作原理。我的问题与这两个有点相关:
对第二个问题的公认答案的评论表明,如果一个小批次太大,无法在单个正向过程中执行梯度更新,因此必须将其拆分为多个子批次,则可以使用累积梯度
考虑以下玩具示例:
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()
方法时计算梯度
最后,我的问题是:“引擎盖下”到底发生了什么
我的理解是,对于损失
变量,计算图会从
动态更新为
操作,除了损失
张量外,任何地方都不会保留关于每个向前传球所用数据的信息,该张量可在向后传球之前更新
以上段落中的推理是否有任何警告?最后,在使用渐变累积时,是否有任何最佳实践可遵循(即,我在示例2中使用的方法是否会产生反效果)?您实际上并没有累积渐变。如果您有一个.backward()
调用,只需退出optimizer.zero_grad()
就没有任何效果,因为梯度从一开始就已经为零(技术上讲无
,但它们将为零)
自动初始化为零)
两个版本之间的唯一区别是如何计算最终损失。第二个示例中的for循环与第一个示例中的PyTorch进行相同的计算,但您单独进行计算,PyTorch无法优化(并行化和向量化)for循环,这在GPU上产生了特别惊人的差异,假设张量不是很小
在讨论渐变累积之前,让我们先从您的问题开始:
最后,我的问题是:“引擎盖下”到底发生了什么
当且仅当其中一个操作数已经是计算图的一部分时,在计算图中跟踪张量上的每个操作。当您设置张量的requires_grad=True
时,它将创建一个具有单个顶点的计算图,即张量本身,它将保持图中的一片叶子。使用该张量的任何操作都将创建一个新的顶点,这是该操作的结果,因此操作数到该顶点之间有一条边,跟踪所执行的操作
a=torch.tensor(2.0,需要_grad=True)
b=火炬张量(4.0)
c=a+b#=>张量(6,梯度fn=
每个中间张量自动需要梯度,并有一个梯度fn
,该函数用于计算与其输入相关的偏导数。由于链式规则,我们可以以相反的顺序遍历整个图形,以计算与每个单叶相关的导数,这是我们使用的参数蚂蚁优化。这就是反向传播的思想,也被称为反向模式分化。更多细节,我建议阅读
PyTorch使用了这个确切的想法,当您调用loss.backward()
时,它以相反的顺序遍历图形,从loss
开始,并计算每个顶点的导数。每当到达一片叶子时,计算出的张量导数存储在其.grad
属性中
在您的第一个示例中,这将导致:
MeanBackward -> PowBackward -> SubBackward -> MulBackward`
第二个示例几乎相同,只是您手动计算平均值,而不是为损失计算单个路径,而是为损失计算的每个元素计算多个路径。为了澄清,单个路径也计算每个元素的导数,但在内部,这再次为某些选项打开了可能性误导
#示例1
损失=(y-y*2
#=>张量([16,4.],梯度fn=)
#例2
损失=[]
对于范围内的k(len(y)):
y_hat=model2(x[k])
损失追加((y[k]-y_-hat)**2)
损失
#=>[张量([16.],梯度fn=),张量([4.],梯度fn=)]
在任何一种情况下,都会创建一个只反向传播一次的图,这就是它不被视为梯度累积的原因
梯度积累
梯度累积是指在更新参数之前执行多次向后传递的情况。目标是对多个输入(批次)具有相同的模型参数,然后根据所有这些批次更新模型参数,而不是在每个批次之后执行更新
让我们重温一下您的示例。x
的大小为[2],这是我们整个数据集的大小。出于某种原因,我们需要基于整个数据集计算梯度。当使用2的批大小时,这是很自然的情况,因为我们将一次拥有整个数据集。但是如果我们只能拥有大小为1的批,会发生什么?我们可以单独运行它们,并在每个批之后更新模型和往常一样,但是我们不计算整个数据集的梯度
我们需要做的是,使用相同的模型参数单独运行每个样本,并在不更新模型的情况下计算梯度。现在您可能会想,这不是您在第二个版本中所做的吗?几乎,但不完全,并且您的版本中存在一个关键问题,即您使用的内存量与第一个版本,因为您有相同的计算,因此计算图中的值数量相同
我们如何释放内存?我们需要去掉前一批的张量和计算图,因为这会使用大量内存来跟踪反向传播所需的所有内容
Batch size 1 (batch 0) - grad: tensor([-16.])
Batch size 1 (batch 0) - weight: tensor([1.], requires_grad=True)
Batch size 1 (batch 1) - grad: tensor([-20.])
Batch size 1 (batch 1) - weight: tensor([1.], requires_grad=True)
Batch size 1 (final) - grad: tensor([-20.])
Batch size 1 (final) - weight: tensor([1.2000], requires_grad=True)