我正在尝试了解 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_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.]))
Run Code Online (Sandbox Code Playgroud)
输出:
empty. it worked
Run Code Online (Sandbox Code Playgroud)
有人可以帮助我了解幕后发生的事情以及设置require_grad为 True 后发生了什么变化导致了这种行为吗?感谢您的阅读
首先,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。这个解释可能有些牵强,但希望能提供一些线索。我还没有找到任何关于此错误的官方信息(诚然,尽管我没有挖得太深)。
这里有一些资源,这里(这个很重要,有人要求某些设计决策的合理性,尽管据我所知没有得到)。
我认为 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,因为在这种情况下可能与用户期望不一致。
例如:
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)
现在b是leaf因为它不需要梯度(因为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_grad为True,您在这里明确地进行了设置。
包含 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()调用期间使用)。