别急着训练 MNIST:先让数据形状、评估与复现实验对齐 | xkmchenmu Blog

别急着训练 MNIST:先让数据形状、评估与复现实验对齐

手写数字分类代码不长,最容易被忽视的却是通道顺序、标签格式、数据泄漏与模型保存。本文按可复现实验闭环组织 TensorFlow/Keras 实现,并给出逐层排错方法。

MNIST 常被当作神经网络的第一段示例,因为数据规模适中、输入固定为灰度小图、输出只有十个数字类别。也正因为它容易跑通,很多隐患会被一个看似不错的准确率掩盖:训练和测试形状不一致、标签与损失函数搭配错误、测试集被反复用于调参、保存的只有权重却忘了模型定义。一个可靠示例应当把这些环节连成闭环,而不是只追求终端打印出某个分数。

从一张样本的契约开始

加载数据后先查看四个数组的形状、数据类型和值域,再随机展示若干图像与标签。原始像素通常以整数保存,进入模型前可转换为浮点并缩放到稳定范围。卷积层还需要显式的通道维:常见布局是批次、高、宽、通道,也有后端采用通道在前的格式。布局必须与模型输入设置一致,不能把旧示例中的变形操作机械搬到当前环境。

import tensorflow as tf; (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data(); x_train = x_train.astype('float32') / 255.0; x_test = x_test.astype('float32') / 255.0; x_train = x_train[..., None]; x_test = x_test[..., None]

这段准备工作的目标不是固定某个 API 写法,而是建立可检查契约:每个样本包含多少数值,通道位于哪里,标签是整数还是独热向量,训练集与测试集是否使用相同预处理。若采用整数标签,可配合稀疏类别交叉熵;若把标签变成十维独热编码,则应选普通类别交叉熵。两条路线都成立,混用才会造成形状错误或错误解释。

训练、验证、测试承担不同责任

训练集用于更新参数,验证集用于选择结构、训练轮数和其他超参数,测试集只在方案固定后评估泛化表现。反复观察测试结果再改模型,实际上已经把测试信息带入决策,使最终数字偏乐观。入门实验可以从训练数据中划出验证部分,或在训练接口中设置验证比例,但要确保划分方式可复现且类别分布没有意外偏斜。

对象 检查内容 常见失误
图像 形状、值域、通道顺序 重复归一化或轴放错
标签 整数或独热编码 与损失函数不匹配
划分 训练、验证、测试隔离 用测试集选择超参数
随机性 种子与运行环境记录 只设置一个库的种子

固定随机种子能减少初始化和抽样差异,却不能保证所有硬件与算子逐位一致。复现实验还要记录依赖环境、数据来源、模型配置、训练参数和评估脚本。若目标只是教学,可说明结果允许小幅波动;若要比较两个方法,则应进行多次运行并报告波动,而不是挑选最好的一次。

用最小卷积网络暴露每次形状变化

别急着训练 MNIST:先让数据形状、评估与复现实验对齐 - MNIST 数据到评估闭环

一个基础网络可以由卷积、池化、展平、全连接和十类 softmax 输出组成。卷积层从局部像素学习边缘与笔画组合,池化降低空间分辨率,展平把特征图交给全连接层。Dropout 可在训练时随机屏蔽部分激活,减少对少数路径的依赖;推理时它会关闭。模型不必堆得很深,先让每层输出形状能够解释,比盲目复制复杂结构更有学习价值。

  • 输入层明确接收单通道的二维图像。
  • 卷积核尺寸与填充方式共同决定空间大小。
  • 池化后检查尺寸是否仍为正且符合预期。
  • 最后一层节点数等于类别数,输出用于解释各类置信度。
  • 损失函数与标签格式保持成对选择。

编译模型时,优化器决定如何根据梯度更新参数,损失函数提供训练目标,准确率只是同步观察的指标。训练日志要同时关注训练损失与验证损失:前者继续下降而后者反弹,可能出现过拟合;两者都停滞,则要检查学习率、输入值域或模型表达能力。仅增加训练轮数无法解决所有问题。

先建立可失败的基线,再增加技巧

第一轮可以使用很少的训练轮数验证数据和梯度链路,确认损失能够下降、输出不是全部相同、保存后可以恢复。随后再尝试更多轮数、正则化或数据增强,并一次只改变少量因素。若一开始同时更换结构、优化器、标签格式和预处理,结果变化就无法归因。

model = tf.keras.Sequential([Input((28,28,1)), Conv2D(32,3,activation='relu'), MaxPooling2D(), Flatten(), Dense(128,activation='relu'), Dropout(0.4), Dense(10,activation='softmax')])

这份代码只是说明层之间的接口,不是唯一答案。不同库版本的推荐保存格式和部分接口可能变化,运行时应以当前安装环境的文档与报错为准。尤其不要继续依赖已经从常用接口中移除的类别预测快捷方法;模型返回概率后,可以沿类别轴取最大值索引得到预测类别,并保留概率用于不确定性分析。

评估要回答“错在哪里”,不只回答“对了多少”

整体准确率把所有错误压成一个数字。更有用的做法是建立混淆矩阵,观察哪些数字容易互相混淆,再抽取高置信度错误和低置信度正确样本。笔画潦草、裁剪偏移、对比度低可能产生不同错误模式。若模型只在训练数据中的常见书写风格上表现好,换到手机拍摄、不同背景或真实表单时未必成立。

  1. 先在完整测试集上一次性计算损失与准确率。
  2. 生成每个类别的召回率,防止某类被整体忽略。
  3. 查看混淆最严重的类别对,寻找可解释的形态差异。
  4. 保存若干错误样本、真实标签、预测类别与置信度。
  5. 用训练流程之外的图片时,严格复用尺寸和归一化步骤。
  6. 把评估代码与模型一同版本化,避免指标口径漂移。

单张预测时,还要增加批次维并保持通道布局,不能把训练数组中的内部形状直接猜出来。可视化输入能快速发现黑白反转、缩放或裁剪错误。若概率极端却预测错误,可能需要检查分布偏移或校准;若所有概率接近平均,模型可能没有学到有效特征,也可能加载了错误权重。

保存的不只是参数文件

只保存权重要求恢复时重新创建完全相同的网络,任何层名、顺序或形状差异都可能导致加载失败。完整交付至少要关联模型结构、权重、预处理说明、类别映射、依赖环境和评估结果。保存后应在新进程中重新加载,对固定样本比较输出,并确认没有依赖训练脚本中的隐式全局变量。

一个合格的 MNIST 项目并不以“训练完成”收尾,而以可重现的数据契约、可解释的模型结构、隔离的评估和可恢复的产物收尾。把这些习惯放进最小实验,之后面对更大数据和更复杂网络时,排错仍能沿同一条链路推进:先看输入,再看形状与损失,接着看训练动态,最后检查真实错误。示例的价值也由此从一段能运行的代码,升级为一套能被验证的实验方法。

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

发表回复

登录后才能评论