Neural network 变量';s backward()方法?

Neural network 变量';s backward()方法?,neural-network,conv-neural-network,backpropagation,pytorch,automatic-differentiation,Neural Network,Conv Neural Network,Backpropagation,Pytorch,Automatic Differentiation,我正在浏览,对retain\u变量的用法感到困惑(已弃用,现在称为retain\u图)。代码示例显示: class ContentLoss(nn.Module): def __init__(self, target, weight): super(ContentLoss, self).__init__() self.target = target.detach() * weight self.weight = weight

我正在浏览,对
retain\u变量的用法感到困惑(已弃用,现在称为
retain\u图
)。代码示例显示:

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(布尔,可选)–如果为False,则为用于计算的图形 毕业生将被释放。请注意,在几乎所有情况下,设置此 选择True是不需要的,通常可以在一个非常复杂的环境中进行处理 更有效的方法。默认为create_graph的值

因此,通过设置
retain\u graph=True
,我们并没有释放向后传递时为图形分配的内存。保留内存有什么好处?我们为什么需要它?

当网络有多个输出时,这是一个非常有用的功能。下面是一个完全虚构的例子:假设你想建立一个随机卷积网络,你可以问两个问题:输入图像中是否包含一只猫,以及图像中是否包含一辆汽车

实现这一点的一种方法是拥有一个共享卷积层的网络,但它有两个并行的分类层(请原谅我糟糕的ASCII图,但这应该是三个层,然后是三个完全连接的层,一个用于猫,一个用于汽车):

给定一幅我们希望在其上运行两个分支的图片,在训练网络时,我们可以通过几种方式来实现。首先(这可能是最好的,说明了这个例子有多糟糕),我们只需计算两次评估的损失并将损失相加,然后反向传播

然而,还有另一种情况——我们希望按顺序进行。首先我们希望通过一个分支进行反向支持,然后通过另一个分支(我以前有过这个用例,所以它不是完全虚构的)。在这种情况下,在一个图上运行
.backward()
也会破坏卷积层中的任何梯度信息,并且第二个分支的卷积计算(因为这些是与另一个分支共享的唯一计算)将不再包含图!这意味着,当我们试图通过第二个分支反向推进时,Pytorch将抛出一个错误,因为它找不到连接输入和输出的图形! 在这些情况下,我们可以通过简单地在第一次向后传递时保留图形来解决问题。然后,图形将不会被使用,而只会被不需要保留它的第一个向后过程使用

编辑:如果在所有反向过程中保留图形,则附加到输出变量的隐式图形定义将永远不会被释放。这里可能也有一个用例,但我想不出一个。因此,一般来说,您应该确保最后一次向后传递不会保留图形信息,从而释放内存

至于多个反向过程会发生什么:正如您所猜测的,pytorch通过将渐变添加到适当的位置(到变量的/parameters
.grad
属性)来累积渐变。
这可能非常有用,因为这意味着循环一个批次并一次处理一次,在最后累积梯度,将执行与执行完整批次更新相同的优化步骤(这也只汇总所有梯度)。虽然完全批处理更新可以更并行化,因此通常更可取,但在某些情况下,批处理计算要么非常、非常难以实现,要么根本不可能实现。然而,利用这种积累,我们仍然可以依靠配料带来的一些良好的稳定性能。(如果不是在性能增益上)

@cleros在使用
retain\u 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()
,我们必须将
d.backward()
中的参数
retain\u graph
设置为
True
,即

d.backward(retain_graph=True)
只要您在后退方法中使用
retain\u graph=True
,您就可以随时进行后退操作:

d.backward(retain_graph=True) # fine
e.backward(retain_graph=True) # fine
d.backward() # also fine
e.backward() # error will occur!
可以找到更有用的讨论

一个真实的用例 现在,一个真正的用例是多任务学习,其中您可能会有多个不同层次的损失。假设您有两个损失:
loss1
loss2
,它们位于不同的层中。为了将
loss1
loss2
w.r.t的梯度分别反向支撑到网络的可学习权重。在第一个反向传播损失中,必须在
backward()
方法中使用
retain\u graph=True

# 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。如果所有向后传递都保留图形,会发生什么情况?这只是浪费内存还是会出现其他问题?2.在你的例子中,假设我们也在训练所有的卷积层。在第一次向后通过时,将计算每个层的坡度。当我们运行第二次向后传递时,同一个卷积层的梯度是加在一起的吗?在你对答案的评论中添加了一个答案:-)这对我来说很有意义。即使您在最后一次向后传递时使用
retain\u graph=False
向后运行,未共享的分支(例如第一个运行的分支)仍然不会清除其资源。在您的示例中,
Conv->Conv->Conv
get在共享分支中被释放,但不是
--FC-FC-FC-cat?
来avoi
# 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