深度学习框架PyTorch——从入门到精通(9)PyTorch简介
这部分是 PyTorch介绍——YouTube系列的内容,每一节都对应一个youtube视频。(可能跟之前的有一定的重复)
- PyTorch张量
- 基本操作
- 随机初始化
- 算术运算
- 自动微分简单示例
- PyTorch模型
- 数据集和数据加载器
- 训练你的PyTorch模型
- 训练模型
本节YouTube视频地址:点击这里
PyTorch张量
首先,我们将导入pytorch。
import torch
基本操作
让我们看看一些基本的张量操作。首先,只是创建张量的几种方法:
z = torch.zeros(5, 3)
print(z)
print(z.dtype)
# 输出
tensor([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
torch.float32
上面,我们创建一个充满零的5x3矩阵,并查询其数据类型,发现零是32位浮点数,这是PyTorch默认的。
如果你想要整数呢?您可以随时覆盖默认值:
i = torch.ones((5, 3), dtype=torch.int16)
print(i)
# 输出
tensor([[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]], dtype=torch.int16)
可以看到,当我们确实更改默认值时,张量在打印时会有自动的输出这一点。即张量的数据类型是默认的就不会输出,如果不是默认的(例如上面特意更改的int16,就会在末尾输出类型:dtype=torch.int16。
随机初始化
随机初始化学习权重很常见,通常使用PRNG的特定种子来实现结果的可重复性:
torch.manual_seed(1729)
r1 = torch.rand(2, 2)
print('A random tensor:')
print(r1)
r2 = torch.rand(2, 2)
print('\nA different random tensor:')
print(r2) # r2 会是一个新的值
torch.manual_seed(1729)
r3 = torch.rand(2, 2)
print('\nShould match r1:')
print(r3) # 因为重新设置了种子,所以r3和r1会一样
# 输出
A random tensor:
tensor([[0.3126, 0.3791],
[0.3087, 0.0736]])
A different random tensor:
tensor([[0.4216, 0.0691],
[0.2332, 0.4047]])
Should match r1:
tensor([[0.3126, 0.3791],
[0.3087, 0.0736]])
算术运算
PyTorch张量直观地执行算术运算。类似形状的张量可以相加、相乘等。带有标量的运算分布在张量上:
ones = torch.ones(2, 3)
print(ones)
twos = torch.ones(2, 3) * 2 # 每个值都乘了2
print(twos)
threes = ones + twos # 形状相投的张量允许相加
print(threes) # 张量是按元素相加的
print(threes.shape) # 相加得到的张量与输入的两个的形状一样
r1 = torch.rand(2, 3)
r2 = torch.rand(3, 2)
# 取消注释下面这一行代码以触发运行时错误(猜猜为什么?)
# r3 = r1 + r2
# 输出
tensor([[1., 1., 1.],
[1., 1., 1.]])
tensor([[2., 2., 2.],
[2., 2., 2.]])
tensor([[3., 3., 3.],
[3., 3., 3.]])
torch.Size([2, 3])
以下是可用数学运算的一小部分示例:(更多内容可以回顾我第(2)节张量的内容,其中张量操作部分有全部操作的链接。)
import torch
r = (torch.rand(2, 2) - 0.5) * 2 # 值在 -1 到 1 之间,注意 rand 本身是取 [0, 1) 的随机数。
print('一个随机矩阵 r:')
print(r)
# 常见的数学运算都被支持:
print('\nr 的绝对值:')
print(torch.abs(r))
# ……三角函数也是如此(也被支持)。
print('\nr 的反正弦值:')
print(torch.asin(r))
# ……还有像求行列式和奇异值分解这样的线性代数运算(也被支持)。
print('\nr 的行列式:')
print(torch.det(r))
print('\nr 的奇异值分解:')
print(torch.svd(r))
# 以及统计和聚合运算(也被支持)
print('\nr 的标准差和均值:')
print(torch.std_mean(r))
print('\nr 的最大值:')
print(torch.max(r))
关于PyTorch张量的强大功能还有很多要了解的,包括如何为GPU上的并行计算设置它们——我们接下来简单讲解。
自动微分简单示例
我们使用一个简单的循环神经网络(RNN)作为对PyTorch中自动微分引擎的介绍。
我们从四个张量开始:
- 输入X:
x = torch.randn(1,10)
,randn()是用于生成服从标准正态分布(均值为 0,标准差为 1)的随机数张量,括号里的(1,10)是张量的形状。 - 赋予RNN记忆的隐藏状态 prev_h:
prev_h = torch.randn(1,20)
。 - 两组学习权重 W_h 和 W_x:
W_h = torch.randn(20,20)
是隐藏状态的学习权重,W_x = torch.randn(20,10)
是关于输入的学习权重。
接下来,我们将权重乘以他们各自的张量。下面图片中的MM
代表的数矩阵乘法。
i2h = torch.mm(W_x , x.t())
和 h2h = torch.mm(W_h , prev_h.t())
分别表示乘法的结果。
这里注意:
- 张量.t():表示这个张量的转置。因为矩阵乘法要求第一个的列等于第二个的行。
torch.mm
仅适用于二维矩阵的乘法。若要处理高维张量的批量矩阵乘法,可使用torch.bmm
函数;若要进行更灵活的张量乘法,可使用torch.matmul
函数。
之后,我们将i2h
和h2h
相加next_h = i2h + h2h
,并通过Tanh激活函数来传递结果 next_h = next_h.tanh()
。
最后我们计算结果的损失(loss):loss = next_h.sum()
损失含义是通过这一系列运算,得到的结果(预测值)与真实值之间的差别。
所以,我们采用了训练输入、通过模型运行它、获得输出并确定损失。
对于这些训练循环中的点,我们必须计算损失的导数(模型的各个参数就是偏导数)。并根据这些内容,决定调整学习中的各个权重,使得损失越来越小。
即使是像上方示例中的这种小模型,那也需要一堆的参数与导数的计算。
但是好消息是,我们可以通过一行代码完成它:loss.backward()
这个计算产生的每个张量都知道它是怎么来的,例如,i2h携带的元数据表明它来自矩阵W_x 和 x 的乘法,所以它继续向下、跟踪其余部分。
这种历史跟踪方式使得反向传播时快速计算你的模型中需要学习的梯度。这个历史追踪方法是在你的模型中实现灵活性与快速迭代的东西,即使在具有决策分支和循环的复杂模型中,计算历史也可以通过特定的模型跟踪特定的路劲,并正确计算反向导数。
PyTorch模型
到目前为止,我们已经讨论了一些关于张量和自动微分的知识,还有他们与pytorch模型交互的方式。但是真正的模型在代码中是什么样子的?
让我吗构建并运行一个简单的模型来感受一下。
首先,我们要先导入PyTorch,以及包含神经网络层的PyTorch.nn。
导入torch.nn.functional
是为了方便调用里面的激活函数与池化层,(我们用它来连接神经网络的各个层)
import torch # for all things PyTorch
import torch.nn as nn # for torch.nn.Module, the parent object for PyTorch models
import torch.nn.functional as F # for the activation function
上图是LeNet-5的示意图,LeNet-5是最早的卷积神经网络之一,也是深度学习爆炸的驱动力之一。它被用来读取手写数字的小图像(MNIST数据集),并正确分类图像中表示的数字。
以下是它工作原理的删节版本:
- 层C1是一个卷积层,这意味着它扫描输入图像,以查找在训练期间学习到的特征。它输出一张图——显示它在图像中看到每个学习到的特征的位置。这个“激活图”在层S2中被下采样(Subsampling)。
- C3层是另一个卷积层,这次扫描C1的激活图以查找特征的组合。它还给出了描述这些特征组合的空间位置的激活图,在S4层进行下采样。
- 最后,末端的全连接层F5、F6和OUTPUT是一个分类器,它获取最终的激活映射,并将其分类为代表10位数字的十个箱之一。
我们如何用代码表达这个简单的神经网络?
import torch.nn as nn
import torch.nn.functional as F
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
# 1 个输入图像通道(黑白图像),6 个输出通道,5x5 方形卷积核
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# 仿射变换操作:y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120) # 5*5 来自图像尺寸
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# 在 (2, 2) 窗口上进行最大池化操作
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# 如果池化窗口是方形的,可以只指定一个数字
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:] # 除了批量维度之外的所有维度
num_features = 1
for s in size:
num_features *= s
return num_features
查看这段代码,您应该能够发现上面图表的一些结构上的相似。
这演示了典型PyTorch模型的结构:
- 它继承自
torch.nn.Module
模块(可能是嵌套的),事实上,即使是Conv2d
和Linear
层类也继承自torch.nn.Module
。 - 模型将有一个
__init__()
函数,它在其中实例化它的层,并加载它可能需要的任何数据工件(例如,NLP模型可能加载词汇表)。 - 模型将具有
forward()
函数。这就是实际计算发生的地方:输入通过网络层和各种函数传递以生成输出。 - 除此之外,您可以像任何其他Python类一样构建模型类,添加支持模型计算所需的任何属性和方法。
现在,让我们实例化这个对象并通过它运行一个示例输入。
net = LeNet()
print(net) # what does the object tell us about itself?
input = torch.rand(1, 1, 32, 32) # stand-in for a 32x32 black & white image
print('\nImage batch shape:')
print(input.shape)
output = net(input) # we don't call forward() directly
print('\nRaw output:')
print(output)
print(output.shape)
输出会像下面这样:(注意,因为输入是随机的,所以在Raw output时数可能不一样。)
上面发生了一些重要的事情:
首先,我们实例化LeNet类,并打印net对象。torch.nn.Module
的子类将报告它创建的层及其形状和参数。如果您想获得模型处理的要点,这可以提供模型的便捷概述。
下面,我们创建一个虚拟输入,表示具有1个颜色通道的32x32图像。通常,您会加载图像图块并将其转换为此形状的张量。
您可能已经注意到我们的张量的一个额外维度-批处理维度。PyTorch模型假设他们正在处理批次数据-例如,我们的一批16个图像块的形状为(16, 1, 32, 32)。由于我们只使用一个图像,我们创建了一个形状为(1, 1, 32, 32)的批次1。
我们通过调用函数:net(input)
来要求模型进行推理。这个调用的输出表示模型对输入表示特定数字的信心。(由于模型的这个实例还没有学到任何东西,我们不应该期望在输出中看到任何信号。)查看output的形状,我们可以看到它也有一个批处理维度,它的大小应该始终与输入批处理维度相匹配。如果我们传入了16个实例的输入批处理,output的形状将为(16, 10)。
数据集和数据加载器
下面,我们将演示如何使用TorchVision中的一个可下载、开放访问的数据集,如何转换图像以供模型使用,以及如何使用DataLoader将批量数据馈送到模型。
我们需要做的第一件事是将传入的图像转换为PyTorch张量。
#%matplotlib inline
import torch
import torchvision
import torchvision.transforms as transforms
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616))])
在这里,我们为输入指定两个转换:
transforms.ToTensor()
将Pillow加载的图像转换为PyTorch张量。transforms.Normalize()
调整张量的值,使其平均值为零,均方差为1.0。大多数激活函数在x=0附近有最强的梯度,因此在那里输入我们的数据可以加快学习速度。传递给转换的值是数据集中图像的rgb值的均值(第一个元组)和标准差(第二个元组)。您可以通过运行这几行代码自己计算这些值:
from torch.utils.data import ConcatDataset transform = transforms.Compose([transforms.ToTensor()])
trainset = torchvision.datasets.CIFAR10(root=’./data’, train=True,download=True, transform=transform)
# 将所有训练图像堆叠在一起,形成一个形状为 (50000, 3, 32, 32) 的张量
# x = torch.stack([sample[0] for sample in ConcatDataset([trainset])])
# 获取每个通道的均值
# mean = torch.mean(x, dim=(0, 2, 3)) # 得到张量 [0.4914, 0.4822, 0.4465]
# std = torch.std(x, dim=(0, 2, 3)) # 得到张量 [0.2470, 0.2435, 0.2616]
还有更多可用的转换,包括裁剪、定心、旋转和反射。
接下来,我们将创建CIFAR10数据集的实例。这是一组32x32彩色图像图块,代表10类对象:6种动物(鸟、猫、鹿、狗、青蛙、马)和4种车辆(飞机、汽车、轮船、卡车)
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
(下载数据集可能需要一点时间)
这是在PyTorch中创建数据集对象的示例。可供下载的数据集(如上面的CIFAR-10)是torch.utils.data.Dataset
。Dataset
类包括TorchVision、Torchtext和TorchAudio中的可下载数据集,以及torchvision.datasets.ImageFolder
等实用程序数据集类,它将读取标记图像的文件夹。您还可以创建自己的Dataset
子类。
当我们实例化我们的数据集时,我们需要告诉它一些事情:
- 我们希望数据到达的文件系统路径。
- 无论我们是否使用此集进行训练;大多数数据集将分为训练子集和测试子集。
- 如果我们还没有下载数据集,我们是否要下载。
- 我们要应用于数据的转换。
一旦您的数据集准备好,您可以将其提供给DataLoader:
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)
Dataset
子类包装对数据的访问,并且专门针对它所服务的数据类型。DataLoader
对数据本身一无所知,但会根据你指定的参数,将数据集提供的输入张量整理成一个个批次。
在上述示例中,我们要求一个数据加载器(DataLoader)从训练数据集(trainset)中为我们提供大小为 4 张图像的批次数据,并且对数据的顺序进行随机化处理(shuffle=True
),同时我们告知它启动两个工作进程来从磁盘加载数据。
可视化DataLoader服务的批次是一种很好的做法:
import matplotlib.pyplot as plt
import numpy as np
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
# get some random training images
dataiter = iter(trainloader)
images, labels = next(dataiter)
# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
运行上面的代码应该会显示一个由四个图像组成的条带,以及每个图像的正确标签。
(这里我就不贴输出了,大家可以自己试一下。)
训练你的PyTorch模型
让我们把所有的部分放在一起,训练一个模型:
#%matplotlib inline
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
首先,我们需要训练和测试数据集。如果您还没有,请运行下面的代码以确保数据集已下载。(可能需要一分钟。)
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')
我们将对DataLoader的输出进行检查:
# 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)))
# get some random training images
dataiter = iter(trainloader)
images, labels = next(dataiter)
# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
这里需要注意,如果你是windows系统,直接用官网代码,会报错:
这个 RuntimeError
报错是由于在当前进程完成自举阶段(bootstrapping phase)之前就尝试启动一个新进程。在Windows系统下使用多进程(如PyTorch的DataLoader
启用多工作进程)时,如果没有正确使用相关代码结构就容易出现该问题。
主要原因是没有在主模块中使用合适的代码结构,Python 的多进程在Windows上的实现与Linux不同,Windows不支持fork
方式启动子进程,需要将多进程相关代码放在if __name__ == '__main__':
语句块中 。如果程序要被冻结成可执行文件,还需要调用freeze_support()
函数。
所以我们应该加上if __name__ == '__main__':
最终代码应该是这样:
#%matplotlib inline
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
if __name__ == '__main__':
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')
def imshow(img):
img = img / 2 + 0.5 # unnormalize
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
# get some random training images
dataiter = iter(trainloader)
images, labels = next(dataiter)
# show images
imshow(torchvision.utils.make_grid(images))
plt.show() # 加上这一行显示图片
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
注意我在这里加了 plt.show()
来展示图像,而这个是一个阻塞函数,你看完图片之后要关掉图片窗口,才能看到输出。
训练模型
下面这个是我们将要训练的模型。如果它看起来很熟悉,那是因为它是LeNet的变体——在本视频前面讨论过——适用于3色图像。
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()
我们需要的最后一个成分是损失函数和优化器:
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
损失函数,如本视频前面所讨论的,是衡量模型预测离我们的理想输出有多远。交叉熵损失是像我们这样的分类模型的典型损失函数。
优化器是驱动学习的东西。在这里,我们创建了一个实现随机梯度下降的优化器,一种更前向的优化算法。除了算法的参数,如学习率(lr)和动量,我们还传入了net.parameters()
,它是模型中所有学习权重的集合——这是优化器调整的。
最后,所有这些都被组装到训练循环中。继续运行这个单元格,因为它可能需要几分钟才能执行:
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
# 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')
在这里,我们只做了2个训练循环(第1行)——也就是说,在训练集上做了两次。每遍都有一个内部循环,迭代训练数据(第4行),提供批量转换的输入图像及其正确的标签。
将梯度归零(第9行)是一个重要的步骤。梯度在一个批次中积累;如果我们不为每个批次重置它们,它们将继续积累,这将提供不正确的梯度值,使学习成为不可能。
在第12行,我们询问模型对该批次的预测。在接下来的第(13)行中,我们计算损失——outputs(模型预测)和labels(正确输出)之间的差异。
在第14行,我们执行backward()
传递,并计算将指导学习的梯度。
在第15行,优化器执行一个学习步骤——它使用来自backward()
调用的梯度将学习权重推向它认为会减少损失的方向。
循环的其余部分对循环次数编号、已完成的训练实例数量以及训练循环上的收集损失进行一些简单的报告。
注意:你运行上面的代码之前,还是需要改一下位置,加入if __name__ == '__main__':
完整代码:
#%matplotlib inline
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
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
if __name__ == '__main__':
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')
net = Net()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
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
# 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')
输出:
请注意,损失是单调下降的,表明我们的模型正在继续提高其在训练集上的性能。
作为最后一步,我们应该检查模型是否真的在进行一般学习,而不是简单地“记忆”数据集。这被称为过度拟合,并且通常表明数据集太小(没有足够的示例进行一般学习),或者模型的学习参数比正确建模数据集所需的要多。
这就是数据集被分成训练子集和测试子集的原因——为了测试模型的通用性,我们要求它对尚未训练的数据进行预测:
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))
如果你继续下去,你应该会看到这个模型在这一点上大约有50%的准确率。这并不是最先进的,但它比我们期望的随机输出的10%准确率要好得多。这表明模型中确实发生了一些一般的学习。