根据前面的三次教程,我们了解了如何定义网络,计算损失和更新网络权重。前面我们的输入数据都是随机的,那么真实的数据呢?这一次我们将用真实数据训练分类器。
一. 关于数据:
通常,我们会处理图像,文本,音频或视频数据。你可以使用下面列出的python标准包将数据加载到numpy数组中,之后像我们记录一中提到的,使用torch.as_tensor()方法,将它转化成torch.Tensor。
-
对于图像数据,可以使用像Pillow, OpenCV等包
-
对于音频数据,使用scipy和librosa等
-
对于文本数据,使用原生的Python语句和Cython,或者NLTK和SpaCy
具体的,我们使用torchvision来处理计算机视觉的任务,它包含一下内容:
- 数据集:torchvision.datasets提供了一些常见的图像数据集,如 ImageNet、CIFAR10 和 MNIST。
- 数据转换器:torchvision.transforms提供了一些图像转换操作,对图像进行预处理,例如将图像调整大小、裁剪、归一化等。
- 数据加载器:torch.utils.data.DataLoader可以按批次(patches)的加载数据,提供了批量加载(以便有效利用GPU)、是否打乱数据顺序(是否在每一个轮次epoch开始时打乱数据,避免模型对数据的顺序敏感)、并行加载(多线程)等功能。
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
在本教程中,我们将使用 CIFAR10 数据集。它包含很多类别:飞机,汽车,鸟,猫,鹿,狗,青蛙,马,船,卡车。图像的大小为 3x32x32,也就是 32x32 像素大小的 3 通道彩色图像。
下面我们将按顺序执行以下步骤:
-
使用torchvision加载并归一化 CIFAR10 训练和测试数据集
-
定义卷积神经网络
-
定义损失函数
-
在训练数据上训练网络
-
在测试数据上测试网络
二. 训练分类器:
1. 加载数据并归一化
为了加速模型的收敛过程,我们首先将数据的范围归一化到[-1, 1]。原因是,torchvision中数据集的格式是范围在[0, 1]的PILImage,不利于训练。然后我们按照批量大小依次加载图像到torch.utils.data.DataLoader的实例trainloader和testloader中。
import torch
import torchvision
import torchvision.transforms as transforms
# 转换成张量,再使用transforms.Normalize()方法进行归一化
# PILImage在[0,1]之间,因此每个通道的均值和标准差都是0.5
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# 批量大小
batch_size = 4
# 训练集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size,
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=batch_size,
shuffle=False, num_workers=2)
# 类标签
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
一个有趣的例子,刚刚我们将图像归一化处理了,现在我们进行反向操作,复原它们并且画出它们。由于torch.utils.data.DataLoader是可迭代的,我们可以使用迭代的方法依次调用这些批次的图像。那么可以像这样打印一个批次的图像的代码:
import matplotlib.pyplot as plt
import numpy as np
# 打印一张图
def imshow(img):
img = img / 2 + 0.5 # 刚刚将图片数据归一化了,现在反归一化,以便显示
npimg = img.numpy()
# transforms.ToTensor()会自动将通道数放第一位,这里要改回来
# 使用np.transpose()更改多维数组的轴顺序
# CHW->HWC
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
# 加载一个批次的训练图片
dataiter = iter(trainloader)
images, labels = next(dataiter)
# 打印图片
imshow(torchvision.utils.make_grid(images))
# 打印标签
print(' '.join(f'{classes[labels[j]]:5s}' for j in range(batch_size)))
这里我们就完成了数据的加载。如果你运行下面的代码,查看images的大小:
images.shape
Out:
torch.Size([4, 3, 32, 32])
这可以验证,每一次迭代输入的数据images,是一个四个三通道32*32的图像,这与我们定义的批次大小(batch_size)相符。
上一个教程中,我们定义了一个神经网络,并使用一张单通道图像对它进行训练。下面我们根据上述加载真实数据的过程对它进行修改,以实现分批次的训练。
2. 定义卷积神经网络
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super().__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 = torch.flatten(x, 1) # flatten all dimensions except batch
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
3. 定义损失函数和优化器
上次我们提到优化器模块optim提供不同的优化器,这里我们选择交叉熵损失计算损失,使用带动量的SGD来更新参数。
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
4. 训练网络:
这里我们就很简单了,循环遍历数据迭代器,将输入提供给网络并进行优化。
for epoch in range(2): # 多次遍历整个数据集
running_loss = 0.0
for i, data in enumerate(trainloader, 0): # 遍历trainloader中的所有批次
# 得到输入; data是[inputs, labels]的list
inputs, labels = data
# 梯度清零
optimizer.zero_grad()
# 前向 + 后向 + 优化
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# 打印统计信息
running_loss += loss.item()
if i % 2000 == 1999: # 每2000个批次,打印一次,每个批次四个图像
# 计算他们损失的平均值
print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.3f}')
running_loss = 0.0
print('Finished Training')
Out:
[1, 2000] loss: 2.156
[1, 4000] loss: 1.803
[1, 6000] loss: 1.630
[1, 8000] loss: 1.563
[1, 10000] loss: 1.502
[1, 12000] loss: 1.472
[2, 2000] loss: 1.409
[2, 4000] loss: 1.361
[2, 6000] loss: 1.359
[2, 8000] loss: 1.346
[2, 10000] loss: 1.322
[2, 12000] loss: 1.286
Finished Training
现在我们可以保存我们训练好的模型了。这里还提供了其他关于保存和加载的方法。训练结束之后,只对模型进行验证的话,就可以直接加载这个训练好的模型。而不用重新训练。
PATH = './trained_net.pth'
torch.save(net.state_dict(), PATH)
5. 使用测试集测试:
我们的模型已经在训练集上训练了两次,下面我们将使用测试集来测试,模型是否从训练集中学到了什么。如果模型正确预测了测试集的标签,那么该样本将被存储在正确预测列表中。
好的,首先我们先用一组测试图片来练习一下,输出图片和它对应的标签。
dataiter = iter(testloader)
images, labels = next(dataiter)
# print images
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join(f'{classes[labels[j]]:5s}' for j in range(4)))
Out:
GroundTruth: cat ship ship plane
现在,让我们重新加载刚才保存的模型(由于你执行了上述代码训练了模型,其实不需要重新加载它,我们这里只是为了展示如何重新加载它)。
net = Net()
net.load_state_dict(torch.load(PATH, weights_only=True)) # PATH就是刚才我们保存的路径
那么,下面我们就可以查看,模型认为这几张图是什么。
outputs = net(images)
如果你查看或者打印outputs,将会得到图片十个类别的能量。能量越高,代表图片属于该类别的可能性越大,我们输出预测的类别。
# predicted是能量最大的类别的索引组成的一维张量
_, predicted = torch.max(outputs, 1)
print('Predicted: ', ' '.join(f'{classes[predicted[j]]:5s}'
for j in range(4)))
Out:
Predicted: cat car ship 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(f'网络在10000个测试数据上的准确率是: {100 * correct // total} %')
准确率是56%,看起来还不错。但如果我们只是随机从十个种类中选择一个,那么正确的概率只有10%。下面,我们需要查看哪些类别被训练的很好,哪些不好,以便我们调整训练的方向。
correct_pred = {classname: 0 for classname in classes}
total_pred = {classname: 0 for classname in classes}
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predictions = torch.max(outputs, 1)
# 找到每个类别被正确预测的数据
# zip()可以将相应未知的元素对应成为元组
for label, prediction in zip(labels, predictions):
if label == prediction:
correct_pred[classes[label]] += 1
total_pred[classes[label]] += 1
# 打印每个类别的正确率
for classname, correct_count in correct_pred.items():
accuracy = 100 * float(correct_count) / total_pred[classname]
print(f'类别正确率: {classname:5s} 是 {accuracy:.1f} %')
Out:
Accuracy for class: plane is 59.1 %
Accuracy for class: car is 67.9 %
Accuracy for class: bird is 23.2 %
Accuracy for class: cat is 39.4 %
Accuracy for class: deer is 54.4 %
Accuracy for class: dog is 45.5 %
Accuracy for class: frog is 54.0 %
Accuracy for class: horse is 68.9 %
Accuracy for class: ship is 79.0 %
Accuracy for class: truck is 57.0 %
后面,我准备用一些示例作为练习,熟悉torch的使用。感谢大家的关注!

274

被折叠的 条评论
为什么被折叠?



