当前位置: 首页 > news >正文

通过代码认识 CNN:用 PyTorch 实现卷积神经网络识别手写数字

目录

一、从代码看 CNN 的核心组件

二、准备工作:库导入与数据加载

三、核心:用代码实现 CNN 并理解各层作用

1.网络层结构

2.重点理解:卷积层参数与输出尺寸计算

四、训练 CNN

五、结果分析


卷积神经网络(CNN)是计算机视觉领域的核心模型,相比全连接网络,它能更高效地提取图像特征。本文不空谈理论,而是通过 PyTorch 代码实现一个完整的 CNN 模型,带你在实战中理解卷积、池化等核心概念,掌握 CNN 的工作原理。


一、从代码看 CNN 的核心组件

在实现模型前,先明确 CNN 的三个核心层 —— 这些是区别于全连接网络的关键,后续代码会逐一对应:

  1. 卷积层(Conv2d):通过滑动窗口提取局部特征(如边缘、纹理);
  2. 激活层(ReLU):引入非线性,让模型学习复杂模式;
  3. 池化层(MaxPool2d):降低特征图尺寸,减少计算量,增强鲁棒性。

我们将用这些组件构建一个识别 MNIST 手写数字的 CNN 模型,边写代码边解释原理。


二、准备工作:库导入与数据加载

首先导入必要的库,加载 MNIST 数据集(28×28 的手写数字图片):

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor# 加载MNIST数据集
training_data = datasets.MNIST(root="data",train=True,download=True,transform=ToTensor()  # 转为张量,形状为[1,28,28]
)
test_data = datasets.MNIST(root="data",train=False,download=True,transform=ToTensor()
)# 按批次加载数据(每批64张图)
batch_size = 64
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)# 查看数据形状([批次, 通道, 高度, 宽度])
for X, y in test_dataloader:print(f"数据形状: {X.shape}")  # 输出:torch.Size([64, 1, 28, 28])break

关键说明:MNIST 图片是单通道(灰度图),所以输入形状为[N,1,28,28](N 为批次大小),这会影响后续卷积层的参数设置。


三、核心:用代码实现 CNN 并理解各层作用

1.网络层结构

我们构建一个包含 4 个卷积块的 CNN 模型,每个卷积块由 “卷积层 + 激活层” 组成,部分块后添加池化层。通过代码注释详细说明每层的作用和参数含义。

# 自动选择设备(优先GPU)
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"使用设备: {device}")class CNN(nn.Module):def __init__(self):super(CNN, self).__init__()# 第一个卷积块:卷积层+激活层+池化层self.conv1 = nn.Sequential(# 卷积层:输入1通道,输出16通道,卷积核5×5,步长1,填充2nn.Conv2d(in_channels=1,    # 输入通道数(灰度图为1)out_channels=16,  # 输出通道数(16个不同的卷积核)kernel_size=5,    # 卷积核大小5×5stride=1,         # 步长1(每次滑动1个像素)padding=2         # 填充2(保持输出尺寸与输入一致:28→28)),nn.ReLU(),  # 激活层:引入非线性,过滤负值# 池化层:2×2窗口,步长2,输出尺寸变为14×14(28/2)nn.MaxPool2d(kernel_size=2, stride=2))# 第二个卷积块:卷积层+激活层(无池化)self.conv2 = nn.Sequential(# 输入16通道(上一层输出),输出32通道,卷积核3×3nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1),nn.ReLU()  # 输出尺寸保持14×14)# 第三个卷积块:卷积层+激活层+池化层self.conv3 = nn.Sequential(nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),nn.ReLU(),nn.MaxPool2d(2, 2)  # 输出尺寸变为7×7(14/2))# 第四个卷积块:卷积层+激活层(无池化)self.conv4 = nn.Sequential(nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),nn.ReLU()  # 输出尺寸保持7×7)# 全连接层:将特征图转为10个类别(0-9)self.fc = nn.Linear(128 * 7 * 7, 10)  # 128通道×7×7尺寸def forward(self, x):# 前向传播:数据依次经过各层x = self.conv1(x)  # 输出形状:[N,16,14,14]x = self.conv2(x)  # 输出形状:[N,32,14,14]x = self.conv3(x)  # 输出形状:[N,64,7,7]x = self.conv4(x)  # 输出形状:[N,128,7,7]x = x.view(x.size(0), -1)  # 展平:[N,128×7×7]x = self.fc(x)     # 输出形状:[N,10](10个类别分数)return x# 创建模型并移动到设备
model = CNN().to(device)
print("CNN模型结构:")
print(model)

2.重点理解:卷积层参数与输出尺寸计算

以第一个卷积层为例,输入是[64,1,28,28](64 张图,1 通道,28×28),经过kernel_size=5, padding=2, stride=1的卷积后,输出尺寸计算公式:

输出尺寸 = (输入尺寸 - 卷积核大小 + 2×填充) / 步长 + 1
即:(28 - 5 + 2×2)/1 + 1 = 28

所以输出仍为 28×28,再经 2×2 池化后变为 14×14—— 这就是卷积层如何在保留特征的同时控制尺寸的核心逻辑。


四、训练 CNN

CNN 的训练流程和全连接网络类似,我们将训练轮次调整为 10 轮,既能保证模型收敛,又能节省训练时间。定义训练和测试函数如下:

# 损失函数(多分类用交叉熵)
loss_fn = nn.CrossEntropyLoss()
# 优化器(Adam,学习率0.0001)
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)# 训练函数
def train(dataloader, model, loss_fn, optimizer):model.train()  # 开启训练模式batch_num = 1for X, y in dataloader:X, y = X.to(device), y.to(device)# 前向传播:计算预测pred = model(X)loss = loss_fn(pred, y)# 反向传播:更新参数optimizer.zero_grad()  # 梯度清零loss.backward()        # 计算梯度optimizer.step()       # 更新参数# 每100批次打印一次损失if batch_num % 100 == 1:print(f"批次 {batch_num} | 损失: {loss.item():.4f}")batch_num += 1# 测试函数
def test(dataloader, model, loss_fn):model.eval()  # 开启测试模式size = len(dataloader.dataset)num_batches = len(dataloader)correct = 0test_loss = 0with torch.no_grad():  # 禁用梯度计算for X, y in dataloader:X, y = X.to(device), y.to(device)pred = model(X)test_loss += loss_fn(pred, y).item()correct += (pred.argmax(1) == y).type(torch.float).sum().item()# 计算准确率和平均损失test_loss /= num_batchescorrect /= sizeprint(f"\n测试集:准确率 {100*correct:.2f}% | 平均损失 {test_loss:.4f}\n")# 开始训练(10轮)
print("="*50)
print("开始训练CNN模型(10轮)")
print("="*50)
for epoch in range(10):print(f"轮次 {epoch+1}/10")print("-"*30)train(train_dataloader, model, loss_fn, optimizer)# 每2轮测试一次if (epoch+1) % 2 == 0:test(test_dataloader, model, loss_fn)
print("="*50)
print("训练结束")

五、结果分析

轮次 10/10
------------------------------
批次 1 | 损失: 0.0002
批次 101 | 损失: 0.0000
批次 201 | 损失: 0.0015
批次 301 | 损失: 0.0190
批次 401 | 损失: 0.0003
批次 501 | 损失: 0.0008
批次 601 | 损失: 0.0001
批次 701 | 损失: 0.0065
批次 801 | 损失: 0.0019
批次 901 | 损失: 0.0310测试集:准确率 99.17% | 平均损失 0.0355==================================================
训练结束

即使只训练 10 轮,CNN 在测试集上的准确率通常也能达到99% 以上,明显高于同轮次的全连接网络。这体现了 CNN 的高效性,原因在于:

  1. 局部感受野:卷积层通过滑动窗口只关注局部像素,更符合图像的局部相关性;
  2. 权值共享:同一通道的卷积核参数共享,大幅减少参数数量(全连接层 784→128 需要近 10 万个参数,而 5×5 的卷积层 1→16 仅需 400 个参数);
  3. 池化层:通过下采样保留关键特征,增强模型对图像位移、缩放的鲁棒性。

这些特性让 CNN 在较少的训练轮次下就能达到较好的性能。

http://www.xdnf.cn/news/1383535.html

相关文章:

  • Maven 项目单元测试实战指南:从环境搭建到问题排查全解析
  • 使用astah制作专业状态图及C/C++实现解析
  • Docker 详解+示例
  • 进程组 会话 作业控制 守护进程
  • 【Canvas与盾牌】“靡不有初,鲜克有终”黄竖条盾牌
  • Redis 哨兵(Sentinel)全面解析
  • 海康相机开发---设备登录
  • Subdev与Media子系统的数据结构
  • redis单哨兵模式
  • 把 AI 塞进「智能水杯」——基于声学指纹的零样本水质检测杯
  • open webui源码分析11-四个特征之记忆
  • GD32VW553-IOT OLED移植
  • Intern-S1-mini模型结构
  • Python训练营打卡 DAY 50 预训练模型+CBAM模块
  • DQN(深度Q网络):深度强化学习的里程碑式突破
  • 【LeetCode每日一题】160.相交链表 206. 反转链表
  • 在Xcode中查看设备日志的完整指南
  • 消息队列核心问题解决方案:从丢失到重复消费的全方位保障
  • Windows 11 中 PowerShell 与 CMD 的深度对比:从定位到实战
  • Python DELL Logo
  • LCEDA电气规则
  • 整体设计 修订 之1 三“先”之“基” 与范畴重构:康德先验哲学的批判性程序化实现
  • MapStruct用法和实践
  • Vibe Coding到底是什么:什么是 Vibe Coding?AI编程?
  • 深度学习----卷积神经网络实现数字识别
  • 从0开始学习Java+AI知识点总结-27.web实战(Maven高级)
  • 漫谈《数字图像处理》之区域生长和区域分离聚合
  • CDN 临时下载链接
  • OpenCV 图像操作进阶:像素、边界与融合技术
  • 嵌入式学习日记(36)TCP并发服务器构建——epoll