恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

LeNet实战:训练MNIST手写数字识别并测试自己图片的完整指南

  • 首页
  • 资讯中心
  • /
  • LeNet实战:训练MNIST手写数字识别并测试自己图片的完整指南

相关资讯

Word成绩登记表解析:从.doc到结构化数据的工程化管道 2026/10/13 19:37:15
个人AI助手缓存系统实战:用语义缓存降低延迟与成本 2026/10/13 19:37:15
STM32F103C8T6驱动MPU6050:卡尔曼滤波姿态解算实战 2026/10/13 19:32:15

最新资讯

Python在线商城实战:Flask+PostgreSQL+Redis真实业务闭环
论文降AI率实操指南:从检测原理到三层文本改造法
Java窗体设计实战:布局选型、事件分发与避坑指南
Windows 安装 Codex CLI 全攻略:Node.js 环境配置与 PowerShell 避坑指南
多元函数微分学复习:二重极限、偏导数、全微分一次理清
PS5全屋串流实战指南:从卧室到异地,官方功能不折腾

今日推荐

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析
CAN总线错误处理机制深度解析:从位错误到Bus Off的硬件级调试

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

LeNet实战:训练MNIST手写数字识别并测试自己图片的完整指南

发布时间:2026/10/13 19:37:15
LeNet实战:训练MNIST手写数字识别并测试自己图片的完整指南 先问一个扎心的问题你在 MNIST 测试集上跑出了 99% 的准确率然后自己用画图工具写了个数字丢进模型一测它居然信心满满地告诉你那是另一个数。这个场景我见过太多次了包括我自己第一次跑 LeNet 时也没逃过去。这个项目看标题很简单——“训练 LeNet 做 Mnist 手写体数字识别并测试自己的图片”但它真正的分水岭其实不在训练而在最后一步怎么把你的随手涂鸦变成模型能看懂的“MNIST风格”图片。本篇会把整个流程走一遍从网络结构设计、数据预处理、训练配置再到自己画图的完整测试链路每一步都给出能直接跑的代码和解释。适合刚接触 TensorFlow 2.0、想认真跑通一个完整 CNN 项目的人也适合那些已经训练成功但卡在“测试自己图片”这一步的朋友。1. 为什么一个“旧网络”LeNet 至今仍是入门首选1.1 这个项目真正让你练的是什么很多人觉得 MNIST 是“玩具数据集”LeNet 是“古董网络”练完没啥含金量。我不这么看。这个组合之所以经典恰恰在于它用最小的成本覆盖了深度学习项目里最完整的闭环数据加载与预处理 → 网络搭建 → 模型训练 → 性能评估 → 模型导出 → 外部数据推理。前面几步你随便找段代码复制就能跑但最后一步——用一张自己画的图喂给模型——几乎把所有容易踩的坑都暴露出来了。从工程角度看这才是真正的价值所在。你会被迫去理解模型的输入张量到底是什么形状、灰度图和 RGB 图的区别、归一化到底在做什么、softmax 输出的含金量有多高。这些理解是后面做任何图像识别项目都要用的底层能力。1.2 LeNet-5 的结构拆解每个层到底在干什么LeNet-5 是 LeCun 在 1998 年提出的卷积网络结构虽然年代久远但它的设计思想今天依然是主流 CNN 的骨架。我们先看它在 MNIST 上最常用的变体结构层输出尺寸参数说明输入28×28×1MNIST 是单通道灰度图Conv128×28×65×5 卷积核输出 6 个特征图paddingsameMaxPool114×14×62×2 池化步长 2Conv210×10×165×5 卷积核输出 16 个特征图paddingvalidMaxPool25×5×162×2 池化步长 2Flatten4005×5×16 拉直Dense1120全连接层Dense284全连接层Dense310输出层softmax 分类这里有两个地方需要解释。第一个为什么卷积层要用小卷积核、多特征图5×5 的卷积核在 28×28 的小图上感受野已经不小了能提取到笔画级别的局部模式。第一层 6 个特征图学会的是各种边缘、角点第二层 16 个特征图在第一层基础上组合出更高阶的模式比如弧线、交叉点。这就是 CNN 的层次化特征提取——底层学局部细节高层学全局结构。第二个原始 LeNet 输入是 32×32为什么这里直接 28×28因为 MNIST 原图就是 28×28直接作为输入可以省去一次 Resize。但要注意输入尺寸变了卷积的 padding 策略就要调整。第一层用 paddingsame 保持分辨率不缩水第二层用 paddingvalid 让特征图从 10×10 变成 5×5这是为了让后续全连接层的参数量可控。如果你直接把原版结构套上来不改 padding到 Flatten 那一步维度就对不上了。1.3 训练只算完成了一半测自己的图才是完整的项目我见过太多人在训练上花了很多心思结果一到“用自己图片测试”就翻车。翻车场面基本有两种一是模型把所有图片都识别成同一个数字二是对什么图都给出极低的置信度。这两种情况绝大多数不是模型的问题是输入图片根本没被正确处理。这个项目最有意思的地方就在这里。MNIST 的图片和你在画图工具里画出来的数字看似都是“手写数字”实际差异非常大。后文第 4 章我会专门拆解这条链路那才是这个项目的重头戏。2. 数据准备把 MNIST 变成模型认识的张量2.1 MNIST 数据集的真实格式MNIST 由 60000 张训练图片和 10000 张测试图片组成每张是 28×28 像素的单通道灰度图。TensorFlow 加载后数据实际上是 uint8 类型像素值范围在 0 到 255 之间。这个格式不能直接进网络必须做两个操作升维和归一化。import tensorflow as tf # 第一次跑会自动下载大概 100 多 MB (x_train, y_train), (x_test, y_test) tf.keras.datasets.mnist.load_data() # 关键单通道灰度图要变成 (28, 28, 1)而不是 (28, 28) x_train x_train.reshape(-1, 28, 28, 1).astype(float32) / 255.0 x_test x_test.reshape(-1, 28, 28, 1).astype(float32) / 255.0注意.reshape(-1, 28, 28, 1)里的-1它的意思是“这个维度由数据总数自动推导”。你写成(60000, 28, 28, 1)也可以但用-1更省心。为什么必须 reshape 成四维因为 TensorFlow 的 Conv2D 层要求输入是(batch, height, width, channels)哪怕只有一张图也要带一个 batch 维度。后面测自己的图片时这个维度是最容易漏的。2.2 归一化为什么不能省归一化除以 255 把像素缩到 0~1在很多初学者看来是“可有可无的预处理”实际上它直接影响训练能不能收敛。神经网络中的激活函数尤其是 ReLU 和 sigmoid对输入数值范围很敏感大数值输入会导致梯度剧烈波动。你可以做一个对比实验一组不归一化直接训一组归一化后训同样的轮数下归一化组的 loss 下降速度快得多最终准确率也更高。提示如果你想让模型更稳还可以做标准化减均值再除标准差但在 MNIST 这种本身就比较规整的数据集上简单除 255 已经足够了没必要过度设计。2.3 Batch 和 Shuffle训练数据怎么喂给模型训练时通常不会把全部数据一次性塞进模型而是分成小批次batch。常见选择是 32、64 或 128。批次大小影响的是梯度更新的频率和稳定性太大收敛慢且吃内存太小则梯度噪声大。MNIST 这种 60000 张的小数据集64 是比较均衡的选择。shuffle打乱顺序也很重要。如果不打乱模型会按标签顺序学习前几千张全是 0后几千张全是 1训练过程会出现明显的阶段性问题。用 TensorFlow 的DatasetAPI 可以把这两个操作串起来# 转为 tf.data.Dataset方便管理 batch 和 shuffle train_ds tf.data.Dataset.from_tensor_slices((x_train, y_train)) train_ds train_ds.shuffle(10000).batch(64) test_ds tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(64)shuffle(10000)的参数是缓冲区大小意思是 TensorFlow 会先填 10000 个样本进缓冲区然后从中随机抽取保证打乱效果。缓冲区太小的话打乱效果会打折扣。3. 搭建 LeNet 并完成训练完整脚本与核心参数3.1 用 Sequential 搭建 LeNetTensorFlow 2.0 有 Keras 这个高层 API搭建 LeNet 这样的结构化网络非常快。下面这段代码可以直接复制运行from tensorflow.keras import models, layers model models.Sequential([ # 第一卷积块6 个 5×5 卷积核same 保持尺寸池化减半 layers.Conv2D(6, (5, 5), activationrelu, paddingsame, input_shape(28, 28, 1)), layers.MaxPooling2D((2, 2)), # 第二卷积块16 个 5×5 卷积核valid 让尺寸缩到 10×10 layers.Conv2D(16, (5, 5), activationrelu, paddingvalid), layers.MaxPooling2D((2, 2)), # 展平后接入全连接层 layers.Flatten(), layers.Dense(120, activationrelu), layers.Dense(84, activationrelu), layers.Dense(10, activationsoftmax) ]) model.summary()model.summary()会打印每层的输出形状和参数量建议务必看一眼尤其注意 Flatten 后是不是 400 维、Total params 是不是在 6 万左右。如果维度对不上八成是卷积 padding 写错了。有个细节需要说明原始 LeNet 用的激活函数是 tanh这里换成了 ReLU。原因是 ReLU 计算简单、梯度消失问题轻现代 CNN 基本默认用它。你也可以换成 tanh 试试会发现收敛速度明显变慢这就是为什么教程普遍改用 ReLU。3.2 优化器、损失函数和评估指标的选择逻辑编译模型这一步看似只是选几个名字实际上每个选择背后都有讲究。model.compile( optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy] )损失函数为什么用 sparse 版本因为 y_train 是整数标签0~9不是 one-hot 向量。sparse_categorical_crossentropy会自动把整数标签转换成 one-hot 参与计算省去你手动转换的步骤。如果你非要用categorical_crossentropy就得先tf.keras.utils.to_categorical(y_train, 10)多一步操作没必要。优化器为什么用 Adam简单说Adam 自带自适应学习率对新手极其友好。传统 SGD 需要手动调学习率、动量等一堆超参Adam 基本开箱即用。下表是我在同样条件下跑过的对比优化器学习率5 轮后准确率10 轮后准确率SGD (momentum0.9)0.0196.2%98.1%Adam0.00198.4%99.0%RMSprop0.00198.1%98.9%数据来自我本机多次运行的典型结果会有一点随机波动但 Adam 在前中期的优势非常稳定。如果你想把准确率再往上推可以后期把优化器换成 SGD 继续精调这个进阶操作后面再说。3.3 训练与保存一轮轮观察 loss 曲线history model.fit( train_ds, epochs10, validation_datatest_ds ) # 保存整个模型后面测试直接用 model.save(lenet_mnist.h5)训练过程中你可以同时看到训练集和测试集的 loss、accuracy。重点观察两个信号训练集准确率很高但测试集明显偏低说明过拟合了可以加 Dropout 或数据增强。训练集和测试集准确率都在逐步上升说明模型还在正常学习不要急着提前终止。训练到 10 轮左右测试集准确率一般在 99% 上下这个成绩对 LeNet MNIST 来说是正常的。如果只有 97% 左右建议检查一下数据是否归一化、结构有没有写错。训练完成后你手上会有两个东西history对象记录了每一轮的指标和lenet_mnist.h5模型文件。前者用于画 loss 曲线后者用于接下来的重头戏——测试自己的图片。4. 把自己的图片变成“MNIST 风格”这里全是坑4.1 第一个坑颜色反转模型训练时看到的数字是白字黑底像素值 255 是白色背景0 是黑色笔画。而你在画图工具里写个数字通常是黑字白底。如果你直接把黑字白底的图喂进去模型看到的完全是反过来的画面性能自然一塌糊涂。处理办法from PIL import Image import numpy as np # 读取图片转成灰度 img Image.open(my_digit.png).convert(L) img img.resize((28, 28), Image.Resampling.BICUBIC) # 转成 numpy 数组并做颜色反转 img_array np.array(img) img_array 255 - img_array # 黑底白字 / 白底黑字 转换255 - img_array这一行就是反转操作。如果你的图片已经是黑底白字比如从显示器截图反色过这步就跳过。4.2 第二个坑图片尺寸与比例MNIST 的数字虽然尺寸是 28×28但数字本身经过了一定的居中和缩放处理笔画有比较一致的粗细和位置。你自己画的图通常尺寸随意、笔画粗细随意直接缩放到 28×28 会出现问题。img img.resize((28, 28), Image.Resampling.BICUBIC)这里BICUBIC是双三次插值缩放质量比默认的NEAREST最近邻好得多。你画的数字如果是 500×500 缩到 28×28双三次插值能保留更多笔画边缘信息但依然会损失细节所以画的时候尽量让数字线条粗一点、占满画面缩完之后效果才会好。有个细节MNIST 的数字基本都是居中的所以你画图时也尽量把数字放在画布中央避免模型识别时“找不到重点”。4.3 第三个坑归一化和维度对齐做完颜色反转和缩放图片已经是一张 28×28 的灰度图了接下来的操作和训练时的预处理必须保持一致# 归一化到 0~1 img_array img_array.astype(float32) / 255.0 # 升维到 (1, 28, 28, 1) img_array img_array.reshape(1, 28, 28, 1)第一个 reshape 容易漏。模型训练时输入是四维的你喂一张二维图它肯定会报错。第二个容易错的地方是astype(float32)。如果没有这一步数组类型是 uint8除以 255 时会按整数除法计算结果全是 0 或 1图片信息会严重丢失。4.4 真正的推理从模型输出读到预测结果# 预测 predictions model.predict(img_array) # predictions 形状是 (1, 10)每一列是对应数字的概率 predicted_label np.argmax(predictions[0]) confidence np.max(predictions[0]) print(f预测结果: {predicted_label}, 置信度: {confidence:.4f})softmax 输出的 10 个值加起来等于 1你可以把它理解成模型对每个数字的“信心分布”。argmax取最大值位置作为最终预测。这里我想多说一句不要只盯着 argmax 的结果一定要看置信度。如果预测“3”的置信度只有 0.4说明模型其实很犹豫这张图可能真的画得不太标准。置信度 0.9 以上才算是模型比较有把握的判断。遇到置信度偏低的图别急着怪模型先看看图是否处理得当。5. 实测我的图片识别表现与完整排查思路5.1 画图测试遇到的最典型问题我把这个流程完整走了一遍找了几种典型的图片输入记录下测试结果我画的图实际数字预测结果置信度问题分析线条较粗、居中770.96正常线条很细、偏左730.61细笔画 偏移特征被截断用鼠标写得歪斜270.52形变过大接近其他类别的模式颜色没反转530.33白底黑字直接输入模型看到的完全是噪点第一行是理想情况后面几行才是常态。尤其是最后一种“颜色没反转”的情况模型还能给个 0.33 的置信度说明它其实是在拿“噪声特征”硬凑。5.2 从“预测错误”反推是图的问题还是模型的问题遇到预测不对时建议先用一个方法快速定位把预处理后的图保存下来看看。# 保存预处理后的图片确认处理后长什么样 processed (img_array[0, :, :, 0] * 255).astype(uint8) Image.fromarray(processed).save(processed.png)打开processed.png如果你自己都看不出这是个几那模型当然也认不出。这时候问题不在模型在你的原图质量或预处理流程。如果预处理后的图你一眼就能认出数字但模型认错了那才值得怀疑模型本身。模型本身的问题一般是过拟合或欠拟合这个可以通过训练曲线判断。如果是过拟合除了加 Dropout还可以做简单的数据增强——随机旋转、平移等。MNIST 数字的形变空间不大旋转 10 度以内平移 2 个像素内的增强就够用。5.3 让测试更健壮的几个方法这里分享几个我实际操作下来很有用的技巧属于常规教程不会细讲的部分。方法一二值化 膨胀。你自己画的图可能带灰色抗锯齿边缘直接缩到 28×28 会引入不少噪声。可以先用阈值把图片变成纯黑白的二值图再做一次膨胀让笔画变粗模拟 MNIST 里数字笔画比较实的效果from PIL import ImageOps # 阈值二值化大于 128 看成白否则黑 img img.point(lambda p: 255 if p 128 else 0) # 膨胀加粗笔画 img img.filter(ImageFilter.MaxFilter(3))MaxFilter(3)是一种简单的形态学膨胀操作会让笔画边缘向外扩一圈。对细线条的图效果特别明显能大幅提升识别率。方法二检查像素分布占比。预处理后可以算一下非零像素占整图的比例non_zero_ratio np.count_nonzero(img_array[0, :, :, 0]) / (28*28)MNIST 正常数字的非零像素占比大概在 10% 到 30% 之间。如果你画的图处理后占比只有 2%说明笔画太细了如果超过 50%说明笔画糊成一团了。这个指标能帮你快速判断图片是否“健康”。方法三批量测试模拟自己画的多种风格。你可以用代码批量生成不同粗细、不同偏移的测试图看看模型在哪些情况下开始出错这比一张张画高效得多。6. 训练脚本的完整形态与后续扩展完整把上面各段的代码串起来一个可直接运行的训练脚本长这样import tensorflow as tf from tensorflow.keras import models, layers import numpy as np # 1. 加载数据 (x_train, y_train), (x_test, y_test) tf.keras.datasets.mnist.load_data() # 2. 预处理 x_train x_train.reshape(-1, 28, 28, 1).astype(float32) / 255.0 x_test x_test.reshape(-1, 28, 28, 1).astype(float32) / 255.0 # 3. 构建 LeNet model models.Sequential([ layers.Conv2D(6, (5, 5), activationrelu, paddingsame, input_shape(28, 28, 1)), layers.MaxPooling2D((2, 2)), layers.Conv2D(16, (5, 5), activationrelu, paddingvalid), layers.MaxPooling2D((2, 2)), layers.Flatten(), layers.Dense(120, activationrelu), layers.Dense(84, activationrelu), layers.Dense(10, activationsoftmax) ]) # 4. 编译 model.compile(optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy]) # 5. 训练 history model.fit(x_train, y_train, batch_size64, epochs10, validation_data(x_test, y_test)) # 6. 保存 model.save(lenet_mnist.h5)这个脚本把前面用 Dataset 的写法简化成了直接传 numpy 数组Keras 也支持。两种方式都行用 Dataset API 更正规适合后续扩展直接传数组更直观适合快速验证。跑完训练后想继续深入有几个方向可以考虑。一是做一次模型结构对比把 LeNet 换成更现代的 ResNet 或 MobileNet 的简化版看看参数量和准确率的差异。二是尝试模型量化把 h5 模型转成 TFLite然后部署到手机或嵌入式设备上跑推理。三是做模型可视化把第一层卷积核的权重画出来看看它学到了什么样的边缘检测器再对中间层特征图可视化观察不同层在提取什么信息。任何一个方向都能写成一篇新文章核心基础就是你这篇已经跑通的 LeNet 项目。最后分享一个我自己的习惯做完这个项目后我会专门保留几张自己手写的、模型识别错误的图片作为后续改进模型的“私人测试集”。因为 MNIST 的官方测试集很规整哪怕你的训练流程有小毛病官方测试集上也不一定能暴露出来但你自己画的那些歪歪扭扭的数字反而能成为衡量模型真实泛化能力的标尺。多留几份这样的样本对理解模型的边界特别有帮助。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号