logo

从零开始:使用PyTorch实现图像分类(附完整代码与注释)

作者:狼烟四起2025.09.18 17:43浏览量:0

简介:本文详细介绍如何使用PyTorch框架实现图像分类任务,包含数据加载、模型构建、训练与评估全流程,提供完整可运行的代码及逐行注释,适合PyTorch初学者和深度学习实践者。

从零开始:使用PyTorch实现图像分类(附完整代码与注释)

引言

图像分类是计算机视觉领域的核心任务之一,广泛应用于人脸识别、医学影像分析、自动驾驶等场景。PyTorch作为主流深度学习框架,以其动态计算图和简洁的API设计,成为实现图像分类任务的理想选择。本文将通过一个完整的案例,展示如何使用PyTorch从零开始实现图像分类,涵盖数据准备、模型构建、训练优化和结果评估的全流程,并提供详细注释的完整代码。

1. 环境准备与依赖安装

在开始之前,需要确保已安装Python 3.6+环境,并安装以下依赖库:

  1. pip install torch torchvision matplotlib numpy

PyTorch提供预编译的二进制包,支持CPU和GPU版本。若使用GPU加速,需安装与CUDA版本匹配的PyTorch版本。

2. 数据准备与预处理

2.1 数据集选择

本文使用经典的CIFAR-10数据集,包含10个类别的6万张32x32彩色图像(5万训练集,1万测试集)。PyTorch的torchvision库提供了便捷的数据加载接口:

  1. import torchvision
  2. import torchvision.transforms as transforms
  3. # 定义数据预处理流程
  4. transform = transforms.Compose([
  5. transforms.ToTensor(), # 将PIL图像或numpy数组转换为Tensor,并缩放至[0,1]
  6. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 归一化至[-1,1]
  7. ])
  8. # 加载训练集和测试集
  9. trainset = torchvision.datasets.CIFAR10(
  10. root='./data',
  11. train=True,
  12. download=True,
  13. transform=transform
  14. )
  15. trainloader = torch.utils.data.DataLoader(
  16. trainset,
  17. batch_size=32,
  18. shuffle=True,
  19. num_workers=2
  20. )
  21. testset = torchvision.datasets.CIFAR10(
  22. root='./data',
  23. train=False,
  24. download=True,
  25. transform=transform
  26. )
  27. testloader = torch.utils.data.DataLoader(
  28. testset,
  29. batch_size=32,
  30. shuffle=False,
  31. num_workers=2
  32. )

关键点说明

  • transforms.Compose:将多个预处理操作组合为一个流水线。
  • ToTensor():自动将图像从[0,255]的uint8类型转换为[0,1]的float32类型Tensor。
  • Normalize:使用均值和标准差进行归一化,公式为(x - mean) / std
  • DataLoader:提供批量加载、多线程加速和随机打乱功能。

2.2 数据可视化

为验证数据加载是否正确,可随机显示部分训练图像:

  1. import matplotlib.pyplot as plt
  2. import numpy as np
  3. def imshow(img):
  4. img = img / 2 + 0.5 # 反归一化
  5. npimg = img.numpy()
  6. plt.imshow(np.transpose(npimg, (1, 2, 0)))
  7. plt.show()
  8. # 获取一个批次的图像和标签
  9. dataiter = iter(trainloader)
  10. images, labels = next(dataiter)
  11. # 显示图像
  12. imshow(torchvision.utils.make_grid(images))
  13. # 打印标签
  14. print(' '.join(f'{trainset.classes[labels[j]]}' for j in range(4)))

3. 模型构建:卷积神经网络(CNN)

3.1 网络架构设计

本文实现一个简化的CNN模型,包含2个卷积层和2个全连接层:

  1. import torch.nn as nn
  2. import torch.nn.functional as F
  3. class Net(nn.Module):
  4. def __init__(self):
  5. super(Net, self).__init__()
  6. # 第一个卷积层:输入通道3(RGB),输出通道6,5x5卷积核
  7. self.conv1 = nn.Conv2d(3, 6, 5)
  8. # 第二个卷积层:输入通道6,输出通道16,5x5卷积核
  9. self.conv2 = nn.Conv2d(6, 16, 5)
  10. # 第一个全连接层:输入16*5*5(经过池化后的特征图尺寸),输出120
  11. self.fc1 = nn.Linear(16 * 5 * 5, 120)
  12. # 第二个全连接层:输入120,输出84
  13. self.fc2 = nn.Linear(120, 84)
  14. # 输出层:输入84,输出10(CIFAR-10类别数)
  15. self.fc3 = nn.Linear(84, 10)
  16. def forward(self, x):
  17. # 最大池化,kernel_size=2, stride=2
  18. x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
  19. x = F.max_pool2d(F.relu(self.conv2(x)), 2)
  20. # 展平特征图
  21. x = x.view(-1, 16 * 5 * 5)
  22. x = F.relu(self.fc1(x))
  23. x = F.relu(self.fc2(x))
  24. x = self.fc3(x)
  25. return x
  26. net = Net()

架构解析

  • 卷积层:提取空间特征,通过Conv2d实现,参数包括输入通道数、输出通道数和卷积核大小。
  • 激活函数:使用ReLU引入非线性。
  • 池化层:通过max_pool2d降低特征图尺寸,减少计算量。
  • 全连接层:将特征映射到类别空间。

3.2 损失函数与优化器

  1. import torch.optim as optim
  2. # 交叉熵损失函数
  3. criterion = nn.CrossEntropyLoss()
  4. # 随机梯度下降优化器,学习率0.001,动量0.9
  5. optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

选择依据

  • 交叉熵损失:适用于多分类问题,直接比较预测概率分布与真实标签。
  • SGD优化器:加入动量项可加速收敛并减少震荡。

4. 模型训练与评估

4.1 训练循环

  1. def train(net, trainloader, criterion, optimizer, epochs=10):
  2. for epoch in range(epochs):
  3. running_loss = 0.0
  4. for i, data in enumerate(trainloader, 0):
  5. # 获取输入和标签
  6. inputs, labels = data
  7. # 梯度清零
  8. optimizer.zero_grad()
  9. # 前向传播
  10. outputs = net(inputs)
  11. # 计算损失
  12. loss = criterion(outputs, labels)
  13. # 反向传播
  14. loss.backward()
  15. # 更新参数
  16. optimizer.step()
  17. # 统计损失
  18. running_loss += loss.item()
  19. if i % 2000 == 1999: # 每2000个batch打印一次
  20. print(f'Epoch {epoch + 1}, Batch {i + 1}, Loss: {running_loss / 2000:.3f}')
  21. running_loss = 0.0
  22. print('Finished Training')
  23. train(net, trainloader, criterion, optimizer, epochs=10)

关键步骤

  • optimizer.zero_grad():清除历史梯度,防止梯度累积。
  • loss.backward():自动计算梯度。
  • optimizer.step():根据梯度更新参数。

4.2 模型评估

  1. def evaluate(net, testloader):
  2. correct = 0
  3. total = 0
  4. with torch.no_grad(): # 禁用梯度计算
  5. for data in testloader:
  6. images, labels = data
  7. outputs = net(images)
  8. _, predicted = torch.max(outputs.data, 1) # 获取概率最大的类别
  9. total += labels.size(0)
  10. correct += (predicted == labels).sum().item()
  11. accuracy = 100 * correct / total
  12. print(f'Accuracy on test set: {accuracy:.2f}%')
  13. return accuracy
  14. evaluate(net, testloader)

评估指标

  • 准确率:正确预测的样本数占总样本数的比例。

5. 完整代码与注释

  1. # 完整代码整合(含详细注释)
  2. import torch
  3. import torch.nn as nn
  4. import torch.nn.functional as F
  5. import torch.optim as optim
  6. import torchvision
  7. import torchvision.transforms as transforms
  8. import matplotlib.pyplot as plt
  9. import numpy as np
  10. # 1. 数据准备
  11. transform = transforms.Compose([
  12. transforms.ToTensor(),
  13. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
  14. ])
  15. trainset = torchvision.datasets.CIFAR10(
  16. root='./data', train=True, download=True, transform=transform
  17. )
  18. trainloader = torch.utils.data.DataLoader(
  19. trainset, batch_size=32, shuffle=True, num_workers=2
  20. )
  21. testset = torchvision.datasets.CIFAR10(
  22. root='./data', train=False, download=True, transform=transform
  23. )
  24. testloader = torch.utils.data.DataLoader(
  25. testset, batch_size=32, shuffle=False, num_workers=2
  26. )
  27. # 2. 定义网络
  28. class Net(nn.Module):
  29. def __init__(self):
  30. super(Net, self).__init__()
  31. self.conv1 = nn.Conv2d(3, 6, 5) # 输入3通道,输出6通道,5x5卷积核
  32. self.conv2 = nn.Conv2d(6, 16, 5)
  33. self.fc1 = nn.Linear(16 * 5 * 5, 120) # 全连接层
  34. self.fc2 = nn.Linear(120, 84)
  35. self.fc3 = nn.Linear(84, 10)
  36. def forward(self, x):
  37. x = F.max_pool2d(F.relu(self.conv1(x)), 2) # 卷积+ReLU+池化
  38. x = F.max_pool2d(F.relu(self.conv2(x)), 2)
  39. x = x.view(-1, 16 * 5 * 5) # 展平
  40. x = F.relu(self.fc1(x))
  41. x = F.relu(self.fc2(x))
  42. x = self.fc3(x)
  43. return x
  44. net = Net()
  45. # 3. 定义损失函数和优化器
  46. criterion = nn.CrossEntropyLoss()
  47. optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
  48. # 4. 训练函数
  49. def train(net, trainloader, criterion, optimizer, epochs=10):
  50. for epoch in range(epochs):
  51. running_loss = 0.0
  52. for i, data in enumerate(trainloader, 0):
  53. inputs, labels = data
  54. optimizer.zero_grad()
  55. outputs = net(inputs)
  56. loss = criterion(outputs, labels)
  57. loss.backward()
  58. optimizer.step()
  59. running_loss += loss.item()
  60. if i % 2000 == 1999:
  61. print(f'Epoch {epoch + 1}, Batch {i + 1}, Loss: {running_loss / 2000:.3f}')
  62. running_loss = 0.0
  63. print('Finished Training')
  64. train(net, trainloader, criterion, optimizer, epochs=10)
  65. # 5. 评估函数
  66. def evaluate(net, testloader):
  67. correct = 0
  68. total = 0
  69. with torch.no_grad():
  70. for data in testloader:
  71. images, labels = data
  72. outputs = net(images)
  73. _, predicted = torch.max(outputs.data, 1)
  74. total += labels.size(0)
  75. correct += (predicted == labels).sum().item()
  76. accuracy = 100 * correct / total
  77. print(f'Accuracy on test set: {accuracy:.2f}%')
  78. return accuracy
  79. evaluate(net, testloader)

6. 实践建议与扩展方向

  1. 模型改进

    • 增加卷积层深度(如使用ResNet架构)。
    • 引入批归一化(nn.BatchNorm2d)加速收敛。
    • 使用数据增强(如随机裁剪、水平翻转)提升泛化能力。
  2. 超参数调优

    • 学习率调度器(如torch.optim.lr_scheduler.StepLR)。
    • 网格搜索或贝叶斯优化寻找最优超参数。
  3. 部署应用

    • 导出模型为TorchScript格式(torch.jit.trace)。
    • 部署至移动端(通过PyTorch Mobile)或云端服务。

总结

本文通过一个完整的案例,展示了使用PyTorch实现图像分类的全流程,包括数据加载、模型构建、训练优化和结果评估。代码中提供了详细的注释,帮助读者理解每个步骤的原理和实现细节。对于初学者,建议从简化模型开始,逐步尝试更复杂的架构和技巧;对于进阶用户,可探索分布式训练、模型压缩等高级主题。”

相关文章推荐

发表评论