参数retain_graph在Variable的backward()方法中意味着什么?

jva*_*ans 26 automatic-differentiation backpropagation neural-network conv-neural-network pytorch

我正在阅读神经转移pytorch教程,并对使用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
Run Code Online (Sandbox Code Playgroud)

文档中

retain_graph(bool,optional) - 如果为False,将释放用于计算grad的图形.请注意,几乎在所有情况下都不需要将此选项设置为True,并且通常可以以更有效的方式解决此问题.默认为create_graph的值.

因此,通过设置retain_graph= True,我们不会释放在向后传递上为图形分配的内存.保持这种记忆的优势是什么,我们为什么需要它?

jdh*_*hao 37

@cleros非常关注使用retain_graph=True.从本质上讲,它将保留任何必要的信息来计算某个变量,这样我们就可以向后传递它.

一个说明性的例子

在此输入图像描述

假设我们有一个如上所示的计算图.变量de是输出,并且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()
Run Code Online (Sandbox Code Playgroud)

当我们这样做时d.backward(),那很好.在此计算之后d,默认情况下将释放计算的图形部分以节省内存.因此,如果我们这样做e.backward(),将弹出错误消息.为了做到这一点e.backward(),我们必须将参数设置retain_graphTruein d.backward(),即

d.backward(retain_graph=True)
Run Code Online (Sandbox Code Playgroud)

只要您retain_graph=True在后向方法中使用,就可以随时向后执行:

d.backward(retain_graph=True) # fine
e.backward(retain_graph=True) # fine
d.backward() # also fine
e.backward() # error will occur!
Run Code Online (Sandbox Code Playgroud)

可以在这里找到更有用的讨论.

一个真实的用例

现在,一个真正的用例是多任务学习,你可能有多个损失,可能在不同的层.假设你有2负:loss1loss2他们居住在不同的层次.为了独立地支持您的网络的可学习权重的梯度loss1loss2wrt.您必须在第一个反向传播的损失中使用retain_graph=Truein 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
Run Code Online (Sandbox Code Playgroud)

  • 假设损失 1 随参数变化很快,但幅度很小。您需要采取一些小步骤来优化它,因为它并不顺利。损失2变化缓慢,但幅度很大。#2 将主导它们的总和,因此一个共享的 ADAM 将选择较大的学习率。但如果将它们分开,ADAM 将为损失 #2 选择较大的学习率,并为损失 #1 选择较小的学习率。(2/2) (17认同)
  • @MAsadAli 我会尝试。ADAM 的每个副本都存储自适应学习率参数,这些参数表示损失函数在参数空间中的“平滑”程度。如果两个损失的平滑度不同,则可能很难选择对两者都适用的值。(1/2) (9认同)
  • 为了避免使用`retain_graph = True`,您可以先执行“ loss = loss1 + loss2”,然后执行“ loss.backward()”。 (4认同)

cle*_*ros 13

当您有多个网络输出时,这是一个非常有用的功能.这是一个完全构成的例子:假设您想要构建一些随机卷积网络,您可以问两个问题:输入图像是否包含猫,图像是否包含汽车?

这样做的一种方法是拥有一个共享卷积层的网络,但是后面有两个平行的分类层(原谅我可怕的ASCII图,但这应该是三个轮换层,接着是三个完全连接的层,一个用于猫和一个汽车):

                    -- FC - FC - FC - cat?
Conv - Conv - Conv -|
                    -- FC - FC - FC - car?
Run Code Online (Sandbox Code Playgroud)

鉴于我们想要在两个分支上运行的图片,在培训网络时,我们可以通过多种方式实现这一目标.首先(这可能是最好的事情,说明示例有多糟糕),我们只是计算两次评估的损失并总结损失,然后反向传播.

但是,还有另一种情况 - 我们希望按顺序执行此操作.首先,我们想通过一个分支,然后通过另一个分支(我之前有过这个用例,所以它没有完全组成).在这种情况下,.backward()在一个图上运行也会破坏卷积层中的任何梯度信息,并且第二个分支的卷积计算(因为这些是与另一个分支共享的唯一)将不再包含图形!这意味着,当我们尝试通过第二个分支进行反向提示时,Pytorch将抛出一个错误,因为它找不到连接输入和输出的图形!在这些情况下,我们可以通过在第一个向后传递上简单地保留图形来解决问题.然后不会消耗该图形,而是仅由不需要保留它的第一个向后传递消耗.

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

至于多个向后传递会发生什么:正如您猜测的那样,pytorch通过将它们就地添加到变量的/ parameters .grad属性来累积渐变.这可能非常有用,因为它意味着循环遍历批处理并一次处理一次,在结束时累积渐变,将执行与完整批处理更新相同的优化步骤(仅将所有渐变总结为好).虽然完全批量更新可以更多地并行化,因此通常是优选的,但是存在批量计算要么非常非常难以实现要么根本不可能的情况.然而,使用这种积累,我们仍然可以依赖于批量带来的一些良好的稳定特性.(如果不是性能增益)

  • 这对我来说很有意义。看起来即使您在最后一次向后传递时使用 `retain_graph=False` 向后运行,*不* 共享的分支,例如第一个运行的分支,仍然不会清除其资源。在您的示例中,`Conv -> Conv -> Conv` 在共享分支中被释放,但不是 `-- FC - FC - FC - cat?` (3认同)