了解“运行时错误:叶变量已移至图形内部”背后的原因

fig*_*uts 3 pytorch

我正在尝试了解 pytorch 以及 autograd 在其中的工作原理。我尝试通过用其他张量的值填充它来创建一个张量,然后检查梯度。RuntimeError: leaf variable has been moved into the graph interior但是,如果我不设置requires_gradequal to ,我就会遇到问题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=True)

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

outi.backward(torch.tensor([0.,1.]))
Run Code Online (Sandbox Code Playgroud)

输出:

---------------------------------------------------------------------------
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
Run Code Online (Sandbox Code Playgroud)

但是,我可以将其更改requires_gradFalse,一切都会正常工作

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.]))
Run Code Online (Sandbox Code Playgroud)

输出:

empty. it worked
Run Code Online (Sandbox Code Playgroud)

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

Szy*_*zke 5

介绍

首先,PyTorch中变量的定义leaf,你可以查看官方文档tensor.is_leaf(重点是我的):

按照惯例,所有具有requires_gradis 的张量都False将是叶张量。

对于具有requires_grad以下特征的张量:True张量,如果它们是由用户创建的,则它们将是叶张量。这意味着它们不是操作的结果等等grad_fn is None

让我们看看它看起来如何outi变量。创建后立即运行此代码片段:

outi = torch.empty(2, requires_grad=True)
print(outi.is_leaf, outi.grad_fn, outi.requires_grad)
Run Code Online (Sandbox Code Playgroud)

给出:

True, None, True
Run Code Online (Sandbox Code Playgroud)

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

现在这一行:

outi[0] = out1
outi[1] = out2
Run Code Online (Sandbox Code Playgroud)

使用两个不是叶子的节点,它们是返回到的图的一部分x(这是其中唯一的叶子)。通过这样做,这outi也是原始x图的一部分,并且必须反向传播,但是您将其指定为叶子(稍后会详细介绍),而不能反向传播(根据定义,它们要么不需要梯度,要么是由用户创建)。outias的版本leaf已经放在图表上,在上面的分配之后,这个片段:

print(outi.is_leaf, outi.grad_fn, outi.requires_grad)
Run Code Online (Sandbox Code Playgroud)

更改为:

False <CopySlices object at 0x7f2dfa83a3d0> True
Run Code Online (Sandbox Code Playgroud)

错误

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

outi = torch.empty(2)
print(outi.is_leaf, outi.grad_fn, outi.requires_grad)
# > True None False
Run Code Online (Sandbox Code Playgroud)

但是,如果您像以前那样使用赋值而不破坏预期行为,则该张量可以“升级”为非叶张量

为什么?因为您隐式(或在代码中显式)表示您不需要此变量的梯度,并且由于内存优化,PyTorch 仅保留叶变量的梯度(除非您指定.retain_grad特定的tensor) 。所以这里唯一的变化是它不再是一片叶子,但这不会违背承诺,.grad无论如何None

如果您像requires_grad=True最初那样,根据 PyTorch 语义,您可以合理地认为:

outi.grad
Run Code Online (Sandbox Code Playgroud)

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

对我来说,这似乎是他们的一个设计决定,以避免与requires_grad=True和破坏预期的用户体验。

顺便提一句。如果他们禁止leaf图形内的变量,那么现在工作正常的操作 ( requires_grad=False) 也应该被禁止。但正如requires_grad=False隐式且经常使用的那样(创建张量或像您所做的那样),允许它似乎并没有太大的延伸。如果不允许的话,后果会严重得多。另一方面,如果您指定requires_grad=True它,则可以假设您更了解自己在做什么并且确实需要此处的渐变

顺便说一句2。这个解释可能有些牵强,但希望能提供一些线索。我还没有找到任何关于此错误的官方信息(诚然,尽管我没有挖得太深)。

这里有一些资源,这里(这个很重要,有人要求某些设计决策的合理性,尽管据我所知没有得到)。

评论

评论 1

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

是的,它现在requires_grad也是True图表的一部分, grad不可用,因为它不再是叶子。outi.grad之后打印backward会给出None以下警告:

UserWarning:正在访问不是叶张量的张量的 .grad 属性。在 autograd.backward() 期间不会填充其 .grad 属性。如果您确实想要非叶张量的梯度,请在非叶张量上使用 .retain_grad() 。如果您错误地访问了非叶张量,请确保您改为访问叶张量。有关更多信息,请参阅 github.com/pytorch/pytorch/pull/30531。

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

评论2

例如:

a = torch.ones(2,requires_grad=False)
b = 2*a
b.requires_grad=True
print(b.is_leaf) #True
Run Code Online (Sandbox Code Playgroud)

我对您的代码进行了一些更改以逐步完成它:

a = torch.ones(2, requires_grad=False)
print(a.is_leaf) # True
Run Code Online (Sandbox Code Playgroud)

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

按照惯例,所有 require_grad 为 False 的张量都将是叶张量。

b = a * 2
print(b.is_leaf)
Run Code Online (Sandbox Code Playgroud)

现在bleaf因为它不需要梯度(因为a不需要梯度,所以不必通过该分支反向传播)。使用 操作张量会requires_grad=False创建不会创建张量的张量,require_grad否则打开它会是浪费​​且无意义的。

b.requires_grad = True
print(b.is_leaf)
Run Code Online (Sandbox Code Playgroud)

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

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

关于澄清 - 由于该张量不是任何数学运算的结果,因此您只是说您希望该b张量require_grad。IMO 它是第一次在图表上放置(创建)时创建的用户tensor(之前不需要它,因为它不需要渐变)。

它确实已设置requires_gradTrue,您在这里明确地进行了设置。

评论 3 和 4

包含 require_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()调用期间使用)。