把一份 MNIST 旧代码变成可复现实验:Gluon 训练循环逐项验收 | xkmchenmu Blog

把一份 MNIST 旧代码变成可复现实验:Gluon 训练循环逐项验收

手写数字识别的价值不在于再得到一个准确率数字,而在于用最小数据集检查形状、设备、损失、更新与评估是否连贯。本文以 MXNet Gluon 为历史实验环境,重建一条可复算、可保存、可迁移的训练流程。

MNIST 常被当成深度学习框架的“你好世界”,但程序能运行不代表实验可信。数据是否按 NCHW 排列,输出层是否给损失函数正确的 logits,CPU 与 GPU 上的张量是否处于同一上下文,训练准确率是否混入测试信息,这些细节都会改变结果。与其照抄一长段旧代码,更好的做法是把实验拆成若干验收点,每通过一个点再进入下一步。

冻结环境后再讨论模型

历史框架、Python、NumPy 与 CUDA 之间常存在版本耦合。复现时使用独立虚拟环境或容器,记录解释器版本、MXNet 包版本、计算设备和随机种子,不要为一份教程直接降级日常环境。安装完成后的第一项测试不是训练,而是创建一个 CPU 张量、执行简单运算并打印框架版本;需要 GPU 时,再单独验证设备可见性与基础算子。

  • 保存依赖清单与安装日志,确保另一台机器能够重建环境。
  • 数据文件记录大小与哈希,避免同名数据包内容不同。
  • 固定拆分规则,训练集用于拟合,测试集只承担最终评价。
  • 给随机种子加注释,说明它控制了哪些随机过程。

先看四张图,再相信数据读取

原始图像应为 28×28 的灰度矩阵,标签是零到九的整数。随机显示若干样本,并同时打印标签、像素最小值、最大值与形状,可以快速发现通道顺序、归一化和标签错位。卷积层通常接收 (批次, 通道, 高, 宽),因此单张图需要增加通道维;像素转为 float32 后除以 255,使输入落在零到一范围。所有转换只改表示,不能改变样本对应关系。

把一份 MNIST 旧代码变成可复现实验:Gluon 训练循环逐项验收 - 训练管线验收板
检查对象 预期状态 失败时的典型现象
图像批次 N×1×28×28 卷积报告维度或通道错误
类别标签 长度为N的整数 损失函数索引异常
像素范围 float32且约在0到1 梯度尺度过大、收敛缓慢
设备上下文 输入、标签和参数一致 CPU与GPU张量无法运算
输出张量 N×10未归一化分数 类别数不符或损失语义错误

网络结构应从输出契约倒推

这个任务需要为十个数字产生十个类别分数。可以用两层卷积提取局部笔画,池化缩小空间尺寸,再通过全连接层得到分类输出。中间层使用 ReLU 等激活,最后一层通常不额外加 softmax 或 softrelu,因为 SoftmaxCrossEntropyLoss 期待未归一化 logits,并在内部完成稳定计算。把非负激活放到最后一层会限制分数表达,也可能与损失实现不匹配。

  1. 创建 Sequential 容器并依次加入卷积、激活、池化和全连接层。
  2. 选择初始化器,将所有参数明确放到目标 context。
  3. 生成一批与真实输入同形状的随机张量,完成首次前向传播。
  4. 核对每层输出形状和总参数量,再创建 Trainer 与损失对象。
  5. 只用一个小批次执行前传、反传和更新,确认参数确实发生变化。

Gluon 的五个角色不能互相替代

Dataset 保存样本与标签关系,DataLoader 负责分批和训练时打乱;Block 或 Sequential 定义前向计算;autograd.record 记录需要求导的运算;Trainer 根据梯度更新参数;context 决定数据和参数位于 CPU 还是某块 GPU。把这五个角色分开理解,遇到错误时就能判断是数据、计算图、优化器还是设备迁移的问题,而不是反复调整学习率碰运气。

训练循环的最小闭环是:清空或覆盖旧梯度,记录前向与损失,反向传播,再按实际批大小更新参数。顺序错误可能仍然运行,却得不到预期学习。

遍历批次时,先把图像与标签移动到参数所在 context。在记录块中计算 logits 和损失,退出后调用 backward,再让 Trainer 以真实批大小执行一步。最后一个批次可能不足预设大小,若仍固定传入 32,梯度归一化会发生偏差。累积轮次损失时也要按样本数加权,不能简单平均每个大小不同的批次均值。

准确率函数也需要单元测试

分类准确率通常对 logits 沿类别维取 argmax,再与整数标签逐项比较。可手工构造三条预测,让其中两条正确,确认函数返回三分之二。评估测试集时不记录梯度,不打乱也不更新参数,并使用与训练相同的像素缩放。若训练模式中含 Dropout 或批归一化,还要明确切换到评估语义;不同框架的接口细节应按对应版本确认。

历史样例曾记录一轮训练后约 95.42% 的训练准确率和约 97.10% 的测试准确率,但这个数字只能视为特定代码、随机状态与数据文件下的观测。测试准确率高于训练准确率可能来自训练阶段的随机性、统计方式或抽样波动,不应据此承诺复现结果。更有价值的是保存每轮损失、训练指标、测试指标与耗时,并检查曲线是否符合预期。

模型文件必须带着预处理一起保存

参数文件本身无法说明输入需要什么形状、像素如何缩放、类别如何映射。保存模型时,同时写出网络结构、框架版本、预处理规则、随机种子、训练数据标识和最佳检查点选择标准。加载后先在 CPU 上对一组固定样本推理,比较类别与 logits 是否接近保存前结果,再测试目标设备。这样才能区分“文件成功读取”和“模型行为成功恢复”。

还可以把若干代表样本及期望输出作为迁移夹具。当需要改用另一套框架时,先复现预处理,再对齐中间张量和最终 logits,而不是只比较总体准确率。逐层差异能更快定位权重排列、卷积 padding 或数据布局问题。

让 MNIST 承担管线验收,而不是成绩展示

完成实验后,应主动加入破坏性测试:交换两个标签确认损失恶化,关闭归一化观察训练变化,尝试空批次或错误形状确保程序给出清楚异常,保存后重载验证输出一致。MNIST 过于简单,不能证明模型能处理现实图像,却非常适合证明训练管线的基本语义。只要环境可重建、每一步形状可解释、指标可复算、参数可恢复,这份旧框架练习就完成了比“跑出一个数字”更有长期价值的任务。

(0)
打赏 支付宝扫一扫 支付宝扫一扫

发表回复

登录后才能评论