PyTorch 60分钟入门

PyTorch 60分钟入门

PyTorch简介

这是一个基于Python的科学计算包,主要针对两类人群:

  • 替代Numpy以发挥GPU的强大能力
  • 一个提供最大灵活性和速度的深度学习研究平台

基础

张量(Tensors)

Tensors类似于numpy的ndarray,但是带了一些附加的功能,例如可以使用GPU加速计算等等。

1
2
from __future__ import print_function
import torch

构建一个未初始化的5*3的矩阵:

1
2
x = torch.empty(5, 3)
print(x)

tensor([[-1.0593e-05,  4.5849e-41,  3.4723e-37],
        [ 0.0000e+00,  0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00,  7.1941e+28],
        [ 3.1036e+27,  0.0000e+00,  0.0000e+00],
        [ 0.0000e+00,  0.0000e+00,  3.4568e-37]])

构建一个随机初始化的5*3的矩阵:

1
2
x = torch.rand(5, 3)
print(x)

tensor([[ 0.7556,  0.7558,  0.2999],
        [ 0.7304,  0.3527,  0.1911],
        [ 0.8654,  0.4880,  0.1987],
        [ 0.5456,  0.9359,  0.2071],
        [ 0.1025,  0.5249,  0.3758]])

构建一个初始化为零类型为long的5*3的矩阵:

1
2
x = torch.zeros(5, 3, dtype=torch.long)
print(x)

tensor([[ 0,  0,  0],
        [ 0,  0,  0],
        [ 0,  0,  0],
        [ 0,  0,  0],
        [ 0,  0,  0]])

从数据构造一个张量

1
2
x = torch.tensor([5.5, 3])
print(x)

tensor([ 5.5000,  3.0000])

根据现有张量创建张量。这些方法将重用输入张量的属性,例如dtype,除非用户提供了新的值

1
2
3
4
5
6
print(x) #打印之前的x值
x = x.new_ones(5, 3, dtype=torch.double) # new_* 方法可以更改x的值,维度和类型
print(x)

x = torch.randn_like(x, dtype=torch.float) # 类型与值进行覆盖
print(x) # 不改变维度

tensor([ 5.5000,  3.0000])
tensor([[ 1.,  1.,  1.],
        [ 1.,  1.,  1.],
        [ 1.,  1.,  1.],
        [ 1.,  1.,  1.],
        [ 1.,  1.,  1.]], dtype=torch.float64)
tensor([[-1.4230, -0.7907, -0.0556],
        [-0.9403, -0.2937,  1.9447],
        [ 0.2958,  0.9914, -0.9550],
        [ 1.2439, -0.1390,  0.2889],
        [-0.1790, -0.0003,  0.5241]])

获取尺寸

1
print(x.size())

torch.Size([5, 3])

torch.Size 实际上是一个元组(tuple),因此它支持所有的元祖(tuple)的操作。

操作(Operations)

Pytorch具有100多种操作符(加减乘除,转置,索引,切片,等等),在这里我们以最简单的加法操作,了解Pytorch的操作方法。

  • 加法:语法1
1
2
y = torch.rand(5, 3)
print(x + y)

tensor([[-1.1514, -0.5880, -0.0083],
        [-0.4967,  0.2964,  2.5860],
        [ 0.7163,  1.0643,  0.0210],
        [ 1.8021,  0.6697,  0.8263],
        [ 0.3601,  0.3765,  1.3859]])
  • 加法:语法2
1
print(torch.add(x, y))

tensor([[-1.1514, -0.5880, -0.0083],
        [-0.4967,  0.2964,  2.5860],
        [ 0.7163,  1.0643,  0.0210],
        [ 1.8021,  0.6697,  0.8263],
        [ 0.3601,  0.3765,  1.3859]])
  • 加法:提供输出张量作为参数
1
2
3
result = torch.empty(5, 3)
torch.add(x, y, out=result)
print(result)

tensor([[-1.1514, -0.5880, -0.0083],
        [-0.4967,  0.2964,  2.5860],
        [ 0.7163,  1.0643,  0.0210],
        [ 1.8021,  0.6697,  0.8263],
        [ 0.3601,  0.3765,  1.3859]])
  • 加法:就地解决((in-place))
1
2
3
# adds x to y
y.add_(x)
print(y)

tensor([[-1.1514, -0.5880, -0.0083],
        [-0.4967,  0.2964,  2.5860],
        [ 0.7163,  1.0643,  0.0210],
        [ 1.8021,  0.6697,  0.8263],
        [ 0.3601,  0.3765,  1.3859]])

任何就地改变一个tensor的操作都以_为后缀。例如:x.copy_(y), x.t_(),都会改变x。

Numpy与Torch张量的相互转换

Torch的Tensor和Numpy的数组会共享它们的底层存储位置,改变其中一个,另外一个也会改变。

Torch张量转换成Numpy数组
1
2
3
4
5
6
7
a = torch.ones(5) # 创建一个torch张量
print(a)
b = a.numpy() # 将torch张量转化为numpy数组
print(b)
a.add_(1) # 就地改变torch张量的值
print(a) # a torch张量发生改变
print(b) # b numpy数组因为共享底层存储所以也同时改变

tensor([ 1.,  1.,  1.,  1.,  1.])
[1. 1. 1. 1. 1.]
tensor([ 2.,  2.,  2.,  2.,  2.])
[2. 2. 2. 2. 2.]
Numpy数组T转换成orch张量
1
2
3
4
5
6
import numpy as np #导入numpy
a = np.ones(5) #创建numpy数组
b = torch.from_numpy(a) #numpy转化成torch张量
np.add(a, 1, out=a) #numpy数组数据加一
print(a) # numpy数组发生变化
print(b) # torch张量因为与numpy共享底层存储因此也发生变化

[2. 2. 2. 2. 2.]
tensor([ 2.,  2.,  2.,  2.,  2.], dtype=torch.float64)

CUDA张量(CUDA Tensors)

可以使用.to方法将张量移动到任何设备上。

1
2
3
4
5
6
7
8
 # 我们使用 ``torch.device`` 对象 将张量移入和移出GPU
if torch.cuda.is_available():
device = torch.device("cuda") # 一个CUDA设备对象
y = torch.ones_like(x, device=device) # 直接在GPU上创建一个张量对象
x = x.to(device) # 或者使用``.to("cuda")``
z = x + y
print(z)
print(z.to("cpu", torch.double)) # ``.to`` 将结果转回cpu存储,还可以改变数据类型

tensor([[-0.4230,  0.2093,  0.9444],
        [ 0.0597,  0.7063,  2.9447],
        [ 1.2958,  1.9914,  0.0450],
        [ 2.2439,  0.8610,  1.2889],
        [ 0.8210,  0.9997,  1.5241]], device='cuda:0')
tensor([[-0.4230,  0.2093,  0.9444],
        [ 0.0597,  0.7063,  2.9447],
        [ 1.2958,  1.9914,  0.0450],
        [ 2.2439,  0.8610,  1.2889],
        [ 0.8210,  0.9997,  1.5241]], dtype=torch.float64)

Autograd:自动求导

在PyTorch中所有神经网络的核心是autograd软件包。我们先来简单介绍一下这个,然后再构建第一个神经网络。
autograd包为Tensors上的所有操作提供了自动求导。它是一个运行过程中定义的框架(define-by-run),这意味着反向传播是由代码的运行方式来定义的,并且每一次迭代都可能不同。

张量(Tensor)->0.4版本前是Variable

torch.Tensor是包的中心类。如果你将属性.requires_grad设置为True,它将开始追踪所有的操作。当你完成了计算过程,你可以调用.backward(),之后所有的梯度计算都是自动的。Tensor的梯度将累积到.grad属性中。

要停止跟踪历史记录的Tensor,可以调用.detach()将其从计算历史记录中分离出来,并防止跟踪将来的计算。

为了防止跟踪历史记录(和使用内存),你也可以用torch.no_grad()包装代码块。 这在评估模型时特别有用,因为该模型可能具有require_grad = True的可训练参数,但我们不需要梯度值。

还有一个类对于autograd实现非常重要:一个Function

TensorFunction是相互关联的,并建立一个非循环图,它编码构建了完整的计算过程。 每个变量都有一个.grad_fn属性,该属性反应在已创建Tensor的函数上(用户创建的Tensor除外 - 它们的grad_fnNone)。

如果你想计算导数,可以在Tensor上调用.backward()。如果Tensor是个标量(一个单元素数据),那么你不用为backward()指定任何参数,然而如果它有多个元素,你需要指定一个gradient参数,它是一个匹配尺寸的Tensor

1
2
3
4
5
6
7
8
9
10
11
12
import torch
x = torch.ones(2, 2, requires_grad=True) # 创建一个张量并设置`requires_grad = True`来跟踪计算
print(x) # 打印x的值
y = x + 2 # 对x张量进行计算操作
print(y) # 打印y值
print(y.grad_fn) # y是一个操作的结果,所以它有一个grad_fn。
print(y.requires_grad) # 打印y的requires_grad标志状态
z = y * y * 3 # 继续实现复杂的操作
out = z.mean() # 输出z的均值
print(z, out) # 打印计算输出结果
print(z.grad_fn)# y是一个操作的结果,所以它有一个grad_fn。
print(z.requires_grad) # 打印z的requires_grad标志状态

tensor([[ 1.,  1.],
        [ 1.,  1.]])
tensor([[ 3.,  3.],
        [ 3.,  3.]])
<AddBackward0 object at 0x7f181420a978>
True
tensor([[ 27.,  27.],
        [ 27.,  27.]]) tensor(27.)
<MulBackward0 object at 0x7f180409e400>
True

.requires_grad_(...)就地更改现有张量的requires_grad标志。如果没有给出,函数输入标志默认为True。需要注意的是:python 的默认参数,调用的时候,test( ) 与 test(True)等价跟内部flag默认值无关。从打印看,内部flag默认值是False,但是输出结果flag为True

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
a = torch.randn(2, 2)  # 创建一个2*2的张量a
a = ((a * 3) / (a - 1))# 计算
print(a.requires_grad) # 打印a的requires_grad标志状态
a.requires_grad_(True) # 就地设置a的requires_grad标志状态
print(a.requires_grad) # 再次打印a的requires_grad标志状态
b = (a * a).sum() # 由a计算引入b
print(b.grad_fn) # b是一个操作的结果,所以它有一个grad_fn。
print(b.requires_grad) # 打印a的requires_grad标志状态

def test(x):
x = x*2
print(x.requires_grad) # False
return y
x = torch.randn(2, 2)
print(x.requires_grad) # False
y = test(x) # False
print(y.requires_grad) # True

False
True
<SumBackward0 object at 0x7f17b4c2d518>
True
False
False
True

梯度(Gradients)

让我们使用反向传播out.backward(),它等同于out.backward(torch.Tensor(1))。

1
2
3
4
5
6
x = torch.ones(2, 2, requires_grad=True) # 创建一个张量并设置`requires_grad = True`来跟踪计算
y = x + 2 # 对x张量进行计算操作
z = y * y * 3 # 继续实现复杂的操作
out = z.mean() # 输出z的均值
out.backward() # 实现反向传播
print(x.grad) # 打印梯度 d(out)/dx

tensor([[ 4.5000,  4.5000],
        [ 4.5000,  4.5000]])

4.5矩阵的计算过程如下所示:

我们还可以使用autograd做一些疯狂的事情!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
x = torch.randn(3, requires_grad=True)
print(x)

y = x * 2

print(type(y))
print(type(y.data))
print(y.data.norm())

while y.data.norm() < 1000:
y = y * 2

print(y)

gradients = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(gradients) # 沿着某方向的梯度

print(x.grad)

tensor([-0.3905,  1.3533,  1.0339])
<class 'torch.Tensor'>
<class 'torch.Tensor'>
tensor(3.4944)
tensor([ -399.9199,  1385.7303,  1058.7094])
tensor([  102.4000,  1024.0000,     0.1024])

我们还可以通过使用torch.no_grad()包装代码块来停止autograd跟踪在张量上的历史记录,其中require_grad = True

1
2
3
4
5
print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
print((x ** 2).requires_grad)

True
True
False

神经网络

神经网络可以使用torch.nn包来构建。

前面的学习大致了解了autogradnn依赖于autograd来定义模型并进行求导。一个nn.Module包含多个神经网络层,以及一个forward(input)方法来返回output

例如,看看以下这个分类数字图像的网络:
LeNet
它是一个简单的前馈网络。它将输入逐步地传递给多个层,然后给出输出。
一个典型的神经网络训练过程如下:

  • 定义一个拥有可学习参数(或权重)的神经网络
  • 在输入数据集上进行迭代
  • 在网络中处理输入数据
  • 计算损失(输出离分类正确有多大距离)
  • 梯度反向传播给网络的参数
  • 更新网络的权重,通常使用一个简单的更新规则(SGD):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
39
40
41
42
43
44
45
46
47
48
import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

def __init__(self):
# nn.Module子类的函数必须在构造函数中执行父类的构造函数
# 等价于nn.Model.__init__(self)
super(Net, self).__init__()
# 一个图像输入通道(灰度图), 6 输出通道(6张FeatureMap), 5x5 卷积核
self.conv1 = nn.Conv2d(1, 6, 5)
# 定义卷积层:输入6张特征图,输出16张特征图,卷积核5x5
self.conv2 = nn.Conv2d(6, 16, 5)
# 定义全连接层:线性连接(y = Wx + b),16*5*5个节点连接到120个节点上
self.fc1 = nn.Linear(16 * 5 * 5, 120)
# 定义全连接层:线性连接(y = Wx + b),120个节点连接到84个节点上
self.fc2 = nn.Linear(120, 84)
# 定义全连接层:线性连接(y = Wx + b),84个节点连接到10个节点上
self.fc3 = nn.Linear(84, 10)

# 定义向前传播函数,并自动生成向后传播函数(autograd)
def forward(self, x):
# 输入x->conv1->relu->2x2窗口的最大池化->更新到x
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# 输入x->conv2->relu->2x2窗口的最大池化->更新到x
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
# view函数将张量x变形成一维向量形式,总特征数不变,为全连接层做准备
x = x.view(-1, self.num_flat_features(x))
# 输入x->fc1->relu,更新到x
x = F.relu(self.fc1(x))
# 输入x->fc2->relu,更新到x
x = F.relu(self.fc2(x))
# 输入x->fc3,更新到x
x = self.fc3(x)
return x

def num_flat_features(self, x):
# 除了批处理维度之外的所有维度。
size = x.size()[1:]
num_features = 1
for s in size:
num_features *= s
return num_features

net = Net()
print(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(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

在实现过程中只需要定义forward函数,backward函数(用来计算梯度)是使用autograd自动定义的。并且可以在forward中使用任意的Tensor运算操作。

模型中可学习的参数是通过net.parameters()返回的:

1
2
3
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight

10
torch.Size([6, 1, 5, 5])

让我们尝试一个随机的32x32输入!
注意:这个网络(LeNet)的预期输入大小是32x32。要在MNIST数据集上使用此网络,请将数据集中的图像调整为32x32

1
2
3
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

tensor([[-0.0819,  0.1214,  0.0144, -0.0429,  0.0046,  0.0520, -0.0673,
          0.0878, -0.1724, -0.1151]])

将梯度缓冲区中所有的参数置0,并使用随机的梯度进行反向传播:

1
2
net.zero_grad()
out.backward(torch.randn(1, 10))

torch.nn仅支持mini-batch。整个的torch.nn包仅支持小批量的数据,而不是一个单独的样本。例如,nn.Conv2d应传入一个4DTensor,维度为(nSamples X nChannels X Height X Width)。如果你有一个单独的样本,使用input.unsqueeze(0)来添加一个伪批维度。

回顾:

  • torch.Tensor 一个支持autograd操作(如backward())的多维数组
  • nn.Module 神经网络模块。封装参数的便捷方式,帮助者将它们移动到GPU,导出,加载等。
  • nn.Parameter 一种Tensor,当给Module赋值时自动注册一个参数。
  • autograd.Function 实现一个autograd 操作的 forwardbackward 定义。每一个Tensor操作,创建至少一个Function节点,来连接那些创建Tensor的函数,并且记录其历史。

在这里,我们涵盖了:

  • 定义神经网络
  • 处理输入并调用backward

定义损失函数

一个损失函数以一个(output, target)对为输入,然后计算一个值用以估计输出结果离目标结果多远。
在nn的包里存在定义了多种损失函数。一个简单的损失函数:nn.MSELoss 它计算输出与目标的均方误差。

1
2
3
4
5
6
7
output = net(input)
target = torch.arange(1, 11) # 一个虚拟的目标
target = target.view(1, -1) # 使其形状与输出相同。
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)
tensor(38.9289)

现在,如果使用其.grad_fn属性反向追踪损失,您将看到一个如下所示的计算图:

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss

因此,当我们调用loss.backward()时,损失对应的整个图都被求导,并且图中所有的Tensor都会带有累积了梯度的.grad属性requres_grad=True

1
2
3
print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0]) # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0]) # ReLU

1
2
3
<MseLossBackward object at 0x000002AE1A0953C8>
<AddmmBackward object at 0x000002AE1A0954A8>
<ExpandBackward object at 0x000002AE1A0953C8>

反向传播

要进行反向传播,我们只需要调用loss.backward()。注意:需要清除现有的梯度,否则梯度将累积到现有梯度

现在我们将调用loss.backward(),并看看conv1偏置在backward之前和之后的梯度变化。

1
2
3
4
5
6
7
8
9
net.zero_grad()     # 清除现有的梯度

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad) # 打印之前的梯度值

loss.backward()

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad) # 打印反向传播之后的梯度值

conv1.bias.grad before backward
tensor([ 0.,  0.,  0.,  0.,  0.,  0.])
conv1.bias.grad after backward
tensor([ 0.0383,  0.1029,  0.0044,  0.1332,  0.0659, -0.0402])

权值更新

实践中最简单的更新规则是随机梯度下降(SGD):
weight = weight - learning_rate * gradient

1
2
3
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)

然而,当使用神经网络时,希望使用各种不同的更新规则,例如SGDNesterov-SGDAdamRMSProp等等。为了实现这一点,Pytorch构建一个优化包:torch.optim,来实现所有的方法。使用非常简单:

1
2
3
4
5
6
7
8
9
10
11
import torch.optim as optim

# 创建优化器
optimizer = optim.SGD(net.parameters(), lr=0.01)

# 在训练的循环迭代中使用
optimizer.zero_grad() # 清除现有的梯度
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # 更新值

注意:在观察梯度变化时,首先要通过optimizer.zero_grad()清除现有的梯度,否则梯度将累积到现有梯度

训练分类器

前面的教程中我们已经学习了如何定义神经网络,计算损失并更新网络的权重。接下来,我们完整的训练一个神经网络模型,并测试其性能。

数据集说明

一般来说,当在处理图像,文本,音频或视频数据时,可以使用标准的python包将数据加载到一个numpy数组中。然后将这个数组转换成torch.Tensor

  • 图像的话,可以用Pillow, OpenCV
  • 声音处理可以用scipylibrosa
  • 文本的处理使用原生Python或者Cython以及NLTKSpaCy都可以。
    特别是对于图像,PyTorch创建了一个名为torchvision的软件包,该软件包具有常用数据集(如ImagenetCIFAR10MNIST等)的数据加载器torchvision.datasets,以及用于图像的数据转换器torch.utils.data.DataLoader。这提供了巨大的便利并避免了编写样板代码。
    本教程使用CIFAR10数据集。 我们要进行的分类的类别有:’airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。 这个数据集中的图像都是3通道,32x32像素的图片。

训练一个图像分类器

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

  • 使用torchvision来读取并预处理CIFAR10数据集
  • 定义一个卷积神经网络
  • 定义一个代价函数
  • 在神经网络中训练训练集数据
  • 使用测试集数据测试神经网络
1.加载和归一化CIFAR10

torchvision加载的数据集的输出是范围[0,1]的PILImage图像。我们将它们转换为归一化范围[-1,1]的张量。

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

Files already downloaded and verified
Files already downloaded and verified

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt
import numpy as np
#notebook模式下
%matplotlib inline


#显示图片的函数
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))


# 获取一些随机的训练图片
dataiter = iter(trainloader)
images, labels = dataiter.next()

# 显示图片
imshow(torchvision.utils.make_grid(images))
# 打印类型
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

deer   cat   dog  ship

2.定义卷积神经网络结构
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
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(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
3
4
import torch.optim as optim

criterion = nn.CrossEntropyLoss()
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
for epoch in range(2):  # 训练集迭代次数

running_loss = 0.0
for i, data in enumerate(trainloader, 0):
# 获取输入和标签
inputs, labels = data

# 梯度初始化置零
optimizer.zero_grad()

# 正向+反向+优化
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()

# 打印loss值
running_loss += loss.item()
if i % 2000 == 1999: # 每2000个batch打印一次
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0

print('Finished Training')

[1,  2000] loss: 2.199
[1,  4000] loss: 1.887
[1,  6000] loss: 1.707
[1,  8000] loss: 1.614
[1, 10000] loss: 1.536
[1, 12000] loss: 1.504
[2,  2000] loss: 1.449
[2,  4000] loss: 1.411
[2,  6000] loss: 1.372
[2,  8000] loss: 1.349
[2, 10000] loss: 1.325
[2, 12000] loss: 1.306
Finished Training
5.测试网络

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

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

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

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

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

GroundTruth:    cat  ship  ship plane

现在让我们看看神经网络认为这些例子是什么:

1
2
outputs = net(images)
print(outputs)

tensor([[-0.4905, -1.5664,  1.0641,  2.4226,  0.1196,  1.9381,  0.9795,
         -0.4404, -1.7645, -1.6992],
        [ 6.1866,  5.8665, -2.2267, -3.2581, -2.6794, -4.9095, -4.2326,
         -5.3548,  6.9980,  2.7097],
        [ 1.9322,  3.0127, -1.2481, -1.1180, -1.4086, -1.7913, -1.8129,
         -1.9674,  2.3132,  1.7559],
        [ 3.6228,  0.1119,  0.6089, -1.5255, -0.5566, -2.7542, -1.1817,
         -3.3743,  4.5489, -0.5763]])

输出是10类对应的数值。一个类对应的数值越高,网络认为这个图像就是越接近这个类。那么,让我们得到最高数值对应的类:

1
2
3
4
_, predicted = torch.max(outputs, 1)

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

Predicted:    cat  ship   car  ship

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

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

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
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs, 1)
c = (predicted == labels).squeeze()
for i in range(4):
label = labels[i]
class_correct[label] += c[i].item()
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 : 59 %
Accuracy of   car : 82 %
Accuracy of  bird : 44 %
Accuracy of   cat : 44 %
Accuracy of  deer : 48 %
Accuracy of   dog : 43 %
Accuracy of  frog : 59 %
Accuracy of horse : 52 %
Accuracy of  ship : 73 %
Accuracy of truck : 40 %

GPU训练

多GPU训练

数据并行(选学)

导入和参数

虚拟DataSet

简单模块

创建模块和数据并行

执行模块

结果

总结

-------------本文结束 感谢您的阅读-------------
0%