10分钟学会PyTorch:用简单的代码打造一个图像分类器
2024-01-09 21:41:45
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))