前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PyTorch 60-Minute Blitz

PyTorch 60-Minute Blitz

作者头像
口仆
发布2020-08-14 16:03:55
7600
发布2020-08-14 16:03:55
举报

1 什么是 PyTorch?

PyTorch 是一个基于 python 的科学计算包,其特点是:

  • 替代 Numpy 以使用 GPU 的运算能力
  • 一个深度学习的研究平台,提供最大化的灵活性和速度

1.1 入门

1.1.1 张量

Tensors(张量)与 Numpy 的 ndarrays 类似,但是其支持在 GPU 上使用来加速计算。

代码语言:javascript
复制
from __future__ import print_function
import torch

下面给出一些张量的使用案例:

创建一个没有初始化的 5*3 矩阵:

代码语言:javascript
复制
x = torch.empty(5, 3)
print(x)

# Output
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])

创建一个随机初始化的矩阵:

代码语言:javascript
复制
x = torch.rand(5, 3)
print(x)

# Output
tensor([[0.5173, 0.7670, 0.7260],
        [0.8115, 0.4518, 0.3867],
        [0.6968, 0.8738, 0.8632],
        [0.6845, 0.5106, 0.0381],
        [0.4081, 0.1894, 0.7733]])

构造一个填满 0 且数据类型为 long 的矩阵:

代码语言:javascript
复制
x = torch.zeros(5, 3, dtype=torch.long)
print(x)

# Output
tensor([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]])

直接从数据构造张量:

代码语言:javascript
复制
x = torch.tensor([5.5, 3])
print(x)

# Output
tensor([5.5000, 3.0000])

根据已有的张量建立新的张量:

代码语言:javascript
复制
x = x.new_ones(5, 3, dtype=torch.double)      # new_* methods take in sizes
print(x)

# Output
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], dtype=torch.float64)

x = torch.randn_like(x, dtype=torch.float)    # override dtype & has the same size
print(x)

# Output
tensor([[ 1.5360, -1.1798,  0.5190],
        [-0.3277,  0.9304,  1.9112],
        [-0.0136,  0.9135, -1.1442],
        [-0.2182, -0.7076, -0.9888],
        [-0.1883, -0.2476,  1.6226]])

获取张量的形状:

代码语言:javascript
复制
print(x.size())

# Output
torch.Size([5, 3])

torch.Size 本质上是一个 tuple,所以支持 tuple 的一切操作。

1.1.2 运算

pytorch 支持多种运算,而每种运算又有着很多种语法。下面给出一些关于加法运算的例子:

「加法」:形式一:

代码语言:javascript
复制
y = torch.rand(5, 3)
print(x + y)

「加法」:形式二:

代码语言:javascript
复制
print(torch.add(x, y))

「加法」:给定一个输出张量作为参数:

代码语言:javascript
复制
result = torch.empty(5, 3)
torch.add(x, y, out=result)
print(result)

「加法」:原地操作(in-place):

代码语言:javascript
复制
# adds x to y
y.add_(x)
print(y)

任何一个 in-place 改变张量的操作后面都固定一个 _,例如 x.copy_(y)x.t_() 将更改 x

上面四种形式的加法均会输出同样的结果:

代码语言:javascript
复制
# Output
tensor([[ 2.5202, -0.4201,  0.5584],
        [ 0.0592,  1.3295,  2.8773],
        [ 0.2609,  1.2124, -1.0648],
        [-0.1337, -0.1146, -0.3190],
        [-0.1339,  0.1608,  1.9009]])

下面再给出一些其他操作的例子:

使用像标准的 NumPy 一样的各种索引操作:

代码语言:javascript
复制
print(x[:, 1])

# Output
tensor([-1.1798,  0.9304,  0.9135, -0.7076, -0.2476])

改变张量的形状:

代码语言:javascript
复制
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)  # the size -1 is inferred from other dimensions
print(x.size(), y.size(), z.size())

# Output
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])

如果是仅包含一个元素的张量,可以使用 .item() 来得到对应的数值:

代码语言:javascript
复制
x = torch.randn(1)
print(x)
print(x.item())

# Output
tensor([-0.5599])
-0.5598675012588501

1.2 Numpy 桥

我们可以轻而易举地在 numpy 数组和 pytorch 张量之间相互转换,两者将共享它们的底层内存位置,更改一个将引起另一个的改变。(需要注意,前提条件是张量位于 CPU ,且 CharTensor 不支持转换)

张量转数组:

代码语言:javascript
复制
a = torch.ones(5)
print(a)

# Output
tensor([1., 1., 1., 1., 1.])

b = a.numpy()
print(b)

# Output
[1. 1. 1. 1. 1.]

a.add_(1) # both a and b will change
print(a)
print(b)

# Output
tensor([2., 2., 2., 2., 2.])
[2. 2. 2. 2. 2.]

数组转张量:

代码语言:javascript
复制
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
print(b)

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

1.3 CUDA 上的张量

张量可以使用 .to 方法移动到任何设备(device)上:

代码语言:javascript
复制
# let us run this cell only if CUDA is available
# We will use ``torch.device`` objects to move tensors in and out of GPU
if torch.cuda.is_available():
    device = torch.device("cuda")          # a CUDA device object
    y = torch.ones_like(x, device=device)  # directly create a tensor on GPU
    x = x.to(device)                       # or just use strings ``.to("cuda")``
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # ``.to`` can also change dtype together!

程序输出:

代码语言:javascript
复制
tensor([2.0897], device='cuda:0')
tensor([2.0897], dtype=torch.float64) # double equals to float64

2 Autograd:自动求导

在 PyTorch 中,所有神经网络的核心都是 autograde 包,它是一个在运行时定义的框架,为张量上的所有运算提供了「自动求导」机制。下面将通过一些例子介绍自动求导包的基本操作。

2.1 张量

torch.Tensor 是这个包的核心类。如果我们设置其属性 .requires_gradTrue ,那么它将会追踪对于该张量的所有操作。当完成计算(前向传播)后,调用 .backward() 函数,即可自动计算所有的梯度。该张量的所有梯度将会自动累加到 .grad 属性。如果 Tensor 是一个标量(即只包含一个元素的数据),则不需要指定任何参数但是如果它有更多的元素,否则需要指定一个 gradient 参数,其形状与输出(该张量)匹配。

有时候,我们可能不再需要追踪一个张量的梯度(例如在进行模型评估时),这时可以使用 .detach() 方法或将代码块包装在 with torch.no_grad(): 中,来防止跟踪历史记录和使用内存。

此外,还有一个类对 autograd 的实现非常重要:Function。该类和 Tensor 类互相连接构成了一个无环图,编码完整的计算历史,以便进行梯度计算。每个张量都有一个 .grad_fn 属性,它引用了一个创建了这个张量的 Function,除非这个张量是用户直接手动创建的,即这个张量的 grad_fnNone

下面通过一个实际的例子对上述操作进行展示。首先创建一个张量并设置 requires_grad=True 来追踪其计算历史:

代码语言:javascript
复制
import torch
x = torch.ones(2, 2, requires_grad=True)
print(x)

# Output
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)

对这个张量进行一次运算:

代码语言:javascript
复制
y = x + 2
print(y)

# Output
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)

y 是计算结果,其具有 grad_fn 属性。

我们对 y 进行更多的运算:

代码语言:javascript
复制
z = y * y * 3
out = z.mean()
print(z, out)

# Output
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)

2.2 梯度

现在开始进行反向传播,由于 out 是一个标量,因此 out.backward()out.backward(torch.tensor(1.)) 等价。

代码语言:javascript
复制
out.backward()
print(x.grad)

# Output
tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

上述结果对应的求导过程为:我们有

o=\frac{1}{4} \sum_{i} z_{i}, z_{i}=3\left(x_{i}+2\right)^{2}

以及

\left.z_{i}\right|_{x_{i}=1}=27

,因此

\frac{\partial o}{\partial x_{i}}=\frac{3}{2}\left(x_{i}+2\right)

,从而得到

\left.\frac{\partial o}{\partial x_{i}}\right|_{x_{i}=1}=\frac{9}{2}=4.5

在数学上,若有向量值函数

\vec{y}=f(\vec{x})

,那么

\vec{y}

相对于

\vec{x}

的梯度是一个雅可比矩阵:

J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)

对于非标量的张量,自动求导包实际上求的是「雅克比向量」。雅克比向量即给定任意向量

v=\left(\begin{array}{cccc} v_{1} & v_{2} & \cdots & v_{m}\end{array}\right)^{T}

,计算乘积

v^T \cdot J

。如果

v

恰好是一个标量函数

l=g\left(\vec{y}\right)

的导数,即

v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}

,那么根据链式法则,雅可比向量积应该是

l

\vec{x}

的导数:

J^T \cdot v=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\\ \vdots\\ \frac{\partial l}{\partial y_{m}} \end{array}\right)=\left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\\ \vdots\\ \frac{\partial l}{\partial x_{n}} \end{array}\right)J^T \cdot v=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\\ \vdots\\ \frac{\partial l}{\partial y_{m}} \end{array}\right)=\left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\\ \vdots\\ \frac{\partial l}{\partial x_{n}} \end{array}\right)

下面给出一个雅克比向量积的例子:

代码语言:javascript
复制
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000: # 计算 L2 范数(平方和开根号)等价于 torch.sqrt(torch.sum(torch.pow(y, 2)))
    y = y * 2
print(y)

# Output
tensor([601.5915, -307.0995, -745.0810], grad_fn=<MulBackward0>)

在本例中 y 不再是一个标量,torch.autograd 不能直接计算完整的雅可比矩阵,但如果我们只想要雅可比向量积,只需要将向量作为参数传给 backward 即可:

代码语言:javascript
复制
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)

# Output
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])

3 神经网络

本章将介绍如何用 PyTorch 构建一个神经网络。

我们可以使用 torch.nn 来构建网络,nn 包依赖于 autograd 包来定义模型并对它们求导。一个 nn.Module 包含各个层和一个 forward(input) 方法用来计算并返回 output

一个神经网络的典型训练过程如下:

  • 定义包含可学习参数(权重)的神经网络
  • 在输入数据集上进行迭代
  • 通过网络处理输入得到输出
  • 计算损失函数
  • 将梯度反向传播给网络的参数
  • 更新网络的权重

下面将通过代码介绍上述训练过程。

3.1 定义网络

通过如下代码定义网络:

代码语言:javascript
复制
import torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module): # 继承nn.Module类,并实现forward方法
    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 3x3 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 6 * 6, 120)  # 6*6 from image dimension
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, 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()
print(net)

我们只需要定义 forward 函数,backward函数会在使用 autograd 时自动定义。注意 view() 方法用于 reshape 张量的形状,-1 表示该维度基于其他维度来决定。

输出:

代码语言:javascript
复制
Net(
  (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=576, 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)
)

一个模型的可学习参数可以通过 net.parameters() 返回:

代码语言:javascript
复制
params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight

# Output
10 # include the bias(intercept)
torch.Size([6, 1, 3, 3])

3.2 计算输出

下面我们尝试一个随机的 32*32 输入,来计算网络的输出:

代码语言:javascript
复制
input = torch.randn(1, 1, 32, 32)
out = net(input) # equal to net.foward(input)
print(out)

# Output
ensor([[-0.0252, -0.1307,  0.0111,  0.0837,  0.0856,  0.2049,  0.1760, -0.1589,
         -0.0831,  0.1625]], grad_fn=<AddmmBackward>)

注意:torch.nn 只支持小批量处理,不支持单独样本。例如 nn.Conv2d 接受一个4维的张量,即 nSamples x nChannels x Height x Width。如果是一个单独的样本,只需要使用 input.unsqueeze(0) 来添加一个“假的” 批大小维度。

3.3 损失函数

一个损失函数接受一对 (output, target) 作为输入,计算一个值来估计网络的输出和目标值相差多少。nn 包中有很多不同的损失函数,其中比较简单的是 nn.MSELoss,即输出与目标的均方误差:

代码语言:javascript
复制
output = net(input)
target = torch.randn(10)  # a dummy target, for example
target = target.view(1, -1)  # make it the same shape as output
criterion = nn.MSELoss()

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

# Output
tensor(1.2990, grad_fn=<MseLossBackward>)

如果使用 loss.grad_fn 属性跟踪反向传播过程,会看到如下的计算图:

代码语言:javascript
复制
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss

当我们调用 loss.backward() ,整张图开始关于 loss 微分,图中所有设置了 requires_grad=True 的张量的 .grad 属性将累积梯度张量:

代码语言:javascript
复制
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

# Output
<MseLossBackward object at 0x7f97fcafbb00>
<AddmmBackward object at 0x7f97fcafbcf8>
<AccumulateGrad object at 0x7f97fcafbcf8>

3.4 反向传播

我们只需要调用 loss.backward() 来反向传播权重。注意需要清零现有的梯度,否则梯度将会与已有的梯度累加。

代码语言:javascript
复制
net.zero_grad()     # zeroes the gradient buffers of all parameters

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)

loss.backward()

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)

输出:

代码语言:javascript
复制
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([-0.0333, -0.0440,  0.0102,  0.0238,  0.0056,  0.0075])

3.5 更新权重

最简单的更新规则是随机梯度下降法(SGD):

代码语言:javascript
复制
weight = weight - learning_rate * gradient

由于只接受批量数据,因此实际上这是一种小批量梯度下降。我们可以通过如下代码实现:

代码语言:javascript
复制
learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

而在实际应用中,我们可能希望使用各种不同的更新规则,如 SGD、Nesterov-SGD、Adam、RMSprop 等,PyTorch 提供了一个较小的包 torch.optim,它实现了所有的这些方法:

代码语言:javascript
复制
import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)

# in your training loop:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update

注意要使用 optimizer.zero_grad() 手动清除梯度缓存。

4 训练分类器

4.1 数据

一般来说,当我们需要处理图片、文本、音频或视频数据时,我们首先使用标准的 python 库将数据载入到一个 numpy 数组中,然后将该数组转化为一个 torch 张量:

  • 对于图片,标准库有 Pillow、OpenCV 等
  • 对于音频,标准库有 scipy、librosa 等
  • 对于文本,可以使用原生的 Python 或 Cython 载入,也可以使用 NLTK、SpaCy 等包

对于视觉(图片)方面,我们创建了一个叫做 torchvision 的包,其中包含了针对 Imagenet、CIFAR10、MINST 等常用数据集的数据加载器,以及对图片变形的操作。

下面的例子中将使用 CIFAR10 数据集,其有如下的分类。图片的数据大小为 3x32x32:

4.2 训练一个图片分类器

我们将进行如下操作:

  1. 通过 torchvision 加载 CIFAR10 训练和测试数据集,并对其进行标准化
  2. 定义卷积神经网络
  3. 定义损失函数
  4. 基于训练数据训练网络
  5. 基于测试数据测试网络
4.2.1 加载并标准化 CIFAR10

使用 torchvision,加载 CIFAR10 非常的简单:

代码语言:javascript
复制
import torch
import torchvision
import torchvision.transforms as transforms

torchvision 输出的数据集为 [0,1] 之间的 PILImage,我们将其标准化为 [-1,1] 之间的张量:

代码语言:javascript
复制
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')

下面查看一下部分训练图片:

代码语言:javascript
复制
import matplotlib.pyplot as plt
import numpy as np

# functions to show an image
def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()

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

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

输出:

4.2.2 定义卷积神经网络

基于之前介绍的神经网络定义,对其稍作修改,可以得到如下的定义:

代码语言:javascript
复制
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()
4.2.3 定义损失函数和优化器

我们使用分类的「交叉熵」作为损失函数,选择「使用动量的随机梯度下降」作为优化器:

代码语言:javascript
复制
import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
4.2.4 训练网络

下面开始进行训练。我们只需要遍历训练数据的迭代器,将输入喂给网络并进行优化即可,代码十分的简单:

代码语言:javascript
复制
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; data is a list of [inputs, labels]
        inputs, labels = data

        # 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.item()
        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')

训练过程的输出如下:

代码语言:javascript
复制
[1,  2000] loss: 2.154
[1,  4000] loss: 1.853
[1,  6000] loss: 1.645
[1,  8000] loss: 1.582
[1, 10000] loss: 1.513
[1, 12000] loss: 1.473
[2,  2000] loss: 1.404
[2,  4000] loss: 1.380
[2,  6000] loss: 1.344
[2,  8000] loss: 1.347
[2, 10000] loss: 1.307
[2, 12000] loss: 1.298
Finished Training

我们可以通过下面的代码快速存储训练好的模型:

代码语言:javascript
复制
PATH = './cifar_net.pth'
torch.save(net.state_dict(), PATH)
4.2.5 使用测试数据测试网络

训练完成后,我们需要在测试集上测试分类效果。首先展示一下测试集数据:

代码语言:javascript
复制
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)))

输出:

下面读取模型并查看模型的预测结果:

代码语言:javascript
复制
net = Net()
net.load_state_dict(torch.load(PATH))

outputs = net(images)

_, predicted = torch.max(outputs, 1) # 得到最高概率的下标
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
                              for j in range(4)))

输出结果为:

代码语言:javascript
复制
Predicted:    dog  ship  ship plane

下面再计算一下整个测试集上的表现:

代码语言:javascript
复制
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))

# Output
Accuracy of the network on the 10000 test images: 55 %

这看起来比随机选择(10%)要好很多。下面再看一下模型在每个类上的表现:

代码语言:javascript
复制
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]))

输出结果为:

代码语言:javascript
复制
Accuracy of plane : 67 %
Accuracy of   car : 54 %
Accuracy of  bird : 31 %
Accuracy of   cat : 29 %
Accuracy of  deer : 41 %
Accuracy of   dog : 54 %
Accuracy of  frog : 64 %
Accuracy of horse : 57 %
Accuracy of  ship : 61 %
Accuracy of truck : 70 %

4.3 在 GPU 上训练

与将一个张量传递给 GPU 一样,我们可以将神经网络转移到 GPU 上。首先定义一个 cuda 设备:

代码语言:javascript
复制
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # Assuming that we are on a CUDA machine, this should print a CUDA device:
print(device)

# Output
cuda:0

然后调用如下方法,其会递归遍历所有模块,将它们的参数和缓冲区转换为 CUDA 张量:

代码语言:javascript
复制
net.to(device)

注意需要将输入和标签在每一步都送进 GPU 中:

代码语言:javascript
复制
inputs, labels = data[0].to(device), data[1].to(device)

由于网络本身不大,所以在 GPU 上训练也不会带来过多的速度提升。

5 思维导图

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 口仆 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 什么是 PyTorch?
    • 1.1 入门
      • 1.1.1 张量
      • 1.1.2 运算
    • 1.2 Numpy 桥
      • 1.3 CUDA 上的张量
      • 2 Autograd:自动求导
        • 2.1 张量
          • 2.2 梯度
          • 3 神经网络
            • 3.1 定义网络
              • 3.2 计算输出
                • 3.3 损失函数
                  • 3.4 反向传播
                    • 3.5 更新权重
                    • 4 训练分类器
                      • 4.1 数据
                        • 4.2 训练一个图片分类器
                          • 4.2.1 加载并标准化 CIFAR10
                          • 4.2.2 定义卷积神经网络
                          • 4.2.3 定义损失函数和优化器
                          • 4.2.4 训练网络
                          • 4.2.5 使用测试数据测试网络
                        • 4.3 在 GPU 上训练
                        • 5 思维导图
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档