ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于AlexNet卷积神经网络的手写数字识别:PyTorch完整源码与训练避坑指南

基于AlexNet卷积神经网络的手写数字识别:PyTorch完整源码与训练避坑指南 简介基于AlexNet卷积神经网络的手写数字识别Python项目内含完整可运行源码与实验报告面向毕业设计、期末大作业和课程设计场景。代码逐行注明关键逻辑从数据加载、模型搭建到训练评估一目了然即使新手也能快速上手配套实验报告详细介绍了数据集准备、AlexNet网络结构、训练参数配置及结果可视化源自98分高分项目获得导师认可下载后简单配置环境即可部署使用。压缩包共18个文件以10个Python脚本为核心覆盖模型定义、数据处理、训练测试等模块另有4个预训练权重文件gz格式以及项目配置、依赖清单、说明文档等文本材料整体仅11.07MB非常轻量。目前已有390人学习适合作为深度学习图像识别任务的入门范例也可直接用于课设答辩或毕设展示是一份结构完整、注释规范的高分模板。1. 基于 AlexNet 卷积神经网络的手写数字识别这份 python 源码到底能帮你省多少事如果你正在为毕业设计或者期末大作业找一份能直接跑、能讲清楚原理的深度学习项目基于 AlexNet 卷积神经网络实现手写数字识别的这套 python 源码大概率就是你想要的答案。它不是一个只有模型的半成品而是把训练、测试、可视化、配置管理都放在同一个工程里的完整项目代码里带注释结构上分了 models、train、utils、data 几大块新手照着 README 也能把训练跑起来。我做过的课程设计和毕设评审里这类项目最大的价值在于模型结构不堆砌、代码风格可读、报告和代码对应得上答辩时问到哪一层都能接住话。2. 从下载到跑通目录结构、环境依赖与首次运行2.1 文件清单哪些文件是核心哪些可以直接忽略这套源码解压之后是一个 folder-master 目录里面并不是所有文件都需要你逐行去读。我一般拿到一个开源项目首先会把目录里每个文件的作用标出来避免把时间浪费在 IDE 配置或者无关文件上。文件/目录职责优先级main.py程序入口负责初始化配置、启动训练或测试流程核心config.py集中管理超参数、路径、设备选择核心models/AlexNet.pyAlexNet 网络定义本项目的主模型核心models/ResNet.pyResNet 版本的对照模型用于对比实验扩展models/BasicModel.py基础 CNN 模型可作为 baseline扩展train/dataset.py数据加载、预处理、划分训练集和验证集核心train/utils/visualize.py绘制 loss 曲线、准确率曲线、样本可视化辅助test/测试脚本加载训练好的权重做评估核心requirements.txtPython 依赖清单部署.idea/vcs.xmlPyCharm 的 IDE 配置文件可忽略.gitignoreGit 忽略规则可忽略README.md项目说明文档先读从经验看训练一个 MNIST 级别的手写数字识别模型AlexNet.py、dataset.py、config.py这三个文件决定成败。.idea是 PyCharm 自动生成的换到 VSCode 也不影响运行。2.2 环境要求与 requirements.txt 部署步骤这套代码是基于 PyTorch 写的所以在跑之前先把深度学习环境准备好。新手最容易在这里卡住装完 Python 之后忘了装 CUDA 版 PyTorch后面训练时只能用 CPU一个 epoch 要跑几分钟影响调参节奏。我的习惯是先在终端里确认 Python 版本再装依赖。python --version # 建议 3.8 及以上版本 pip install -r requirements.txtrequirements.txt里一般包含 torch、torchvision、numpy、matplotlib 这几个核心库。torchvision 负责下载和预处理 MNIST 数据集matplotlib 用于 visualize.py 画图。如果你用的是 NVIDIA 显卡建议单独安装 CUDA 版本对应的 PyTorch训练速度能快一个量级如果没有独立显卡CPU 版本也能跑只是需要把config.py里的设备改成cpu。2.3 首次运行从 config.py 改参数到启动训练跑通这个项目不需要改任何模型代码只需要打开config.py确认几个关键参数batch_size、learning_rate、epochs、device。我第一次拿到这套源码时直接把 epochs 改成了 10batch_size 保持 64在 CPU 上跑了大约二十分钟验证集准确率就到了 98% 以上。python main.py运行之后终端会逐行打印当前 epoch、loss、accuracy。如果看到 loss 在稳步下降说明数据和模型已经正常交互。如果报错先看是不是缺依赖再看是不是路径问题。项目里data目录带__init__.py说明数据加载是包结构不用手动去下载 MNISTdataset.py会在第一次运行时自动下载到本地。2.4 IDE 与运行方式不是必须用 PyCharm.idea目录会让很多人误以为必须用 PyCharm 打开其实不是。VSCode 配置好 Python 解释器之后一样能跑关键在于main.py的当前工作目录要指向项目根目录否则相对路径会找不到models和train包。我一般这样处理cd folder-master python main.py先在终端里 cd 到项目根目录再执行脚本这样最不容易出路径问题。如果你在 IDE 里直接点运行按钮记得检查 Run Configuration 里的 working directory 是否指向项目根目录。3. AlexNet 网络结构拆解5 个卷积层如何适配 28×28 的 MNIST3.1 原始 AlexNet 与 MNIST 版的差异原始 AlexNet 是 2012 年 ImageNet 比赛的冠军模型输入是 224×224 的 RGB 三通道图像用 5 个卷积层加 3 个全连接层提取特征。但 MNIST 手写数字是 28×28 的灰度单通道图像直接把原始结构搬过来并不合适。最大的问题在于感受野第一层卷积核是 11×11、步长是 4作用在 28×28 图像上特征图会迅速缩小信息损失太严重。所以这套源码里的 AlexNet 是经过适配的版本核心改动集中在两点输入通道从 3 改成 1第一层卷积核从 11×11 改成 5×5步长从 4 改成 1。这样既保留了 AlexNet 的深层结构特性又能适配小尺寸灰度图。这也是答辩时老师最喜欢问的一个点。3.2 models/AlexNet.py 核心代码AlexNet.py里定义的网络结构并不复杂关键在于每一层的参数怎么设、输出尺寸怎么算。下面是核心代码的简化版本import torch.nn as nn class AlexNet(nn.Module): def __init__(self, num_classes10): super(AlexNet, self).__init__() # 输入: (batch, 1, 28, 28) 灰度图 self.features nn.Sequential( # conv1: 1 - 96, 5x5 卷积步长 1padding 2 nn.Conv2d(1, 96, kernel_size5, stride1, padding2), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size3, stride2), # 28 - 13 # conv2: 96 - 256, 5x5 卷积 nn.Conv2d(96, 256, kernel_size5, stride1, padding2), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size3, stride2), # 13 - 6 # conv3: 256 - 384, 3x3 卷积 nn.Conv2d(256, 384, kernel_size3, stride1, padding1), nn.ReLU(inplaceTrue), # conv4: 384 - 384, 3x3 卷积 nn.Conv2d(384, 384, kernel_size3, stride1, padding1), nn.ReLU(inplaceTrue), # conv5: 384 - 256, 3x3 卷积 nn.Conv2d(384, 256, kernel_size3, stride1, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size3, stride2), # 6 - 2 ) # 最后特征图尺寸: 256 * 2 * 2 self.classifier nn.Sequential( nn.Dropout(p0.5), nn.Linear(256 * 2 * 2, 1024), nn.ReLU(inplaceTrue), nn.Dropout(p0.5), nn.Linear(1024, 512), nn.ReLU(inplaceTrue), nn.Linear(512, num_classes), ) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) # 展平 x self.classifier(x) return x这段代码里有三个参数是手动调过的第一个 MaxPool2d 的 stride2 会把 28×28 降到 13×13第二个降到 6×6第三个降到 2×2。全连接层输入维度是 256×2×2也就是 1024 个特征值。如果你照搬原始 AlexNet 的 6×6 输出这里全连接层维度就要改成 256×6×69216显存和计算量会大很多。Dropout 设置在训练时随机丢弃一半神经元能有效降低过拟合风险在测试时 PyTorch 会自动关闭 Dropout不需要手动处理。3.3 输入尺寸计算从卷积到全连接层维度对齐新手最容易算错的就是卷积层输出尺寸。公式是(输入尺寸 - kernel_size 2 × padding) / stride 1。以第一层为例(28 - 5 2×2) / 1 1 28padding 2 让输出尺寸保持 28 不变。池化层则按(输入 - kernel_size) / stride 1计算28×28 经过 3×3 池化步长 2 变成(28-3)/2113第二次从 13 变 6第三次从 6 变 2。这个计算过程建议自己手推一遍因为实验报告里需要写清楚每一层的输出形状。答辩时老师会问「为什么全连接层第一层是 1024 而不是 9216」你只要能说出尺寸推导过程这个项目就立住了。3.4 ResNet.py 与 BasicModel.py作为对比基线这套源码还额外给了 ResNet 和 BasicModel 两个模型这说明作者在设计时就考虑了对照实验。毕设和课程设计里对比实验是很加分的部分用同一个数据集跑三组模型最后对比准确率和训练收敛速度。BasicModel 是一个两层卷积的简单网络作为 baselineResNet 则引入了残差结构理论上收敛更快。我见过很多学生只跑一个模型就交报告答辩时被问「为什么不用 ResNet 试试」直接卡住。这套源码自带三个模型你有充分理由在报告里写横向对比导师会认为你对模型选型有思考而不是只会调库。4. dataset.py 与 config.py数据管线和超参数下放4.1 数据加载与预处理MNIST 的归一化是硬要求train/dataset.py负责把 MNIST 原始数据处理成模型能接受的 Tensor。MNIST 本身是 PIL 图像格式像素值范围 0 到 255如果不做归一化直接喂进网络第一层卷积的输入分布会很差训练初期 loss 可能直接不降。这套代码里用的是transforms.ToTensor()加transforms.Normalize((0.1307,), (0.3081,))这两个值是 MNIST 数据集的全局均值和标准差属于官方推荐配置。标准的 PyTorch 数据加载写法是from torchvision import datasets, transforms from torch.utils.data import DataLoader transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset datasets.MNIST( root./data, trainTrue, downloadTrue, transformtransform ) train_loader DataLoader( datasettrain_dataset, batch_size64, shuffleTrue, num_workers2 )num_workers2表示用两个子进程做数据加载能缓解 GPU 训练时的数据瓶颈。如果在 Windows 上跑num_workers大于 0 可能会报多进程相关的错误改成 0 就行。4.2 超参数集中管理config.py 是调参的后悔药这个项目把超参数全部集中到config.py而不是散落在各个脚本里。这点我特别认可因为调参的时候你不需要去翻代码打开一个文件改了就能跑。常见参数包括参数名推荐值说明batch_size64显存不够就降到 32learning_rate0.001Adam 优化器常用初始值epochs10 ~ 15MNIST 上超过 15 个 epoch 容易过拟合devicecuda / cpu无显卡时强制设成 cpunum_classes100~9 十个数字learning_rate的设定值直接影响收敛行为。0.001 是 Adam 优化器的经验值配合交叉熵损失函数在 MNIST 上效果稳定。如果 loss 震荡不下降优先把学习率降到 0.0001 再试这是最有效的后悔药。4.3 训练测试流程与日志输出main.py会把训练和测试串起来每个 epoch 结束之后在测试集上做一次评估打印当前准确率。我建议训练过程中每 500 个 batch 就打印一次 loss这样能提前发现训练异常。比如 loss 在某个数值附近震荡不降很可能就是学习率偏大或者数据没归一化。for epoch in range(config.epochs): 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() if batch_idx % 100 0: print(fEpoch {epoch}, Batch {batch_idx}, Loss {loss.item():.6f})训练完成后权重会保存成.pth文件test目录里的脚本会自动加载这个权重文件做最终评估。这里要记住保存的应该是model.state_dict()而不是整个 model 对象后者在跨环境加载时容易踩兼容性的坑。5. 训练避坑排查Loss 不降、维度不匹配与设备失控的五个常见坑5.1 坑一全连接层维度不匹配报错现象运行python main.py时抛RuntimeError: size mismatch, m1: [64 x 8192], m2: [1024 x 512]。原因卷积层输出特征图被展平后是 8192 维但全连接层第一层期望输入 1024 维。多半是改了batch_size或者调整了池化层参数导致特征图尺寸变了而全连接层维度没有同步修改。解决在AlexNet.py的forward函数里加一行打印print(x.shape)然后根据实际的展平维度去改classifier的第一层nn.Linear的输入尺寸。我一般会在模型里加一段注释记下特征图尺寸计算过程下次改参数时直接对照。5.2 坑二Loss 降不下去训练像没开始一样现象loss 一直停留在 2.3 左右几乎不变化类似随机猜测的水平。原因最常见的有三种。第一学习率设置过大loss 在震荡第二输入数据没有归一化像素值太大导致梯度爆炸第三模型初始化权重有问题。其中数据归一化是最容易被忽略的。解决先检查transforms.Normalize是否生效再看learning_rate是否在合理区间。如果用的是 SGD学习率通常是 0.01如果用的是 Adam0.001 是起点。两个优化器的默认学习率差一个数量级混用会踩坑。5.3 坑三CPU 与 GPU 设备不匹配现象报错RuntimeError: Expected all tensors to be on the same device模型在 cuda 上数据在 cpu 上或者反过来。原因config.py里设置了device torch.device(cuda)但当前机器没有可用 GPU或者数据没有调用.to(device)。解决把设备选择改成 device torch.device(cuda if torch.cuda.is_available() else cpu)然后确保每个 batch 的data和target都执行了.to(device)。模型也要在训练前model.to(device)。这三处只要有一处漏了就会触发这个报错。5.4 坑四MNIST 数据集下载失败或卡住现象第一次运行脚本时终端停在Downloading ...长时间不动或者直接超时报错。原因torchvision 默认从国外服务器下载 MNIST网络不稳定时就容易断。解决手动从镜像站下载mnist.pkl.gz文件放到data目录下然后把downloadTrue改成downloadFalse。如果已经完全下载失败删掉data目录下的残留文件再重新跑避免文件损坏。5.5 坑五训练准确率高测试准确率却普通现象训练集最后几个 epoch 准确率接近 99%但测试集只有 96% 左右。原因这是典型的过拟合信号模型把训练集的特征背下来了没有泛化到新样本。MNIST 数据量不算小但 15 个 epoch 以上的训练依然可能过拟合尤其在全连接层参数比较多时。解决调整config.py里的epochs或者增大AlexNet.py中 Dropout 的比例从 0.5 调到 0.7。如果做过对比实验也可以用 ResNet 的残差结构来抑制过拟合。报告里写清楚调参前后对比就行。6. 进阶验证把 loss 曲线画出来再对一张真实手写图做预测6.1 可视化脚本的使用方式别把visualize.py当成可有可无的装饰实验报告里最有说服力的就是那张 loss 下降曲线和 accuracy 上升曲线。我一般会在训练完模型之后单独跑一次可视化脚本生成两张图然后直接贴进报告。visualize.py内部会读取训练过程中记录的 loss 列表再用 matplotlib 绘制。import matplotlib.pyplot as plt def plot_loss(train_losses, val_losses): plt.figure(figsize(8, 5)) plt.plot(train_losses, labelTrain Loss) plt.plot(val_losses, labelVal Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.legend() plt.title(Loss Curve on MNIST) plt.savefig(loss_curve.png, dpi150)如果你的训练过程没有手动记录 loss 列表最简单的办法是在main.py的训练循环里定义一个空列表每个 epoch 结束时把平均 lossappend进去。导出 PNG 之后图片质量够清晰实验报告直接能用。6.2 单张图片推理流程训练完模型除了在测试集上算准确率我建议再跑一次单张图片推理把自己手写的数字或测试集里的某张图单独拎出来预测。这段代码可以作为实验报告最后的验证环节from PIL import Image import torchvision.transforms as transforms # 加载模型权重 model AlexNet(num_classes10) model.load_state_dict(torch.load(checkpoint.pth, map_locationcpu)) model.eval() # 预处理单张图片 img Image.open(test_digit.png).convert(L).resize((28, 28)) transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) input_tensor transform(img).unsqueeze(0) # 推理 with torch.no_grad(): output model(input_tensor) pred output.argmax(dim1, keepdimTrue) print(fPredicted digit: {pred.item()})这里的model.eval()务必加上它会把 Dropout 和 BatchNorm 切换到推理模式。如果不加同一个输入每次预测结果可能都不一样这是新手最容易忽略的细节。我记得曾经有个学生拿这套项目跑完之后把测试准确率直接写在报告里就交了结果答辩时被要求现场用鼠标画一个数字让模型识别当场翻车。从那以后我每跑完一个分类模型都会强制走一遍单张推理流程确认不只是一个测试指标是真的能对新样本做预测。这套源码本身已经把所有关键环节都准备好了你按照上面的步骤把训练跑完、把可视化做完再顺手验证一张真实图片整个项目就能形成一个完整闭环希望帮到你。本文还有配套的精品资源点击获取
返回列表