【问题标题】:How to get rid of Variable API in PyTorch.autograd?如何摆脱 PyTorch.autograd 中的变量 API?
【发布时间】:2019-07-23 15:24:17
【问题描述】:

我正在通过两个简单的 nn.Module PyTorch 模型实例 model1model2 转发和反向支持张量数据 X

如果不使用depreciated Variable API,我无法让这个过程工作。

所以这很好用:

    y1 = model1(X)
    v = Variable(y1.data, requires_grad=training)         # Its all about this line!
    y2 = model2(v)
    criterion = nn.NLLLoss()
    loss = criterion(y2, y)
    loss.backward()
    y1.backward(v.grad)
    self.step()

但这会抛出错误:

    y1 = model1(X)
    y2 = model2(y1)
    criterion = nn.NLLLoss()
    loss = criterion(y2, y)
    loss.backward()
    y1.backward(y1.grad) # it breaks here
    self.step()
>>> RuntimeError: grad can be implicitly created only for scalar outputs

我似乎无法找到第一个实现中的v 和第二个实现中的y1 之间的相关区别。在这两种情况下,requires_grad 都设置为 True。我唯一能找到的是y1.grad_fn=<ThnnConv2DBackward>v.grad_fn=<ThnnConv2DBackward>

我在这里缺少什么? 我不知道什么(张量属性?),如果 Variable 被贬值,还有什么其他实现可以工作?

【问题讨论】:

  • 虽然我看不到您的完整代码(model1 的定义或自身所属的类),但您确定这不仅仅是一个错字吗?第一个写y1.backward(v.grad),第二个写y1.backward(y1.grad)。注意y1
  • 模型中没有错别字,它们是单层 nn.Module 实例。我没有得到您认为第二行中的错字
  • y1.backward(y1.grad.data) 在第二种情况下没有帮助吗?

标签: python machine-learning pytorch


【解决方案1】:

[更新] 在第二个示例中,您没有正确地将 y1.grad 传递给 y1.backward。在第一个backward 之后,所有中间渐变都将被破坏,您需要一个特殊的钩子来提取该渐变。在您的情况下,您正在传递 None 值。这是重现您的案例的小示例:

代码:

import torch
import torch.nn as nn


torch.manual_seed(42)


class Model1(nn.Module):

    def __init__(self):
        super().__init__()

    def forward(self, x):
        return x.pow(3)


class Model2(nn.Module):

    def __init__(self):
        super().__init__()

    def forward(self, x):
        return x / 2


model1 = Model1()
model2 = Model2()
criterion = nn.MSELoss()

X = torch.randn(1, 5, requires_grad=True)
y = torch.randn(1, 5)

y1 = model1(X)
y2 = model2(y1)

loss = criterion(y2, y)
# We are going to backprop 2 times, so we need to 
# retain_graph=True while first backward
loss.backward(retain_graph=True)

try:
    y1.backward(y1.grad)
except RuntimeError as err:
    print(err)
    print('y1.grad: ', y1.grad)

输出:

grad can be implicitly created only for scalar outputs
y1.grad:  None

所以你需要正确提取它们:

代码:

def extract(V):
    """Gradient extractor.
    """
    def hook(grad):
        V.grad = grad
    return hook


model1 = Model1()
model2 = Model2()
criterion = nn.MSELoss()

X = torch.randn(1, 5, requires_grad=True)
y = torch.randn(1, 5)

y1 = model1(X)
y2 = model2(y1)

loss = criterion(y2, y)
y1.register_hook(extract(y1))
loss.backward(retain_graph=True)

print('y1.grad', y1.grad)

y1.backward(y1.grad)

输出:

y1.grad:  tensor([[-0.1763, -0.2114, -0.0266, -0.3293,  0.0534]])

【讨论】:

  • 我在理解您的答案时遇到了一些麻烦,但y1v 都是相同形状的多维张量,一个有效,另一个无效,那么归约如何解决问题?
  • 哦,我明白了,你在第二种情况下传递了 None 。我更新了答案。
  • 我不确定为什么它与变量一起使用,但不建议使用它,并且您传递的是 y1.data,而不是所有渐变的 y1,所以我不确定是否采用这种方法,这可能会导致你的梯度在这种情况下没有正确累积
  • 此外,提出的解决方案会导致 OOM 错误(由于计算图被保留)。使用 Variable 释放此图实际上是我的 PyTorch mods 建议的,大约一年前 discuss.pytorch.org/t/…
  • 我从讨论中检查了那个答案。是的,我有点惊讶,没有其他“务实”的方法可以做到这一点:( PS。你没有提供任何额外的代码,所以很难想象你的模型的复杂性。
【解决方案2】:

经过一番调查,我得出了以下两个解决方案。 该线程中其他地方提供的解决方案手动保留了计算图,没有释放它们的选项,因此最初运行良好,但后来导致 OOM 错误。

第一个解决方案是使用内置的torch.nn.Sequential 将模型绑定在一起:

model = torch.nn.Sequential(Model1(), Model2())

就这么简单。它看起来很干净,行为与普通模型完全一样。

另一种方法是简单地手动将它们绑定在一起:

        model1 = Model1()
        model2 = Model2()
        y1 = model1(X)
        y2 = model2(y1)
        loss = criterion(y2, y)
        loss.backward()

我担心这只会反向传播 model2 被证明是没有根据的,因为 model1 也存储在反向传播的计算图中。 与之前的实现相比,这种实现提高了两个模型之间接口的透明度。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多