CNN卷积神经网络入门实战:从图像分类到实现细节

1次阅读
没有评论

共计 3972 个字符,预计需要花费 10 分钟才能阅读完成。

image.webp

为什么需要 CNN?

在计算机视觉任务中,传统的全连接神经网络面临两个主要问题:

CNN 卷积神经网络入门实战:从图像分类到实现细节

  1. 参数爆炸:对于一张 100×100 像素的 RGB 图片,输入层就需要 3 万个节点,全连接层的参数量会变得极其庞大。
  2. 局部特征丢失:全连接网络无法有效利用图像中相邻像素之间的空间关系。

CNN 通过两种核心思想解决这些问题:

  • 局部感受野:每个神经元只连接输入图像的局部区域
  • 参数共享:同一组卷积核在整个图像上滑动使用

CNN 三大核心组件

1. 卷积层(Convolutional Layer)

卷积层是 CNN 的核心,通过卷积核(filter)在输入数据上滑动计算特征响应。举个例子:

  • 一个 3 ×3 的卷积核在图像上滑动,计算每个位置的加权和
  • 多个卷积核可以提取不同类型的特征(边缘、纹理等)
  • 输出特征图的尺寸由输入大小、卷积核大小、步长 (stride) 和填充 (padding) 共同决定

2. 池化层(Pooling Layer)

池化层主要用于降维和保持平移不变性,常见的有:

  • 最大池化(Max Pooling):取窗口内的最大值
  • 平均池化(Average Pooling):取窗口内的平均值

池化层能有效减少计算量,同时使网络对小的平移变化更加鲁棒。

3. 全连接层(Fully Connected Layer)

全连接层通常位于网络最后,将学到的特征映射到样本的标记空间。在 CNN 中:

  • 前面的卷积层和池化层提取低级到高级的特征
  • 全连接层将这些特征组合起来进行分类决策

PyTorch 实战:手写数字分类

数据准备

我们使用经典的 MNIST 数据集,包含 0 - 9 的手写数字图片:

import torch
from torchvision import datasets, transforms

# 定义数据预处理
transform = transforms.Compose([transforms.ToTensor(),  # 转为 Tensor 并归一化到[0,1]
    transforms.Normalize((0.1307,), (0.3081,))  # MNIST 的均值标准差
])

# 加载数据集
train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('./data', train=False, transform=transform)

# 创建数据加载器
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=1000, shuffle=True)

网络定义

下面定义一个简单的 CNN 网络结构:

import torch.nn as nn
import torch.nn.functional as F

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        # 卷积层 1:输入 1 通道,输出 10 通道,卷积核 5x5
        self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
        # 卷积层 2:输入 10 通道,输出 20 通道,卷积核 5x5
        self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
        # Dropout 层
        self.dropout = nn.Dropout2d()
        # 全连接层 1:320 输入,50 输出
        self.fc1 = nn.Linear(320, 50)
        # 全连接层 2:50 输入,10 输出(对应 10 个类别)self.fc2 = nn.Linear(50, 10)

    def forward(self, x):
        # 第一层卷积 + ReLU + 最大池化
        x = F.relu(F.max_pool2d(self.conv1(x), 2))
        # 第二层卷积 + ReLU + 最大池化 + Dropout
        x = F.relu(F.max_pool2d(self.dropout(self.conv2(x)), 2))
        # 展平特征图
        x = x.view(-1, 320)
        # 全连接层 1 + ReLU
        x = F.relu(self.fc1(x))
        # 应用 Dropout
        x = F.dropout(x, training=self.training)
        # 全连接层 2 + LogSoftmax
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)

训练过程

定义好网络后,我们需要设置优化器和损失函数,然后编写训练循环:

def train(model, device, train_loader, optimizer, epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        if batch_idx % 100 == 0:
            print(f'Train Epoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)}'
                  f'({100. * batch_idx / len(train_loader):.0f}%)]\tLoss: {loss.item():.6f}')

# 初始化模型和优化器
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = SimpleCNN().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

# 训练 10 个 epoch
for epoch in range(1, 11):
    train(model, device, train_loader, optimizer, epoch)

模型评估

训练完成后,我们需要评估模型在测试集上的表现:

def test(model, device, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            test_loss += F.nll_loss(output, target, reduction='sum').item()
            pred = output.argmax(dim=1, keepdim=True)
            correct += pred.eq(target.view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset)
    print(f'\nTest set: Average loss: {test_loss:.4f},'
          f'Accuracy: {correct}/{len(test_loader.dataset)}'
          f'({100. * correct / len(test_loader.dataset):.0f}%)\n')

# 评估模型
test(model, device, test_loader)

常见问题与优化

过拟合问题

过拟合表现为训练集表现很好但测试集表现差,常用解决方法:

  • 数据增强:在训练时对图像进行随机变换
    transform_train = transforms.Compose([transforms.RandomRotation(10),
        transforms.RandomAffine(0, translate=(0.1, 0.1)),
        transforms.ToTensor(),
        transforms.Normalize((0.1307,), (0.3081,))
    ])
  • Dropout:随机丢弃一部分神经元
  • L2 正则化:在优化器中设置 weight_decay 参数

模型调优技巧

  1. 学习率调整:使用学习率调度器
    scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)
  2. 批量归一化:在卷积层后添加 BatchNorm 层
  3. 更先进的架构:尝试 ResNet、EfficientNet 等现代架构

避坑指南

新手常犯的错误:

  1. 忘记调用 model.train()model.eval()切换训练 / 评估模式
  2. 在反向传播前没有清零梯度(optimizer.zero_grad()
  3. 输入数据没有归一化导致训练困难
  4. 卷积后特征图尺寸计算错误
  5. 全连接层输入尺寸与前面层输出不匹配

延伸思考

掌握了基础的图像分类后,可以思考:

  1. 如何将这个模型应用到其他视觉任务,如目标检测或语义分割?
  2. 如果要处理彩色图像,网络需要做哪些调整?
  3. 当遇到更大的图像(如 224×224)时,网络结构应该如何设计?

学习资源推荐

  1. PyTorch 官方教程:https://pytorch.org/tutorials/
  2. CS231n 课程笔记:https://cs231n.github.io/
  3. 《Deep Learning with PyTorch》书籍

希望这篇教程能帮助你快速入门 CNN 和 PyTorch!在实际项目中,建议从简单模型开始,逐步增加复杂度,并通过实验验证每项改进的效果。

正文完
 0
评论(没有评论)