扩展 PyTorch
译者:@那伊抹微笑
校对者:@Twinkle
在本文中, 我们将介绍如何扩展 torch.nn, torch.autograd 模块, 并且使用我们的 C 库来编写自定义的 C 扩展工具.
扩展 torch.autograd 模块
将操作添加到 autograd 模块需要为每一个操作实现一个新的 Function 类的子类. 回想一下, Function 函数是 autograd 模块用来计算结果和梯度, 并对操作历史进行编码的. 每一个新的函数需要你来实现两个方法:
forward()- 进行操作的代码. 如果您指定默认值, 则可以根据需要使用任意数量的参数, 其中一些参数是可选的. 参数可接收各种类型的 Python 对象.Variable参数在被调用之前将被转换为Tensor对象, 并且它们的使用情况将会被注册到 graph (图) 中. 请注意, 这个逻辑不会遍历 lists, dicts, 和任何其它的数据结构, 只会考虑被调用为直接参数的变量. 如果有多个输出, 则可以考虑返回单个的Tensor类格式的输出, 或者Tensor类的tuple类格式输出. 此外, 请参阅Function类的文档来查找只能从forward()调用的有用方法的描述.backward()- 计算梯度的公式. 它将被赋予与输出一样多的Variable参数, 其中的每一个表示对应梯度的输出. 它应该返回与输入一样多的Variable, 其中的每一个表示都包含其相应输入的梯度. 如果输入不需要计算梯度 (请参阅needs_input_grad属性), 或者是非Variable对象, 则可返回None类. 此外, 如果你在forward()方法中有可选的参数, 则可以返回比输入更多的梯度, 只要它们都是None类型即可.
下面你可以找到来自 torch.nn 模块的 Linear 函数代码, 以及注解
# 继承自 Function
class LinearFunction(Function):
    # Note that both forward and backward are @staticmethods
    @staticmethod
    # bias is an optional argument
    def forward(ctx, input, weight, bias=None):
        ctx.save_for_backward(input, weight, bias)
        output = input.mm(weight.t())
        if bias is not None:
            output += bias.unsqueeze(0).expand_as(output)
        return output
    # This function has only a single output, so it gets only one gradient
    @staticmethod
    def backward(ctx, grad_output):
        # This is a pattern that is very convenient - at the top of backward
        # unpack saved_tensors and initialize all gradients w.r.t. inputs to
        # None. Thanks to the fact that additional trailing Nones are
        # ignored, the return statement is simple even when the function has
        # optional inputs.
        input, weight, bias = ctx.saved_variables
        grad_input = grad_weight = grad_bias = None
        # These needs_input_grad checks are optional and there only to
        # improve efficiency. If you want to make your code simpler, you can
        # skip them. Returning gradients for inputs that don't require it is
        # not an error.
        if ctx.needs_input_grad[0]:
            grad_input = grad_output.mm(weight)
        if ctx.needs_input_grad[1]:
            grad_weight = grad_output.t().mm(input)
        if bias is not None and ctx.needs_input_grad[2]:
            grad_bias = grad_output.sum(0).squeeze(0)
        return grad_input, grad_weight, grad_bias
现在, 为了更方便地使用这些自定义操作, 我们推荐使用 apply 方法
linear = LinearFunction.apply
在这里, 我们给出了一个由非变量参数参数化的函数的例子
class MulConstant(Function):
    @staticmethod
    def forward(ctx, tensor, constant):
        # ctx is a context object that can be used to stash information
        # for backward computation
        ctx.constant = constant
        return tensor * constant
    @staticmethod
    def backward(ctx, grad_output):
        # We return as many input gradients as there were arguments.
        # Gradients of non-Tensor arguments to forward must be None.
        return grad_output * ctx.constant, None
你可能想要检测你刚刚实现的 backward 方法是否正确的计算了梯度. 你可以使用小而有限的微分进行数值估计
from torch.autograd import gradcheck
# gradchek takes a tuple of tensor as input, check if your gradient
# evaluated with these tensors are close enough to numerical
# approximations and returns True if they all verify this condition.
input = (Variable(torch.randn(20,20).double(), requires_grad=True), Variable(torch.randn(30,20).double(), requires_grad=True),)
test = gradcheck(Linear.apply, input, eps=1e-6, atol=1e-4)
print(test)
扩展 torch.nn 模块
nn 模块有两种类型的接口 - modules 和 their functional versions. 你可以用两种方法扩展它, 但是我们推荐使用各种层的模块, 用来存放任何 parameters(参数) 或者 buffers(缓冲), 并且推荐使用一个函数形式的无参数操作, 比如激活函数, 池化等等.
添加操作的函数版本已经在上面的章节中完整的介绍了.
添加 Module 类
由于 nn 模块大量的利用了 autograd 模块, 添加一个新的 Module 类需要实现一个 Function 类, 它会执行对应的操作并且计算梯度. 从现在开始, 假设我们想要实现一个 Linear 模块, 并且我们具有如上所列实现的功能. 有很少的代码需要添加这个. 现在有两个函数需要实现:
__init__(optional) - 接收诸如 kernel sizes (核大小) , numbers of features (特征数量) 等参数, 并初始化 parameters(参数) 和 buffers(缓冲区).forward()- 实例化一个Function类, 并且用于执行操作. 这与上面的 functional wrapper (函数的包装) 非常相似.
这就是 Linear 模块的实现方式
class Linear(nn.Module):
    def __init__(self, input_features, output_features, bias=True):
        super(Linear, self).__init__()
        self.input_features = input_features
        self.output_features = output_features
        # nn.Parameter is a special kind of Variable, that will get
        # automatically registered as Module's parameter once it's assigned
        # as an attribute. Parameters and buffers need to be registered, or
        # they won't appear in .parameters() (doesn't apply to buffers), and
        # won't be converted when e.g. .cuda() is called. You can use
        # .register_buffer() to register buffers.
        # nn.Parameters can never be volatile and, different than Variables,
        # they require gradients by default.
        self.weight = nn.Parameter(torch.Tensor(output_features, input_features))
        if bias:
            self.bias = nn.Parameter(torch.Tensor(output_features))
        else:
            # You should always register all possible parameters, but the
            # optional ones can be None if you want.
            self.register_parameter('bias', None)
        # Not a very smart way to initialize weights
        self.weight.data.uniform_(-0.1, 0.1)
        if bias is not None:
            self.bias.data.uniform_(-0.1, 0.1)
    def forward(self, input):
        # See the autograd section for explanation of what happens here.
        return LinearFunction.apply(input, self.weight, self.bias)
编写自定义的 C 扩展
现在你可以在 GitHub 中找到一些例子.