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

卷积神经网络搭建实战(一)-----torch库中的MNIST手写数字数据集(简明版)

上一节里我们介绍了卷积神经网络的原理和相关概念,这一节,我们从torch库云端自带的手写数字数据集出发,来看一下卷积神经网络的具体实现 

目录

引言

一、MNIST数据集:手写数字识别的"黄金基准"

1.1 数据集背景与特点

1.2 为什么选择MNIST?

1.3 应用场景

二、MNIST+CNN搭建全流程:从数据到模型

2.1 数据准备与预处理

2.2 模型构建(CNN设计)

2.3 损失函数与优化器选择

2.4 训练循环与模型评估

三、代码实现:从0到1搭建MNIST识别系统

3.1 完整代码(带逐行注释)

3.2 代码分段详细解析

3.2.1 数据加载与预处理

3.2.2 设备选择(GPU/MPS/CPU)

3.2.3 CNN模型设计

3.2.4 训练函数(train)

3.2.5 测试函数(test)

四、训练结果与优化方向

4.1 典型训练结果

4.2 常见优化方向

五、总结


引言

在深度学习的入门学习中,"Hello World"级别的任务不是简单的打印语句,而是一个能直观验证模型能力的经典数据集训练任务。对于计算机视觉领域而言,这个"Hello World"就是​​MNIST手写数字识别​​。它不仅是学术界验证新模型的"试金石",也是工业界快速搭建视觉模型的"基准线"。前文介绍pytorch框架中已经介绍过使用BP神经网络实现MNIST手写数字识别,本文将继续以PyTorch框架为核心,手把手带你从数据加载到模型训练,完成一个基于卷积神经网络(CNN)的MNIST手写数字识别系统,并深入解析每一步的技术细节。


一、MNIST数据集:手写数字识别的"黄金基准"

1.1 数据集背景与特点

MNIST数据集由加拿大高级研究所(CIFAR)于1998年发布,包含​​70,000张28×28像素的灰度手写数字图像​​(0-9共10个类别)。其中60,000张用于训练模型(train=True),10,000张用于测试模型泛化能力(train=False)。所有图像均经过严格居中处理,有效减少了预处理的工作量,使得研究者能更专注于模型本身的设计。

1.2 为什么选择MNIST?

  • ​标准化​​:全球研究者和开发者使用同一数据集,实验结果可直接对比;
  • ​小而精​​:70,000张图像的规模适中,适合快速验证模型思路;
  • ​低门槛​​:单张图像仅784个像素(28×28),计算资源需求低,即使是入门级GPU也能轻松处理;
  • ​典型性​​:手写数字识别是典型的多分类任务,覆盖了卷积神经网络的核心应用场景。

1.3 应用场景

MNIST不仅是教学工具,更是工业界的"基础能力验证器"。例如:

  • 验证新提出的卷积层结构(如深度可分离卷积)的有效性;
  • 测试不同优化器(如Adam、SGD)在基础任务上的表现;
  • 快速搭建模型原型,为复杂任务(如OCR文字识别)提供技术储备。

二、MNIST+CNN搭建全流程:从数据到模型

在PyTorch中搭建MNIST识别系统,通常遵循以下核心步骤:

2.1 数据准备与预处理

  • ​下载数据集​​:通过torchvision.datasets.MNIST接口自动下载并存储到本地;
  • ​数据转换​​:使用ToTensor将PIL图像转换为PyTorch张量(Tensor),并自动归一化到[0,1]区间;
  • ​数据加载​​:通过DataLoader封装数据集,设置batch_size实现批量加载,提升训练效率。

2.2 模型构建(CNN设计)

卷积神经网络(CNN)是处理图像任务的核心模型,其核心思想是通过​​局部感知​​和​​权值共享​​高效提取图像特征。典型CNN结构包含:

  • ​卷积层(Conv2d)​​:通过滑动窗口(卷积核)提取局部特征(如边缘、纹理);
  • ​激活函数(ReLU)​​:引入非线性变换,增强模型对复杂模式的表达能力;
  • ​池化层(MaxPool2d)​​:通过下采样(如2×2区域取最大值)降低特征图维度,减少计算量并增强平移不变性;
  • ​全连接层(Linear)​​:将高维特征映射到类别空间,输出分类概率。

2.3 损失函数与优化器选择

  • ​损失函数​​:多分类任务首选​​交叉熵损失(CrossEntropyLoss)​​,它直接衡量预测概率分布与真实标签的差异;
  • ​优化器​​:Adam优化器因自适应学习率的特性,常作为默认选择,平衡了收敛速度与稳定性。

2.4 训练循环与模型评估

  • ​训练阶段​​:通过前向传播计算预测值,反向传播计算梯度,优化器更新模型参数;
  • ​测试阶段​​:关闭梯度计算(torch.no_grad()),评估模型在测试集上的准确率和损失,验证泛化能力。

三、代码实现:从0到1搭建MNIST识别系统

3.1 完整代码(带逐行注释)

# 导入必要的PyTorch库
import torch
from torch import nn  # 神经网络模块
from torch.utils.data import DataLoader  # 数据加载器
from torchvision import datasets  # 视觉数据集
from torchvision.transforms import ToTensor  # 张量转换工具# ---------------------- 步骤1:下载并加载训练/测试数据集 ----------------------
# 训练数据集(60,000张图像+标签)
training_data = datasets.MNIST(root="data",          # 数据集存储根目录(自动创建)train=True,           # 标记为训练集(对应training.pt文件)download=True,        # 若本地无数据则自动下载transform=ToTensor()  # 转换函数:将PIL图像转为Tensor(形状[1,28,28],值域[0,1])
)# 测试数据集(10,000张图像+标签)
test_data = datasets.MNIST(root="data",train=False,          # 标记为测试集(对应test.pt文件)download=True,transform=ToTensor()  # 与训练集相同的转换规则
)# ---------------------- 步骤2:创建数据加载器(DataLoader) ----------------------
# 训练数据加载器:按批次加载数据(batch_size=64)
train_dataloader = DataLoader(dataset=training_data,  # 指定数据集batch_size=64,          # 每批64张图像(平衡内存与训练效率)shuffle=True            # 训练时随机打乱数据(默认True,防止模型学习顺序偏差)
)# 测试数据加载器:无需打乱数据
test_dataloader = DataLoader(dataset=test_data,batch_size=64,shuffle=False           # 测试时保持顺序便于结果分析
)# ---------------------- 步骤3:选择计算设备(GPU优先) ----------------------
# 自动检测可用设备:优先CUDA(NVIDIA GPU),其次MPS(Apple Silicon GPU),最后CPU
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"当前使用设备:{device}")  # 输出设备信息(如"cuda"或"mps")# ---------------------- 步骤4:定义卷积神经网络(CNN)模型 ----------------------
class CNN(nn.Module):  # 继承PyTorch的神经网络基类nn.Moduledef __init__(self):super(CNN, self).__init__()  # 调用父类初始化方法# ---------------------- 卷积块1:特征提取 ----------------------self.conv1 = nn.Sequential(  # 序列容器:按顺序组合多个层nn.Conv2d(in_channels=1,   # 输入通道数(MNIST是灰度图,1通道)out_channels=16, # 输出通道数(16个卷积核,生成16张特征图)kernel_size=5,   # 卷积核尺寸5×5(感受野大小)stride=1,        # 滑动步长1(每次移动1像素)padding=2        # 填充2像素(使输出尺寸与输入相同:(28+2 * 2-5)/1 +1=28)),nn.ReLU(),  # ReLU激活函数(引入非线性,公式:max(0, x))nn.MaxPool2d(kernel_size=2)  # 最大池化层(2×2窗口,步长2,输出尺寸减半:28→14))  # 输出形状:[batch_size, 16, 14, 14](batch_size动态变化)# ---------------------- 卷积块2:特征深化 ----------------------self.conv2 = nn.Sequential(nn.Conv2d(16, 32, 5, 1, 2),  # 输入16通道→输出32通道,5×5核,步长1,填充2nn.ReLU(),  # 激活函数nn.Conv2d(32, 32, 5, 1, 2), # 输入32通道→输出32通道,5×5核,步长1,填充2nn.ReLU(),  # 激活函数nn.MaxPool2d(2)  # 2×2池化,输出尺寸14→7)  # 输出形状:[batch_size, 32, 7, 7]# ---------------------- 卷积块3:特征精炼 ----------------------self.conv3 = nn.Sequential(nn.Conv2d(32, 64, 5, 1, 2),  # 输入32通道→输出64通道,5×5核,步长1,填充2nn.ReLU()  # 激活函数)  # 输出形状:[batch_size, 64, 7, 7](池化后尺寸不变,因无MaxPool)# ---------------------- 全连接层:分类决策 ----------------------self.out = nn.Linear(64 * 7 * 7, 10)  # 输入维度:64通道×7×7特征图 → 输出10类(0-9)def forward(self, x):  # 前向传播函数(定义数据流动路径)x = self.conv1(x)  # 输入:[batch_size, 1, 28, 28] → 输出:[batch_size, 16, 14, 14]x = self.conv2(x)  # 输入:[batch_size, 16, 14, 14] → 输出:[batch_size, 32, 7, 7]x = self.conv3(x)  # 输入:[batch_size, 32, 7, 7] → 输出:[batch_size, 64, 7, 7]x = x.view(x.size(0), -1)  # 展平操作:[batch_size, 64, 7, 7] → [batch_size, 64 * 7 * 7]output = self.out(x)       # 全连接层:[batch_size, 64 * 7 * 7] → [batch_size, 10]return output  # 返回预测值(未归一化的logits)# 初始化模型并移动到目标设备(GPU/MPS/CPU)
model = CNN().to(device)
print("模型结构:
", model)  # 打印模型参数结构# ---------------------- 步骤5:定义损失函数与优化器 ----------------------
loss_fn = nn.CrossEntropyLoss()  # 交叉熵损失函数(适用于多分类任务)
optimizer = torch.optim.Adam(params=model.parameters(),  # 指定需要优化的参数(模型的所有可学习参数)lr=0.001                    # 学习率(控制参数更新步长,0.001是Adam的常用初始值)
)# ---------------------- 步骤6:定义训练函数 ----------------------
def train(dataloader, model, loss_fn, optimizer):model.train()  # 开启训练模式(影响某些层的行为,如Dropout、BatchNorm)batch_count = 0  # 记录当前处理的批次序号for X, y in dataloader:  # 遍历数据加载器(逐批次获取数据)X, y = X.to(device), y.to(device)  # 将数据和标签移动到目标设备# ---------------------- 前向传播 ----------------------pred = model(X)  # 模型预测:[batch_size, 10](未归一化的logits)loss = loss_fn(pred, y)  # 计算损失:预测值与真实标签的差异# ---------------------- 反向传播与参数更新 ----------------------optimizer.zero_grad()  # 清空上一轮的梯度(避免累积)loss.backward()        # 反向传播:计算各参数的梯度(存储在参数的.grad属性中)optimizer.step()       # 优化器更新:根据梯度调整参数(沿梯度下降方向)# ---------------------- 打印训练进度 ----------------------loss_value = loss.item()  # 将损失张量转换为Python数值(脱离计算图)batch_count += 1print(f"批次 {batch_count}: 损失值 = {loss_value:.6f}")# ---------------------- 步骤7:定义测试函数 ----------------------
def test(dataloader, model, loss_fn):model.eval()  # 开启测试模式(关闭Dropout、BatchNorm的随机行为)total_loss = 0.0  # 累计总损失correct = 0       # 累计正确预测数total_samples = len(dataloader.dataset)  # 总样本数(10,000)num_batches = len(dataloader)            # 总批次数(10,000/64≈157)with torch.no_grad():  # 关闭梯度计算(测试阶段无需更新参数,节省内存)for X, y in dataloader:X, y = X.to(device), y.to(device)# 前向传播(仅计算预测值,不记录梯度)pred = model(X)# 累计损失(.item()避免张量运算)total_loss += loss_fn(pred, y).item()# 计算正确预测数(pred.argmax(1)获取每行最大值的索引,即预测类别)correct += (pred.argmax(1) == y).type(torch.float).sum().item()# 计算平均损失和准确率avg_loss = total_loss / num_batchesaccuracy = (correct / total_samples) * 100  # 转换为百分比print(f"
测试结果:准确率 = {accuracy:.2f}% | 平均损失 = {avg_loss:.6f}
")# ---------------------- 步骤8:启动训练与测试 ----------------------
if __name__ == "__main__":epochs = 10  # 训练轮次(遍历整个训练集的次数)for epoch in range(epochs):print(f"
===== 第 {epoch+1}/{epochs} 轮训练开始 =====")train(train_dataloader, model, loss_fn, optimizer)  # 执行一轮训练test(test_dataloader, model, loss_fn)               # 每轮训练后测试print("
===== 训练完成!最终测试 =====")test(test_dataloader, model, loss_fn)  # 输出最终测试结果

3.2 代码分段详细解析

3.2.1 数据加载与预处理
  • datasets.MNIST​:PyTorch内置的MNIST数据集加载工具,root指定存储路径,train区分训练/测试集,download=True自动下载(首次运行时需要)。
  • ToTensor()​:将PIL图像转换为PyTorch张量,形状从(H, W, C)(28,28,1)变为(C, H, W)(1,28,28),并将像素值从[0,255]归一化到[0,1]。
  • DataLoader​:将数据集包装为可迭代的加载器,batch_size=64表示每次加载64张图像,shuffle=True在训练时随机打乱数据(避免模型学习到数据顺序的偏差)。
3.2.2 设备选择(GPU/MPS/CPU)
  • torch.cuda.is_available()​:检测是否有可用的NVIDIA GPU(CUDA支持)。
  • torch.backends.mps.is_available()​:检测是否有Apple Silicon芯片(如M1/M2)的MPS加速支持。
  • .to(device)​:将模型和张量移动到目标设备(GPU/MPS/CPU),利用硬件加速提升计算速度。
3.2.3 CNN模型设计
  • nn.Sequential​:顺序容器,按添加顺序执行各层操作(无需手动定义forward中的层调用)。
  • ​卷积层(nn.Conv2d)​​:
    • in_channels=1:输入为灰度图(1通道);
    • out_channels=16:使用16个不同的5×5卷积核,生成16张特征图(每个核提取一种局部特征);
    • padding=2:在图像边缘填充2像素,使卷积后输出尺寸与输入相同(28×28→28×28)。
  • ​激活函数(nn.ReLU)​​:将负数像素值置0,保留正数信息,引入非线性变换(否则多层卷积等价于单层线性变换)。
  • ​池化层(nn.MaxPool2d)​​:2×2窗口取最大值,将特征图尺寸减半(28×28→14×14),减少计算量的同时保留主要特征(平移不变性)。
  • ​全连接层(nn.Linear)​​:将展平后的高维特征(64×7×7=3136维)映射到10维输出(对应0-9的分类概率)。
3.2.4 训练函数(train
  • model.train()​:显式声明模型进入训练模式(影响依赖训练状态的层,如Dropout会随机失活神经元,BatchNorm会计算当前批次的均值方差)。
  • ​前向传播​​:输入图像经过卷积、激活、池化后,通过全连接层输出预测值(pred)。
  • ​损失计算​​:使用交叉熵损失函数(CrossEntropyLoss)比较预测值(pred)与真实标签(y)。
  • ​反向传播与优化​​:
    • optimizer.zero_grad():清空上一轮的梯度(避免梯度累积导致错误更新);
    • loss.backward():反向传播计算各参数的梯度(存储在参数的.grad属性中);
    • optimizer.step():优化器根据梯度调整参数(Adam会自适应调整学习率)。
3.2.5 测试函数(test
  • model.eval()​:显式声明模型进入测试模式(关闭DropoutBatchNorm的随机行为,确保测试结果稳定)。
  • torch.no_grad()​:上下文管理器,关闭梯度计算(测试阶段无需更新参数,节省内存和时间)。
  • ​准确率计算​​:通过pred.argmax(1) == y判断预测类别是否正确,统计正确数后计算准确率(correct / total_samples)。

四、训练结果与优化方向

4.1 典型训练结果

在云端环境(如Google Colab,使用Tesla T4 GPU)运行上述代码,10轮训练后通常能达到​​99%以上的测试准确率​​。训练过程中,损失值会随着轮次增加逐渐下降(从初始的2.3左右降至0.03以下),准确率稳步上升(从随机猜测的10%升至99%+)。

4.2 常见优化方向

  • ​调整网络深度​​:增加卷积层或全连接层(需注意过拟合,可通过Dropout层缓解);
  • ​调整超参数​​:尝试不同的batch_size(如32、128)、lr(如0.0001、0.01);
  • ​数据增强​​:对训练图像进行旋转、平移、缩放等变换(torchvision.transforms中的RandomRotation等),提升模型泛化能力;
  • ​正则化​​:添加nn.Dropout层(如在全连接层前加nn.Dropout(0.5)),随机失活部分神经元,减少过拟合;
  • ​学习率调度​​:使用torch.optim.lr_scheduler动态调整学习率(如StepLR每10轮降低学习率)。

五、总结

通过本文的实战,我们完整实现了基于PyTorch的MNIST手写数字识别系统,涵盖了从数据加载到模型训练的全流程。关键收获包括:

  1. ​数据预处理的重要性​​:ToTensor转换和DataLoader批量加载是高效训练的基础;
  2. ​CNN的核心结构​​:卷积层提取特征,池化层降维,全连接层分类;
  3. ​训练循环的关键步骤​​:前向传播、损失计算、反向传播、参数更新的协同工作;
  4. ​设备选择的影响​​:GPU/MPS加速能显著缩短训练时间(相比CPU可能快10-100倍)。

MNIST是深度学习入门的"起点",但绝不是终点。掌握本文的方法后,你可以尝试挑战更复杂的任务(如Fashion-MNIST多分类、CIFAR-10彩色图像识别),或探索更先进的模型(如ResNet残差网络、Transformer视觉模型)。记住,深度学习的核心是"实践-观察-改进"的循环,动手编写代码并分析结果是提升能力的最快途径!

​附:完整代码可直接复制到Google Colab(选择GPU运行时)或本地PyTorch环境运行,建议尝试修改网络结构(如增加卷积层)并观察准确率变化,加深对CNN设计的理解。​


文章转载自:

http://W4JGyqCd.xhftj.cn
http://PtmG1BEH.xhftj.cn
http://y0IjJBXd.xhftj.cn
http://DXUuWamU.xhftj.cn
http://oKKeEloN.xhftj.cn
http://5cMYvRGX.xhftj.cn
http://iq8C1tKc.xhftj.cn
http://IQthkThW.xhftj.cn
http://qJBVc1CM.xhftj.cn
http://eU8l86eO.xhftj.cn
http://utgDA75Q.xhftj.cn
http://mDvhjupt.xhftj.cn
http://Jf3KXHLh.xhftj.cn
http://JzcZIDkA.xhftj.cn
http://w3rJARve.xhftj.cn
http://L9qCfUOF.xhftj.cn
http://SIpw5bN2.xhftj.cn
http://tXCuYWuc.xhftj.cn
http://aykPiQFu.xhftj.cn
http://QS1V5D55.xhftj.cn
http://MndGHXQc.xhftj.cn
http://bB12Ta8W.xhftj.cn
http://GFUJ0DF2.xhftj.cn
http://gv2KOzjw.xhftj.cn
http://uScS3hfX.xhftj.cn
http://Gq0X2WbR.xhftj.cn
http://Gf3w3OR8.xhftj.cn
http://zqWzD5oX.xhftj.cn
http://nty2beo1.xhftj.cn
http://JK3JQfDL.xhftj.cn
http://www.dtcms.com/a/386971.html

相关文章:

  • 2025 Android 知识体系总结(含面试要点,持续补充,更新中...)
  • elementui中表单先上传但不请求接口,点击按钮后在请求接口的方式上传文件,及校验
  • el-input自动填充与设置input背景色无效
  • java设计模式-工厂模式(文件上传)
  • Keras+Flask手写数字识别Web应用
  • PPTist+cpolar:开源演示文稿的远程创作方案
  • Chapter8—组合模式
  • vmware的ub系统长时间不动会黑屏
  • 从0到1打造一个能上传任意GeoJSON的交互式Web地图
  • 深入理解数据结构之复杂度
  • Silicon EFR32xG22 CMU
  • 运维面试笔记(持续补充版)
  • 托福阅读35-1
  • qt QCandlestickSet详解
  • 在Linux和Windows系统下使用Qt监测U盘的插拔事件
  • 文字识别接口的应用场景-发票识别接口-OCR API
  • 鸿蒙NEXT ArkWeb同层渲染:原生与Web的完美融合
  • 基于springboot的4s店汽车销售服务系统
  • ARM芯片的调试访问端口 DAP(Debug Access Port)
  • 减少推导式中的重复计算:赋值表达式(:=)的优雅应用 (Effective Python 第29条)
  • 空压机远程控制与数据采集的御控物联网解决方案
  • 瑞萨MCU RA4M1 FLASH锁死问题记录
  • Kubernetes 调度器(Scheduler)
  • Java设计模型-责任链模式
  • Linux 服务器安全优化:firewalld SSH 限制 白名单与 SCP 服务禁用流程
  • bisheng 智能体
  • 学完Python之后我写了一个免费看电影的软件
  • 【ROS2】Concept(Advanced )
  • Apifox自动化测试场景设计
  • 知识复用缺乏跨角色适配该如何改善