PyTorch 卷积神经网络
卷积神经网络(Convolutional Neural Network, CNN)是深度学习中用于处理图像、视频等结构化数据的强大模型。PyTorch 提供了灵活的工具来构建、训练和评估 CNN。以下是关于使用 PyTorch 实现 CNN 的详细说明,包括核心概念、代码示例和最佳实践,特别结合之前讨论的数据处理和线性回归的上下文。
1. 核心概念
CNN 主要用于处理网格状数据(如图像),通过卷积操作提取局部特征,结合池化层和全连接层实现分类、回归等任务。关键组件包括:
- 卷积层(
nn.Conv2d
):通过卷积核提取特征(如边缘、纹理)。 - 池化层(
nn.MaxPool2d
/nn.AvgPool2d
):下采样,减少计算量并增强鲁棒性。 - 激活函数(
nn.ReLU
):引入非线性,增强模型表达能力。 - 全连接层(
nn.Linear
):将特征映射到输出(如分类类别)。 - 数据加载:使用
torch.utils.data.Dataset
和DataLoader
(如前文数据处理部分所述)加载图像数据。 - 损失函数和优化器:分类任务常用交叉熵损失(
nn.CrossEntropyLoss
),优化器常用 Adam 或 SGD。
2. 实现 CNN 的步骤
以下是使用 PyTorch 构建 CNN 的完整流程,以图像分类任务(如 CIFAR-10 数据集)为例。
2.1 准备数据
我们使用 CIFAR-10 数据集(10 类图像,32×32 像素),并应用数据预处理(如前文数据处理部分所述)。
import torch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# 数据预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.247, 0.243, 0.261])
])
# 加载 CIFAR-10 数据集
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4, pin_memory=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=4, pin_memory=True)
2.2 定义 CNN 模型
我们定义一个简单的 CNN,包含多个卷积层、池化层和全连接层。
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self, num_classes=10):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
self.conv3 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(64 * 4 * 4, 512) # 假设输入图像为 32x32,经过池化后为 4x4
self.fc2 = nn.Linear(512, num_classes)
self.dropout = nn.Dropout(0.5) # 正则化,防止过拟合
def forward(self, x):
x = self.relu(self.conv1(x))
x = self.pool(x)
x = self.relu(self.conv2(x))
x = self.pool(x)
x = self.relu(self.conv3(x))
x = self.pool(x)
x = x.view(x.size(0), -1) # 展平
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.fc2(x)
return x
# 实例化模型
model = SimpleCNN(num_classes=10)
2.3 定义损失函数和优化器
- 损失函数:分类任务使用交叉熵损失。
- 优化器:使用 Adam 优化器,学习率通常设为 0.001。
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
2.4 训练模型
训练过程包括前向传播、计算损失、反向传播和参数更新。
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
num_epochs = 20
for epoch in range(num_epochs):
model.train()
total_loss = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
# 前向传播
outputs = model(images)
loss = criterion(outputs, labels)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {total_loss/len(train_loader):.4f}')
2.5 评估模型
在测试集上评估模型的准确率。
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(f'Test Accuracy: {accuracy:.2f}%')
3. 完整示例代码
以下是将上述步骤整合的完整代码:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# 数据预处理
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.247, 0.243, 0.261])
])
# 加载数据
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4, pin_memory=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=4, pin_memory=True)
# 定义模型
class SimpleCNN(nn.Module):
def __init__(self, num_classes=10):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
self.conv3 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(64 * 4 * 4, 512)
self.fc2 = nn.Linear(512, num_classes)
self.dropout = nn.Dropout(0.5)
def forward(self, x):
x = self.relu(self.conv1(x))
x = self.pool(x)
x = self.relu(self.conv2(x))
x = self.pool(x)
x = self.relu(self.conv3(x))
x = self.pool(x)
x = x.view(x.size(0), -1)
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.fc2(x)
return x
# 初始化
model = SimpleCNN(num_classes=10)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练
num_epochs = 20
for epoch in range(num_epochs):
model.train()
total_loss = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {total_loss/len(train_loader):.4f}')
# 评估
model.eval()
correct = 0
total = 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(f'Test Accuracy: {accuracy:.2f}%')
4. 可视化结果
可以使用 matplotlib 可视化训练数据和预测结果。例如,绘制部分测试图像及其预测标签:
import matplotlib.pyplot as plt
import numpy as np
# CIFAR-10 类别
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# 获取一批测试数据
images, labels = next(iter(test_loader))
images, labels = images[:8].to(device), labels[:8].to(device)
outputs = model(images)
_, predicted = torch.max(outputs, 1)
# 绘制
fig, axes = plt.subplots(2, 4, figsize=(12, 6))
images = images.cpu().numpy()
for i, ax in enumerate(axes.flat):
img = np.transpose(images[i], (1, 2, 0)) # 转换为 HxWxC
img = img * np.array([0.247, 0.243, 0.261]) + np.array([0.4914, 0.4822, 0.4465]) # 反归一化
img = np.clip(img, 0, 1)
ax.imshow(img)
ax.set_title(f'Pred: {classes[predicted[i]]}\nTrue: {classes[labels[i]]}')
ax.axis('off')
plt.show()
5. 最佳实践与优化
- 数据增强:在
transforms
中添加RandomCrop
、RandomHorizontalFlip
等,增强模型泛化能力。
transform = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], std=[0.247, 0.243, 0.261])
])
- 批量归一化(BatchNorm):在卷积层后添加
nn.BatchNorm2d
,加速训练并提高稳定性。
self.bn1 = nn.BatchNorm2d(16)
# 在 forward 中:x = self.relu(self.bn1(self.conv1(x)))
- 学习率调度:使用
torch.optim.lr_scheduler
动态调整学习率。
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)
scheduler.step() # 在每个 epoch 末调用
- 正则化:使用 Dropout 或 L2 正则化(
weight_decay
)防止过拟合。 - 模型复杂度:根据任务调整层数和通道数,复杂任务可参考 ResNet、VGG 等结构。
6. 常见问题与解决方案
- 损失不下降:
- 检查学习率(过高或过低)。
- 确保数据预处理正确(归一化、数据增强)。
- 检查模型结构是否适合任务。
- 过拟合:
- 增加数据增强。
- 添加 Dropout 或 BatchNorm。
- 使用更大数据集或预训练模型。
- 内存不足:
- 减小
batch_size
。 - 使用
torch.cuda.amp
进行混合精度训练。
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
with autocast():
outputs = model(images)
loss = criterion(outputs, labels)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
7. 扩展
- 预训练模型:使用
torchvision.models
加载预训练模型(如 ResNet18)进行迁移学习。
from torchvision.models import resnet18
model = resnet18(pretrained=True)
model.fc = nn.Linear(model.fc.in_features, num_classes)
- 自定义数据集:如前文数据处理部分所述,使用
datasets.ImageFolder
或自定义Dataset
加载本地数据。 - 多 GPU 训练:使用
nn.DataParallel
或torch.distributed
。
model = nn.DataParallel(model)
8. 与线性回归的对比
与之前讨论的线性回归相比,CNN 更适合处理图像等高维数据:
- 线性回归使用
nn.Linear
处理一维输入,CNN 使用nn.Conv2d
提取空间特征。 - CNN 需要更复杂的数据预处理(如图像归一化、增强),而线性回归通常只需简单标准化。
- CNN 的训练通常需要更多计算资源和更复杂的超参数调优。
如果你有具体的 CNN 任务(例如特定数据集、模型结构优化等),请提供更多细节,我可以进一步定制代码或解决方案!