简介:基于MNIST数据库的手写体数字识别CNN设计,matlab实现.zip是一套完整的MATLAB深度学习项目,面向熟悉MATLAB但希望上手计算机视觉的课程设计、毕业设计学生。项目针对MNIST经典手写数字数据集(6万训练样本、1万测试样本,28×28灰度图),涵盖数据预处理、模型构建、训练与评估全流程,包括ReLU、Pool、Softmax、Conv及DeltaMiniBatch等核心函数,配合MNISTData.mat数据集与README说明,可帮助理解CNN各层作用及反向传播实现。压缩包共17个文件,以m源码为主,辅以jpg/png结果截图和md说明文档,整体大小约29.22MB,目录结构清晰。已有84人学习下载。它既可作为课程作业参考,也可作为复现经典手写数字识别任务的实践起点,读者能从中获得可直接运行的MATLAB代码、网络结构分解笔记以及训练过程可视化素材;从数据归一化、卷积层参数配置到损失曲线输出,均有对应代码和截图可供对照。
1. 从MINST这个拼写说起:手写体数字识别CNN到底解决什么问题
搜“MINST”的人,十有八九是在找MNIST——深度学习领域最经典的手写体数字数据库,60000张28×28灰度图,标注0到9。标题里的MINST不是别的项目,就是MNIST的常见笔误。而CNN卷积神经网络做手写体数字识别,是所有涉足深度学习的人绕不过去的第一道坎:任务简单到只用几十行代码就能跑通,又完整覆盖数据读取、网络结构、训练验证和预测这条主干链路。这个zip包的价值不在算法有多新,而在于它把这条链路在MATLAB里落了地。它适合两类人:拿数字图像处理课设或毕设交差的学生,以及不打算切进Python生态、想用MATLAB搞懂深度学习CNN的工程师。说白了,你要的是一台能跑出准确率数字的机器,不是一张网络结构PPT。
先说清楚:MNIST这个拼写很关键,整个方案都围绕28×28的黑白数字展开,下面这份拆解就是你照着复现时最容易出问题的那几条线。
2. 把MNIST数据喂给MATLAB:读取、归一化与标签处理的四个关键细节
很多zip包的第一步都是把MNIST的IDX文件读进MATLAB,这一读就是第一道坑。MNIST原始文件不是mat也不是csv,而是IDX二进制格式,头部有32位magic number、样本数、行数、列数,然后才是像素数据。更要命的是它一律用大端字节序存储,而MATLAB的fread默认按本机小端解析。你如果直接把官方文件拖进MATLAB用load处理,得到的要么是一个错得离谱的矩阵,要么是能读到但与图像方向不一致的排列。下面这段读取函数是我一直沿用的版本,能同时处理图片和标签,存成mat后后续所有训练代码都从load开始。
2.1 从IDX二进制到mat文件:读取脚本与字节序处理
function images = loadMNISTImages(filename) % 读取MNIST的IDX图片文件,返回 [28 28 1 N] 的 double 数组,数值范围 [0,1] fid = fopen(filename, 'rb', 'ieee-be'); if fid == -1 error('打不开文件,请确认路径和文件名'); end magic = fread(fid, 1, 'uint32'); numImgs = fread(fid, 1, 'uint32'); rows = fread(fid, 1, 'uint32'); cols = fread(fid, 1, 'uint32'); raw = fread(fid, rows * cols * numImgs, 'uint8=>uint8'); fclose(fid); images = reshape(raw, cols, rows, numImgs); images = permute(images, [2 1 3]); % 转置每一张图 images = double(images) / 255; images = reshape(images, 28, 28, 1, []); end这段代码有三个必须写对的地方。第一个是fopen的第三个参数'ieee-be',它告诉MATLAB按大端读,漏掉它,头部四个字段都会解析成错误数值。第二个是reshape与permute的配合:IDX按行优先存像素,MATLAB按列优先做reshape,直接写成reshape(raw, rows, cols, numImgs)得到的是转置后的图,后续训练虽然能跑,但可视化时所有数字都是横着的。第三个是最后reshape成28×28×1×N,多出的那个1是通道维度,因为MATLAB的CNN输入层要求H×W×C×N,灰度图通道数就是1,漏掉这个维度trainNetwork会在输入层直接报维度不匹配。
对应的标签读取更简单,头部结构完全一致,只是后面没有行列信息:
function labels = loadMNISTLabels(filename) fid = fopen(filename, 'rb', 'ieee-be'); magic = fread(fid, 1, 'uint32'); numItems = fread(fid, 1, 'uint32'); labels = fread(fid, numItems, 'uint8'); fclose(fid); end读完之后先别急着训练,用imshow(XTrain(:, :, 1, 1))看一眼第一张图是不是一个端正的5,确认字节序和方向都对,再进入下一节。这一步花十秒钟,能省掉后面所有“准确率怎么都上不去”的排查时间。
2.2 归一化为什么必须做:uint8进卷积层就是翻车现场
MNIST像素是0到255的uint8,直接拿它训练,卷积层的加权求和数值会变得很大,配合默认的学习率,梯度要么爆炸要么让损失在训练初期反复震荡。更直接的问题是MATLAB不少版本对uint8输入会先做一次隐式转换,一旦转换路径和你的预处理步骤冲突,同一个脚本在R2019a和R2023b上的收敛曲线能差出十个百分点。我一般会在读取函数里就直接double(images) / 255,让数据落在[0,1],而不是等trainNetwork之前再处理。注意这里不要用mat2gray,它会把每张图各自拉伸到全范围,等于给每张手写数字做了不同的对比度缩放,实话说对MNIST不是不能用,但会让黑底白字样本的边缘范围不一致,没必要引入这个变量。
还有一层是imageInputLayer的Normalization参数。MATLAB的imageInputLayer默认会对输入做zerocenter归一化,也就是减训练集均值。如果你已经在脚本里手动把像素除以255,再叠加这个默认行为,输入就变成负值分布,不是错,但和你在PyTorch里看到的预处理习惯对不上,调参时会莫名困惑。最省心的做法是在imageInputLayer里显式声明'Normalization', 'none',把预处理完全掌握在自己手里,后面网络定义里我会给出完整写法。
2.3 标签类别顺序与训练测试集划分:categorical的隐藏细节
读取到的标签是0到9的uint8数字,但trainNetwork在分类任务里要求标签是categorical类型,或者至少是能映射到类别标签的格式。直接把数值数组丢进去不是百分百报错,但测试时classify返回的也是categorical,和数值数组做比较需要转换,容易出类型不匹配的幺蛾子。我习惯写成:
YTrain = categorical(YTrain, 0:9); YTest = categorical(YTest, 0:9);第二个参数0:9是显式的类别集合,这个细节很容易被忽略。如果不指定,categorical会按你数据中实际出现的值自动建立类别,测试集恰好缺了某个数字时,训练和测试两边的categorical类别集合不一致,后面的confusionchart会产生多余的列或行,看着就烦。显式指定类别还有额外的好处:模型输出层softmax节点顺序和数据类别顺序完全对齐,出报告的时候每个数字对应哪一列不会错位。
训练测试划分方面,MNIST本身就是60000张训练加10000张测试,市面上绝大多数包都直接用这个官方划分,不需要自己再按比例切。真正要小心的是shuffle策略别默认关掉,训练选项里设置'Shuffle', 'every-epoch',避免每个epoch都按同一个顺序喂数据,这个细节放到第三章的训练选项里展开。
2.4 存成mat文件:-v7.3到底要不要加
这段有点玄学,但值得说。IDX文件每次读取都要做字节序解析,训练代码每次启动都重新解析一遍,浪费的时间在小数据集上不明显,但反复调试网络结构时就显得很烦。我更愿意在拿到的第一天就把四个文件读一遍,存成一个总的mnist_data.mat:
save('mnist_data.mat', 'XTrain', 'YTrain', 'XTest', 'YTest', '-v7.3');参数-v7.3是否要加取决于你的数据集大小。XTrain归一化之后是double,占60000×28×28×8字节约376MB,加上四份数据不会超过2GB,理论上不加-v7.3也能存。但MATLAB在存超过2GB的mat文件时会自动警告,而且老版本默认存的v7格式遇到大数组容易卡住,我习惯一开始就加上,代价是mat文件体积略大、load稍慢,换来的是稳定。如果你的zip包自带的是别人转换好的mat,记得检查格式,版本太老的mat在R2023b上打开会提示重新保存,不要在那个旧文件上直接改网络参数。
3. 用MATLAB搭建手写数字CNN:卷积层、全连接层和训练选项怎么定才不翻车
网络结构是这份代码最值钱的部分。MNIST不是ImageNet,不需要ResNet级别的深度,但也不是随便两层卷积就能稳定到99%,参数互相牵扯,网上很多半年前能跑通的脚本在换版本后准确率掉到92%的比比皆是。下面给出一个我反复使用的基础结构,并在每个关键层解释为什么这样定。
3.1 输入层和卷积层参数:28×28×1、3×3卷积核与Padding策略
layers = [ imageInputLayer([28 28 1], 'Name', 'input', 'Normalization', 'none') convolution2dLayer(3, 32, 'Padding', 'same', 'Name', 'conv1') batchNormalizationLayer('Name', 'bn1') reluLayer('Name', 'relu1') maxPooling2dLayer(2, 'Stride', 2, 'Name', 'pool1') convolution2dLayer(3, 64, 'Padding', 'same', 'Name', 'conv2') batchNormalizationLayer('Name', 'bn2') reluLayer('Name', 'relu2') maxPooling2dLayer(2, 'Stride', 2, 'Name', 'pool2') fullyConnectedLayer(128, 'Name', 'fc1') reluLayer('Name', 'relu3') dropoutLayer(0.5, 'Name', 'dropout') fullyConnectedLayer(10, 'Name', 'fc2') softmaxLayer('Name', 'softmax') classificationLayer('Name', 'output') ];输入层尺寸必须和前面读数据时reshape出来的维度对上,即28×28×1,灰度图通道数是1,如果误写成3,trainNetwork报错信息会显示输入层期望3通道而数据只有1通道。Normalization设为none是把预处理权收回到自己手里的关键一步,前面2.2讲过原因。第一层卷积用3×3核、32个滤波器、padding为same,意思是输出尺寸仍然28×28,只改变通道数。为什么选3×3而不是5×5,两层3×3卷积的感受野已经达到5×5,但参数量是3×3×2对5×5×1的对比,这个问题面试被问到的概率极高,自己写代码时也要养成这个意识。32个滤波器对MNIST来说够用,起步不要上128,浪费显存且容易过拟合。
卷积层后面紧跟batchNormalizationLayer再进ReLU,这是目前收敛最稳的排列顺序,MATLAB中batchNormalizationLayer默认在训练时统计当前batch的均值和方差,在预测时使用历史滑动均值,你不需要手动处理训练/测试两种模式。如果你在旧版本里见过'BatchNorm'层写法,那是Deep Learning Toolbox的旧路径,现在统一用batchNormalizationLayer,注意别混用。
3.2 池化、全连接与Dropout:容量多大的网络才够用
池化层用最大池化,2×2窗口、步长2,每次把特征图长宽减半。28×28经过第一个池化变成14×14,经过第二个变成7×7,这是MNIST非常经典的一条尺寸链路。不要在这类小数据集上换平均池化,手写数字的边缘信息对识别更关键,最大池化保留的是局部最强的响应。池化层之后接全连接层,中间这个128是需要讨论的。很多人一上来就堆512甚至1024,MNIST总共10类,中间层太大导致最后一层全连接要学大量低信息量特征,短期看训练准确率很高,测试集却容易卡在98.5%上不去,本质是过拟合。
Dropout放在第二个全连接层前面而不是后面,也是常见误区。dropoutLayer(0.5)表示训练时随机丢弃一半神经元,验证和测试时自动关闭,它起作用的阶段前一个全连接层仍在训练,如果放在fc2之后、softmax之前,它对输出层做随机抑制,效果远没有放在fc1之后明显。想往99%走,可以把这个层扩成两个全连接,即fc1做128、fc1b做64,再各自跟ReLU和Dropout,但对课设和入门项目,单层128配合0.5的dropout已经足够。参数太多时优先调dropout比例和fc宽度,不要先从卷积层下手,卷积层特征提取能力不足时加宽度也白搭。
3.3 trainingOptions参数表:学习率、MiniBatchSize与验证频率
下面这段训练选项参数,我标了每个值的调整方向,直接抄是一个能用的起点,改的时候知道你是在改什么:
options = trainingOptions('sgdm', ... 'InitialLearnRate', 0.01, ... % 大了发散,小了收敛慢 'MiniBatchSize', 128, ... % GPU显存不足就降到64或32 'MaxEpochs', 10, ... % MNIST不需要跑几十轮 'Shuffle', 'every-epoch', ... % 每个epoch打乱训练数据 'ValidationData', {XTest, YTest}, ... % 用官方测试集做验证 'ValidationFrequency', 30, ... % 每30个iteration验证一次 'Plots', 'training-progress', ... % 边训练边看曲线 'Verbose', true);solver选择sgdm还是adam,对MNIST这种小数据差别不大,我默认用sgdm配学习率0.01,理由是收敛节奏直观,训练进度图上的曲线下降形态更容易判断学习率是否合适。如果用adam,初始学习率0.001更稳,但收敛曲线不如sgdm能暴露出学习率问题。MiniBatchSize如果调小,学习率通常也要等比下调,比如128改成32,我会把学习率从0.01降到0.005左右,否则每个batch的梯度噪声变大,训练曲线会非常毛糙。ValidationFrequency的单位是iteration不是epoch,每个epoch的iteration数由样本数除以batch大小决定,60000除以128约469,所以30意味着每个epoch里能观察到十几个验证点,设置太低训练变慢,太高过拟合发现得晚。ValidationData用官方测试集很自然,但如果你的报告要求测试集只能评估一次,也可以从训练集划出5000张做validation,训练完再用测试集算最终准确率,属于实验规范问题,看你的要求来。
4. 训练到预测的全流程:损失曲线判断、准确率诊断与泛化测试
把前面两章拼起来,就是一整套可运行流程。下面先给你一份能直接跑的脚本骨架,再教你从训练输出判断网络到底行不行,因为很多zip包里虽然出结果,但没人告诉你“看起来不错的结果其实已经过拟合了”。
4.1 从load数据到trainNetwork:一页纸跑通的脚本骨架
load('mnist_data.mat'); % 2.4节存的mat XTrain = reshape(XTrain, 28, 28, 1, []); % 确保维度是HWCN XTest = reshape(XTest, 28, 28, 1, []); YTrain = categorical(YTrain, 0:9); YTest = categorical(YTest, 0:9); layers = [...]; % 第3.1节定义 options = trainingOptions('sgdm', ...); % 第3.3节定义 net = trainNetwork(XTrain, YTrain, layers, options);这里reshape一个已达标的数组不会报错,但好习惯是在脚本入口处统一检查一次尺寸。检查方法很简单:
disp(size(XTrain)); % 期望 [28 28 1 60000]如果你拿到手的zip包里面数据已经是28×28×60000,没有通道维度,reshape的作用就是把它变成网络要求的28×28×1×60000。如果数据本身是10000×784这样的二维表,也就是一行一个样本,你要先reshape成[28 28 1 10000]再转置,顺序反了之后所有数字都会倒着训练。用一句话记住MATLAB的CNN数据约定:最后一位是样本数,倒数第二位是通道数,前两位是宽高。
4.2 读训练进度图:验证准确率和训练准确率的gap说明什么
训练进度图是trainingOptions里'Plots','training-progress'打开的那个窗口,左上角有两条准确率曲线。关注点不是最终到没到99%,而是训练开始时两条曲线之间的gap。训练几十个iteration后训练准确率已经到95%,验证准确率还在88%附近,且gap持续扩大,这是过拟合正在发生的信号,不是训练还没收敛。反过来训练和验证都低,比如都在85%附近缓慢爬,那是学习率偏低或者网络容量不够,优先把InitialLearnRate调大一档看反应,或者增加第一个卷积层的滤波器数量。
损失曲线的形状也有讲究。训练损失从1.2左右往下掉到0.1是正常节奏,如果第一个iteration就掉到0.01,大概率是学习率高到让梯度异常,后面会看到准确率停在某个值来回震荡。如果损失曲线几乎不变,先别怀疑网络,检查数据维度里是不是大多数像素都是0。MNIST图片边缘大量空白,归一化后输入分布本身偏向0,和常规图像数据不同,这不是bug,是数据特性。
4.3 用混淆矩阵和错题集定位:哪些数字最容易认错
训练结束后不要只看一个总准确率数字,损失曲线上的细节不够定位问题。我习惯先画混淆矩阵:
YTestPred = classify(net, XTest); figure; confusionchart(YTest, YTestPred); acc = mean(YTestPred == YTest); fprintf('测试集准确率: %.2f%%\n', acc * 100);混淆矩阵对角线上是正确分类,非对角线的亮点就是系统性的认错模式。MNIST上最常见的几组混淆是4和9、3和8、7和1,原因是这些数字在收笔和连笔笔法上高度相似,某几个方向的卷积特征很容易被共用。如果某一组的混淆明显偏高,比如5被大量认成3,可以考虑在数据增强环节加入小幅旋转,旋转角度控制在±10°以内,超过这个范围数字语义就可能改变,1旋转15°看起来已经像7了。这是整个项目里最值得花时间调的部分,数据增强用MATLAB的imageDataAugmenter可以做成平移和旋转的组合,具体参数我在第五章过拟合那条里给。
想看具体是哪几张图认错,用一个循环把错题集可视化出来,比看混淆矩阵更有实感:
bad = find(YTestPred ~= YTest); figure; for i = 1:min(9, numel(bad)) subplot(3, 3, i); imshow(XTest(:, :, 1, bad(i))); title(sprintf('真:%d 判:%d', double(YTest(bad(i)))-1, double(YTestPred(bad(i)))-1)); end注意title里categorical不能直接减1,必须先用double转换再减。这个错题集能让你一眼看出哪些样本是真正的“反例”:有些是训练集标注本身有歧义,有些是笔迹太潦草,程序认错不是缺陷,是数据分布的自然结果。遇到这种情况,别急着调网络,先确认你的验证集和测试集划分没有被污染,比如误把训练集样本混进了测试集。
5. MATLAB手写体数字识别避坑指南:五个高频问题与排查方法
下面这些全是能直接对号入座的case。每个现象先说是啥,再说为什么,最后给解决路径,按我自己的排错习惯来写。
5.1 数据读取阶段:全黑、乱码与像素值爆炸
现象:imshow读出来的MNIST图片全黑,或者是一堆噪声点;训练时又报错“输入数据不是浮点”或损失一开始就是NaN。
原因通常有两个。第一是字节序没指定,fread时少了'ieee-be',像素字节被拼成完全不同的数值,噪声点居多;还有reshape方向反了,图是转置的,不仔细看和乱码很像,但其实是横着躺的数字。第二是数据没归一化,uint8的0到255直接进卷积层,配合稍大的学习率让梯度爆炸,损失变成NaN。有些zip包的数据是整型但范围已经是0到1,比如uint8类型存0和1,这种数据再除以255会把有效像素变成接近0的值,训练直接废掉。
解决:回到2.1节,把fopen第三个参数补齐、permute方向确认,读取后立刻imshow单张确认。然后检查数据范围,用max(max(XTrain(:,:,1,1)))看第一张图的最大像素值,再决定除以255还是保持原样。这套检查做完,数据读取阶段的坑基本能一次清完。
5.2 训练阶段:GPU内存不足、准确率卡90%与过拟合
现象:训练到中途报Out of memory on GPU或者CUDA错误。
原因:最大概率是MiniBatchSize设置太大,尤其在你开着训练进度图的时候,MATLAB要保存每个iteration的曲线数据,显存占用比纸面上算的还高。如果机器本来没有可用GPU而MATLAB自动选了gpu执行环境,也会报这类错。
解决:把MiniBatchSize从128降到64甚至32,同时把学习率从0.01降到0.005,观察训练时间。如果还报错,就在trainingOptions里加'ExecutionEnvironment', 'cpu'强制CPU跑,MNIST这种规模CPU也就几分钟到十几分钟,不会等太久。
现象:准确率卡在90%附近上不去,不管怎么加层都无效。
原因:MNIST上出现这个现象,绝大多数不是网络深度问题,而是预处理和数据约定不匹配。我见过最多的场景是白底黑字的图片直接代入网络,MNIST是黑底白字,像素值语义完全反了,训练时网络学的是色调而不是笔迹形状。另一个原因是没加BatchNorm,浅层网络不加还能凑合,超过两层卷积后BN缺失会让收敛速度慢很多。
解决:检查输入的均值方向,算mean(XTrain(:)),如果数值接近0.9说明大部分像素接近255,图像颜色极性反了,需要做1 - images反转。同时确认网络里有batchNormalizationLayer,加上之后通常一两轮内就能看到准确率跳到95%以上。
现象:训练集准确率99%,测试集只能到94%,且gap越来越大。
原因:教科书级的过拟合。MNIST上过拟合的另一个隐蔽来源是全连接层后面没有dropout却有一个很大的fc,前面3.2已经解释过。
解决:先加dropoutLayer,再调fc层宽度,最后考虑数据增强。数据增强可以这样写:
aug = imageDataAugmenter('RandRotation', [-10 10], ... 'RandXTranslation', [-2 2], 'RandYTranslation', [-2 2]); options = trainingOptions('sgdm', 'Augmentation', aug, ... 'InitialLearnRate', 0.01, 'MiniBatchSize', 128, ... 'MaxEpochs', 12, 'Shuffle', 'every-epoch');augmentedImageDatastore在这里其实更好,但imageDataAugmenter配合trainNetwork直接在options里传'Augmentation'参数最省事。这个组合通常能追回2到3个百分点,代价是训练时间变长,课设阶段酌情取舍。
5.3 预测阶段:概率向量、黑白反转与尺寸顺序错误
现象:classify返回的结果不是数字,而是一个概率向量或者一堆奇怪的东西;对自定义图片预测时,不管画什么都识别成同一个数字。
原因:前半段是你用了predict而不是classify。predict返回的是网络softmax层的10维概率向量,对应0到9共10个类别的置信度,想拿到标签要用classify,想解释置信度用max(predict(net, X))。后半段是预处理问题,自定义图片没有做尺寸和反色处理,网络输入严格是28×28,图片如果不是这个尺寸会被imresize强行拉伸,笔画边缘糊掉;手机拍的白纸黑字和MNIST的黑底白字又是反的,模型学的是白色笔迹,你喂给它黑色笔迹自然全识别成同一个类。
解决:预测统一用classify;单张图片的预处理顺序写在第六章,先转灰度、resize到28×28、归一化、判断底色再反色,最后补上通道维度。单张预测的输入格式是H×W×C,虽然N为1也要保持四维习惯,MATLAB不少方法对缺维度的输入会给出很隐晦的报错,不如自己按规范来。
6. 模型保存与自定义图片验证:把手写识别用到实际图片上
6.1 用gather和save留住训练好的模型
如果训练使用了GPU,net里权重可能驻留在显存,直接save的mat文件load回另一台机器时会找不到对应GPU。这一步我吃过亏。save之前先gather:
net = gather(net); save('mnist_cnn_model.mat', 'net');gather把网络参数从GPU复制到CPU内存,之后save的模型在任何机器上都能load。重新load时用clear或者直接覆盖,别让旧net留在工作区,classify会优先使用工作区里的变量而忽略你刚load的,这个顺序问题排查起来有点浪费时间。
6.2 预处理一张手机拍的算式照片:顺序错一步结果就差十万八千里
img = imread('my_digit.png'); img = im2gray(img); % 彩色转灰度,老版本用rgb2gray img = imresize(img, [28 28], 'bilinear'); img = double(img) / 255; if mean(img(:)) > 0.5 img = 1 - img; % 白底黑字转成黑底白字 end X = reshape(img, [28 28 1]); label = classify(net, X); disp(char(label));im2gray这个函数在老版本叫rgb2gray,新版用im2gray能处理灰度输入不报错。imresize到28×28这一步,数字本身占原始图的面积比例会影响结果,我的经验是把数字裁剪得贴近边缘再resize,给四周留的空白控制在10%到20%以内,留太多会把笔画压成一根细线。mean(img(:)) > 0.5判断白底黑字,如果是黑底白字这个判断会自动跳过反色,所以这段脚本对两种底色都成立。最后reshape成[28 28 1],单张预测不需要样本维度N。
如果你想做实时手写板验证,常见做法是开一个figure,用WindowButtonDownFcn记录鼠标按下、WindowButtonMotionFcn记录拖动轨迹,松开后把轨迹点画到一个28×28的栅格上,再走上面这段预处理。涉及鼠标回调的代码在不同MATLAB版本上兼容性一般,第一次做用ginput点出数字轮廓就行,把坐标散点画成图,再imresize,效果已经足够展示。用App Designer做一个带画布的应用是加分项,但不影响模型本身。
我自己做这类验证最大的教训是:不要只用测试集图片测试模型,一定要用自己画的新数字。MNIST测试集再准确也只是在同一个数据分布里自证,真实手机拍的算式、不同笔迹的连笔数字,才是手写体数字识别这道题真正要过的关。如果模型在自己手写图上总是翻车,优先怀疑预处理而不是网络结构,把画出的数字和MNIST样本放在一起看一眼,底色、粗细、尺寸和偏移四个维度对比一遍,问题基本就清楚了。希望帮到你。
本文还有配套的精品资源,点击获取