notes_extending.md 7.7 KB
Newer Older
P
PEGASUS 已提交
1
# 扩展PyTorch
W
wizardforcel 已提交
2

飞龙 已提交
3
> 译者:[PEGASUS1993](https://github.com/PEGASUS1993)
W
wizardforcel 已提交
4

P
PEGASUS 已提交
5
本章中,将要介绍使用我们的C库如何扩展`torch.nn``torch.autograd`和编写自定义的`C`扩展工具。
W
wizardforcel 已提交
6

P
PEGASUS 已提交
7
## 扩展torch.autograd
W
wizardforcel 已提交
8

P
PEGASUS 已提交
9 10 11 12
添加操作`autograd`需要`Function`为每个操作实现一个新的子类。回想一下,`Function`使用`autograd`来计算结果和梯度,并对操作历史进行编码。每个新功能都需要您实现两种方法:

*   `forward()` - 执行操作的代码。如果您指定了默认值,则可以根据需求使用任意参数,其中一些参数可选。这里支持各种`Python`对象。`Variable`参数在调用之前会被转换`Tensor`,并且它们的使用情况将在`graph`中注册。请注意,此逻辑不会遍历`lists`/`dicts`/和其他任何数据的结构,并且只考虑被直接调用的`Variables`参数。如果有多个输出你可以返回单个`Tensor``Tensor`格式的元组。另外,请参阅`Function`文档查找只能被`forward()`调用的有用方法的说明。

飞龙 已提交
13
*   `backward()` - 计算梯度的公式. 它将被赋予与输出一样多的`Variable`参数, 其中的每一个表示对应梯度的输出. 它应该返回与输入一样多的`Variable`, 其中的每一个表示都包含其相应输入的梯度. 如果输入不需要计算梯度 (请参阅`needs_input_grad`属性),或者是非`Variable`对象,则可返回`None`类.此外,如果你在`forward()`方法中有可选的参数,则可以返回比输入更多的梯度,只要它们都是`None`类型即可.
P
PEGASUS 已提交
14 15

你可以从下面的代码看到`torch.nn`模块的`Linear`函数, 以及注解
W
wizardforcel 已提交
16 17 18

```py
# Inherit from Function
P
PEGASUS 已提交
19
class Linear(Function):
W
wizardforcel 已提交
20 21

    # bias is an optional argument
P
PEGASUS 已提交
22 23
    def forward(self, input, weight, bias=None):
        self.save_for_backward(input, weight, bias)
W
wizardforcel 已提交
24 25 26 27 28 29
        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
P
PEGASUS 已提交
30
    def backward(self, grad_output):
W
wizardforcel 已提交
31 32 33 34 35
        # 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.
P
PEGASUS 已提交
36
        input, weight, bias = self.saved_tensors
W
wizardforcel 已提交
37 38 39 40 41 42
        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.
P
PEGASUS 已提交
43
        if self.needs_input_grad[0]:
W
wizardforcel 已提交
44
            grad_input = grad_output.mm(weight)
P
PEGASUS 已提交
45
        if self.needs_input_grad[1]:
W
wizardforcel 已提交
46
            grad_weight = grad_output.t().mm(input)
P
PEGASUS 已提交
47
        if bias is not None and self.needs_input_grad[2]:
W
wizardforcel 已提交
48 49 50 51 52
            grad_bias = grad_output.sum(0).squeeze(0)

        return grad_input, grad_weight, grad_bias
```

P
PEGASUS 已提交
53
现在,为了更方便使用这些自定义操作,推荐使用`apply`方法:
W
wizardforcel 已提交
54 55 56 57 58

```py
linear = LinearFunction.apply
```

P
PEGASUS 已提交
59
我们下面给出一个由非变量参数进行参数化的函数的例子:
W
wizardforcel 已提交
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

```py
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
```

P
PEGASUS 已提交
77
* 注意
片刻小哥哥's avatar
片刻小哥哥 已提交
78
向后输入,即grad_output,也可以是跟踪历史的张量。因此,如果使用可微运算来实现向后运算(例如,调用另一个自定义函数),则更高阶导数将起作用。
W
wizardforcel 已提交
79

P
PEGASUS 已提交
80
你可能想检测你刚刚实现的`backward`方法是否正确的计算了梯度。你可以使用小的有限差分法(`Finite Difference`)进行数值估计。
W
wizardforcel 已提交
81 82 83 84 85 86 87

```py
from torch.autograd import gradcheck

# gradcheck takes a tuple of tensors 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.
P
PEGASUS 已提交
88 89
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)
W
wizardforcel 已提交
90 91 92
print(test)
```

P
PEGASUS 已提交
93
有关有限差分梯度比较的更多详细信息,请参见[数值梯度检查](../autograd.html#grad-check)
W
wizardforcel 已提交
94

P
PEGASUS 已提交
95
## 扩展 torch.nn
W
wizardforcel 已提交
96

P
PEGASUS 已提交
97
`nn`模块包含两种接口 - `modules`和他们的功能版本。你可以用两种方法扩展它,但是我们建议,在扩展`layer`的时候使用`modules`, 因为`modules`保存着参数和`buffer`。如果使用无参数操作的话,那么建议使用激活函数,池化等函数。
W
wizardforcel 已提交
98

P
PEGASUS 已提交
99
在上面的章节中,添加操作的功能版本已经介绍过了。
W
wizardforcel 已提交
100

P
PEGASUS 已提交
101
#### 增加一个`Module`。
W
wizardforcel 已提交
102

W
url fix  
w4ctech 已提交
103
由于`nn`大量使用`autograd`。所以, 添加一个新的[Module](https://pytorch.org/docs/master/nn.html#torch.nn.Module)类需要实现一个`Function`类, 它会执行对应的操作并且计算梯度。我们只需要很少的代码就可以实现上面`Linear`模块的功能。现在,我们需要实现两个函数:
W
wizardforcel 已提交
104

P
PEGASUS 已提交
105 106
*   `__init__ (optional)` - 接收`kernel sizes`内核大小,特征数量等参数,并初始化`parameters`参数和`buffers`缓冲区。
*   `forward()` - 实例化`Function`并使用它来执行操作。它与上面显示的`functional wrapper`非常相似。
W
wizardforcel 已提交
107

P
PEGASUS 已提交
108
下面是实现`Linear`模块的方式:
W
wizardforcel 已提交
109 110 111 112 113 114 115 116

```py
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

P
PEGASUS 已提交
117
        # nn.Parameter is a special kind of Variable, that will get
W
wizardforcel 已提交
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
        # 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 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)

    def extra_repr(self):
        # (Optional)Set the extra information about this module. You can test
        # it by printing an object of this class.
        return 'in_features={}, out_features={}, bias={}'.format(
            self.in_features, self.out_features, self.bias is not None
        )
```

P
PEGASUS 已提交
149
### 编写自定义的C++扩展  
W
wizardforcel 已提交
150

P
PEGASUS 已提交
151
有关详细说明和示例,请参阅此[PyTorch教程](https://pytorch.org/tutorials/advanced/cpp_extension.html)
飞龙 已提交
152
文档可在[torch.utils.cpp_extension](../cpp_extension.html).获得。
P
PEGASUS 已提交
153
### 编写自定义的C扩展
W
wizardforcel 已提交
154

P
PEGASUS 已提交
155
可用示例可以在[这个Github](https://github.com/pytorch/extension-ffi)仓库里面查看参考。