Pytorch Quick Notes

Reference: https://zhuanlan.zhihu.com/p/25572330

Pytorch

  1. 基于Python
  2. 替代numpy,可以用gpu进行计算
  3. 还能有深度学习相关的库

Pytorch 核心3库:Tensors数据类、Autograd自动求导、nn神经网络类

Tensors

Tensors和numpy中的ndarrays很像,二者可以互相转换,而它的优势在于可以被放在gpu上运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from __future__ import print_function
import torch
x = torch.Tensor(5, 3) # 构造一个未初始化的5*3的矩阵
x = torch.rand(5, 3) # 构造一个随机初始化的矩阵
x
x.size()
# NOTE: torch.Size 事实上是一个tuple, 所以其支持相关的操作*

y = torch.rand(5, 3)

#此处 将两个同形矩阵相加有两种语法结构
x + y # 语法一
torch.add(x, y) # 语法二

# 另外 输出tensor也有两种写法
result = torch.Tensor(5, 3) # 语法一
torch.add(x, y, out=result) # 语法二
y.add_(x) # 将y与x相加

# 特别注明:任何可以改变tensor内容的操作都会在方法名后加一个下划线'_'
# 例如:x.copy_(y), x.t_(), 这俩都会改变x的值。

#另外python中的切片操作也是资次的。
x[:,1] #这一操作会输出x矩阵的第二列的所有值

Tensors和ndarray可以互相转化。Torch的Tensor和numpy的array会共享他们的存储空间,修改一个会导致另外的一个也被修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 此处演示tensor和numpy数据结构的相互转换
a = torch.ones(5)
b = a.numpy()

# 此处演示当修改numpy数组之后,与之相关联的tensor也会相应的被修改
a.add_(1)
print(a)
print(b)

# 将numpy的Array转换为torch的Tensor
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
print(b)

# 另外除了CharTensor之外,所有的tensor都可以在CPU运算和GPU预算之间相互转换
# 使用CUDA函数来将Tensor移动到GPU上
# 当CUDA可用时会进行GPU的运算
if torch.cuda.is_available():
x = x.cuda()
y = y.cuda()
x + y

注意:把一个Tensor移动到gpu上运算,只需要调用tensor1.cuda()

Autograd

Autograd可以进行自动求导。一旦把一个Tensor包装成autograd.Variable类,它就会自动有grad(梯度值)和creator(引用了上一个创建这个Variable的Function)。当通过一系列运算,将input转变成output之后,只要对output调用output.backward(),则input就会由反向传播获得其grad值input.grad

img
img
  • autograd.Variable 这是这个包中最核心的类。 它包装了一个Tensor,并且几乎支持所有的定义在其上的操作。一旦完成了你的运算,你可以调用 .backward()来自动计算出所有的梯度。

  • 可以通过属性 .data 来访问原始的tensor,而关于这一Variable的梯度则集中于 .grad 属性中

  • 还有一个在自动求导中非常重要的类 Function。

    Variable 和 Function 二者相互联系并且构建了一个描述整个运算过程的无环图。每个Variable拥有一个 .creator 属性,其引用了一个创建Variable的 Function。(除了用户创建的Variable其 creator 部分是 None)。(新版本把creator改成了grad_fn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from torch.autograd import Variable
x = Variable(torch.ones(2, 2), requires_grad = True)
y = x + 2
y.creator

# y 是作为一个操作的结果创建的因此y有一个creator
z = y * y * 3
out = z.mean()

# 现在我们来使用反向传播
out.backward()

# out.backward()和操作out.backward(torch.Tensor([1.0]))是等价的
# 在此处输出 d(out)/dx
x.grad

torch.nn

通常写一个神经网络的方法为:将一个神经网络模型定义为一个nn.Module的子类,然后在__init__中定义网络结构,在forward中定义前向传播的计算过程。【nb的是backward甚至不用写,autograd自动会做。】

举个例子,来看一下这个数字图像分类的神经网络。

preview
preview

这是一个简单的前馈神经网络。 从前面获取到输入的结果,从一层传递到另一层,最后输出最后结果。

一个典型的神经网络的训练过程是这样的:

  • 定义一个有着可学习的参数(或者权重)的神经网络

  • 对着一个输入的数据集进行迭代:

    • 用神经网络对输入进行处理

    • 计算代价值 (对输出值的修正到底有多少)

    • 将梯度传播回神经网络的参数中

    • 更新网络中的权重

      • 通常使用简单的更新规则: weight = weight + learning_rate * gradient
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
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 6, 5) # 1 input image channel, 6 output channels, 5x5 square convolution kernel
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16*5*5, 120) # 16*5*5: input, 120: output
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x): # x is input
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) # Max pooling over a (2, 2) window; x -> relu -> maxPool
x = F.max_pool2d(F.relu(self.conv2(x)), 2) # If the size is a square you can only specify a single number
x = x.view(-1, self._num_flat_features(x)) # need to flaten the x for the fc layer
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x # now return the output
def _num_flat_features(self, x): # for flaten x
size = x.size()[1:] # all dimensions except the batch dimension
num_features = 1
for s in size:
num_features *= s
return num_features

net = Net()
net

'''神经网络的输出结果是这样的
Net (
(conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear (400 -> 120)
(fc2): Linear (120 -> 84)
(fc3): Linear (84 -> 10)
)
'''

仅仅需要定义一个forward函数就可以了,backward会自动地生成。

你可以在forward函数中使用所有的Tensor中的操作。

模型中可学习的参数会由net.parameters()返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight

input = Variable(torch.randn(1, 1, 32, 32))
out = net(input)
'''out 的输出结果如下
Variable containing:
-0.0158 -0.0682 -0.1239 -0.0136 -0.0645 0.0107 -0.0230 -0.0085 0.1172 -0.0393
[torch.FloatTensor of size 1x10]
'''

net.zero_grad() # 对所有的参数的梯度缓冲区进行归零
out.backward(torch.randn(1, 10)) # 使用随机的梯度进行反向传播

注意: torch.nn 接收的数据的第一个维度为batch size 整个torch.nn包只接受那种小批量样本的数据,而非单个样本。 例如,nn.Conv2d能够结构一个四维的TensornSamples x nChannels x Height x Width。 如果你拿的是单个样本,使用input.unsqueeze(0)来加一个假维度就可以了。

到现在我们已经明白的部分:

  • 定义了一个神经网络。
  • 处理了输入以及实现了反馈。

仍然没整的:

  • 计算代价。
  • 更新网络中的权重。

计算代价

用nn.loss function,接收(预测值,真实值)这一对输入,然后会算出loss值。

执行loss.backward(),这样就会直接对网络中所有结构计算出其grad了

更新权重

这个也很简单。以最简单的随机梯度下降法SGD为例: \[ weight = weight - learning\_rate * gradient \] 有一个写满了各种optimizer的包torch.optim

1
2
3
4
5
6
7
8
9
10
import torch.optim as optim
# create your optimizer
optimizer = optim.SGD(net.parameters(), lr = 0.01) # 把网络的参数绑定到optimizer上

# in your training loop:
optimizer.zero_grad() # zero the gradient buffers
output = net(input) # 网络输出
loss = criterion(output, target) # 计算loss
loss.backward() # 反馈计算grad
optimizer.step() # 它就会自动根据parameters,以及各自已经被更新好的grad,来更新parameters了

有两个我自己一直在混淆的概念,但其实很简单:

1
2
3
4
5
criterion = loss_function(outputs, real_outputs) 
# 损失计算方法,例如最简单的MSE均方误差;当然还有mIoU等等
optimizer = 权重更新方法(net.parameters(), lr = 0.001)
# 例如最简单的SGD随机梯度下降;当然还有Adam等等
# 还可以给optimizer定义learning rate的scheduler

在GPU上训练

  1. net.cuda()
  2. inputs.cuda()
  3. labels.cuda()

一个典型的图片分类练习

下面是对torch神经网络使用的一个实战练习。

训练一个图片分类器

我们要按顺序做这几个步骤:

  1. 使用torchvision来读取并预处理CIFAR10数据集
  2. 定义一个卷积神经网络
  3. 定义一个代价函数
  4. 在神经网络中训练训练集数据
  5. 使用测试集数据测试神经网络

1. 读取并预处理CIFAR10

使用torchvision读取CIFAR10相当的方便。

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
import torchvision
import torchvision.transforms as transforms


# torchvision数据集的输出是在[0, 1]范围内的PILImage图片。
# 我们此处使用归一化的方法将其转化为Tensor,数据范围为[-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')
'''注:这一部分需要下载部分数据集 因此速度可能会有一些慢 同时你会看到这样的输出

Downloading http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data/cifar-10-python.tar.gz
Extracting tar file
Done!
Files already downloaded and verified
'''

我们来从中找几张图片看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# functions to show an image
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1,2,0)))

# show some random training images
dataiter = iter(trainloader)
images, labels = dataiter.next()

# print images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s'%classes[labels[j]] for j in range(4)))

结果是这样的:

img
img

2. 定义一个卷积神经网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

net = Net()

3. 定义代价函数和优化器

1
2
criterion = nn.CrossEntropyLoss() # use a Classification Cross-Entropy loss
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

4. 训练网络

事情变得有趣起来了。 我们只需一轮一轮迭代然后不断通过输入来进行参数调整就行了。

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
for epoch in range(2): # loop over the dataset multiple times

running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# get the inputs
inputs, labels = data

# wrap them in Variable
inputs, labels = Variable(inputs), Variable(labels)

# zero the parameter gradients
optimizer.zero_grad()

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

# print statistics
running_loss += loss.data[0]
if i % 2000 == 1999: # print every 2000 mini-batches
print('[%d, %5d] loss: %.3f' % (epoch+1, i+1, running_loss / 2000))
running_loss = 0.0
print('Finished Training')
'''这部分的输出结果为
[1, 2000] loss: 2.212
[1, 4000] loss: 1.892
[1, 6000] loss: 1.681
[1, 8000] loss: 1.590
[1, 10000] loss: 1.515
[1, 12000] loss: 1.475
[2, 2000] loss: 1.409
[2, 4000] loss: 1.394
[2, 6000] loss: 1.376
[2, 8000] loss: 1.334
[2, 10000] loss: 1.313
[2, 12000] loss: 1.264
Finished Training
'''

我们已经训练了两遍了。 此时需要测试一下到底结果如何。

通过对比神经网络给出的分类和已知的类别结果,可以得出正确与否,如果预测的正确,我们可以将样本加入正确预测的结果的列表中。

好的第一步,让我们展示几张照片来熟悉一下。

1
2
3
4
5
6
dataiter = iter(testloader)
images, labels = dataiter.next()

# print images
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s'%classes[labels[j]] for j in range(4)))

结果是这样的:

img
img

好的,接下来看看神经网络如何看待这几个照片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
outputs = net(Variable(images))

# the outputs are energies for the 10 classes.
# Higher the energy for a class, the more the network
# thinks that the image is of the particular class

# So, let's get the index of the highest energy
_, predicted = torch.max(outputs.data, 1)

print('Predicted: ', ' '.join('%5s'% classes[predicted[j][0]] for j in range(4)))

'''输出结果为
Predicted: cat plane car plane
'''

结果看起来挺好。

看看神经网络在整个数据集上的表现结果如何。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
correct = 0
total = 0
for data in testloader:
images, labels = data
outputs = net(Variable(images))
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum()

print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))

'''输出结果为
Accuracy of the network on the 10000 test images: 54 %
'''

看上去这玩意输出的结果比随机整的要好,随机选择的话从十个中选择一个出来,准确率大概只有10%。

看上去神经网络学到了点东西。

嗯。。。那么到底哪些类别表现良好又是哪些类别不太行呢?

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
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
for data in testloader:
images, labels = data
outputs = net(Variable(images))
_, predicted = torch.max(outputs.data, 1)
c = (predicted == labels).squeeze()
for i in range(4):
label = labels[i]
class_correct[label] += c[i]
class_total[label] += 1

for i in range(10):
print('Accuracy of %5s : %2d %%' % (classes[i], 100 * class_correct[i] / class_total[i]))

'''输出结果为
Accuracy of plane : 73 %
Accuracy of car : 70 %
Accuracy of bird : 52 %
Accuracy of cat : 27 %
Accuracy of deer : 34 %
Accuracy of dog : 37 %
Accuracy of frog : 62 %
Accuracy of horse : 72 %
Accuracy of ship : 64 %
Accuracy of truck : 53 %
'''