【问题标题】:How to wrap PyTorch functions and implement autograd?如何包装 PyTorch 函数并实现 autograd?
【发布时间】:2019-07-02 08:56:32
【问题描述】:

我正在学习 Defining new autograd functions 上的 PyTorch 教程。我要实现的 autograd 函数是 torch.nn.functional.max_pool1d 的包装器。这是我目前所拥有的:

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.autograd as tag

class SquareAndMaxPool1d(tag.Function):

    @staticmethod
    def forward(ctx, input, kernel_size, stride=None, padding=0, dilation=1, \
                return_indices=False, ceil_mode=False):
        ctx.save_for_backward( input )

        inputC = input.clone() #copy input
        inputC *= inputC

        output = F.max_pool1d(inputC, kernel_size, stride=stride, \
                              padding=padding, dilation=dilation, \
                              return_indices=return_indices, \
                              ceil_mode=ceil_mode)

        return output

    @staticmethod
    def backward(ctx, grad_output):
        input, = ctx.saved_tensors
        grad_input = get_max_pool1d_grad_somehow(grad_output)
        return 2.0*input*grad_input

我的问题是:如何获得包装函数的渐变?鉴于我提供的示例非常简单,我知道可能还有其他方法可以做到这一点,但我想做的适合这个框架,并且需要我实现一个 autograd 函数。

编辑:检查this blog post 后,我决定为backward 尝试以下操作:

def backward(ctx, grad_output):
    input, output = ctx.saved_tensors
    grad_input = output.backward(grad_output)
    return 2.0*input*grad_input

output 添加到已保存的变量中。然后我运行以下代码:

x = np.random.randn(1,1,5)
xT = torch.from_numpy(x)
xT.requires_grad=True
f = SquareAndMaxPool1d.apply
s = torch.sum(f(xT,2))
s.backward()

我得到Bus error: 10

假设xTtensor([[[ 1.69533562, -0.21779421, 2.28693953, -0.86688095, -1.01033497]]], dtype=torch.float64),那么我希望在调用s.backward() 后发现xT.gradtensor([[[ 3.39067124, -0. , 9.14775812, -0. , -2.02066994]]], dtype=torch.float64)(即2*x*grad_of_max_poolgrad_of_max_pool 包含tensor([[[1., 0., 2., 0., 1.]]], dtype=torch.float64))。

我知道为什么我会收到Bus error: 10。上面的代码似乎导致我的backwardgrad_input = output.backward(grad_output) 的递归调用。所以我需要找到其他方法来获得max_pool1d的渐变。我知道如何在纯 Python 中实现这一点,但结果会比我可以包装库代码慢得多。

【问题讨论】:

  • “获取渐变”是什么意思?实施?计算?
  • @Jatentaki 我的意思是我相信 PyTorch 有一种方法来计算有问题的梯度,给定正确的函数调用。我很难弄清楚那个电话可能是什么。我刚刚为失败的问题添加了一个尝试的解决方案。希望这可以解决问题。

标签: python-3.x pytorch autograd


【解决方案1】:

递归调用的问题实际上来自output,并且默认情况下with no_grad 是从torch.autograd.Function 继承的类声明中的默认行为。如果您在forward 中检查output.grad_fn,它可能是None,而在backward 中,它可能会链接到函数对象<SquareAndMaxPool1d...>,从而导致递归调用。如果您仍然对如何完全按照您的要求进行操作感兴趣,这里有一个F.linear 的示例:

import torch
import torch.nn.functional as F

class custom_Linear(nn.Linear):
    def forward(self, _input):
        return Custom_Linear_AGfn_getAround.apply(_input, self.weight, self.bias)

class Custom_Linear_AGfn_getAround(torch.autograd.Function):
    @staticmethod
    def forward(ctx, _input, _weight, _bias):
        print('Custom forward')
        with torch.enable_grad():
            detached_input = _input.detach()
            detached_input.requires_grad_(True)
            detached_weight = _weight.detach()
            detached_weight.requires_grad_(True)
            detached_bias = _bias.detach()
            detached_bias.requires_grad_(True)
            _tmp = F.linear(detached_input, detached_weight, detached_bias)
        ctx.saved_input = detached_input
        ctx.saved_param = detached_weight, detached_bias
        ctx.save_for_backward(_tmp)
        _output = _tmp.detach()
        return _output

    @staticmethod
    def backward(ctx, grad_out):
        print('Custom backward')
        _tmp, = ctx.saved_tensors
        _weight, _bias = ctx.saved_param
        detached_input = ctx.saved_input
        with torch.enable_grad():
            _tmp.backward(grad_out)
        return detached_input.grad, _weight.grad, _bias.grad

基本上,它只是为感兴趣的部分构建一个小的孤立图,而不会弄乱主图,并使用grad_fnrequires_grad 在查看要分离的内容和内容时跟踪图孤立图需要。

关于棘手的部分:

  • 分离权重和偏差:你可以不用,但是你可以通过save_for_backward 传递_weight_bias 并且将_weight.grad_bias.grad 作为Nonebackward 内部但是一次在_weight.grad 之外,_bias.grad 将有它们的正确值,或者您将它们传递给一个属性,例如ctx.saved_param,在这种情况下,您必须手动将None 放入backward 的最后两个返回值(返回detached_input.grad, None, None),否则在后面检查权重和偏差梯度时会得到两倍的正确值。
  • 如开头所说,backwardforward 的继承类torch.autograd.Function 似乎默认具有with no_grad 行为。因此,在上述代码中删除with torch.enable_grad(): 将导致_tmp.grad_fn 成为None(无法理解为什么默认情况下_tmpgrad_fnNonerequires_gradFalseforward尽管需要 detached_input 的渐变,直到我碰到:https://github.com/pytorch/pytorch/issues/7698)
  • 我相信,但我没有检查如果你不分离它,你可能会得到一个双 grad_fn_output,就像我没有 with torch.enable_grad() 并且不分离输出时一样,导致 @ 987654364@ 在转发中为 None,它确实在 backward 中获取 <Custom_Linear_AGfn_getAround...> grad_fn(并导致无限递归调用)。

【讨论】:

    【解决方案2】:

    你选择了一个相当不幸的例子。 torch.nn.functional.max_pool1d 不是 torch.autograd.Function 的实例,因为它是 PyTorch 内置的,在 C++ 代码中定义并具有 autogenerated Python 绑定。我不确定是否可以通过其接口获取backward 属性。

    首先,如果你没有注意到,你不需要为这个公式的反向传播编写任何自定义代码,因为 power 操作和 max_pool1d 已经定义了它,所以它们的组成也被 autograd 覆盖.假设您的目标是一项练习,我建议您更多地手动进行(不要退回到max_pool1dbackward)。下面是一个例子

    import torch
    import torch.nn.functional as F
    import torch.autograd as tag
    
    class SquareAndMaxPool1d(tag.Function):
        @staticmethod
        def forward(ctx, input, kernel_size, **kwargs):
            # we're gonna need indices for backward. Currently SquareAnd...
            # never actually returns indices, I left it out for simplicity
            kwargs['return_indices'] = True
    
            input_sqr = input ** 2
            output, indices = F.max_pool1d(input_sqr, kernel_size, **kwargs)
            ctx.save_for_backward(input, indices)
    
            return output
    
        @staticmethod
        def backward(ctx, grad_output):
            input, indices = ctx.saved_tensors
    
            # first we need to reconstruct the gradient of `max_pool1d`
            # by putting all the output gradient elements (corresponding to
            # input elements which made it through the max_pool1d) in their
            # respective places, the rest has gradient of 0. We do it by
            # scattering it against a tensor of 0s
            grad_output_unpooled = torch.zeros_like(input)
            grad_output_unpooled.scatter_(2, indices, grad_output)
    
            # then incorporate the gradient of the "square" part of your
            # operator
            grad_input = 2. * input * grad_output_unpooled
    
            # the docs for backward
            # https://pytorch.org/docs/stable/autograd.html#torch.autograd.Function.backward
            # say that "it should return as many tensors, as there were inputs
            # to forward()". It fails to mention that if an argument was not a
            # tensor, it should return None (I remember reading this somewhere,
            # but can't find it anymore). Anyway, we need to
            # return a (grad_input, None) tuple to avoid a complaint that two
            # outputs were expected
            return grad_input, None
    

    然后我们可以使用numerical gradient checker 来验证操作是否按预期工作。

    f = SquareAndMaxPool1d.apply
    xT = torch.randn(1, 1, 6, requires_grad=True, dtype=torch.float64)
    tag.gradcheck(lambda t: f(t, 2), xT)
    

    很抱歉,如果这不能解决您关于如何获得max_pool1dbackward 的问题,但希望您发现我的回答足够有用。

    【讨论】:

    • 谢谢!你是对的,这是一个关于如何(或是否有可能)包装 pytorch 函数并让 autograd 工作而不知道 pytorch 函数细节的练习。我想我会采取一种不同的方法,将max_pool1d 之前的部分放入一个 autograd 函数中,然后将其后的部分放入另一个函数中,但我发现您所写的内容在其他方面有用且有帮助。 :)
    猜你喜欢
    • 1970-01-01
    • 2020-05-13
    • 2020-05-12
    • 2017-10-27
    • 2021-02-22
    • 2023-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多