20170121-Pytorch入门笔记

Pytorch顾名思义是torch移植到python的一个实现,标榜的是与numpy相似的操作与高效利用GPU计算资源。
本来已经加入Tensorflow阵营的我在tf的静态图构建和缓慢的gpu加速的夹击之下感到绝望,故尝试Pytorch会不会对代码进行加速以及利用更好的架构来优化代码。
Pytorch与其他框架最大的不同感觉是在于Pytorch构建的是动态的计算流图,据说在计算的时候可以动态改变图的架构而不用重新构建一个新的图。
为了快速上手Pytorch,我们先把MNIST例程放在首位,通过对例程的理解学习Pytorch。

Practice

source: https://github.com/pytorch/tutorials/blob/master/Deep%20Learning%20with%20PyTorch.ipynb

Hello MNIST

本节利用torch.nn创建一个MNIST的网络,创建网络的时候继承torch.nn.Module,定义一个forward函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module): #注意这里继承的是nn.Module
def __init__(self):
super(Net, self).__init__() #调用super的init方法
#创建层实例
self.conv1 = nn.Conv2d(1, 6, 5) # 1 输入图像通道数, 6 输出通道数, 5x5 正方形卷积核大小
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84,10)

def forward(self,x):
x = F.max_pool2d(F.relu(self.conv1(x), (2,2)) # Maxpooling ove 2x2 window
x = F.max_pool2d(F.relu(self.conv2(x), 2) # 如果是方阵可以用一个数字代替
x = x.view(-1, self.num_flat_features(x)) # view是什么作用呢..?
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x)) # 有意思的是函数实例的call方法直接输入input
x = self.fc3(x)
return x

def num_flat_features(self, x):
size = x.size()[1:]
num_features = 1
for s in size:
numfeatures *= s
return num_features

net = Net() #创建Net实例

当我们定义了一个forward函数之后backward函数同时自动定义了,可以使用autograd进行自动求导。
模型中可训练的参数可以用net.parameters()返回,定义在nn.Module中。
1
params = list(net.parameters())

进行一次forward和backward:
1
2
3
4
5
6
7
8
9
from torch.autograd import Variable
input = Variable(torch.randn(1, 1, 32, 32)) # 输入一定要Variable,而且Variable中的为 FloatTensor
out = net(input)

target = Variable(torch.range(1,10)) # 给一个假的target
criterion = nn.MSELoss() # 创建一个Loss
loss = criterion(out, target)

loss.backward() #计算梯度

计算梯度后需要利用optimizer进行更新:
1
2
3
4
5
6
7
8
9
10
import torch.optim as optim
# create optimizer
optimizer = optim.SGD(net.parameters(),lr = 0.01)

# in your training loop:
optimizer.zero_grad() # 将梯度的buffer设0
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # update

定义与训练网络的基本要件的齐全了,那么写一个统一的文件来训练一下看看效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
import torch.optim as optim
from torch.autograd import Variable

# 定义网络
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2,2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)

def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16*5*5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x

# 创建输入数据
# The output of torchvision datasets are PILImage images of range [0, 1].# We transform them to Tensors of normalized range [-1, 1]
transform=transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

# 创建网络实例
net = Net()
# 定义Loss和optimizer
criterion = nn.CrossEntropyLoss() # 交叉熵损失
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

# 训练网络
for epoch in range(2):
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# get the input
inputs, labels = data

# 将输入转变为Variable
inputs, labels = Variable(inputs), Variable(labels)

# zero the parameter gradients
optimizer.zero_grad()

# forward + backward + optimize
outputs = net(inputs) # Forward
loss = criterion(outputs, labels)
loss.backward() # Backward()
optimizer.step() # Optimize

# print statistics
running_loss += loss.data[0]
if i % 2000 == 1999:
print('[%d, %5d] loss: %.3f' % (epoch+1, i+1, running_loss/2000))
running_loss = 0.0
print('Finished Training')

# 看看测试图片的结果
dataiter = iter(testloader)
images, labels = dataiter.next()
outputs = net(Variable(images))

_, predicted = torch.max(outputs.data, 1)
print('Predicted: ', ' '.join('%5s'% classes[predicted[j][0]] for j in range(4)))

# 将网络放在GPU上
net.cuda()
# 将网络放在GPU上之后,对应与net有关的要重新定义
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
# 而且net的输入要是cuda.Tensor, 所以要转换一下
inputs, labels = Variable(inputs), Variable(labels)
inputs, labels = inputs.cuda(), labels.cuda()

文档Note部分:

第一章 Autograd mechanics:

主要介绍的是关于Pytorch中变量(variable)的一些内容。

Excluding subgraphs from backward

重点一:requires_grad,如果有其中一个输入的variable带有这个flag,那么跟这个输入有关的所有输出(output)都自动带有这个flag。

1
2
3
4
5
6
7
8
9
>>> x = Variable(torch.randn(5, 5))
>>> y = Variable(torch.randn(5, 5))
>>> z = Variable(torch.randn(5, 5), requires_grad=True)
>>> a = x + y
>>> a.requires_grad
False
>>> b = a + z
>>> b.requires_grad
True

因此可以利用这个flag来使得指定的变量不进行更新(比如当使用predtrain网络时不希望前面卷积层的参数进行更新):
Example:
1
2
3
4
5
6
7
8
9
model = torchvision.models.resnet18(pretrained=True)
for param in model.parameters():
param.requires_grad = False
# Replace the last fully-connected layer
# Parameters of newly constructed modules have requires_grad=True by default
model.fc = nn.Linear(512, 100)

# Optimize only the classifier
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)

以上代码中关于model.resnet18的参数都不会在optimizer中更新,但fc层中的参数会更新。
重点二:volatile,如果确定你的计算图中不需要计算梯度的话(不需要调用.backward()),可以设置volatile的flag使得Pytorch使用最高效的内存管理。只要有一个输入变量的volatile设为True,则与其有关的输出变量的volatile也为True。
1
2
3
4
5
6
7
8
9
10
11
>>> regular_input = Variable(torch.randn(5, 5))
>>> volatile_input = Variable(torch.randn(5, 5), volatile=True)
>>> model = torchvision.models.resnet18(pretrained=True)
>>> model(regular_input).requires_grad
True
>>> model(volatile_input).requires_grad
False
>>> model(volatile_input).volatile
True
>>> model(volatile_input).creator is None
True

How autograd encodes the history

每个Variable中都有.creator的属性,指向一个输出这个Variable的函数。每进行一个操作(operation),新的函数(Function)的实例的forward()方法被调用,该函数的输出的creator就会指向这个函数。
在Pytorch中,每次迭代都会重新构建一个graph,这就使得在运行过程中可以动态改变图的结构,而不需要在初始化时把所有有可能的路径都定义好。

第二章 CUDA semantics

torch.cuda会track当前使用的GPU,所有的CUDA tensors都会在该GPU上创建。可以利用torch.cuda.device进行GPU的选择。
Cross-GPU的操作一般不被允许,除非使用了copy_()操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
x = torch.cuda.FloatTensor(1)
# x.get_device() == 0
y = torch.FloatTensor(1).cuda()
# y.get_device() == 0

with torch.cuda.device(1):
# allocates a tensor on GPU 1
a = torch.cuda.FloatTensor(1)

# transfers a tensor from CPU to GPU 1
b = torch.FloatTensor(1).cuda()
# a.get_device() == b.get_device() == 1

z = x + y
# z.get_device() == 1

# even within a context, you can give a GPU id to the .cuda call
c = torch.randn(2).cuda(2)
# c.get_device() == 2