这是一份阅读PyTorch教程的笔记,记录jupyter notebook的关键点。原地址位于GitHub repo。
PyTorch简介
PyTorch是一个较新的深度学习框架。从名字可以看出,其和Torch不同之处在于PyTorch使用了Python作为开发语言,所谓“Python first”。一方面,使用者可以将其作为加入了GPU支持的numpy
,另一方面,PyTorch也是强大的深度学习框架。
目前有很多深度学习框架,PyTorch主推的功能是动态网络模型。例如在Caffe中,使用者通过编写网络结构的prototxt
进行定义,网络结构是不能变化的。而PyTorch中的网络结构不再只能是一成不变的。同时PyTorch实现了多达上百种op的自动求导(AutoGrad)。
Tensors
Tensor
,即numpy
中的多维数组。上面已经提到过,PyTorch对其加入了GPU支持。同时,PyTorch中的Tensor
可以与numpy
中的array
很方便地进行互相转换。
通过Tensor(shape)
便可以创建所需要大小的tensor
。如下所示。
1 | x = torch.Tensor(5, 3) # construct a 5x3 matrix, uninitialized |
PyTorch中已经实现了很多常用的op
,如下所示。
1 | # addition: syntax 1 |
PyTorch中的元素索引方式和numpy
相同。
1 | # standard numpy-like indexing with all bells and whistles |
对于更多的op
,可以参见PyTorch的文档页面。
Tensor
可以和numpy
中的数组进行很方便地转换。并且转换前后并没有发生内存的复制(这里文档里面没有明说?),所以修改其中某一方的值,也会引起另一方的改变。如下所示。
1 | # Tensor 转为 np.array |
PyTorch中使用GPU计算很简单,通过调用.cuda()
方法,很容易实现GPU支持。
1 | # let us run this cell only if CUDA is available |
Neural Network
说完了数据类型Tensor
,下一步便是如何实现一个神经网络。首先,对自动求导做一说明。
我们需要关注的是autograd.Variable
。这个东西包装了Tensor
。一旦你完成了计算,就可以使用.backward()
方法自动得到(以该Variable
为叶子节点的那个)网络中参数的梯度。Variable
有一个名叫data
的字段,可以通过它获得被包装起来的那个原始的Tensor
数据。同时,使用grad
字段,可以获取梯度(也是一个Variable
)。
Variable
是计算图的节点,同时Function
实现了变量之间的变换。它们互相联系,构成了用于计算的无环图。每个Variable
有一个creator
的字段,表明了它是由哪个Function
创建的(除了用户自己显式创建的那些,这时候creator
是None
)。
当进行反向传播计算梯度时,如果Variable
是标量(比如最终的loss
是欧氏距离或者交叉熵),那么backward()
函数不需要参数。然而如果Variable
有不止一个元素的时候,需要为其中的每个元素指明其(由上层传导来的)梯度(也就是一个和Variable
shape匹配的Tensor
)。看下面的说明代码。
1 | from torch.autograd import Variable |
下面的代码就是结果不是标量,而是普通的Tensor
的例子。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19# 也可以通过Tensor显式地创建Variable
x = torch.randn(3)
x = Variable(x, requires_grad = True)
# 一个更复杂的 op例子
y = x * 2
while y.data.norm() < 1000:
y = y * 2
# 计算 dy/dx
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
x.grad
"""
Variable containing:
204.8000
2048.0000
0.2048
[torch.FloatTensor of size 3]
"""
说完了NN的构成元素Variable
,下面可以介绍如何使用PyTorch构建网络了。这部分主要使用了torch.nn
包。我们自定义的网络结构是由若干的layer
组成的,我们将其设置为 nn.Module
的子类,只要使用方法forward(input)
就可以返回网络的output
。下面的代码展示了如何建立一个包含有conv
和max-pooling
和fc
层的简单CNN网络。
1 | import torch.nn as nn # 以我的理解,貌似有参数的都在nn里面 |
我们可以列出网络中的所有参数。
1 | params = list(net.parameters()) |
给出网络的输入,得到网络的输出。并进行反向传播梯度。
1 | input = Variable(torch.randn(1, 1, 32, 32)) |
注意一点,torch.nn
只支持mini-batch。所以如果你的输入只有一个样例的时候,使用input.unsqueeze(0)
人为给它加上一个维度,让它变成一个4-D的Tensor
。
网络训练
给定target和网络的output,就可以计算loss函数了。在torch.nn
中已经实现好了一些loss函数。
1 | output = net(input) |
网络的整体结构如下所示。
1 | input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d |
我们可以使用previous_functions
来获得该节点前面Function
的信息。
1 | # For illustration, let us follow a few steps backward |
进行反向传播后,让我们查看一下参数的变化。
1 | # now we shall call loss.backward(), and have a look at conv1's bias gradients before and after the backward. |
计算梯度后,自然需要更新参数了。简单的方法可以自己手写:
1 | learning_rate = 0.01 |
不过,torch.optim
中已经提供了若干优化方法(SGD, Nesterov-SGD, Adam, RMSProp, etc)。如下所示。
1 | import torch.optim as optim |
数据载入
由于PyTorch的Python接口和np.array
之间的方便转换,所以可以使用其他任何数据读入的方法(例如OpenCV等)。特别地,对于vision的数据,PyTorch提供了torchvision
包,可以方便地载入常用的数据集(Imagenet, CIFAR10, MNIST, etc),同时提供了图像的各种变换方法。下面以CIFAR为例子。
1 | import torchvision |
接下来,我们对上面部分的CNN网络进行小修,设置第一个conv
层接受3通道的输入。并使用交叉熵定义loss。
1 | class Net(nn.Module): |
接下来,我们进行模型的训练。我们loop over整个dataset两次,对每个mini-batch进行参数的更新。并且设置每隔2000个mini-batch打印一次loss。
1 | for epoch in range(2): # loop over the dataset multiple times |
我们在测试集上选取一个mini-batch(也就是4张,见上面testloader
的定义),进行测试。
1 | dataiter = iter(testloader) |
测试一下整个测试集合上的表现。
1 | correct = 0 |
对哪一类的预测精度更高呢?
1 | class_correct = list(0. for i in range(10)) |
上面这些训练和测试都是在CPU上进行的,如何迁移到GPU?很简单,同样用.cuda()
方法就行了。
1 | net.cuda() |
不过记得在每次训练测试的迭代中,images
和label
也要传送到GPU上才可以。
1 | inputs, labels = Variable(inputs.cuda()), Variable(labels.cuda()) |