PyTorch 卷积神经网络

卷积神经网络(Convolutional Neural Network, CNN)是深度学习中用于处理图像、视频等结构化数据的强大模型。PyTorch 提供了灵活的工具来构建、训练和评估 CNN。以下是关于使用 PyTorch 实现 CNN 的详细说明,包括核心概念、代码示例和最佳实践,特别结合之前讨论的数据处理和线性回归的上下文。

1. 核心概念

CNN 主要用于处理网格状数据(如图像),通过卷积操作提取局部特征,结合池化层和全连接层实现分类、回归等任务。关键组件包括:

  • 卷积层(nn.Conv2d:通过卷积核提取特征(如边缘、纹理)。
  • 池化层(nn.MaxPool2d / nn.AvgPool2d:下采样,减少计算量并增强鲁棒性。
  • 激活函数(nn.ReLU:引入非线性,增强模型表达能力。
  • 全连接层(nn.Linear:将特征映射到输出(如分类类别)。
  • 数据加载:使用 torch.utils.data.DatasetDataLoader(如前文数据处理部分所述)加载图像数据。
  • 损失函数和优化器:分类任务常用交叉熵损失(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 中添加 RandomCropRandomHorizontalFlip 等,增强模型泛化能力。
  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.DataParalleltorch.distributed
  model = nn.DataParallel(model)

8. 与线性回归的对比

与之前讨论的线性回归相比,CNN 更适合处理图像等高维数据:

  • 线性回归使用 nn.Linear 处理一维输入,CNN 使用 nn.Conv2d 提取空间特征。
  • CNN 需要更复杂的数据预处理(如图像归一化、增强),而线性回归通常只需简单标准化。
  • CNN 的训练通常需要更多计算资源和更复杂的超参数调优。

如果你有具体的 CNN 任务(例如特定数据集、模型结构优化等),请提供更多细节,我可以进一步定制代码或解决方案!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注