在Google Colab上搭建一个能够识别CIFAR-10数据集的卷积神经网络(CNN),是深度学习入门者的经典练手项目。这个32x32像素的彩色图像数据集包含10个类别,总共有60,000张图片,其中50,000张用于训练,10,000张用于测试。作为计算机视觉领域的基础数据集,CIFAR-10因其适中的规模和复杂度,成为验证模型性能的理想选择。
我最近在参加一个深度学习训练营,第二周的任务就是完成这个项目。虽然整体流程与上周的MNIST手写数字识别类似,但彩色图像的加入带来了新的挑战。下面我将详细记录整个实现过程,包括代码解析、参数设置和性能优化技巧,特别适合刚接触PyTorch和计算机视觉的开发者参考。
在Google Colab中启用GPU可以显著加快模型训练速度。通过以下代码检查并设置GPU设备:
python复制import torch
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
提示:Colab提供的免费GPU通常是NVIDIA T4或K80,对于小型CNN模型已经足够。如果遇到内存不足的情况,可以尝试减小batch size或简化模型结构。
CIFAR-10数据集可以通过torchvision直接加载。合理的预处理对模型性能至关重要:
python复制import torchvision.transforms as transforms
from torchvision.datasets import CIFAR10
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
train_data = CIFAR10(root='./data', train=True, download=True, transform=transform)
test_data = CIFAR10(root='./data', train=False, download=True, transform=transform)
这里使用的归一化参数(0.5, 0.5, 0.5)对应RGB三个通道的均值和标准差。这种标准化处理有助于模型更快收敛。
在投入训练前,先检查数据样本是个好习惯:
python复制import matplotlib.pyplot as plt
import numpy as np
classes = ('plane', 'car', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck')
def imshow(img):
img = img / 2 + 0.5 # 反归一化
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
# 获取随机样本
dataiter = iter(train_loader)
images, labels = next(dataiter)
# 显示图像
imshow(torchvision.utils.make_grid(images[:4]))
print(' '.join(f'{classes[labels[j]]:5s}' for j in range(4)))
可视化可以帮助我们发现数据加载是否正确,也能直观感受图像质量。CIFAR-10的32x32分辨率相对较低,这增加了分类难度。
针对CIFAR-10的特性,我设计了一个包含两个卷积层的简单网络:
python复制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) # 输入通道3,输出通道6,5x5卷积核
self.pool = nn.MaxPool2d(2, 2) # 2x2最大池化
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) # 展平除batch维度外的所有维度
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
model = Net().to(device)
这个架构遵循经典的"卷积-池化-全连接"模式。第一个卷积层使用5x5的核从3通道RGB图像中提取特征,输出6个特征图;第二个卷积层进一步处理这些特征,输出16个特征图。两个2x2的最大池化层逐步降低空间维度。
理解各层的维度变化对调试CNN至关重要。让我们计算一下从输入到输出的维度变化:
注意:如果调整了卷积核大小、步长或填充,这些计算会相应变化。建议在修改架构后打印中间层的形状进行验证。
选择合适的超参数对训练效果影响显著:
python复制import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
batch_size = 4
num_epochs = 10
这里使用带动量的随机梯度下降(SGD)作为优化器,初始学习率设为0.001。动量项(0.9)有助于加速收敛并减少震荡。batch size设为4是考虑到Colab的GPU内存限制,在更大显存设备上可以适当增加。
完整的训练过程包括前向传播、损失计算、反向传播和参数更新:
python复制def train(model, train_loader, criterion, optimizer, epoch):
model.train()
running_loss = 0.0
for i, data in enumerate(train_loader, 0):
inputs, labels = data[0].to(device), data[1].to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if i % 2000 == 1999: # 每2000个batch打印一次
print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.3f}')
running_loss = 0.0
关键点:
optimizer.zero_grad()清除之前的梯度,避免梯度累积loss.backward()自动计算梯度optimizer.step()根据梯度更新参数训练过程中需要定期评估模型在测试集上的表现:
python复制def test(model, test_loader):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for data in test_loader:
images, labels = data[0].to(device), data[1].to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(f'Accuracy on test set: {accuracy:.2f}%')
return accuracy
torch.no_grad()上下文管理器禁用梯度计算,节省内存并加速推理过程。torch.max返回每个样本预测概率最大的类别。
训练完成后,我们可以绘制损失和准确率曲线:
python复制plt.plot(train_losses, label='Training loss')
plt.plot(test_accuracies, label='Test accuracy')
plt.xlabel('Epoch')
plt.legend()
plt.show()
典型的训练过程会显示训练损失逐渐下降,测试准确率逐步提升。如果出现训练损失下降但测试准确率停滞,可能表明模型开始过拟合。
在初始配置下,这个简单CNN在CIFAR-10上的测试准确率大约在60%左右。这远低于人类水平(约94%),说明模型还有很大改进空间。主要限制因素包括:
基于以上分析,可以考虑以下改进措施:
python复制self.dropout = nn.Dropout(0.25) # 添加到模型定义中
python复制transform_train = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomCrop(32, padding=4),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
在Colab上可能会遇到CUDA out of memory错误,解决方法包括:
torch.cuda.empty_cache()清理缓存如果损失几乎不下降,可以尝试:
当训练准确率远高于测试准确率时,表明过拟合,应对措施:
完成这个基础实现后,可以从以下几个方向深入:
我在实现过程中发现,虽然这个基础模型能达到60%左右的准确率,但要进一步提升性能,需要更深入理解CNN的工作原理和各种优化技巧。下一步我计划研究更复杂的网络架构,并尝试实现数据增强策略来提升模型泛化能力。