nn package

译者:@unknown

校对者:@bringtree

我们重新设计了 nn package, 以便与 autograd 完全集成. 让我们来回顾一下这些变化.

用 autograd 替换 containers:

你不再需要使用像 ConcatTable 这样的 Containers, 或者像 CAddTable 这样的模块, 或者使用 nngraph 并且 debug. 我们将无缝地使用 autograd 来定义我们的神经网络. 例如,

  • output = nn.CAddTable():forward({input1, input2}) 简化为 output = input1 + input2
  • output = nn.MulConstant(0.5):forward(input) 简化为 output = input * 0.5

中间状态不再存放在上述提到的那些模块中, 而是存放在计算图中:

因为这个原因, 所以使用循环网络变得更加简单. 如果你想创建一个循环网络, 只需多次使用相同的 Linear 层, 而不必考虑共享权重.

torch-nn-vs-pytorch-nn

torch-nn-vs-pytorch-nn

Simplified debugging:

使用Python的pdb调试器进行调试是直观的, 调试器和堆栈跟踪在发生错误的地方停止. What you see is what you get(所见即所得, 译者注:应该是说可视化吧).

Example 1: ConvNet

让我们来创建一个小的 ConvNet.

你所有的网络都来自 nn.Module 基类:

  • 在构造函数中, 声明你想要使用的所有层.
  • 在 forward 函数中, 你可以定义模型从输入到输出将如何运行
  1. import torch
  2. from torch.autograd import Variable
  3. import torch.nn as nn
  4. import torch.nn.functional as F
  5. class MNISTConvNet(nn.Module):
  6. def __init__(self):
  7. # 这是你实例化所有模块的地方
  8. # 你可以稍后使用你在此给出的相同名称访问它们
  9. super(MNISTConvNet, self).__init__()
  10. self.conv1 = nn.Conv2d(1, 10, 5)
  11. self.pool1 = nn.MaxPool2d(2, 2)
  12. self.conv2 = nn.Conv2d(10, 20, 5)
  13. self.pool2 = nn.MaxPool2d(2, 2)
  14. self.fc1 = nn.Linear(320, 50)
  15. self.fc2 = nn.Linear(50, 10)
  16. # 这是 forward 函数, 它定义了只接受一个输入的网络结构,
  17. # 如果你愿意, 可以随意定义支持使用更多输入的网络结构.
  18. def forward(self, input):
  19. x = self.pool1(F.relu(self.conv1(input)))
  20. x = self.pool2(F.relu(self.conv2(x)))
  21. # 在你的创建模型的过程中, 你可以疯狂地使用任意的python代码创建你的模型结构,
  22. # 这些操作都是完全合法的, 并且会被autograd正确处理:
  23. # if x.gt(0) > x.numel() / 2:
  24. # ...
  25. #
  26. # 你甚至可以做一个循环来重复使用相同的模块, 模块内部的模块不再
  27. # 处于临时状态, 所以你可以在 forward 时多次使用它们.
  28. # while x.norm(2) < 10:
  29. # x = self.conv1(x)
  30. x = x.view(x.size(0), -1)
  31. x = F.relu(self.fc1(x))
  32. x = F.relu(self.fc2(x))
  33. return x

现在让我们来使用定义好的 ConvNet. 你应该先创建一个类的实例.

  1. net = MNISTConvNet()
  2. print(net)

注解:

torch.nn 只支持 mini-batches , 整个 torch.nn package 只支持输入 mini-batch 格式的样本, 而不支持输入单个样本.

例如, nn.Conv2d 将采用 nSamples x nChannels x Height x Width 的 4D Tensor.

如果你有一个单个的样本, 只需使用 input.unsqueeze(0) 添加一个 虚假的 batch 维度.

创建一个包含随机数据的单个样本的 mini-batch, 并将该样本传入到 ConvNet .

  1. input = Variable(torch.randn(1, 1, 28, 28))
  2. out = net(input)
  3. print(out.size())

定义一个虚拟目标标签, 并使用损失函数来计算 error.

  1. target = Variable(torch.LongTensor([3]))
  2. loss_fn = nn.CrossEntropyLoss() # LogSoftmax + ClassNLL Loss
  3. err = loss_fn(out, target)
  4. err.backward()
  5. print(err)

ConvNet 的 out 是一个 Variable. 我们使用它来计算损失, 计算结果 err 也是一个 Variable. 调用 err.backward 方法将会通过 ConvNet 将梯度传播到它的权重.

让我们来访问单个层的权重和梯度:

  1. print(net.conv1.weight.grad.size())
  1. print(net.conv1.weight.data.norm()) # norm of the weight
  2. print(net.conv1.weight.grad.data.norm()) # norm of the gradients

Forward and Backward Function Hooks

我们已经检查了权重和梯度. 但是如何检查 / 修改一个层的输出和 grad_output?

我们为此引出了 hooks.

你可以在一个 Module 或一个 Variable 上注册一个函数. hook 可以是 forward hook 也可以是一个 backward hook. 当 forward 被执行后 forward hook 将会被执行. backward hook 将在执行 backward 阶段被执行. 让我们来看一个例子.

我们在 conv2 注册一个 forward hook 来打印一些信息

  1. def printnorm(self, input, output):
  2. # input是将输入打包成的 tuple 的input
  3. # 输出是一个 Variable. output.data 是我们感兴趣的 Tensor
  4. print('Inside ' + self.__class__.__name__ + ' forward')
  5. print('')
  6. print('input: ', type(input))
  7. print('input[0]: ', type(input[0]))
  8. print('output: ', type(output))
  9. print('')
  10. print('input size:', input[0].size())
  11. print('output size:', output.data.size())
  12. print('output norm:', output.data.norm())
  13. net.conv2.register_forward_hook(printnorm)
  14. out = net(input)

我们在 conv2 注册一个 backward hook 来打印一些信息

  1. def printgradnorm(self, grad_input, grad_output):
  2. print('Inside ' + self.__class__.__name__ + ' backward')
  3. print('Inside class:' + self.__class__.__name__)
  4. print('')
  5. print('grad_input: ', type(grad_input))
  6. print('grad_input[0]: ', type(grad_input[0]))
  7. print('grad_output: ', type(grad_output))
  8. print('grad_output[0]: ', type(grad_output[0]))
  9. print('')
  10. print('grad_input size:', grad_input[0].size())
  11. print('grad_output size:', grad_output[0].size())
  12. print('grad_input norm:', grad_input[0].data.norm())
  13. net.conv2.register_backward_hook(printgradnorm)
  14. out = net(input)
  15. err = loss_fn(out, target)
  16. err.backward()

一个完整的可以运行的 MNIST 例子在此链接中 https://github.com/pytorch/examples/tree/master/mnist

Example 2: Recurrent Net

接下来, 让我们看一下用 PyTorch 创建 recurrent nets.

由于网络的状态是保存在图中, 而不是在 layer 中, 所以您可以简单地 创建一个 nn.Linear 并重复使用它.

  1. class RNN(nn.Module):
  2. # 你也可以在你模型的构造函数中传入参数
  3. def __init__(self, data_size, hidden_size, output_size):
  4. super(RNN, self).__init__()
  5. self.hidden_size = hidden_size
  6. input_size = data_size + hidden_size
  7. self.i2h = nn.Linear(input_size, hidden_size)
  8. self.h2o = nn.Linear(hidden_size, output_size)
  9. def forward(self, data, last_hidden):
  10. input = torch.cat((data, last_hidden), 1)
  11. hidden = self.i2h(input)
  12. output = self.h2o(hidden)
  13. return hidden, output
  14. rnn = RNN(50, 20, 10)

更完整的使用 LSTMs 和 Penn Tree-bank 的语言模型位于 here

PyTorch 默认已经为 ConvNets 和 Recurrent Nets 提供了无缝的 CuDNN 集成.

  1. loss_fn = nn.MSELoss()
  2. batch_size = 10
  3. TIMESTEPS = 5
  4. # 创建一些假数据
  5. batch = Variable(torch.randn(batch_size, 50))
  6. hidden = Variable(torch.zeros(batch_size, 20))
  7. target = Variable(torch.zeros(batch_size, 10))
  8. loss = 0
  9. for t in range(TIMESTEPS):
  10. # 是的! 你可以多次使用同一个网络,
  11. # 将损失相加, 并且调用 call backward!
  12. hidden, output = rnn(batch, hidden)
  13. loss += loss_fn(output, target)
  14. loss.backward()