快捷方式

训练分类器

创建于: 2017 年 3 月 24 日 | 最后更新于: 2024 年 12 月 20 日 | 最后验证于: 未验证

就是这样。您已经了解了如何定义神经网络、计算损失以及更新网络权重。

现在您可能会想,

数据怎么办?

通常,当您需要处理图像、文本、音频或视频数据时,可以使用标准的 Python 包将数据加载到 numpy 数组中。然后,您可以将此数组转换为 torch.*Tensor

  • 对于图像,Pillow、OpenCV 等包很有用

  • 对于音频,scipy 和 librosa 等包很有用

  • 对于文本,可以使用原生 Python 或基于 Cython 的加载方式,或者使用 NLTK 和 SpaCy 等包

特别是在视觉领域,我们创建了一个名为 torchvision 的包,它包含了 ImageNet、CIFAR10、MNIST 等常见数据集的数据加载器,以及图像数据转换器,即 torchvision.datasetstorch.utils.data.DataLoader

这提供了极大的便利,避免编写样板代码。

在本教程中,我们将使用 CIFAR10 数据集。它包含以下类别:‘airplane’(飞机)、‘automobile’(汽车)、‘bird’(鸟)、‘cat’(猫)、‘deer’(鹿)、‘dog’(狗)、‘frog’(青蛙)、‘horse’(马)、‘ship’(船)、‘truck’(卡车)。CIFAR-10 中的图像大小为 3x32x32,即大小为 32x32 像素的 3 通道彩色图像。

cifar10

cifar10

训练图像分类器

我们将按以下步骤进行

  1. 使用 torchvision 加载并归一化 CIFAR10 训练集和测试集

  2. 定义一个卷积神经网络

  3. 定义一个损失函数

  4. 在训练数据上训练网络

  5. 在测试数据上测试网络

1. 加载并归一化 CIFAR10

使用 torchvision 加载 CIFAR10 非常容易。

import torch
import torchvision
import torchvision.transforms as transforms

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

注意

如果在 Windows 上运行并出现 BrokenPipeError,请尝试将 torch.utils.data.DataLoader() 的 num_worker 设置为 0。

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')
  0%|          | 0.00/170M [00:00<?, ?B/s]
  0%|          | 459k/170M [00:00<00:37, 4.58MB/s]
  4%|4         | 7.63M/170M [00:00<00:03, 44.1MB/s]
 11%|#         | 18.3M/170M [00:00<00:02, 72.2MB/s]
 17%|#6        | 28.5M/170M [00:00<00:01, 83.9MB/s]
 23%|##2       | 39.2M/170M [00:00<00:01, 92.1MB/s]
 29%|##9       | 49.7M/170M [00:00<00:01, 96.5MB/s]
 36%|###5      | 60.6M/170M [00:00<00:01, 101MB/s]
 42%|####1     | 70.8M/170M [00:00<00:00, 101MB/s]
 48%|####7     | 81.4M/170M [00:00<00:00, 103MB/s]
 54%|#####3    | 91.8M/170M [00:01<00:00, 103MB/s]
 60%|######    | 103M/170M [00:01<00:00, 104MB/s]
 67%|######6   | 114M/170M [00:01<00:00, 106MB/s]
 73%|#######2  | 124M/170M [00:01<00:00, 106MB/s]
 79%|#######9  | 135M/170M [00:01<00:00, 107MB/s]
 86%|########5 | 146M/170M [00:01<00:00, 105MB/s]
 92%|#########1| 157M/170M [00:01<00:00, 105MB/s]
 98%|#########8| 167M/170M [00:01<00:00, 104MB/s]
100%|##########| 170M/170M [00:01<00:00, 97.7MB/s]

让我们展示一些训练图像,以供参考。

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 = next(dataiter)

# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join(f'{classes[labels[j]]:5s}' for j in range(batch_size)))
cifar10 tutorial
frog  plane deer  car

2. 定义一个卷积神经网络

复制前面“神经网络”部分中的神经网络,并修改它以接收 3 通道图像(而不是之前定义的 1 通道图像)。

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. 定义损失函数和优化器

让我们使用分类交叉熵损失和带动量的 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):  # 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(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.3f}')
            running_loss = 0.0

print('Finished Training')
[1,  2000] loss: 2.143
[1,  4000] loss: 1.833
[1,  6000] loss: 1.674
[1,  8000] loss: 1.574
[1, 10000] loss: 1.524
[1, 12000] loss: 1.445
[2,  2000] loss: 1.405
[2,  4000] loss: 1.363
[2,  6000] loss: 1.339
[2,  8000] loss: 1.342
[2, 10000] loss: 1.311
[2, 12000] loss: 1.274
Finished Training

让我们快速保存训练好的模型

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

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

5. 在测试数据上测试网络

我们已经对训练数据集进行了 2 次完整的训练。但我们需要检查网络是否学到了任何东西。

我们将通过预测神经网络输出的类别标签,并与真实标签进行对比来检查。如果预测正确,我们将样本添加到正确预测列表中。

好的,第一步。让我们显示测试集中的一张图像以熟悉情况。

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)))
cifar10 tutorial
GroundTruth:  cat   ship  ship  plane

接下来,让我们重新加载我们保存的模型(注意:此处并非必须保存和重新加载模型,我们只是为了说明如何操作)。

net = Net()
net.load_state_dict(torch.load(PATH, weights_only=True))
<All keys matched successfully>

好的,现在让我们看看神经网络认为上面的这些示例是什么

outputs = net(images)

输出是 10 个类别的“能量”。某个类别的能量越高,网络越认为图像属于该类别。因此,让我们获取能量最高的索引

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

print('Predicted: ', ' '.join(f'{classes[predicted[j]]:5s}'
                              for j in range(4)))
Predicted:  frog  ship  ship  plane

结果看起来相当不错。

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

correct = 0
total = 0
# since we're not training, we don't need to calculate the gradients for our outputs
with torch.no_grad():
    for data in testloader:
        images, labels = data
        # calculate outputs by running images through the network
        outputs = net(images)
        # the class with the highest energy is what we choose as prediction
        _, predicted = torch.max(outputs, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f'Accuracy of the network on the 10000 test images: {100 * correct // total} %')
Accuracy of the network on the 10000 test images: 55 %

这看起来比随机猜测(10 个类别中随机选择一个,准确率为 10%)好得多。看来网络学到了一些东西。

嗯,哪些类别的表现很好,哪些类别的表现不好呢?

# prepare to count predictions for each class
correct_pred = {classname: 0 for classname in classes}
total_pred = {classname: 0 for classname in classes}

# again no gradients needed
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predictions = torch.max(outputs, 1)
        # collect the correct predictions for each class
        for label, prediction in zip(labels, predictions):
            if label == prediction:
                correct_pred[classes[label]] += 1
            total_pred[classes[label]] += 1


# print accuracy for each class
for classname, correct_count in correct_pred.items():
    accuracy = 100 * float(correct_count) / total_pred[classname]
    print(f'Accuracy for class: {classname:5s} is {accuracy:.1f} %')
Accuracy for class: plane is 43.5 %
Accuracy for class: car   is 67.2 %
Accuracy for class: bird  is 44.7 %
Accuracy for class: cat   is 29.5 %
Accuracy for class: deer  is 48.9 %
Accuracy for class: dog   is 47.0 %
Accuracy for class: frog  is 60.5 %
Accuracy for class: horse is 70.9 %
Accuracy for class: ship  is 79.8 %
Accuracy for class: truck is 65.4 %

好的,接下来该怎么办?

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

在 GPU 上训练

就像将张量转移到 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)
cuda:0

本节的其余部分假设 device 是一个 CUDA 设备。

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

net.to(device)

记住,您还需要在每一步将输入和目标发送到 GPU

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

为什么我没有注意到与 CPU 相比有巨大的加速?因为您的网络很小。

练习:尝试增加网络的宽度(第一个 nn.Conv2d 的参数 2 和第二个 nn.Conv2d 的参数 1 – 它们需要是相同的数字),看看能获得什么样的加速。

目标达成:

  • 高度理解 PyTorch 的张量库和神经网络。

  • 训练一个小型神经网络对图像进行分类

在多个 GPU 上训练

如果您想使用所有 GPU 看到更大的加速,请查阅可选:数据并行性

文档

查阅全面的 PyTorch 开发者文档

查看文档

教程

获取面向初学者和高级开发者的深度教程

查看教程

资源

查找开发资源并获得问题解答

查看资源