ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PyTorch实战:MNIST手写数字识别从环境搭建到99.5%准确率

PyTorch实战:MNIST手写数字识别从环境搭建到99.5%准确率 简介这份资源面向计算机、电子信息工程、数学等专业的大学生适用于课程设计、期末大作业或毕业设计等场景提供基于PyTorch实现MNIST手写数字识别的完整参考方案帮助读者理解卷积神经网络从数据加载、模型搭建到训练评估的全流程。压缩包共24个文件约25.24MB包含2个py源码文件、2个pth权重文件、MNIST原始数据文件含train-labels、t10k-images等idx格式、8个gz压缩数据、4个xml配置及说明文档目录结构清晰便于按模块查阅与复现。目前已有2378人学习下载说明该案例在入门深度学习任务中具有较高的参考价值。读者可借助源码与配套数据直接运行实验观察训练过程与识别效果并在此基础上自行调整网络结构、超参数或添加功能适合具备一定Python与PyTorch基础、希望快速上手图像分类实践的学习者。1. 从一份 .rar 说起MNIST 手写数字识别到底能跑出什么结果很多人第一次接触深度学习都是从 MNIST 手写数字数据集识别开始的。你拿到一个压缩包名字叫「基于Pytorch实现MNIST手写数字数据集识别源码数据.rar」解压之后大概率是几个 .py 文件加一个 data 目录。问题来了这份源码能不能直接跑跑出来准确率是多少为什么我这边一运行就报错这篇文章就是围绕这些问题展开的。MNIST 本身不复杂6 万张训练图、1 万张测试图每张 28×28 灰度图10 个类别。但真正落地时你会发现环境搭建、数据下载、模型结构选择、训练参数调节、结果验证每一步都有坑。适合谁看刚入门 PyTorch 想找个完整项目练手的、手里有这份源码但跑不通的、想搞清楚 MNIST 识别从数据到部署全流程的从业者。下面按「先跑通、再调优、最后避坑」的顺序讲清楚。2. 环境搭建与数据准备把 PyTorch 和 MNIST 数据集落到本地2.1 PyTorch 安装的三种路径与选择依据安装 PyTorch 这件事说简单也简单说坑也多。目前主流有三种方式pip 直接装、conda 装、以及 WSL 环境下装。选哪种取决于你的显卡和操作系统。如果你有 NVIDIA 显卡并且想用 GPU 加速先确认 CUDA 版本。命令行执行nvidia-smi右上角会显示驱动支持的 CUDA 版本。然后去 PyTorch 官网找到对应命令。常见做法是# 查看显卡驱动和CUDA版本 nvidia-smi # 以CUDA 11.8为例pip安装GPU版本 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 如果没有GPU装CPU版本即可 pip install torch torchvision torchaudio这里的关键参数是--index-url它决定了你从哪个源下载。不指定的话默认走 PyPI下载的是 CPU 版本。很多人装完发现torch.cuda.is_available()返回 False八成就是装成了 CPU 版。conda 方式适合需要管理多个虚拟环境的人# 创建独立环境避免和系统Python冲突 conda create -n mnist_env python3.10 conda activate mnist_env # 安装PyTorchconda会自动处理CUDA依赖 conda install pytorch torchvision torchaudio pytorch-cuda11.8 -c pytorch -c nvidiaconda 的好处是 CUDA 运行时库会一起装好不用自己配。坏处是下载慢国内建议配镜像源。WSL 方式适合 Windows 用户想用 Linux 开发环境的情况。在 WSL2 里装 PyTorch 和原生 Linux 一样但要注意 WSL 的 CUDA 直通需要 Windows 端驱动版本足够新。我一般会先跑一个最小验证脚本确认环境没问题import torch import torchvision # 检查PyTorch版本和CUDA可用性 print(PyTorch version:, torch.__version__) print(CUDA available:, torch.cuda.is_available()) if torch.cuda.is_available(): print(CUDA device:, torch.cuda.get_device_name(0)) # 检查torchvision版本后面加载MNIST要用 print(torchvision version:, torchvision.__version__)这段代码跑通说明基础环境没问题。如果torchvision导入报错多半是版本和 PyTorch 不匹配重新按官网命令装一次。2.2 MNIST 数据集加载torchvision 下载 404 的根因与手动方案torchvision.datasets.MNIST是最常用的加载方式但很多人卡在下载这一步。典型报错是HTTP Error 404或者连接超时。原因不复杂MNIST 原始文件托管在境外服务器网络不稳定时就会失败。标准加载代码长这样import torch from torchvision import datasets, transforms # 定义预处理转Tensor并归一化 transform transforms.Compose([ transforms.ToTensor(), # 把PIL图像转成[0,1]的Tensor transforms.Normalize((0.1307,), (0.3081,)) # MNIST全局均值和标准差 ]) # 加载训练集 train_dataset datasets.MNIST( root./data, # 数据存放路径 trainTrue, # 训练集 downloadTrue, # 自动下载 transformtransform ) # 加载测试集 test_dataset datasets.MNIST( root./data, trainFalse, downloadTrue, transformtransform ) print(训练集大小:, len(train_dataset)) print(测试集大小:, len(test_dataset))参数说明root是数据目录下载的文件会放在./data/MNIST/raw/下downloadTrue表示本地没有就自动下载transform定义预处理流水线。归一化用的 0.1307 和 0.3081 是 MNIST 训练集的全局均值和标准差这个值是固定的直接用就行。如果自动下载一直失败手动方案是找到 MNIST 的四个原始文件train-images-idx3-ubyte.gz、train-labels-idx1-ubyte.gz、t10k-images-idx3-ubyte.gz、t10k-labels-idx1-ubyte.gz放到./data/MNIST/raw/目录下然后把download改成 False。注意文件名必须完全一致torchvision 会校验文件完整性。提示手动放置文件后如果还报错检查文件是否解压。torchvision 需要的是 .gz 压缩包本身不是解压后的文件。2.3 DataLoader 配置batch_size 和 shuffle 怎么定数据加载进来后要用 DataLoader 包装成批次。这一步的参数直接影响训练效果和速度from torch.utils.data import DataLoader # 训练集DataLoader train_loader DataLoader( datasettrain_dataset, batch_size64, # 每批64张图 shuffleTrue, # 每个epoch打乱顺序 num_workers2, # 2个子进程加载数据 pin_memoryTrue # 锁页内存GPU训练时加速数据传输 ) # 测试集DataLoader test_loader DataLoader( datasettest_dataset, batch_size1000, # 测试时可以大一些 shuffleFalse, # 测试集不需要打乱 num_workers2, pin_memoryTrue )batch_size64是 MNIST 上的经典选择显存占用小梯度更新频率适中。如果你显卡显存大可以调到 128 或 256训练会更快但学习率可能要相应调整。shuffleTrue对训练集是必须的否则模型会学到样本顺序的伪规律。num_workers在 Windows 上有时会出问题如果报错就设为 0用主进程加载。3. 模型定义与训练循环从 LeNet 到两层 CNN 的取舍3.1 网络结构设计为什么 MNIST 不需要太深的模型MNIST 图片只有 28×28信息量有限用 ResNet 这种深层网络属于杀鸡用牛刀而且容易过拟合。常见做法是两层卷积加两层全连接参数量在 100 万左右训练快、效果好。import torch.nn as nn import torch.nn.functional as F class MNISTNet(nn.Module): def __init__(self): super(MNISTNet, self).__init__() # 第一层卷积1通道输入32通道输出3x3卷积核 self.conv1 nn.Conv2d(1, 32, kernel_size3, padding1) # 第二层卷积32通道输入64通道输出 self.conv2 nn.Conv2d(32, 64, kernel_size3, padding1) # 最大池化2x2窗口 self.pool nn.MaxPool2d(2, 2) # 全连接层经过两次池化后28x28变成7x764通道 self.fc1 nn.Linear(64 * 7 * 7, 128) self.fc2 nn.Linear(128, 10) # 10个类别 # Dropout层防止过拟合 self.dropout nn.Dropout(0.25) def forward(self, x): # 第一层卷积 - ReLU - 池化 x self.pool(F.relu(self.conv1(x))) # 28x28 - 14x14 # 第二层卷积 - ReLU - 池化 x self.pool(F.relu(self.conv2(x))) # 14x14 - 7x7 # 展平 x x.view(-1, 64 * 7 * 7) # 全连接 Dropout x F.relu(self.fc1(x)) x self.dropout(x) x self.fc2(x) return x # 实例化模型 model MNISTNet() print(model)结构说明padding1保证卷积后尺寸不变池化层负责降维。两次池化后 28→14→7所以全连接输入是 64×7×7。Dropout 设 0.25 是轻量正则化MNIST 上不需要太强的 dropout。这个模型在测试集上通常能到 99% 以上准确率。3.2 训练循环损失函数、优化器和 epoch 数训练循环是核心几个关键选择损失函数用交叉熵优化器用 Adam 或 SGD学习率从 1e-3 开始试。import torch.optim as optim # 设备选择 device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) # 损失函数交叉熵内置Softmax criterion nn.CrossEntropyLoss() # 优化器Adam学习率0.001 optimizer optim.Adam(model.parameters(), lr0.001) # 训练轮数 epochs 10 for epoch in range(epochs): model.train() # 切换到训练模式 running_loss 0.0 correct 0 total 0 for batch_idx, (data, target) in enumerate(train_loader): data, target data.to(device), target.to(device) # 梯度清零 optimizer.zero_grad() # 前向传播 output model(data) # 计算损失 loss criterion(output, target) # 反向传播 loss.backward() # 更新参数 optimizer.step() # 统计 running_loss loss.item() _, predicted output.max(1) total target.size(0) correct predicted.eq(target).sum().item() train_acc 100. * correct / total print(fEpoch {epoch1}/{epochs}, Loss: {running_loss/len(train_loader):.4f}, Train Acc: {train_acc:.2f}%)参数说明lr0.001是 Adam 的常用起点MNIST 上收敛很快。epochs10足够再多可能过拟合。model.train()和后面的model.eval()切换很重要影响 Dropout 和 BatchNorm 的行为。3.3 测试集评估准确率之外还要看什么训练完必须用测试集验证不能只看训练准确率model.eval() # 切换到评估模式 test_loss 0 correct 0 total 0 # 不需要计算梯度节省显存 with torch.no_grad(): for data, target in test_loader: data, target data.to(device), target.to(device) output model(data) test_loss criterion(output, target).item() _, predicted output.max(1) total target.size(0) correct predicted.eq(target).sum().item() test_acc 100. * correct / total print(fTest Loss: {test_loss/len(test_loader):.4f}, Test Acc: {test_acc:.2f}%)除了准确率建议看一下混淆矩阵搞清楚哪些数字容易混。MNIST 上常见的混淆是 4 和 9、3 和 8。如果某个类别准确率明显低可以针对性做数据增强。from sklearn.metrics import confusion_matrix import numpy as np # 收集所有预测结果 all_preds [] all_targets [] model.eval() with torch.no_grad(): for data, target in test_loader: data data.to(device) output model(data) _, predicted output.max(1) all_preds.extend(predicted.cpu().numpy()) all_targets.extend(target.numpy()) # 打印混淆矩阵 cm confusion_matrix(all_targets, all_preds) print(混淆矩阵:) print(cm)混淆矩阵对角线是正确分类数非对角线是错误。看哪一行数值大但对应列不对就知道哪个数字容易被认错。4. 避坑与排查MNIST 训练中最容易翻车的五个地方4.1 报错「CUDA out of memory」但显存明明够现象训练一开始就报显存不足但nvidia-smi看显存占用很低。原因多半是 DataLoader 的num_workers设太大每个 worker 都会复制一份数据到显存或者模型没有及时释放中间变量。解决把num_workers降到 0 或 1pin_memory设为 False 试试。如果还不行在训练循环里手动del中间变量并torch.cuda.empty_cache()。4.2 训练准确率一直卡在 10% 左右现象loss 不下降准确率接近随机猜测。原因最常见的是标签和输出维度对不上或者学习率太大导致梯度爆炸。解决先检查criterion和模型输出维度是否匹配。然后打印一个 batch 的 loss 值如果 loss 是 nan说明学习率太大降到 1e-4 再试。另外确认optimizer.zero_grad()有没有漏掉。4.3 测试准确率远低于训练准确率现象训练集 99%测试集只有 90%。原因过拟合。MNIST 虽然简单但模型参数量太大或者训练轮数太多也会过拟合。解决加 Dropout 层或者用数据增强随机旋转、平移。也可以减少全连接层神经元数量。早停策略也有效如果测试集 loss 连续几个 epoch 不降就停。4.4 手动放置 MNIST 文件后仍然报错现象已经把四个 .gz 文件放到 raw 目录但 torchvision 还是尝试下载。原因文件名不对或者文件损坏。torchvision 会校验文件的 MD5。解决确认文件名完全一致注意大小写。如果是从其他来源复制的文件重新下载一份确保完整。也可以直接读本地文件绕过 torchvision 的下载逻辑。4.5 GPU 利用率低训练速度慢现象nvidia-smi显示 GPU 利用率只有 20% 到 30%。原因数据加载是瓶颈GPU 在等数据。解决增大batch_size提高num_workers开启pin_memory。如果数据预处理复杂可以先把整个数据集预处理完存成 Tensor训练时直接读。5. 进阶技巧把 MNIST 准确率推到 99.5% 以上的三个手段第一个手段是数据增强。MNIST 虽然简单但加一点随机变换能提升泛化能力# 增强版预处理 train_transform transforms.Compose([ transforms.RandomRotation(10), # 随机旋转±10度 transforms.RandomAffine(0, translate(0.1, 0.1)), # 随机平移 transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ])注意测试集的 transform 不能加随机变换只能用 ToTensor 和 Normalize。第二个手段是学习率调度。用StepLR或CosineAnnealingLR在训练后期降低学习率让模型更精细地收敛from torch.optim.lr_scheduler import StepLR scheduler StepLR(optimizer, step_size3, gamma0.5) # 在每个epoch结束后调用 scheduler.step()step_size3表示每 3 个 epoch 学习率乘以 0.5。这样前期快速下降后期微调。第三个手段是模型集成。训练 3 到 5 个不同初始化的模型预测时取平均# 假设有多个模型 models [model1, model2, model3] all_probs [] for m in models: m.eval() with torch.no_grad(): for data, _ in test_loader: data data.to(device) output m(data) probs F.softmax(output, dim1) all_probs.append(probs.cpu()) # 平均概率 avg_probs torch.stack(all_probs).mean(dim0) _, final_preds avg_probs.max(1)集成能把准确率再推 0.2 到 0.3 个百分点代价是推理时间成倍增加。手段预期准确率训练时间推理时间基础 CNN99.0%1x1x数据增强99.3%1.2x1x学习率调度99.4%1.2x1x模型集成99.6%3x3x最后说一个我自己的习惯每次跑完实验把模型权重、训练日志、混淆矩阵图存到一个带时间戳的目录里。MNIST 训练快很容易跑很多组参数不记录的话过两天就忘了哪组是哪组。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表