返回

10分钟学会PyTorch:用简单的代码打造一个图像分类器

日志

PyTorch入门:如何用简单的代码实现一个图像分类器

你好,我是Kyle,一名热爱深度学习的程序员。今天,我想和你分享一下如何用PyTorch这个强大的深度学习框架实现一个简单的图像分类器。如果你对图像识别有兴趣,或者想学习一下PyTorch的基本用法,那么这篇文章就是为你准备的。

什么是图像分类器?

图像分类器是一种能够识别图像中物体类别的模型。例如,给定一张猫的图片,图像分类器可以输出“猫”的标签。图像分类器是计算机视觉领域的一个基础任务,也是很多高级应用的前提,比如人脸识别、行人检测、车牌识别等。

如何用PyTorch实现一个图像分类器?

要用PyTorch实现一个图像分类器,我们需要完成以下几个步骤:

  • 加载和预处理数据集
  • 定义一个卷积神经网络(CNN)
  • 定义一个损失函数和一个优化器
  • 在训练集上训练网络
  • 在测试集上评估网络

下面,我们就来逐一介绍这些步骤。

加载和预处理数据集

为了方便起见,我们使用PyTorch自带的torchvision模块来加载和预处理数据集。torchvision提供了很多常用的图像数据集,比如MNIST、CIFAR10、ImageNet等。我们在这里使用CIFAR10数据集,它包含了10个类别的彩色图片,每个类别有6000张图片,每张图片的大小是32x32像素。这些类别分别是:

  • 飞机
  • 汽车
  • 鹿
  • 青蛙
  • 卡车

我们可以用以下代码来加载和预处理CIFAR10数据集:

import torch
import torchvision
import torchvision.transforms as transforms

# 定义一个变换,将PILImage转换为归一化范围为[-1, 1]的张量
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

# 下载并加载训练集,每批64张图片
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64,
                                          shuffle=True, num_workers=2)

# 下载并加载测试集,每批64张图片
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64,
                                         shuffle=False, num_workers=2)

# 定义类别标签
classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

定义一个卷积神经网络(CNN)

卷积神经网络(CNN)是一种特殊的神经网络,它能够有效地提取图像中的特征,并且具有较少的参数和较强的泛化能力。CNN由多个卷积层、池化层、激活层和全连接层组成。我们在这里定义一个简单的CNN,它由以下几个层组成:

  • 卷积层:输入通道数为3,输出通道数为12,内核大小为5,步长为1,填充为1
  • 批量归一化层:输入通道数为12
  • ReLU层:激活函数
  • 卷积层:输入通道数为12,输出通道数为12,内核大小为5,步长为1,填充为1
  • 批量归一化层:输入通道数为12
  • ReLU层:激活函数
  • 最大池化层:内核大小为2,步长为2
  • 卷积层:输入通道数为12,输出通道数为24,内核大小为5,步长为1,填充为1
  • 批量归一化层:输入通道数为24
  • ReLU层:激活函数
  • 卷积层:输入通道数为24,输出通道数为24,内核大小为5,步长为1,填充为1
  • 批量归一化层:输入通道数为24
  • ReLU层:激活函数
  • 全连接层:输入特征数为24x10x10,输出特征数为10

我们可以用以下代码来定义这个CNN:

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

# 定义一个卷积神经网络类
class Network(nn.Module):
    def __init__(self):
        super(Network, self).__init__()
        # 定义第一个卷积层
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=12, kernel_size=5, stride=1, padding=1)
        # 定义第一个批量归一化层
        self.bn1 = nn.BatchNorm2d(12)
        # 定义第二个卷积层
        self.conv2 = nn.Conv2d(in_channels=12, out_channels=12, kernel_size=5, stride=1, padding=1)
        # 定义第二个批量归一化层
        self.bn2 = nn.BatchNorm2d(12)
        # 定义第一个最大池化层
        self.pool = nn.MaxPool2d(2, 2)
        # 定义第三个卷积层
        self.conv4 = nn.Conv2d(in_channels=12, out_channels=24, kernel_size=5, stride=1, padding=1)
        # 定义第三个批量归一化层
        self.bn4 = nn.BatchNorm2d(24)
        # 定义第四个卷积层
        self.conv5 = nn.Conv2d(in_channels=24, out_channels=24, kernel_size=5, stride=1, padding=1)
        # 定义第四个批量归一化层
        self.bn5 = nn.BatchNorm2d(24)
        # 定义第一个全连接层
        self.fc1 = nn.Linear(24*10*10, 10)

    def forward(self, input):
        # 将输入通过第一个卷积层、批量归一化层和ReLU激活函数传递
        output = F.relu(self.bn1(self.conv1(input)))
        # 将输出通过第二个卷积层、批量归一化层和ReLU激活函数传递
        output = F.relu(self.bn2(self.conv2(output)))
        # 将输出通过第一个最大池化层传递
        output = self.pool(output)
        # 将输出通过第三个卷积层、批量归一化层和ReLU激活函数传递
        output = F.relu(self.bn4(self.conv4(output)))
        # 将输出通过第四个卷积层、批量归一化层和ReLU激活函数传递
        output = F.relu(self.bn5(self.conv5(output)))
        # 将输出展平成一维向量
        output = output.view(-1, 24*10*10)
        # 将输出通过第一个全连接层传递
        output = self
        fc1(output)
                # 返回输出
                return output
      
        # 实例化一个神经网络模型
        model = Network()

定义一个损失函数和一个优化器

损失函数是用来衡量模型的输出和真实标签之间的差距,优化器是用来更新模型的参数以减小损失函数的值。在PyTorch中,我们可以使用torch.nn包和torch.optim包来定义损失函数和优化器。我们在这里使用分类交叉熵损失(CrossEntropyLoss)作为损失函数,使用Adam优化器作为优化器。我们还需要设置一个学习率(lr),它控制了我们根据损失梯度调整网络权重的程度。我们将学习率设置为0.001,这是一个常用的值。学习率越低,训练速度就越慢,但也可能更稳定;学习率越高,训练速度就越快,但也可能更容易发生震荡或过拟合。

我们可以用以下代码来定义损失函数和优化器:

from torch.optim import Adam

# 定义损失函数为分类交叉熵损失
loss_fn = nn.CrossEntropyLoss()
# 定义优化器为Adam优化器
optimizer = Adam(model.parameters(), lr=0.001, weight_decay=0.0001)

在训练集上训练网络

现在,我们已经准备好了数据集、网络、损失函数和优化器,我们就可以开始训练网络了。训练网络的过程是一个循环的过程,每次循环称为一个epoch。在每个epoch中,我们需要遍历所有的训练数据,计算每批数据的输出和损失,反向传播梯度,并更新网络参数。我们还需要记录每个epoch的平均损失,并打印出来,以便我们观察训练过程中的变化。

我们可以用以下代码来在训练集上训练网络:

# 定义训练的epoch数
num_epochs = 10

# 遍历每个epoch
for epoch in range(num_epochs):
    # 初始化累计损失为0
    running_loss = 0.0
    # 遍历训练集中的每批数据
    for i, data in enumerate(trainloader, 0):
        # 获取输入图片和标签
        inputs, labels = data
        # 将梯度清零
        optimizer.zero_grad()
        # 计算输出和损失
        outputs = model(inputs)
        loss = loss_fn(outputs, labels)
        # 反向传播梯度并更新参数
        loss.backward()
        optimizer.step()
        # 累加损失
        running_loss += loss.item()
        # 每200批数据打印一次平均损失
        if i % 200 == 199:
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 200))
            running_loss = 0.0

print('Finished Training')

在测试集上评估网络

在训练完成后,我们需要在测试集上评估网络的性能,看看它是否能够正确地识别出图像中的物体。我们可以使用torch.max函数来获取每个输出向量中最大值的索引,这个索引就是模型预测的类别标签。然后,我们可以将预测的标签和真实的标签进行比较,计算出正确预测的百分比,即模型的准确率。

我们可以用以下代码来在测试集上评估网络:

# 初始化正确预测的数量为0
correct = 0
# 初始化总的图片数量为0
total = 0
# 不计算梯度,节省内存
with torch.no_grad():
    # 遍历测试集中的每批数据
    for data in testloader:
        # 获取输入图片和标签
        images, labels = data
        # 计算输出
        outputs = model(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))