我正在浏览neural transfer pytorch tutorial并对使用retain_variable
感到困惑(已弃用,现在称为retain_graph
)。代码示例显示:
class ContentLoss(nn.Module):
def __init__(self, target, weight):
super(ContentLoss, self).__init__()
self.target = target.detach() * weight
self.weight = weight
self.criterion = nn.MSELoss()
def forward(self, input):
self.loss = self.criterion(input * self.weight, self.target)
self.output = input
return self.output
def backward(self, retain_variables=True):
#Why is retain_variables True??
self.loss.backward(retain_variables=retain_variables)
return self.loss
retain_graph(bool,optional) - 如果为False,则用于计算的图形 毕业生将被释放。请注意,在几乎所有情况下设置此项 不需要选项为True,通常可以在很多方面解决 更有效的方式。默认为create_graph的值。
因此,通过设置retain_graph= True
,我们不会释放在向后传递上为图形分配的内存。保持这种记忆的优势是什么,为什么我们需要它呢?
答案 0 :(得分:26)
@cleros关于retain_graph=True
的使用非常重要。从本质上讲,它将保留任何必要的信息来计算某个变量,以便我们可以向后传递它。
假设我们有一个如上所示的计算图。变量d
和e
是输出,a
是输入。例如,
import torch
from torch.autograd import Variable
a = Variable(torch.rand(1, 4), requires_grad=True)
b = a**2
c = b*2
d = c.mean()
e = c.sum()
当我们d.backward()
时,这很好。在此计算之后,默认情况下将释放计算d
的图形部分以节省内存。因此,如果我们执行e.backward()
,则会弹出错误消息。为了e.backward()
,我们必须在retain_graph
中将参数True
设置为d.backward()
,即
d.backward(retain_graph=True)
只要在后向方法中使用retain_graph=True
,就可以随时向后执行:
d.backward(retain_graph=True) # fine
e.backward(retain_graph=True) # fine
d.backward() # also fine
e.backward() # error will occur!
可以找到更有用的讨论here。
目前,一个真实的用例是多任务学习,你可能会遇到多个不同层次的损失。假设您有2个损失:loss1
和loss2
,它们位于不同的层中。为了将loss1
和loss2
w.r.t的梯度单独支持到您网络的可学习权重。在第一次反向传播的损失中,您必须在retain_graph=True
方法中使用backward()
。
# suppose you first back-propagate loss1, then loss2 (you can also do the reverse)
loss1.backward(retain_graph=True)
loss2.backward() # now the graph is freed, and next process of batch gradient descent is ready
optimizer.step() # update the network parameters
答案 1 :(得分:10)
当您有多个网络输出时,这是一个非常有用的功能。这是一个完全构成的例子:想象你想构建一些随机卷积网络,你可以问两个问题:输入图像是否包含猫,图像是否包含汽车?
这样做的一种方法是拥有一个共享卷积层的网络,但是后面有两个并行的分类层(原谅我可怕的ASCII图,但这应该是三个轮换层,后面是三个完全连接的层,一个用于猫,一个用于汽车):
-- FC - FC - FC - cat?
Conv - Conv - Conv -|
-- FC - FC - FC - car?
鉴于我们想要运行两个分支的图片,在培训网络时,我们可以通过多种方式实现这一目标。首先(这可能是最好的事情,说明示例有多糟糕),我们只是计算两次评估的损失并总结损失,然后反向传播。
但是,还有另一种情况 - 我们希望按顺序执行此操作。首先,我们想通过一个分支,然后通过另一个分支(我之前有过这个用例,所以它没有完全组成)。在这种情况下,在一个图上运行.backward()
也会破坏卷积层中的任何梯度信息,并且第二个分支的卷积计算(因为这些是与另一个分支共享的唯一分支)不会再包含一个图形!这意味着,当我们尝试通过第二个分支进行反向提示时,Pytorch将抛出一个错误,因为它找不到连接输入和输出的图形!
在这些情况下,我们可以通过在第一个向后传递上简单地保留图形来解决问题。然后不会消耗该图形,而是仅由不需要保留它的第一个向后传递消耗。
编辑:如果在所有后向传递中保留图形,则永远不会释放附加到输出变量的隐式图形定义。这里也可能有一个用例,但我想不出一个。所以一般来说,你应该确保最后一次向后传递通过不保留图形信息来释放内存。
对于多个向后传递会发生什么:正如您猜测的那样,pytorch通过将它们就地添加(到变量&f;参数.grad
属性)来累积渐变。
这可能非常有用,因为它意味着循环遍历批处理并一次处理一次,在结束时累积渐变,将执行与完整批处理更新相同的优化步骤(仅将所有渐变总结为好)。虽然完全批量更新可以更多地并行化,因此通常是优选的,但是存在批量计算要么非常非常难以实现要么根本不可能的情况。然而,使用这种积累,我们仍然可以依赖于批量带来的一些良好的稳定特性。 (如果不是性能提升)