【问题标题】:Understanding the reason behind 'RuntimeError: leaf variable has been moved into the graph interior'了解“RuntimeError:叶变量已移至图形内部”背后的原因
【发布时间】:2023-04-11 02:37:01
【问题描述】:

我正在尝试了解 pytorch 以及 autograd 在其中的工作原理。我尝试通过用其他张量的值填充它然后检查梯度来创建一个张量。但是,如果我不将requires_grad 设置为等于False,我会遇到RuntimeError: leaf variable has been moved into the graph interior

代码:

x = torch.ones(3,5,requires_grad=True)

y = x+2

z = y*y*3

out1 = z.mean()
out2 = 2*z.mean()

outi = torch.empty(2,requires_grad=True)

outi[0] = out1
outi[1] = out2

outi.backward(torch.tensor([0.,1.]))

输出:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-22-1000fc52a64c> in <module>
     13 outi[1] = out2
     14 
---> 15 outi.backward(torch.tensor([0.,1.]))

~/anaconda3/envs/pytorch/lib/python3.8/site-packages/torch/tensor.py in backward(self, gradient, retain_graph, create_graph)
    183                 products. Defaults to ``False``.
    184         """
--> 185         torch.autograd.backward(self, gradient, retain_graph, create_graph)
    186 
    187     def register_hook(self, hook):

~/anaconda3/envs/pytorch/lib/python3.8/site-packages/torch/autograd/__init__.py in backward(tensors, grad_tensors, retain_graph, create_graph, grad_variables)
    123         retain_graph = create_graph
    124 
--> 125     Variable._execution_engine.run_backward(
    126         tensors, grad_tensors, retain_graph, create_graph,
    127         allow_unreachable=True)  # allow_unreachable flag

RuntimeError: leaf variable has been moved into the graph interior

但是,我可以将 requires_grad 更改为 False,它会正常工作

x = torch.ones(3,5,requires_grad=True)

y = x+2

z = y*y*3

out1 = z.mean()
out2 = 2*z.mean()

outi = torch.empty(2,requires_grad=False)

outi[0] = out1
outi[1] = out2

outi.backward(torch.tensor([0.,1.]))

输出:

empty. it worked

有人可以帮我了解幕后发生的事情,以及将 require_grad 设置为 True 导致这种行为发生了什么变化吗?感谢阅读

【问题讨论】:

    标签: pytorch


    【解决方案1】:

    简介

    首先,PyTorch 中leaf 变量的定义,您可以查看official documentation for tensor.is_leaf(强调我的):

    所有具有requires_gradFalse 的张量将是叶子 约定的张量。

    对于具有requires_gradTrue 的张量,它们将是 叶张量(如果它们是由用户创建的)。这意味着他们 不是操作的结果,所以grad_fn is None

    那么让我们看看它是如何在原始代码中查找outi 变量的。创建后立即运行这个 sn-p:

    outi = torch.empty(2, requires_grad=True)
    print(outi.is_leaf, outi.grad_fn, outi.requires_grad)
    

    给予:

    True, None, True
    

    因为它是由用户创建的,并且之前没有创建它的操作,所以它应该是上述引用中的第二个粗体案例。

    现在这一行:

    outi[0] = out1
    outi[1] = out2
    

    使用两个节点不是叶子,它们是图的一部分,可以追溯到x(这是图中唯一的叶子)。通过这样做outi 也是原始x 图的一部分,并且必须通过反向传播,但是您将其指定为叶子(稍后会详细介绍),它不能通过反向传播(根据定义它们要么 不需要渐变或由用户创建)。 outileaf 的版本已经放在图表上,经过上述分配,这个 sn-p:

    print(outi.is_leaf, outi.grad_fn, outi.requires_grad)
    

    更改为:

    False <CopySlices object at 0x7f2dfa83a3d0> True
    

    错误

    现在,我同意这是一个非常不具信息性的错误,因为更改 requires_grad=False 不会使其成为非叶变量requires_grad=False 是隐含的):

    outi = torch.empty(2)
    print(outi.is_leaf, outi.grad_fn, outi.requires_grad)
    # > True None False
    

    但是,如果您像以前一样使用分配而不破坏预期行为,则此张量可以“升级”为非叶张量。

    为什么?因为您隐含地(或在代码的情况下明确地)说 您不需要此变量的渐变 并且 PyTorch 仅为叶变量保留渐变(除非您为特定的 @ 指定 .retain_grad 987654347@) 由于内存优化。所以这里唯一的变化是它不再是一片叶子,但是这不会违背承诺,因为.grad 无论如何都会是None

    如果您像原来那样拥有requires_grad=True,那么根据 PyTorch 语义,您可以合理地认为:​​

    outi.grad
    

    会给你一个带有渐变的tensor。但是如果这个requires_grad=True张量被改成非叶张量,那么,根据定义,它就不会有这个字段(因为非叶张量有.grad=None)。

    在我看来,他们的设计决定似乎是为了避免与 requires_grad=True 混淆并破坏预期的用户体验。

    顺便说一句。如果他们要禁止图形内的 leaf 变量,那么现在可以正常工作的操作 (requires_grad=False) 也应该被禁止。但是由于requires_grad=False 是隐含的并且经常使用(创建张量或类似的东西),因此允许它似乎并没有太大的难度。拒绝它会更严重。另一方面,如果您指定requires_grad=True,则可以假设您更清楚自己在做什么,并且确实需要这里的渐变

    顺便说一句。 这个解释可能有点牵强,但希望能有所启发。我还没有找到任何关于这个错误的官方信息(诚然,虽然我没有深入挖掘)。

    一些资源herehere(这个很重要,有人要求证明某些设计决策的合理性,但没有得到一个 AFAIK)。

    评论

    评论 1

    我认为 requires_grad 是从切片继承的,并且 .grad 可用。

    是的,它有 requires_gradTrue 一样,因为它现在是图表的一部分,但是 grad 不可用,因为它不再是叶子。在backward 之后打印outi.grad 会给你None 和以下警告:

    UserWarning:不是叶子张量的张量的 .grad 属性 正在被访问。它的 .grad 属性不会被填充 autograd.backward()。如果您确实想要非叶子的渐变 张量,在非叶张量上使用 .retain_grad()。如果您访问 错误的非叶子张量,请确保您访问叶子张量 反而。有关更多信息,请参见 github.com/pytorch/pytorch/pull/30531 信息。

    所以.grad 属性无论如何都是None,因为用户期望将requires_grad=False 作为创建参数。如果用户要设置 requires_grad=True,则用户可能会期望梯度不是 None,这就是 PyTorch 引发错误的时候,IMO 由于在这种情况下可能与用户期望不一致。

    评论 2

    例如:

    a = torch.ones(2,requires_grad=False)
    b = 2*a
    b.requires_grad=True
    print(b.is_leaf) #True
    

    我已经稍微修改了你的代码来逐步完成它:

    a = torch.ones(2, requires_grad=False)
    print(a.is_leaf) # True
    

    我们应该从a 开始,a 是一片叶子,根据文档如下:

    所有 requires_grad 为 False 的张量将是叶子 约定的张量。

    b = a * 2
    print(b.is_leaf)
    

    现在bleaf,因为它不需要渐变(因为a 不需要渐变,它不必通过这个分支反向传播)。用requires_grad=False 操作张量会创建不require_grad 的张量,否则打开它会很浪费且没有意义。

    b.requires_grad = True
    print(b.is_leaf)
    

    现在这个也返回True。再一次,这里的文档措辞可能不是最好的(正如我之前所说的),但是(我的补充以粗体显示):

    对于 requires_grad 为真的张量 (我们现在的情况) 如果它们是由用户创建的,它们将是叶张量(在此处创建存在争议,因为您确实修改了现有的张量)。这意味着它们不是操作的结果,因此 grad_fn 为 None (这个 IMO 澄清了前一点)

    关于澄清-由于这个张量不是任何数学运算的结果,你只是说你想要这个b张量到require_grad。 IMO 它是用户创建的tensor,因为它第一次被放置(创建)在图表上(之前不需要这样做,因为它不需要渐变)。

    并且确实requires_grad 设置为True,你在这里明确地做了。

    评论 3 & 4

    带有 requires_grad=True 的所有内容都在图表上

    是的,但是带有requires_grad=False 的东西也可以在图上,如果它是叶子的话。实际上,每个 PyTorch 操作都是动态创建并添加到计算图上的,这里我们使用简化:如果它参与了backpropagation,它就在图上。例如,神经网络参数是叶子,但它们在图上作为反向传播期间的最后一部分,并且它们有自己的梯度(因为它们已经过优化,它们需要在图中才能通过它进行反向传播)。

    图上没有的都是叶子

    是的,基本上

    图上所有不是图上运算的产物 张量是一片叶子

    是的,如果你添加一些 tensor 到它(例如由 torch.randn 或类似创建)是一片叶子

    图表上的每一片叶子和我设置了 retain_grad=True 的非叶子 手动将填充 .grad 属性。

    是的,如果它是backpropagation 的一部分,这在我们的“心理图”案例中几乎总是如此(我认为至少)。除非它已经有requires_grad=True,在这种情况下它将填充渐变。基本上,除了创建之外,您不应该修改设置requires_grad=True,因为它很容易失败(如您所见)并且肯定会引起其他阅读您代码的人的注意。

    图表上的每个非叶子都有一个与之关联的grad_fn

    是的,这是因为必须通过某些操作来创建它(如果它是由某些操作创建的并且该操作是可区分的,则grad_fn 已注册为在backward() 调用期间使用)。

    【讨论】:

    • 谢谢!您已经提到,使用 require_grad=False 创建张量,然后将其他张量分配给它的切片只会改变它的 is_leaf 状态 - “所以这里唯一的变化是它不再是叶子,但这不会违反承诺,因为 .无论如何,毕业生都不会。”但是,我试过了,我认为 requires_grad 是从切片继承的,而且 .grad 也是可用的。 ``` outi = torch.empty(2,requires_grad=False); outi[0] = out1; outi[1] = out2; print(outi.is_leaf, outi.grad_fn, outi.requires_grad) #output: False True```
    • @MiloMinderbinder 添加了一个编辑以澄清我的想法,希望现在更清楚
    • 我在张量的.is_leafgrad_fnrequires_gradgrad 属性之间的相互作用中遇到了各种不一致。例如:a = torch.ones(2,requires_grad=False); b = 2*a; b.requires_grad=True; print(b.is_leaf) #True.. 这里的 b 既不是用户创建的,也没有它的 requires_grad True。根据定义,我猜这不应该是叶变量。那为什么b.is_leaf 是真的呢?
    • @MiloMinderbinder 再次为您的示例添加了一个编辑,希望这次会更清楚。同意,当你进入它时会感到困惑。
    • 我认为requires_grad=True 本质上就是图中的所有内容。 discuss.pytorch.org/t/…。我尝试使用来自torchvizmake_dot。我使用了这个玩具代码:a = torch.ones(2,requires_grad=True); b = torch.tensor(2.,requires_grad=True)*a; c*=torch.tensor(1.,requires_grad=True); make_dot(c) #will show multipliers in the graph。当我在乘数创建中更改requires_grad 标志时,它们从渲染图中消失了。我错过了什么吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-28
    • 1970-01-01
    • 2015-03-08
    • 2018-06-07
    • 1970-01-01
    相关资源
    最近更新 更多