1. 神经网络

1.1 多层感知机

以多层感知机(multilayer perceptronMLP)为例,介绍神经⽹络的概念。

多层感知机在输出层与输⼊层之间加⼊了⼀个或多个全连接隐藏层,并通过激活函数对隐藏层输出进⾏变换

常⽤的激活函数包括ReLU函数、 sigmoid函数和tanh函数。

1.2 隐藏层

多层感知机在单层神经⽹络的基础上引⼊了⼀到多个隐藏层(hidden layer)。隐藏层位于输⼊层和输出层之间。 多层感知机中的隐藏层和输出层都是全连接层

给定⼀个小批量样本$X\in \mathrm{R}^{n\times d}$,其批量⼤小为$n$,输⼊个数为$d$。假设多层感知机只有⼀个隐藏层,其中隐藏单元个数为$h$。记隐藏层的输出(也称为隐藏层变量或隐藏变量)为$H$,有$H\in \mathrm{R}^{n \times h}$。因为隐藏层和输出层均是全连接层,可以设隐藏层的权重参数和偏差参数分别为$W_h \in \mathrm{R}^{d \times h}$和$b_h \in \mathrm{R}^{1\times h}$,输出层的权重和偏差参数分别为$W_o \in \mathrm{R}^{h\times q}$和$b_o \in \mathrm{R}^{1\times q}$。

单隐藏层的多感知机,其输出$O\in R^{n\times q}$的计算为

$$H=XW_h+B_h,O=HW_0+b_0$$

联立有

$$O=(XW_h+b_h)W_o+b_o=XW_hW_o+b_hW_o+b_o$$

虽然神经⽹络引⼊了隐藏层,却依然等价于⼀个单层神经⽹络:其中输出层权重参数为$WhW_o$,偏差参数为$bhW_o+b_o$

1.3 激活函数

全连接层只是对数据做仿射变换(affine transformation),而多个仿射变换的叠加仍然是⼀个仿射变换 。例如对隐藏变量使⽤按元素运算的⾮线性函数进⾏变换,然后再作为下⼀个全连接层的输⼊。这个⾮线性函数被称为激活函数(activation function

1.3.1 ReLU函数

ReLUrectified linear unit)函数提供了⼀个很简单的⾮线性变换。给定元素$x$,该函数定义为$$ReLU(x)=\rm max(x,0)$$

当输⼊为负数时, ReLU函数的导数为0;当输⼊为正数时, ReLU函数的导数为1。尽管输
⼊为
0ReLU函数不可导,但是我们可以取此处的导数为0

1.3.2 sigmoid函数

sigmoid函数可以将元素的值变换到01之间: $$\rm sigmoid(x)=\frac{1}{1+exp(-x)}$$

依据链式法则, sigmoid函数的导数为

$$\rm sigmoid^{\prime}(x)=\rm sigmoid(x)(1-\rm  sigmoid(x))$$

1.3.2 tanh 函数

tanh(双曲正切)函数可以将元素的值变换到-11之间:$$\rm tanh(x)=\frac{1-\rm exp(-2x)}{1+\rm exp(-2x)}$$

tanh函数的导数为$\rm tanh^{\prime}(x)=1-\rm tanh^2(x)$

1.2 卷积层

可以使用该torch.nn软件包构建神经网络。

现在,您已经了解了autograd,这nn取决于 autograd定义模型并对其进行区分。一个nn.Module包含层,和一种方法forward(input),它返回output

例如,查看以下对数字图像进行分类的网络:

这是一个简单的前馈网络。它获取输入,将其一层又一层地馈入,然后最终给出输出。

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

  1. 定义具有一些可学习参数(或权重)的神经网络
  2. 遍历输入数据集
  3. 通过网络处理输入
  4. 计算损失(输出正确的距离有多远)
  5. 将梯度传播回网络的参数
  6. 通常使用简单的更新规则来更新网络的权重: weight=weight-learning_rate*gradient

2. 定义网络

让我们定义这个网络:

import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

    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)

Out:

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)
)

您只需要定义forward函数,backward 就可以使用自动定义函数(计算梯度)autograd。您可以在forward函数中使用任何Tensor操作。

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

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

Out:

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

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

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

Out:

tensor([[ 0.0773, -0.0830,  0.0375,  0.0779,  0.0300,  0.1016,  0.0051,  0.0286,
         -0.0308,  0.0601]], grad_fn=<AddmmBackward>)

使用随机梯度将所有参数和反向传播的梯度缓冲区归零:

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

注意

  • torch.nn仅支持小批量。整个torch.nn 程序包仅支持作为小批量样本的输入,而不支持单个样本。
  • 例如,nn.Conv2d将采用的4D张量 。nSamples x nChannels x Height x Width
  • 如果您只有一个样本,则只需使用input.unsqueeze(0)即可添加伪造的批次尺寸。

3. 损失函数

3.1 softmax运算

将输出值变换成值为正且和为1的概率分布:$y_i=softmax(o_i),i=1,\cdots,n$

$$y_i=\frac{exp(o_i)}{\sum_{i=1}^{n}exp(o_i)}$$

3.2 交叉熵损失函数

使⽤更适合衡量两个概率分布差异的测量函数,为使预测概率分布$\hat{y}^{(i)}$尽可能接近真实的标签概率分布$y^{(i)}$。其中,交叉熵(cross entropy)是⼀个常⽤的衡量⽅法

$$H(y^{(i)},\hat{y}^{(i)})=-\sum_{j=1}^{q}y^{(i)}{\rm log}{\hat{y}^{(i)}},$$

假设训练数据集的样本数为$n$,交叉熵损失函数定义为
$$l(\theta)=\frac{1}{n}\sum_{i=1}^{n}H(y^{(i)},\hat{y}^{(i)}),$$其中$\theta$为模型参数。

3.3 计算损失函数

损失函数采用一对(输出,目标)输入,并计算一个值,该值估计输出与目标之间的距离。

nn软件包下有几种不同的 损失函数。一个简单的损失是:nn.MSELoss计算输入和目标之间的均方误差。

例如:

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)

Out:

tensor(0.7633, grad_fn=<MseLossBackward>)

现在,如果您loss使用.grad_fn属性的属性向后移动, 您将看到如下所示的计算图:

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

因此,当我们调用时loss.backward(),整个图与损失是微分的,并且图中的所有张量都requires_grad=True 将具有.grad随梯度累积的张量。

为了说明,让我们向后走几步:

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

Out:

<MseLossBackward object at 0x7fee168d6a90>
<AddmmBackward object at 0x7fee168d6ac8>
<AccumulateGrad object at 0x7fee168d6ac8>

4. 正向反向传播

4.1 正向传播

正向传播(forward propagation)是指对神经⽹络沿着从输⼊层到输出层的顺序,依次计算并存
储模型的中间变量(包括输出)。

假设输⼊是⼀个特征为$x\in \mathrm{R}^d$的样本,且不考虑偏差项,那么中间变量

$$z  = W^{(1)}x,$$

其中$W^{(1)}$是隐藏层的权重参数。把中间变量$z\in \mathrm{R}^h$输入按元素运算的激活函数$\phi$后,将得到向量长度为$h$的隐藏层变量

$$h = \phi(z)$$

隐藏层变量$h$也是一个中间变量。假设输出层参数只有权重$W^{(2)}\in \mathrm{R}^{q\times h}$,可以得到一个向量长度为$q$的输出层变量

$$o = W^{(2)}h$$

假设损失函数为$l$,且样本标签为$y$,可以计算单个数据样本的损失项

$$L = l(o,y).$$

根据$L_2$范数正则化的定义,给定超参数$\lambda$,正则化项即

$$s = \frac{\lambda}{2}\left(\left\|W^{(1)} \right\|^2_F + \left\|W^{(2)}\right\|^@_F \right),$$

其中矩阵的Frobenius范数等价于将矩阵变平为向量后计算的$L_2$范数。 最终,模型在给定的数据样本上带正则化的损失为

$$J = L + s.$$

将$J$称为有关给定数据样本的⽬标函数,简称⽬标函数。

通常绘制计算图(computational graph)来可视化运算符和变量在计算中的依赖关系

4.2 反向传播

反向传播(back-propagation)指的是计算神经⽹络参数梯度的⽅法。总的来说,反向传播依据微积分中的链式法则,沿着从输出层到输⼊层的顺序,依次计算并存储⽬标函数有关神经⽹络各层的中间变量以及参数的梯度。 对输⼊或输出$X,Y,Z$为任意形状张量的函数$Y=f(X)$和$Z=g(Y)$,通过链式法则,我们有
$$\frac{\partial Z}{\partial X} = prod \left(\frac{\partial Z}{\partial Y},\frac{\partial Y}{\partial X} \right),$$

其中prod运算符将根据两个输⼊的形状,在必要的操作(如转置和互换输⼊位置)后对两个输⼊做乘法。

考虑上述样例模型,它的参数是$W^{(1)}$和$W^{(2)}$,因此反向传播的⽬标是计算${\partial J}/{\partial W^{(1)}}$和${\partial J}/{\partial W^{(2)}}$ 应⽤链式法则依次计算各中间变量和参数的梯度,其计算次序与前向传播中相应中间变量的计算次序恰恰相反。 ⾸先,分别计算⽬标函数$J = L+s$有关损失项$L$和正则项$s$的梯度
$$\frac{\partial J}{\partial L}=1, \frac{\partial J}{\partial s}=1.$$

其次, 依据链式法则计算⽬标函数有关输出层变量的梯度${\partial J}/{\partial o}\in \mathrm{R}^q$:

$$\frac{\partial J}{\partial o} = prod \left(\frac{\partial J}{\partial J},\frac{\partial L}{\partial o} \right) = \frac{\partial L}{\partial o}.$$

接下来,计算正则项有关两个参数的梯度:

$$\frac{\partial s}{\partial W^{(1)}} = \lambda W^{(1)},\frac{\partial s}{\partial W^{(2)}} = \lambda W^{(2)}.$$

现在,我们可以计算最靠近输出层的模型参数的梯度${\partial J}/{\partial W^{(2)}} \in \mathrm{R}^{q \times h}$。依据链式法则,得到

$$\frac{\partial J}{\partial W^{(2)}} = {\rm prod} \left(\frac{\partial J}{\partial o},\frac{\partial o}{\partial W^{(2)}} \right) + {\rm prod} \left(\frac{\partial J}{\partial s},\frac{\partial s}{\partial W^{(2)}} \right) = \frac{\partial J}{\partial o}h^{\top} + \lambda W^{(2)}.$$

沿着输出层向隐藏层继续反向传播,隐藏层变量的梯度${\partial J}/{\partial h} \in \mathrm{R}^h$可以这样计算:

$$\frac{\partial J}{\partial h} = {\rm prod} \left(\frac{\partial J}{\partial o},\frac{\partial o}{\partial h} \right) = W^{{(2)}^{\top}} \frac{\partial J}{\partial o}.$$

由于激活函数$\phi$是按元素运算的,中间变量$z$的梯度${\partial J}/{\partial z}\in \mathrm{R}^h$的计算需要使⽤按元素乘法符$\odot$

$$\frac{\partial J}{\partial z} = {\rm prod} \left(\frac{\partial J}{\partial h},\frac{\partial h}{\partial z} \right) = \frac{\partial J}{\partial o} \cdot \phi^{\prime}(z).$$

最终,我们可以得到最靠近输⼊层的模型参数的梯度${\partial J}/{\partial W^{(1)}}\in \mathrm{R}^{h\times d}$。依据链式法则,得到

$$\frac{\partial J}{\partial W^{(1)}} = {\rm prod} \left(\frac{\partial J}{\partial z},\frac{\partial z}{\partial W^{(1)}} \right) + {\rm prod} \left(\frac{\partial J}{\partial s},\frac{\partial s}{\partial W^{(1)}} \right) = \frac{\partial J}{\partial z}x^{\top} + \lambda W^{(1)}.$$

要反向传播错误,我们要做的就是loss.backward()。不过,您需要清除现有的渐变,否则渐变将累积到现有的渐变中。

现在,我们将调用loss.backward(),并查看向后前后conv1的偏差梯度。

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)

Out:

conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([-0.0014, -0.0118,  0.0123, -0.0003,  0.0080,  0.0007])

5. 更新权重

5.1 权重衰减

过拟合现象,即模型的训练误差远小于它在测试集上的误差。虽然增⼤训练数据集可能会减轻过拟合,但是获取额外的训练数据往往代价⾼昂。本节介绍应对过拟合问题的常⽤⽅法:权重衰减(weight decay)。

  • 权重衰减等价于L2范数正则化(regularization)。正则化通过为模型损失函数添加惩罚项使学出的模型参数值较小,是应对过拟合的常⽤⼿段。
  • L2范数正则化在模型原损失函数基础上添加L2范数惩罚项,从而得到训练所需要最小化的函数。
  • L2范数惩罚项指的是模型权重参数每个元素的平⽅和与⼀个正的常数的乘积。

$$l(\omega_1,\omega_2,b)=\frac{1}{n}\sum_{i=1}^{n} \frac{1}{2}\left(x^{(i)}_1 \omega_1 + x^{(2)}_2\omega_2 +b -y^{(i)} \right)^2$$

其中$\omega_1,\omega_2$是权重参数,$b$是偏置参数,样本$i$的输入为$x^{(i)}_1,x^{(i)}_2$,标签为$y^{(i)}$,样本数为$n$。 将权重参数⽤向量$\omega=[\omega_1,\omega_2]$表⽰,带有$L_2$范数惩罚项的新损失函数为

$$l(\omega_1,\omega_2,b)+\frac{\lambda}{2} \left\| \omega \right\|^2,$$

其中超参数$\lambda>0$。 当权重参数均为0时,惩罚项最小。当$\lambda$较⼤时,惩罚项在损失函数中的⽐重较⼤,这通常会使学到的权重参数的元素较接近0。当$\lambda$设为0时,惩罚项完全不起作⽤。

5.1.1 实现方法

  • 初始化模型参数
  • 定义$L_2$范数惩罚项
  • 定义训练和测试
  • 观察过拟合
  • 使用权重衰减: 训练误差虽然有所提⾼,但测试集上的误差有所下降。 过拟合现象得到⼀定程度的缓解。

5.2 Dropout

深度学习模型常常使⽤丢弃法(dropout来应对过拟合问题。

例如⼀个单隐藏层的多层感知机,其中输⼊个数为4,隐藏单元个数为5,且隐藏单元$h_i(i=1,\cdots,5)$的计算表达式为

$$h_i = \phi(x_1 \omega_{1i} + x_2 \omega_{2i}+x_3 \omega_{3i}+x_4 \omega_{4i}+b_i)$$

这⾥$\omega$是激活函数,$x_1,\cdots,x_n$是输⼊,隐藏单元i的权重参数为$w_{1i},\cdots,w_{4i}$偏差参数为$b_i$。当对该隐藏层使⽤丢弃法时,该层的隐藏单元将有⼀定概率被丢弃掉。设丢弃概率为$p$,那么有$p$的概率$h_i$会被清零,有$1-p$的概率$h_i$会除以$1-p$做拉伸。丢弃概率是丢弃法的超参数。具体来说,设随机变量$\xi_i$01的概率分别为$p$和$1 - p$。使⽤丢弃法时我们计算新的隐藏单元$h^{\prime}_i$

$$h^{\prime}_i = \frac{\xi_i}{1-p}h_i$$

由于$E(\xi_i)=1-p$,因此

$$E(h^{\prime}_i) = \frac{E(\xi)}{1-p}h_i = h_i$$

即丢弃法不改变其输入的期望值。

由于在训练中隐藏层神经元的丢弃是随机的,即$h_1,\cdots,h_5$都有可能被清零,输出层的计算⽆法过度依赖$h_1,\cdots,h_5$中的任⼀个,从而在训练模型时起到正则化的作⽤,并可以⽤来应对过拟合。在测试模型时,我们为了得到更加确定性的结果,⼀般不使⽤丢弃法。

5.2.1 实现方法

  • 定义模型函数: 定义⼀个包含两个隐藏层的多层感知机
  • 定义模型: 将全连接层和激活函数ReLU串起来,并对每个激活函数的输出使⽤丢弃法。
  • 训练与测试模型

5.3 随机梯度下降

实践中使用的最简单的更新规则是随机梯度下降(SGD):

weight = weight - learning_rate * gradient

我们可以使用简单的Python代码实现此目的:

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

但是,在使用神经网络时,您希望使用各种不同的更新规则,例如SGD,Nesterov-SGD,Adam,RMSProp等。为实现此目的,我们构建了一个小程序包:torch.optim实现所有这些方法。使用它非常简单:

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

6. 训练图像分类器

我们将使用CIFAR10数据集。它具有以下类别:“飞机”,“汽车”,“鸟”,“猫”,“鹿”,“狗”,“青蛙”,“马”,“船”,“卡车”。CIFAR-10中的图像尺寸为3x32x32,即尺寸为32x32像素的3通道彩色图像。

6.1 加载并标准化数据集

使用torchvision,加载CIFAR10非常容易。

import torch
import torchvision
import torchvision.transforms as transforms

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

If running on Windows and you get a BrokenPipeError, try setting
the num_worker of torch.utils.data.DataLoader() to 0.
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')

Out:

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

我们展示一些训练图像。

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)))

Out:

ship  bird   car plane

6.2 定义卷积神经网络

之前从“神经网络”部分复制神经网络,然后对其进行修改以获取3通道图像(而不是定义的1通道图像)

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()

6.3 定义损失函数、优化器

让我们使用分类交叉熵损失和带有动量的SGD。

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

6.4 训练网络

遍历数据迭代器,然后将输入馈送到网络并进行优化。

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')

Out:

[1,  2000] loss: 2.210
[1,  4000] loss: 1.831
[1,  6000] loss: 1.675
[1,  8000] loss: 1.595
[1, 10000] loss: 1.567
[1, 12000] loss: 1.511
[2,  2000] loss: 1.424
[2,  4000] loss: 1.394
[2,  6000] loss: 1.383
[2,  8000] loss: 1.356
[2, 10000] loss: 1.341
[2, 12000] loss: 1.311
Finished Training

保存我们训练的模型:

PATH = './cifar_net.pth'
torch.save(net.state_dict(), PATH)

有关 保存PyTorch模型的更多详细信息,请参见此处

6.5 在测试集上进行测试

我们已经在训练数据集中对网络进行了2次训练。但是我们需要检查网络是否学到了什么。

我们将通过预测神经网络输出的类标签并根据实际情况进行检查来进行检查。如果预测正确,则将样本添加到正确预测列表中。

第一步。让我们显示测试集中的图像以使其熟悉。

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)))

Out:

GroundTruth:    cat  ship  ship plane

接下来,让我们重新加载保存的模型(注意:这里不需要保存和重新加载模型,我们只是为了说明如何进行操作):

net = Net()
net.load_state_dict(torch.load(PATH))

现在让我们看看神经网络对以上这些示例:

outputs = net(images)

输出是10类的能量。一个类别的能量越高,网络就认为该图像属于特定类别。因此,让我们获得最高能量的指数:

_, predicted = torch.max(outputs, 1)

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

Out:

Predicted:    cat   car   car  ship

让我们看一下网络在整个数据集上的表现。

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))

看起来比偶然更好,准确率是10%(从10个班级中随机选择一个班级)。好像网络学到了一些东西。哪些类的表现良好,哪些类的表现不佳:

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]))

Out:

Accuracy of plane : 50 %
Accuracy of   car : 77 %
Accuracy of  bird : 44 %
Accuracy of   cat : 44 %
Accuracy of  deer : 60 %
Accuracy of   dog : 35 %
Accuracy of  frog : 51 %
Accuracy of horse : 50 %
Accuracy of  ship : 71 %
Accuracy of truck : 52 %

那我们如何在GPU上运行这些神经网络?

6.6 在GPU上训练

就像将Tensor转移到GPU上一样,您也将神经网络转移到GPU上。

如果我们有可用的CUDA,首先让我们将设备定义为第一个可见的cuda设备:

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)

Out:

cuda:0

本节的其余部分假定这device是CUDA设备。

然后,这些方法将递归遍历所有模块,并将其参数和缓冲区转换为CUDA张量:

net.to(device)

最后,还必须将每一步的输入和目标也发送到GPU:

inputs, labels = data[0].to(device), data[1].to(device)