Pillow展示MNIST数据方法及LeNet-5手写数字识别精度问题排查
嘿,这个问题我之前帮不少开发者解决过——你的LeNet-5在MNIST上能跑98%准确率,但碰自己拍的手写数字就拉胯,核心原因就是训练数据和测试数据的分布完全不一样(行业里叫“分布偏移”),咱们一步步来排查和解决:
1. 先检查灰度图的像素模式是否匹配MNIST
MNIST的图片是黑底白字:背景像素值是0(纯黑),数字是255(纯白)。但你用手机拍的照片大概率是白底黑字,这相当于把输入完全反转了,模型肯定认不出来!
你可以在灰度转换后加一步像素反转:
from PIL import Image, ImageOps # 你的原有代码 im = Image.open(path) im_resized = im.resize((28, 28)) # 注意LeNet-5输入是28x28,这里要明确size gr = ImageOps.grayscale(im_resized) # 新增:反转像素,匹配MNIST的黑底白字 gr = ImageOps.invert(gr)
另外,记得和训练时的归一化逻辑保持一致——比如MNIST训练时通常会把像素值缩到[0,1],那你也要做同样的处理:
import numpy as np img_array = np.array(gr) / 255.0 # 如果训练时用了减均值/除标准差,也要对应上
2. 预处理流程要和训练时完全对齐
除了像素反转,这些细节也很关键:
- 数字要居中:MNIST的数字都是居中的,你拍的照片可能数字偏左/偏右,试试先裁剪出数字的区域,再resize到28x28:
可以用PIL的getbbox()方法找到数字的边界框,然后裁剪:bbox = gr.getbbox() if bbox: gr_cropped = gr.crop(bbox) gr_resized = gr_cropped.resize((28, 28)) - 降噪处理:拍摄的照片可能有噪点,用高斯模糊轻量处理下:
from PIL import ImageFilter gr = gr.filter(ImageFilter.GaussianBlur(radius=0.5)) - 二值化阈值:把灰度图转成纯黑纯白的二值图,过滤掉浅灰色的背景干扰:
threshold = 127 # 可以根据你的照片亮度调整,比如调到150或100 img_binary = gr.point(lambda x: 0 if x < threshold else 255)
3. 给模型做少量微调,适配你的数据
LeNet-5的容量很小,对陌生数据的泛化能力弱。你可以用自己拍的10-20张手写数字图片(每个数字拍几张)做微调:
- 冻结模型的前几层(卷积层),只训练最后的全连接层
- 用很小的学习率(比如1e-4)训练几个epoch,让模型快速适应你的手写风格
4. 拍摄时的小技巧
- 用纯白背景,避免复杂纹理
- 光线均匀,不要有阴影
- 数字尽量写得和MNIST里的风格接近(粗细、大小)
如果还有后续问题,比如微调的具体代码、阈值怎么调更合适,随时补充细节就行~
内容的提问来源于stack exchange,提问作者Abhisek




