快捷方式

训练分类器

创建于: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,即 3 通道彩色图像,大小为 32x32 像素。

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%|          | 655k/170M [00:00<00:25, 6.54MB/s]
  5%|4         | 8.52M/170M [00:00<00:03, 48.9MB/s]
 11%|#1        | 19.1M/170M [00:00<00:02, 74.9MB/s]
 18%|#7        | 30.5M/170M [00:00<00:01, 90.4MB/s]
 24%|##4       | 41.3M/170M [00:00<00:01, 96.5MB/s]
 31%|###       | 52.4M/170M [00:00<00:01, 101MB/s]
 37%|###7      | 63.5M/170M [00:00<00:01, 104MB/s]
 43%|####3     | 74.2M/170M [00:00<00:00, 105MB/s]
 50%|####9     | 84.7M/170M [00:00<00:00, 105MB/s]
 56%|#####5    | 95.2M/170M [00:01<00:00, 104MB/s]
 62%|######2   | 106M/170M [00:01<00:00, 106MB/s]
 69%|######8   | 117M/170M [00:01<00:00, 107MB/s]
 75%|#######5  | 128M/170M [00:01<00:00, 107MB/s]
 82%|########1 | 139M/170M [00:01<00:00, 109MB/s]
 88%|########8 | 150M/170M [00:01<00:00, 108MB/s]
 95%|#########4| 162M/170M [00:01<00:00, 109MB/s]
100%|##########| 170M/170M [00:01<00:00, 101MB/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.144
[1,  4000] loss: 1.835
[1,  6000] loss: 1.677
[1,  8000] loss: 1.573
[1, 10000] loss: 1.526
[1, 12000] loss: 1.447
[2,  2000] loss: 1.405
[2,  4000] loss: 1.363
[2,  6000] loss: 1.341
[2,  8000] loss: 1.340
[2, 10000] loss: 1.315
[2, 12000] loss: 1.281
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:  cat   ship  truck ship

结果看起来相当不错。

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

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: 54 %

这看起来比偶然情况要好得多,偶然情况的准确率为 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 37.9 %
Accuracy for class: car   is 62.2 %
Accuracy for class: bird  is 45.6 %
Accuracy for class: cat   is 29.2 %
Accuracy for class: deer  is 50.3 %
Accuracy for class: dog   is 45.9 %
Accuracy for class: frog  is 60.1 %
Accuracy for class: horse is 70.3 %
Accuracy for class: ship  is 82.9 %
Accuracy for class: truck is 63.1 %

好的,接下来做什么?

我们如何在 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 相比 MASSIVE 的加速?因为您的网络真的很小。

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

目标已实现:

  • 在高层次上理解 PyTorch 的张量库和神经网络。

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

在多个 GPU 上训练

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

文档

访问 PyTorch 的全面开发者文档

查看文档

教程

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

查看教程

资源

查找开发资源并获得解答

查看资源