
简介面向高校期末大作业和课程设计场景这份基于PyTorch的MNIST手写数字图像分类项目源码覆盖了从数据解压、预处理、模型搭建、训练调参到测试评估的完整流程。压缩包共包含14个文件核心为三个Python脚本分别承担数据加载、模型定义和训练测试任务同时附带MNIST原始训练与测试数据集、依赖环境说明和README文档解压后按说明配置即可复现实验整体大小约22MB。项目目录组织规范关键模块配有注释能帮助初学者快速理解卷积神经网络处理图像分类任务的数据流与关键参数的作用。目前已有443人在线学习说明其作为期末大作业或课程设计模板具有较强参考价值。通过动手实践读者不仅可以获得一个可直接提交的高分项目还能掌握从图像数据读取、模型训练到准确率评估的完整工程方法对于写实验报告和答辩演示也很有帮助。1. 基于pytorch的mnist图像数据集分类实战这门大作业到底在考什么同样是 MNIST 手写数字识别课程作业和线上教程有一个明显分水岭教程教你把模型跑出 99% 准确率就收工高分大作业要求的是“把分类项目做成一套能交给别人运行的源码”。这意味着数据读取、模型定义、训练、评估、预测、可视化、异常处理必须各就各位而多数新手恰恰栽在数据加载和评估环节。基于 PyTorch 做 MNIST 图像数据集分类技术栈很轻真正决定分数的部分在工程组织能不能换一台机器直接复现、能不能讲清楚每个参数为什么这么设、能不能用分类评估指标证明模型不是靠偷数据刷分。适合正在做课设、准备复试或补 CV 基础的人把 PyTorch 基础框架的整体流转吃透。2. pytorch安装与mnist数据集加载torchvision下载404的处理方法2.1 先配好能跑CNN的pytorch环境再谈数据集MNIST 分类项目最怕第一步就倒在环境上。常见的 pytorch 环境搭建方式有 Anaconda 创建虚拟环境、pip 直装、以及 IDE 内嵌解释器。我一般会用 Anaconda 新建一个独立环境避免把系统 Python 搞乱。CPU 机器直接安装 CPU 版即可有 NVIDIA GPU 的同学再装 CUDA 版注意torch、torchvision、python三者版本需要配对。conda create -n mnist_env python3.9 -y conda activate mnist_env # CPU 版 pip install torch torchvision torchaudio # GPU 版则用官网生成的命令例如 cuda 12.1 组合包 # pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121这段命令创建名为mnist_env的环境并安装依赖。参数说明python3.9是因为 3.10/3.11 也可以但 3.9 对国内镜像源兼容性更好torchvision是处理图像数据集和预训练变换的库MNIST 数据集的下载接口就在它里面加注释的 GPU 命令需要根据机器驱动选择合适的 CUDA 版本。很多同学装完跑torch.cuda.is_available()显示False多半是安装了 CPU 版或者驱动太老别急着怪代码。2.2 torchvision下载mnist会404根本原因与解决方案torchvision.datasets.MNIST默认从https://yann.lecun.com/exdb/mnist/下载但这个老站点经常跳转异常导致torchvision下载时出现 404。另一个高发点是校园网或代理环境下 SSL 证书校验失败。这个问题和模型代码无关是数据源访问问题但会让整个大作业卡在第一步。常见做法是改用国内镜像或离线包。我建议先手动下载四个.gz文件再让代码读取本地文件。MNIST 的官网镜像很多但不要在代码里写死不可靠的 URL。更稳妥的方案是修改MNIST类的urls属性from torchvision.datasets import MNIST from torchvision.datasets.utils import extract_archive, check_integrity class LocalMNIST(MNIST): urls [ https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz, https://ossci-datasets.s3.amazonaws.com/mnist/train-labels-idx1-ubyte.gz, https://ossci-datasets.s3.amazonaws.com/mnist/t10k-images-idx3-ubyte.gz, https://ossci-datasets.s3.amazonaws.com/mnist/t10k-labels-idx1-ubyte.gz, ] train_set LocalMNIST(root./data, trainTrue, downloadTrue, transformtransform)这段代码的关键是重写urls把数据源换到较稳定的对象存储服务。root./data表示数据存放在当前目录的data文件夹下trainTrue加载训练集downloadTrue表示若本地没有文件就执行下载。参数说明transform不能省MNIST 原始数据是 PIL 图像需要转成张量并归一化否则模型输入格式会报错。如果仍然 404最保险的办法是把下载好的.gz文件手动放进./data/MNIST/raw/目录然后设置downloadFalse。文件命名必须与库预期一致否则check_integrity会判定文件缺失。2.3 数据预处理与DataLoader参数怎么调MNIST 的图片是 28×28 灰度图模型输入一般设计成[batch_size, 1, 28, 28]。transforms.Compose里最常用的三段式是ToTensor、归一化和可选的增强。from torchvision import transforms transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_set LocalMNIST(root./data, trainTrue, downloadFalse, transformtransform) valid_set LocalMNIST(root./data, trainFalse, downloadFalse, transformtransform) train_loader DataLoader(train_set, batch_size64, shuffleTrue, num_workers2, pin_memoryTrue) valid_loader DataLoader(valid_set, batch_size256, shuffleFalse)Normalize((0.1307,), (0.3081,))里的两个值是 MNIST 数据集的全局均值和标准差是公开的统计量直接复用即可。要注意ToTensor会把像素值从 0255 缩放到 01Normalize再把它变成均值为 0、方差接近 1 的数据。shuffleTrue只在训练集使用验证集必须保持False否则评估指标会因数据顺序波动。num_workers2在 Windows 上如果报多进程错误就改成 0这是 Windows 和 Linux 在多进程数据加载上的常见差异。参数建议值说明batch_size训练 64验证 256小 batch 收敛稳大 batch 验证更快shuffle训练 True验证 False打乱顺序能减少批次间相关性num_workers24Windows 可用 0并行加载不一定会更快可能引入报错pin_memoryTrue使用 GPU 时可减少数据传输时间drop_last依赖样本数MNIST 训练集是 60000能被 64 整除不必设置3. 用pytorch基础框架构建mnist图像数据集分类模型3.1 为什么选择CNN而不是多层感知机MNIST 图片尺寸小但像素之间的邻域关系才是关键。多层感知机把 28×28 拉平成 784 维向量会丢失空间结构想达到高准确率需要很宽的隐藏层参数量大且泛化差。CNN 通过卷积核提取局部特征用下采样降低分辨率再通过全连接层输出 10 类概率。对于这个数据集两到三层卷积就足够过深的网络反而容易过拟合。下面是一个典型的高分作业模型结构把卷积、池化、Dropout、全连接完整呈现import torch import torch.nn as nn class MnistCNN(nn.Module): def __init__(self, num_classes10, dropout0.25): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 32, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Dropout2d(dropout), nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Dropout2d(dropout), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 7 * 7, 256), nn.ReLU(inplaceTrue), nn.Dropout(dropout), nn.Linear(256, num_classes), ) def forward(self, x): return self.classifier(self.features(x))逻辑说明第一层卷积输入通道为 1对应灰度图输出 32 个特征图。经过 3×3 卷积加padding1特征图尺寸保持不变所以两次卷积后仍是 28×28。MaxPool2d把尺寸减半第一次池化后 14×14第二次池化后 7×7。全连接层输入维度是128 * 7 * 7这个数字是特征图通道数和尺寸的乘积改卷积层结构时这里必须同步修改否则Linear会报维度错误。Dropout 是防止过拟合的关键。Dropout2d按通道随机置零适合卷积层Dropout按神经元置零适合全连接层。大作业里如果训练准确率很高但验证准确率上不去多半是 Dropout 强度不够或数据增强缺失。3.2 模型参数表与可调整空间下表列出了适合 MNIST 的默认参数和调整方向写报告时可以直接对照解释模块参数默认值调整方向Conv2d输出通道32 / 64 / 128加大通道数提升表达力但会变慢Conv2dkernel_size3×35×5 感受野更大但参数量增加MaxPool2dkernel_size / stride2 / 2常用配置改 3/2 会重叠池化Dropoutp0.25过拟合时增到 0.5BatchNorm无不启用加入后可提高收敛稳定性OptimizerAdam / SGD由训练脚本决定Adam 省心SGD 配合动量泛化好3.3 初始化与随机种子复现高分结果的前提大作业源码里最容易忽略的是随机种子。PyTorch 的卷积层和 Dropout 都依赖随机数如果源码里不固定种子每次运行结果会不同。教师或审查方复现时发现分数波动极可能质疑代码稳定性。建议在所有训练入口加这一段import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark Falsecudnn.deterministic True会让 GPU 上的卷积算法变成确定性实现代价是速度稍慢但能保证多次运行结果一致。benchmark False禁止 cuDNN 自动选择最优算法这也是复现性必需的。很多在 CPU 上跑得好好的代码放到 GPU 上结果有细微差异根源就在这里。4. 训练、分类评估与模型保存高分项目的得分点4.1 训练循环的完整写法模型定义完成后训练过程要清晰记录每个 epoch 的损失和准确率。不要只打印 loss要显式计算准确率并区分训练集和验证集。下面的代码段可以直接嵌入训练脚本device torch.device(cuda if torch.cuda.is_available() else cpu) model MnistCNN().to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr0.001) def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss, correct, total 0, 0, 0 for images, labels in loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * labels.size(0) correct (outputs.argmax(dim1) labels).sum().item() total labels.size(0) return total_loss / total, correct / total def evaluate(model, loader, criterion, device): model.eval() total_loss, correct, total 0, 0, 0 with torch.no_grad(): for images, labels in loader: images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) total_loss loss.item() * labels.size(0) correct (outputs.argmax(dim1) labels).sum().item() total labels.size(0) return total_loss / total, correct / total这段代码有两个细节model.train()和model.eval()切换工作模式影响 Dropout 和 BatchNorm 的表现loss.item()取出的是当前 batch 的标量值乘以labels.size(0)是把 loss 还原到样本维度最后除以总样本数得到的是平均样本损失。outputs.argmax(dim1)沿类别维度取最大值索引和真实标签做比较得到预测正确的样本数。CrossEntropyLoss会自动把模型输出的 logits 转为概率并计算损失不需要在模型最后一层加 Softmax。这是一个高频误解训练时用 CrossEntropyLoss模型输出过 Softmax 反而会降低数值稳定性。4.2 分类评估指标不止准确率MNIST 测试集准确率很容易做到 99% 以上但如果只报告准确率项目报告会显得单薄。高分大作业通常要求做分类评估至少包含 precision、recall、F1-score以及最直观的混淆矩阵。类别数量是 10正好能看出哪些数字容易互相混淆比如 4 和 9、3 和 8。from sklearn.metrics import classification_report, confusion_matrix all_preds, all_labels [], [] model.eval() with torch.no_grad(): for images, labels in valid_loader: images images.to(device) outputs model(images) all_preds.extend(outputs.argmax(dim1).cpu().numpy()) all_labels.extend(labels.cpu().numpy()) print(classification_report(all_labels, all_preds, digits4)) conf confusion_matrix(all_labels, all_preds) print(conf)classification_report会列出每个数字类别的 precision、recall、F1 和样本数。confusion_matrix返回一个 10×10 矩阵第 i 行第 j 列表示真实类别 i 被预测成 j 的数量。矩阵对角线越深越好非对角线亮点就是错误聚集处。报告里贴出这张矩阵比单句“测试准确率 99.3%”更有说服力。每个指标的含义需要注意指标计算方式MNIST 中关注点accuracy正确预测数 / 总数整体水平类别均衡时可作主指标precisionTP / (TP FP)“预测为 7”中有多少真是 7recallTP / (TP FN)“真实为 7”中有多少被找出来F1-score2 * precision * recall / (precision recall)综合指标适合关注少数类错误4.3 模型保存与断点续训训练结束后源码里必须有保存模型权重的代码同时还要保存一个结构文件或说明文件方便加载。PyTorch 官方推荐只保存state_dict因为它不绑定模型类定义路径迁移性更好。torch.save(model.state_dict(), mnist_cnn.pt) # 加载时先实例化模型再填权重 loaded_model MnistCNN() loaded_model.load_state_dict(torch.load(mnist_cnn.pt)) loaded_model.eval()保存state_dict时需要注意load_state_dict要求model的结构和保存时的结构完全一致别在训练后临时改了num_classes再加载。另外如果训练过程很长建议每个 epoch 都保存一个快照并在训练代码里实现“验证准确率提高就覆盖保存”的机制避免后期过拟合覆盖最好权重。5. 高分大作业源码的组织技巧与3个容易忽略的边界问题5.1 源码目录结构让老师一眼看懂模块划分一份能拿高分的 pytorch mnist 分类项目源码不会只放一个.ipynb文件而是按职责拆成脚本。常见布局如下mnist_project/ ├── README.md ├── requirements.txt ├── config.py ├── data/ │ └── MNIST/ ├── models/ │ ├── __init__.py │ └── cnn.py ├── utils/ │ ├── __init__.py │ ├── dataset.py │ └── metrics.py ├── train.py ├── evaluate.py └── predict.pytrain.py负责训练并保存模型evaluate.py负责在测试集上输出分类评估报告predict.py接收单个图片路径并输出预测值。requirements.txt里固定核心依赖即可不要把整个 conda 环境列表贴进去。大作业报告中解释每个文件的职责比贴全部代码更能体现工程能力。5.2 三个容易忽略的边界问题第一个是验证集和测试集的混用。很多学生顺手把trainFalse的数据集既当验证集又当测试集调参后又再测一次。合理做法是从原始训练集切出 10% 作为验证集trainFalse作为测试集只在最终评估时使用。可以用torch.utils.data.random_split完成切分但注意要让MNIST的trainTrue数据先经过Subset再传入 DataLoader。第二个是模型处于训练模式就做预测。若在model.train()状态下直接调用分类Dropout 会随机丢弃部分神经元导致同一张图片每次预测结果不同。所有预测和评估前必须执行model.eval()并用torch.no_grad()包裹前者固定 Dropout 行为后者关闭梯度计算降低显存占用。第三个是设备适配问题。源码必须同时兼容 CPU 和 GPU代码里不要出现images.cuda()这种硬编码而是用images.to(device)。在predict.py中加载训练好的模型后还要检查输入图片通道数和尺寸否则灰度图或 RGBA 图会直接报错。可以在predict函数入口加一段灰度转换from PIL import Image import torchvision.transforms.functional as F def preprocess_image(path): img Image.open(path).convert(L) img img.resize((28, 28)) tensor F.to_tensor(img) tensor (tensor - 0.1307) / 0.3081 return tensor.unsqueeze(0)convert(L)将图片转成单通道灰度图resize((28, 28))强行对齐输入尺寸unsqueeze(0)在批次维增加一维使张量形状成为[1, 1, 28, 28]。注意推理时要做和训练时完全一样的归一化操作均值和标准差不能改动否则模型的输入分布被破坏准确率会明显下降。本文还有配套的精品资源点击获取