使用 NumPy 和 SciPy 创建扩展

# For tips on running notebooks in Google Colab, see
# https://pytorch.org/tutorials/beginner/colab
%matplotlib inline

使用 NumPy 和 SciPy 创建扩展#

作者: Adam Paszke

更新者: Adam Dziedzic

在本教程中,我们将完成以下两个任务:

  1. 创建没有参数的神经网络层。

    • 该层的实现中会调用 NumPy

  2. 创建具有可学习权重的神经网络层

    • 该层的实现中会调用 SciPy

import torch
from torch.autograd import Function

无参数示例#

这个层并没有特别有用或数学上正确。

它被恰当地命名为 BadFFTFunction

层实现

from numpy.fft import rfft2, irfft2


class BadFFTFunction(Function):
    @staticmethod
    def forward(ctx, input):
        numpy_input = input.detach().numpy()
        result = abs(rfft2(numpy_input))
        return input.new(result)

    @staticmethod
    def backward(ctx, grad_output):
        numpy_go = grad_output.numpy()
        result = irfft2(numpy_go)
        return grad_output.new(result)

# 由于这个层没有任何参数,可以
# 简单地将其声明为函数,而不是 ``nn.Module`` 类


def incorrect_fft(input):
    return BadFFTFunction.apply(input)

创建层的示例用法:

input = torch.randn(8, 8, requires_grad=True)
result = incorrect_fft(input)
print(result)
result.backward(torch.randn(result.size()))
print(input)
tensor([[ 1.4051, 13.6226,  9.5634,  3.7766,  5.9795],
        [ 0.9438,  3.6594, 13.2123,  1.4907, 11.1425],
        [ 6.0420,  3.6192,  6.0886, 13.8669,  4.3223],
        [ 1.7715,  5.5878,  4.6413,  2.0451,  6.5434],
        [ 6.7019, 11.7479,  5.7401,  9.0250,  0.7763],
        [ 1.7715,  7.4761,  2.4372,  2.5261,  6.5434],
        [ 6.0420,  1.6046,  7.2918,  3.5582,  4.3223],
        [ 0.9438,  6.5624,  7.5281,  2.8141, 11.1425]],
       grad_fn=<BadFFTFunctionBackward>)
tensor([[-1.6377e+00,  2.0592e-01,  2.0573e-01,  7.8468e-01,  1.7276e+00,
          1.3217e-01, -2.4822e-01, -3.9126e-01],
        [ 6.2618e-01,  2.6570e-01, -5.2193e-01,  5.0499e-01, -1.4101e+00,
         -6.7571e-01, -5.6869e-01, -8.3366e-01],
        [ 1.9149e-01, -7.3538e-01,  1.0080e+00,  2.1421e-01,  1.1228e+00,
          5.3282e-01,  3.4630e-01, -1.3304e+00],
        [ 7.8825e-01, -2.5452e-01,  2.9769e-01, -4.0304e-01,  7.7959e-01,
          1.4877e+00, -2.9209e-01, -1.2098e+00],
        [-1.6950e+00,  4.3674e-01, -4.5096e-01,  6.6104e-01,  1.0375e+00,
          3.0109e-01,  8.0961e-03,  2.7743e-02],
        [ 1.1005e-01, -5.9287e-01, -1.8919e+00,  1.5949e+00,  1.1019e+00,
         -6.7195e-01, -9.6363e-01, -2.7448e-01],
        [-2.8044e-02, -1.0913e+00, -7.7489e-01,  2.2238e+00,  4.3961e-01,
          3.5224e-01, -9.1448e-01,  1.3916e+00],
        [ 9.5001e-01,  5.7468e-04,  1.9211e-01,  2.7369e-01, -8.5658e-01,
          2.6022e-01, -9.6591e-01,  5.0487e-01]], requires_grad=True)

参数化示例#

在深度学习文献中,这个层被混淆地称为卷积,而实际操作是互相关(唯一的区别是卷积中滤波器需要翻转,而互相关不需要)。

实现具有可学习权重的层,其中互相关有表示权重的滤波器(核)。

反向传播计算输入的梯度和滤波器的梯度。

from numpy import flip
import numpy as np
from scipy.signal import convolve2d, correlate2d
from torch.nn.modules.module import Module
from torch.nn.parameter import Parameter


class ScipyConv2dFunction(Function):
    @staticmethod
    def forward(ctx, input, filter, bias):
        # detach so we can cast to NumPy
        input, filter, bias = input.detach(), filter.detach(), bias.detach()
        result = correlate2d(input.numpy(), filter.numpy(), mode='valid')
        result += bias.numpy()
        ctx.save_for_backward(input, filter, bias)
        return torch.as_tensor(result, dtype=input.dtype)

    @staticmethod
    def backward(ctx, grad_output):
        grad_output = grad_output.detach()
        input, filter, bias = ctx.saved_tensors
        grad_output = grad_output.numpy()
        grad_bias = np.sum(grad_output, keepdims=True)
        grad_input = convolve2d(grad_output, filter.numpy(), mode='full')
        # the previous line can be expressed equivalently as:
        # grad_input = correlate2d(grad_output, flip(flip(filter.numpy(), axis=0), axis=1), mode='full')
        grad_filter = correlate2d(input.numpy(), grad_output, mode='valid')
        return torch.from_numpy(grad_input), torch.from_numpy(grad_filter).to(torch.float), torch.from_numpy(grad_bias).to(torch.float)


class ScipyConv2d(Module):
    def __init__(self, filter_width, filter_height):
        super(ScipyConv2d, self).__init__()
        self.filter = Parameter(torch.randn(filter_width, filter_height))
        self.bias = Parameter(torch.randn(1, 1))

    def forward(self, input):
        return ScipyConv2dFunction.apply(input, self.filter, self.bias)

示例用法

module = ScipyConv2d(3, 3)
print("Filter and bias: ", list(module.parameters()))
input = torch.randn(10, 10, requires_grad=True)
output = module(input)
print("Output from the convolution: ", output)
output.backward(torch.randn(8, 8))
print("Gradient for the input map: ", input.grad)
Filter and bias:  [Parameter containing:
tensor([[-1.0271,  1.0463,  0.3066],
        [ 0.2833,  1.0729,  1.0716],
        [ 0.1538,  0.7043,  0.4240]], requires_grad=True), Parameter containing:
tensor([[-1.5248]], requires_grad=True)]
Output from the convolution:  tensor([[-3.0503, -2.0735, -2.9300, -3.5769,  0.5267, -3.8087,  1.5216,  3.6531],
        [-3.0974,  2.9297, -3.3729, -6.7103,  0.3726, -5.8613,  3.5687,  4.6253],
        [-1.5237,  1.0010, -0.1776, -7.9033, -2.9091, -2.9002,  0.9109,  2.4959],
        [-4.8617, -2.4489, -4.9739, -6.3394, -3.2192, -3.8084, -0.6294, -0.5166],
        [-2.9100, -3.5628, -2.0847, -2.4449, -4.0917, -3.0924, -2.7929,  1.4404],
        [-0.3139, -0.0623, -1.9976, -5.0148, -4.9538, -2.1379, -0.3020, -0.6575],
        [ 0.8865, -3.4737, -4.3420, -5.2427, -5.6424, -0.8231, -1.4015, -0.8467],
        [-3.3883, -3.7128, -2.9577, -3.0117, -5.9017,  1.5836, -0.2116, -6.0861]],
       grad_fn=<ScipyConv2dFunctionBackward>)
Gradient for the input map:  tensor([[ 1.2887e+00, -8.1603e-01,  3.3014e-03, -7.7249e-01, -3.4147e-01,
         -2.7340e-02, -1.1833e+00,  1.1249e+00, -1.1679e-01, -1.0715e-01],
        [-9.9647e-01, -1.4424e+00, -1.7103e+00, -1.5938e+00, -4.4227e-01,
         -1.2288e+00,  1.0444e+00,  9.9767e-01, -5.3399e-01, -6.2767e-01],
        [ 2.0647e-01, -2.1494e-01, -1.4147e+00,  3.2380e+00, -1.5417e+00,
          1.0907e+00,  3.6141e+00, -1.9452e+00, -1.9549e+00, -1.0021e+00],
        [-1.4297e+00,  1.0584e+00,  3.6035e+00,  8.7692e-01,  1.6264e+00,
         -1.2343e+00,  2.2948e+00, -3.2134e+00, -2.6682e+00, -5.5733e-02],
        [ 1.0553e+00,  8.0772e-01,  7.6035e-01,  3.0935e-01, -2.4132e+00,
          1.2373e-01, -1.4208e+00, -6.5298e-01, -6.9090e-01,  6.7234e-01],
        [-5.0215e-01,  7.1934e-01,  2.9100e-01, -6.9082e-01,  1.5414e+00,
          2.7353e+00, -1.8814e+00,  1.2451e+00,  3.2584e-01, -4.7738e-02],
        [-4.6497e-01,  1.3912e+00,  1.5108e+00, -6.6143e-01,  3.2445e+00,
          4.2020e+00, -1.8766e+00, -5.2713e-01,  8.4413e-01, -7.1390e-01],
        [ 1.6174e+00,  4.3118e-01, -1.7414e+00, -3.2706e+00,  2.5401e+00,
          2.7037e+00, -3.7122e-01,  2.3204e+00,  9.5514e-01, -4.5598e-02],
        [-3.0912e-01, -1.5266e+00, -3.0772e+00, -1.5461e+00,  2.7041e+00,
          4.1396e+00,  2.6272e+00,  3.1130e+00,  1.6704e+00, -3.2189e-01],
        [-2.0918e-01, -1.1258e+00, -1.3340e+00, -1.2008e-01,  1.4991e+00,
          1.6114e+00,  1.5005e+00,  1.5106e+00,  3.0398e-01, -1.9151e-01]])

检查梯度

from torch.autograd.gradcheck import gradcheck

moduleConv = ScipyConv2d(3, 3)

input = [torch.randn(20, 20, dtype=torch.double, requires_grad=True)]
test = gradcheck(moduleConv, input, eps=1e-6, atol=1e-4)
print("Are the gradients correct: ", test)
Are the gradients correct:  True