PyTorch 60-Minute Blitz

2020-08-14 16:03:55 浏览数 (1)

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}=3left(x_{i} 2right)^{2}

以及

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

,因此

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

,从而得到

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=gleft(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, ]] 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 : - %%' % (
        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 思维导图

0 人点赞