深度学习入门:卷积神经网络与 MNIST 手写数字识别

深度学习入门:卷积神经网络与 MNIST 手写数字识别

前言 :上一篇我们完成了 PyTorch 环境搭建,并用一个三层全连接神经网络实现了 MNIST 手写数字识别,测试准确率约为 72%。本篇我们将学习深度学习中最重要的网络结构------卷积神经网络(CNN)。相比全连接网络,CNN 通过卷积层自动提取图像的局部特征,参数量更少、识别效果更好,是图像分类任务的主流方案。

目录

  • 一、为什么需要卷积神经网络
  • 二、卷积神经网络的核心组件
  • 三、CNN 网络结构设计
  • 四、完整代码实现
  • 五、运行结果
  • 六、总结

一、为什么需要卷积神经网络

1.1 全连接网络处理图像的问题

上一篇我们使用全连接网络处理 MNIST 图像时,将 28×28 的图像展平成 784 维向量输入网络。这种方式存在两个问题:

问题 说明
参数量巨大 一张 28×28 的图片展平后是 784 维,如果隐藏层有 1000 个神经元,仅一层就有 78.4 万个参数
丢失空间信息 展平操作破坏了像素之间的空间关系,图像相邻像素的关联信息被丢弃

1.2 CNN 的优势

卷积神经网络通过局部连接权值共享解决了上述问题:

优势 说明
局部感知 每个卷积核只关注图像的一小块区域,捕捉局部特征
权值共享 同一个卷积核在整张图上滑动,大幅减少参数量
保留空间信息 不展平图像,保留像素间的空间关系
平移不变性 无论目标出现在图像哪个位置,都能被识别

二、卷积神经网络的核心组件

2.1 卷积层(Conv2d)

卷积层是 CNN 的核心,通过卷积核在图像上滑动进行特征提取。

核心参数

参数 说明
in_channels 输入通道数(灰度图为 1,RGB 图为 3)
out_channels 输出通道数(卷积核的个数)
kernel_size 卷积核大小(如 5×5)
stride 步长,卷积核每次滑动的距离
padding 边界填充,保持输出尺寸不变时通常设为 (kernel_size-1)/2

输出尺寸计算公式

out = in − kernel + 2 × padding stride + 1 \text{out} = \frac{\text{in} - \text{kernel} + 2 \times \text{padding}}{\text{stride}} + 1 out=stridein−kernel+2×padding+1

2.2 激活函数(ReLU)

ReLU(Rectified Linear Unit)是 CNN 中最常用的激活函数:

f ( x ) = max ⁡ ( 0 , x ) f(x) = \max(0, x) f(x)=max(0,x)

特点:计算简单、梯度不会消失、加速收敛。

2.3 池化层(MaxPool2d)

池化层用于降低特征图尺寸,减少参数量,同时保留主要特征。

python 复制代码
nn.MaxPool2d(kernel_size=2)  # 2×2 区域取最大值,尺寸减半

2.4 全连接层(Linear)

经过多次卷积和池化后,将特征图展平,通过全连接层输出分类结果。

三、CNN 网络结构设计

本案例使用的 CNN 网络结构如下:

复制代码
输入: 1×28×28 (灰度图)
    		↓
Conv1: Conv2d(1, 16, 5, 1, 2) → ReLU → MaxPool2d(2)
    → 输出: 16×14×14
    		↓
Conv2: Conv2d(16, 32, 5, 1, 2) → ReLU → Conv2d(32, 32, 5, 1, 2) → ReLU → MaxPool2d(2)
    → 输出: 32×7×7
    		↓
Conv3: Conv2d(32, 64, 5, 1, 2) → ReLU
    → 输出: 64×7×7
    		↓
Flatten: 展平为 64×7×7 = 3136 维
   			↓
Linear: 3136 → 10 (对应数字 0-9)

3.1 各层输出尺寸变化

层级 操作 输出尺寸
输入 - 1×28×28
Conv1 Conv2d(1, 16, 5, 1, 2) 16×28×28
Pool1 MaxPool2d(2) 16×14×14
Conv2 Conv2d(16, 32, 5, 1, 2) 32×14×14
Conv2 Conv2d(32, 32, 5, 1, 2) 32×14×14
Pool2 MaxPool2d(2) 32×7×7
Conv3 Conv2d(32, 64, 5, 1, 2) 64×7×7
Flatten view 3136
Linear 3136→10 10

四、完整代码实现

python 复制代码
# 导入必要的库
import torch
import torchvision
import torchaudio

from torch import nn  # 导入神经网络模块
from torch.utils.data import DataLoader  # 数据包管理工具,打包数据
from torchvision import datasets  # 封装了很多与图像相关的模型,数据集
from torchvision.transforms import ToTensor  # 数据转换
from matplotlib import pyplot as plt
import torch.nn.functional as F

# ====================下载数据集====================
training_data = datasets.MNIST(root='data',
                               train=True,
                               download=True,
                               transform=ToTensor()
                               )

test_data = datasets.MNIST(root='data',
                           train=False,
                           download=True,
                           transform=ToTensor()
                           )

# ====================创建数据加载器====================
train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 检查一批数据的形状
for X, y in test_dataloader:
    print(f"Shape of X [N, C, H, W]: {X.shape}")
    print(f"Shape of y: {y.shape} {y.dtype}")
    break

# ====================选择设备====================
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")


# ====================定义CNN模型====================
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5, stride=1, padding=2),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(16, 32, 5, 1, 2),
            nn.ReLU(),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),
        )
        self.conv3 = nn.Sequential(
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.ReLU(),
        )
        self.out = nn.Linear(64 * 7 * 7, 10)

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = x.view(x.size(0), -1)
        output = self.out(x)
        return output


model = CNN().to(device)
print(model)


# ====================训练函数====================
def train(dataloader, model, loss_fn, optimizer):
    model.train()
    batch_size_num = 1

    for X, y in dataloader:
        X, y = X.to(device), y.to(device)

        pred = model.forward(X)
        loss = loss_fn(pred, y)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        loss_value = loss.item()

        if batch_size_num % 100 == 0:
            print(f"loss: {loss_value:>7f}  [number:{batch_size_num}]")

        batch_size_num += 1


# ====================测试函数====================
def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    model.eval()
    test_loss, correct = 0, 0

    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model.forward(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()

    test_loss /= num_batches
    correct /= size
    print(f"Test result: \n Accuracy: {(100 * correct)}%, Avg loss: {test_loss}")


# ====================损失函数和优化器====================
loss_fn = nn.CrossEntropyLoss()

optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=0.001,
    betas=(0.9, 0.999),
    weight_decay=1e-4
)

# ====================开始训练====================
epochs = 20
for t in range(epochs):
    print(f"Epoch {t + 1}\n====================================")
    train(train_dataloader, model, loss_fn, optimizer)
print("Done!")
test(test_dataloader, model, loss_fn)

五、运行结果

5.1 训练过程输出

复制代码
Epoch 1
====================================
loss: 0.086762  [number:100]
loss: 0.260086  [number:200]
......
Epoch 20
====================================
loss: 0.030822  [number:100]
loss: 0.000040  [number:200]
......
loss: 0.000118  [number:800]
loss: 0.000056  [number:900]
Done!
Test result: 
 Accuracy: 98.92%, Avg loss: 0.05751212948347662

5.2 结果对比

模型 测试准确率 特点
全连接网络(上一篇) 约 72% 参数量大,丢失空间信息
CNN(本篇) 约 99% 参数少,自动提取特征

使用 CNN 后,MNIST 手写数字识别的准确率从 72% 提升到了 99% 以上,效果提升非常显著。

六、总结

核心知识点速查

知识点 关键概念
CNN 卷积神经网络,图像分类的主流方案
卷积层 nn.Conv2d(),通过卷积核提取局部特征
ReLU 激活函数,nn.ReLU()
池化层 nn.MaxPool2d(),降低特征图尺寸
全连接层 nn.Linear(),输出分类结果
参数计算 输出尺寸 = (输入 - 卷积核 + 2×padding) / stride + 1

核心 API 一览

用途 对应模块 / 方法
卷积层 nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)
激活函数 nn.ReLU()
池化层 nn.MaxPool2d(kernel_size)
全连接层 nn.Linear(in_features, out_features)
展平 x.view(x.size(0), -1)
优化器 torch.optim.AdamW()

注意事项

要点 说明
输入通道 灰度图 in_channels=1,RGB 图 in_channels=3
padding 计算 保持尺寸不变时,padding = (kernel_size - 1) / 2
展平维度 需根据前一层输出尺寸计算,如 64×7×7 = 3136
优化器选择 AdamW 比 SGD 收敛更快,适合初学者
学习率 0.001 是 Adam 系列的常用初始值

系列直达

相关推荐
明志数科1 小时前
流水线数据采集工程实践:MES产线监控数据与机器人Ego训练数据的本质区别与选型指南
人工智能
DogDaoDao1 小时前
DexPIE:让人类手把手教灵巧手“回炉重造“——真实世界后训练 RL 深度拆解
深度学习·机器学习·机器人·人形机器人·运动轨迹·模仿学习·dexpie
乐迪信息1 小时前
航道船舶逆行AI识别,港口安全智能告警系统
大数据·前端·人工智能·安全·计算机视觉·音视频
憨波个1 小时前
【SSL】WavLM
人工智能·深度学习·语音识别
七夜zippoe1 小时前
AI Agent 的三位一体架构:模型(大脑)+ 工具(双手)+ 记忆(海马体)的深度解析
人工智能·ai·架构·agent·三位一体
一木 之林1 小时前
李沐深度学习191集课程全解析:模块拆解、学习路径
人工智能·深度学习
tqs_123451 小时前
OPC量产变现|全网挖掘6个可直接落地AI软件需求清单
大数据·人工智能
IT古董1 小时前
《FDE前沿部署工程师实战教程》11 - 企业Agent部署实战:Docker、API Gateway与生产环境
人工智能·学习