我有一个特殊用例,必须将推理和反向传播分开:我必须推理所有图片和 slice 输出分成几批,然后逐批反向传播。我不需要需要更新网络的导航。
我将cifar10_tutorial的代码段修改为以下内容以模拟我的问题:j
是一个变量,代表通过我自己的逻辑返回的索引,并且我希望某些变量的梯度。
for epoch in range(2): # loop over the dataset multiple times
for i, data in enumerate(trainloader, 0):
# get the inputs
inputs, labels = data
inputs.requires_grad = True
# zero the parameter gradients
optimizer.zero_grad()
# forward + backward + optimize
outputs = net(inputs)
for j in range(4): # j is given by external logic in my own case
loss = criterion(outputs[j, :].unsqueeze(0), labels[j].unsqueeze(0))
loss.backward()
print(inputs.grad.data[j, :]) # what I really want
我遇到以下错误:
RuntimeError:尝试第二次向后浏览图形,但缓冲区已被释放。第一次回叫时,请指定keep_graph = True。
我的问题是:
根据我的理解,由于第一个向后传播将整个outputs
和outputs[1,:].unsqueeze(0)
向后释放,所以第二个向后传播失败了,因此出现了问题。我说的对吗?
在我的情况下,如果设置retain_graph=True
,根据此post的运行速度会越来越慢吗?
有没有更好的方法可以实现我的目标?
答案 0 :(得分:2)
是的,您是正确的。第一次(第一次迭代)通过outputs
进行反向传播时,缓冲区将被释放,并且在接下来的时间(循环的下一次迭代)会失败。用于该计算的数据已被删除。
是的,该图越来越大,因此根据GPU (或CPU)的使用情况和您的网络,它可能变慢。我曾经使用过一次,但是速度慢得多,但这在很大程度上取决于您的网络体系结构。但是肯定地,使用retain_graph=True
会比没有存储更多的内存。
根据您的outputs
和labels
的形状,您应该能够一次计算出所有outputs
和labels
的损耗:
criterion(outputs, labels)
您必须跳过j
-循环,这也将使您的代码更快。也许您需要重塑(分别为view
)数据,但这应该可以正常工作。
如果由于某些原因您不能这样做,则可以手动对张量进行求和,并在循环之后调用backward
。这也应该可以正常工作,但是比上面的解决方案慢。
因此,您的代码将如下所示:
# init loss tensor
loss = torch.tensor(0.0) # move to GPU if you're using one
for j in range(4):
# summing up your loss for every j
loss += criterion(outputs[j, :].unsqueeze(0), labels[j].unsqueeze(0))
# ...
# calling backward on the summed loss - getting gradients
loss.backward()
# as you call backward now only once on the outputs
# you shouldn't get any error and you don't have to use retain_graph=True
编辑:
损失的累积和以后的回叫是完全等效的,这是一个有或没有累积损失的小例子:
首先创建一些数据data
:
# w in this case will represent a very simple model
# I leave out the CE and just use w to map the output to a scalar value
w = torch.nn.Linear(4, 1)
data = [torch.rand(1, 4) for j in range(4)]
data
如下:
[tensor([[0.4593, 0.3410, 0.1009, 0.9787]]),
tensor([[0.1128, 0.0678, 0.9341, 0.3584]]),
tensor([[0.7076, 0.9282, 0.0573, 0.6657]]),
tensor([[0.0960, 0.1055, 0.6877, 0.0406]])]
让我们先做一下,就像您正在做的那样,分别对每个迭代j
进行回调:
# code for directly applying backward
# zero the weights layer w
w.zero_grad()
for j, inp in enumerate(data):
# activate grad flag
inp.requires_grad = True
# remove / zero previous gradients for inputs
inp.grad = None
# apply model (only consists of one layer in our case)
loss = w(inp)
# calling backward on every output separately
loss.backward()
# print out grad
print('Input:', inp)
print('Grad:', inp.grad)
print()
print('w.weight.grad:', w.weight.grad)
以下是每个输入的打印输出,以及相应的模型梯度和各个梯度。在我们简化的情况下,w
层:
Input: tensor([[0.4593, 0.3410, 0.1009, 0.9787]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.1128, 0.0678, 0.9341, 0.3584]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.7076, 0.9282, 0.0573, 0.6657]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.0960, 0.1055, 0.6877, 0.0406]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
w.weight.grad: tensor([[1.3757, 1.4424, 1.7801, 2.0434]])
现在,我们不再对每次迭代 j 都调用一次,而是累加值,并对总和调用backward
并比较结果:
# init tensor for accumulation
loss = torch.tensor(0.0)
# zero layer gradients
w.zero_grad()
for j, inp in enumerate(data):
# activate grad flag
inp.requires_grad = True
# remove / zero previous gradients for inputs
inp.grad = None
# apply model (only consists of one layer in our case)
# accumulating values instead of calling backward
loss += w(inp).squeeze()
# calling backward on the sum
loss.backward()
# printing out gradients
for j, inp in enumerate(data):
print('Input:', inp)
print('Grad:', inp.grad)
print()
print('w.grad:', w.weight.grad)
让我们看一下结果:
Input: tensor([[0.4593, 0.3410, 0.1009, 0.9787]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.1128, 0.0678, 0.9341, 0.3584]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.7076, 0.9282, 0.0573, 0.6657]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
Input: tensor([[0.0960, 0.1055, 0.6877, 0.0406]], requires_grad=True)
Grad: tensor([[-0.0999, 0.2665, -0.1506, 0.4214]])
w.grad: tensor([[1.3757, 1.4424, 1.7801, 2.0434]])
比较结果时,我们可以看到两者相同。
这是一个非常简单的示例,但是我们仍然可以看到,在每个张量上调用backward()
并对张量求和,然后调用backward()
就两个输入的结果梯度而言是等效的和重量。
如 3 中所述,一次对所有 j 使用CE时,可以使用标记reduction='sum'
为了总结与上述相同的行为并汇总CE值,默认值为“平均值”,这可能会导致结果略有不同。