拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Matlab实现CNN手写数字识别:从MNIST数据到模型部署全流程

简介面向计算机相关专业的毕业设计与课程设计需求这套基于Matlab的卷积神经网络算法实现MNIST手写字体识别的项目提供了完整可运行源码和配套文档说明。项目源自经导师指导并以98分通过评审的毕业设计围绕手写数字识别场景完整覆盖数据加载与预处理、卷积网络构建、交叉熵损失计算、Softmax分类、模型训练与评估等环节适合正在做大作业或毕业设计的学生以及需要项目实战的入门研究者。压缩包共包含17个文件以m脚本为主体包含训练、评估、主程序与数据处理等模块同时提供mat格式的MNIST数据集、7张png结果图以及md与txt两种说明文档整体大小29.33MB目录结构按代码、数据和结果区分便于按需查阅。目前已有144人浏览学习。读者可以借助源码逐步理解卷积神经网络各模块的实现细节结合结果图与说明文档定位训练效果并在现成数据集与脚本基础上进行调参、扩展或二次开发对完成课程设计、期末大作业或毕业设计具有直接参考价值。1. 毕业设计里满大街都是 Matlab CNN 手写字体识别可为什么能跑通的不多每年到了四五月份总能在答辩教室外面看到拿着“基于 Matlab 的 CNN 手写字体识别”题目的学生。MNIST 数据集只有 28×28 的灰度图类别固定为 0-9训练样本充足环境配置也不像目标检测那么复杂所以它经常被当成深度学习入门的“第一个完整网络”。真正动手才发现题目虽小从数据格式、网络参数到训练选项每一处都能讲出独立知识点。更常见的情况是代码在 A 电脑上能跑到 98%换到 B 电脑上准确率直接掉到 90%问起来谁都说不清是数据问题、随机初始化问题还是训练选项没对齐。下面按数据读取、网络构造、训练调参、测试保存这条路径完整走一遍给出的代码可以直接在本地跑也可以作为毕设文档的对照材料。2. 把 MNIST 数据读进来4D 数组、归一化和验证集划分2.1 digitTrain4DArrayData 返回的是 4 维数组不是图片文件夹第一次被卡住的地方往往不是网络而是读数据。MNIST 原版是 IDX 二进制格式需要用 fopen 按字节读网上还遍布各种 Python 解析脚本。题目既然限定在 Matlab 里最省事的路径是用 Deep Learning Toolbox 自带的digitTrain4DArrayData和digitTest4DArrayData。这两个函数不需要联网下载额外数据返回的已经是 4 维数组维度顺序是“高度×宽度×通道×样本数”。灰度图没有 RGB 概念所以第三维固定为 1。[trainImages, trainLabels] digitTrain4DArrayData; [testImages, testLabels] digitTest4DArrayData;多数版本下trainImages是 28×28×1×60000 的 uint8 数组trainLabels是 60000×1 的向量。这里要注意testImages是独立测试集不要混进训练过程。在毕设文档里“没加载到数据”不是问题“没讲清数据布局”才容易被答辩老师问倒。2.2 手动划分训练集和验证集训练集本身有 6 万张直接拿全部数据训练也能做但我一般会先留出一部分作为验证集。验证集有两个作用一是训练过程中观察有没有过拟合二是在毕设文档里画训练曲线时不至于只有一条单调变化的训练 loss。有人会用splitEachLabel自动按标签比例划分但它主要面向imageDatastore直接处理 4D 数组反而不方便。这里用randperm手动打乱并切分rng(0); idx randperm(size(trainImages, 4)); numVal 5000; valIdx idx(1:numVal); trainIdx idx(numVal1:end); valImages trainImages(:,:,:,valIdx); valLabels trainLabels(valIdx); trainImages trainImages(:,:,:,trainIdx); trainLabels trainLabels(trainIdx);rng(0)让每次随机划分结果一致如果不固定随机种子两次跑出来的准确率可能有 0.5 个百分点的差异这会干扰调参判断。这里留出 5000 张做验证剩下 55000 张训练。如果你的机器内存小可以把验证集比例调到 10%但对 MNIST 这种 28×28 小图没有太大必要。2.3 归一化、single 类型和 categorical 标签原始图像是 uint8取值范围 0-255。卷积网络一般期望输入范围在 0 到 1 左右这样权值初始化和梯度更新更稳定。另外trainNetwork默认不支持 uint8 数组直接训练需要转成 single 或 double。trainImages single(trainImages) / 255; valImages single(valImages) / 255; testImages single(testImages) / 255; trainLabels categorical(trainLabels); valLabels categorical(valLabels); testLabels categorical(testLabels);除以 255 是为了归一化single类型能减少一半内存占用。categorical这一步很容易被忽略trainNetwork处理分类问题时要求标签是 categorical 向量或字符串向量不能把 0-9 当作数值回归目标。即使digitTrain4DArrayData返回的标签已经带有类别信息再包一层categorical也不会出错。到这里数据准备可以总结成一张对照表变量形状处理前处理后trainImages28×28×1×55000uint80-255single0-1valImages28×28×1×5000uint80-255single0-1testImages28×28×1×10000uint80-255single0-1trainLabels 等N×1double0-9categorical3. 手写 MNIST 的 CNN 网络结构怎么定义卷积层、池化层、输出层逐个讲参数3.1 先看一张 CNN 结构图再定义层MNIST 不需要很深的网络常见的毕设方案是简化版 LeNet-5卷积、激活、池化、再卷积、再池化最后接全连接和 softmax。下面这张表既是网络结构也是答辩时可以直接解释的“CNN 结构图”。层输出尺寸主要作用imageInputLayer28×28×1接收单通道灰度图convolution2dLayer(3,8)28×28×8提取边缘、笔画局部特征batchNormalizationLayer28×28×8稳定中间数据分布reluLayer28×28×8引入非线性maxPooling2dLayer(2)14×14×8降采样扩大感受野convolution2dLayer(3,16)14×14×16组合低级特征batchNormalizationLayer14×14×16同上reluLayer14×14×16非线性maxPooling2dLayer(2)7×7×16降采样convolution2dLayer(3,32)7×7×32提取更抽象特征reluLayer7×7×32非线性fullyConnectedLayer(10)10输出 10 个类别分数softmaxLayer10转成概率classificationLayer1计算交叉熵损失第一层卷积选择了 3×3 小卷积核而不是 5×5 或 7×7。理由是 MNIST 笔画本身很细太大卷积核容易把相邻数字结构混在一起。通道数从 8 增加到 32是因为越往后越需要组合高层语义但也不能盲目把第一层加到 64参数增加后小数据集容易过拟合。3.2 用 layer 数组把网络写成 Matlab 代码layers [ imageInputLayer([28 28 1], Normalization, none) convolution2dLayer(3, 8, Padding, same) batchNormalizationLayer reluLayer maxPooling2dLayer(2, Stride, 2) convolution2dLayer(3, 16, Padding, same) batchNormalizationLayer reluLayer maxPooling2dLayer(2, Stride, 2) convolution2dLayer(3, 32, Padding, same) reluLayer fullyConnectedLayer(10) softmaxLayer classificationLayer ];Padding,same让卷积后特征图尺寸不变否则 28×28 经过三层 3×3 卷积会缩成 22×22再加两个池化层后分辨率太小。maxPooling2dLayer(2, Stride, 2)把 2×2 区域合并成 1 个值输出尺寸减半。这里的Stride, 2必须写清楚有些版本默认步长和池化窗口一致但显式写出更稳。batchNormalizationLayer对 MNIST 这种小数据集通常能带来 1 到 2 个百分点的提升代价是训练时间略长。如果电脑配置低可以先删掉它跑通流程再补回来对比。3.3 输出层为什么是 10 个神经元加 softmaxMNIST 有 0 到 9 共 10 个类别所以最后一层fullyConnectedLayer(10)输出 10 个实数。softmaxLayer把这 10 个实数转成和为 1 的概率分布classificationLayer再与 categorical 标签计算交叉熵损失。答辩时经常被问“为什么不是 9 个神经元”标准回答是每个输出节点对应一个类别模型对每个数字学习一个独立的判别函数而不是建立 0 到 9 的回归关系。回归会把“1”和“2”当作数值上的接近但这种接近没有意义。4. trainNetwork 训练选项和调参accuracy 卡在 90% 时改哪里4.1 一组能直接跑的训练选项网络定义好之后真正影响结果的是trainingOptions。下面这组参数是 MNIST 手写数字识别里最常用的一组默认起点options trainingOptions(adam, ... MiniBatchSize, 64, ... MaxEpochs, 8, ... InitialLearnRate, 0.001, ... Shuffle, every-epoch, ... ValidationData, {valImages, valLabels}, ... ValidationFrequency, 50, ... Plots, training-progress, ... Verbose, false); net trainNetwork(trainImages, trainLabels, layers, options);trainNetwork是 Deep Learning Toolbox 的核心入口输入分别是训练图像、训练标签、层数组和训练选项。ValidationData对应前面划分出的验证集没有这一项训练图里就看不到验证准确率也就很难判断过拟合。ValidationFrequency表示每隔多少次迭代算一次验证训练集 55000 张除以 MiniBatchSize 64一个 epoch 约 860 次迭代50 次间隔会让验证曲线细密但略微拖慢速度如果希望训练速度快一点可以改成 200。Plots设置为training-progress会弹出实时训练窗口第一次调参时非常有用。等网络结构固定后再改成none节省资源。4.2 先调 InitialLearnRate再动网络结构遇到准确率卡在 90% 不涨时优先检查学习率而不是马上加深网络。下面是一张常用的调参表参数常用范围失败表现处理方向InitialLearnRate0.0001 到 0.01loss 震荡或不下降调低一个数量级MiniBatchSize32 到 256内存不足或收敛慢小批量梯度噪声大大批量需要调学习率MaxEpochs5 到 20loss 还没收敛就结束看训练 loss 曲线尾端是否仍下降ValidationFrequency每个 epoch 1 到 3 次验证曲线粗糙设为迭代次数的整数倍Shuffleevery-epoch收敛不稳定固定 RandomSeed 后重试InitialLearnRate从 0.001 起步是安全的。MNIST 图像简单用 0.01 也能跑但训练后期容易出现验证集抖动。如果你发现训练集准确率一直在上升验证集却在某一个点停住先做两步把学习率降到 0.0005同时增加一个dropoutLayer。在fullyConnectedLayer前插入 dropout 是常见做法layers [ imageInputLayer([28 28 1], Normalization, none) convolution2dLayer(3, 8, Padding, same) batchNormalizationLayer reluLayer maxPooling2dLayer(2, Stride, 2) convolution2dLayer(3, 16, Padding, same) batchNormalizationLayer reluLayer maxPooling2dLayer(2, Stride, 2) convolution2dLayer(3, 32, Padding, same) reluLayer fullyConnectedLayer(64) reluLayer dropoutLayer(0.5) fullyConnectedLayer(10) softmaxLayer classificationLayer ];dropoutLayer(0.5)的意思是在训练时随机让 50% 的神经元输出置零减少神经元之间的联合依赖测试时会自动缩放回原尺度。加了这一层后验证准确率通常比不加更稳缺点是训练迭代次数需要增加一点。4.3 从 loss 曲线判断欠拟合还是过拟合实时训练图里有两条关键曲线训练准确率/训练 loss以及验证准确率/验证 loss。如果训练 loss 一直很高验证 loss 也跟着高这通常是欠拟合。处理方式不是堆更多层而是先确认数据归一化是否做了、学习率是否太低、epochs 是否不够。MNIST 在这种浅层网络上8 个 epoch 一般足够看到明显收敛。如果训练准确率到了 99%验证准确率却只有 93%就是过拟合。对 MNIST 来说6 万张样本不算少普通小网络不容易过拟合但全连接层过多时会遇到。此时优先加 dropout或者把fullyConnectedLayer(64)改成fullyConnectedLayer(32)。提示如果验证集 loss 在第 5 个 epoch 后开始上升训练集 loss 还在下降建议直接减小MaxEpochs。毕业设计里不要追求训练到 100%测试集表现才是最终指标。4.4 随机种子和 GPU 环境也要写进文档trainNetwork在训练前会随机初始化权重哪怕网络和数据完全一样两次结果也会有微小差别。为了保证毕业论文里“参数对照表”可信建议在训练脚本最前面加上rng(0)。如果用了Shuffle,every-epoch每个 epoch 的数据打乱顺序也是随机的同样受rng影响。有 GPU 的机器不需要额外改代码trainingOptions默认ExecutionEnvironment是auto能检测到 GPU 就会自动调用。如果希望强制 CPU 训练可以在trainingOptions里加ExecutionEnvironment,cpu。CPU 上建议把MaxEpochs降到 5MiniBatchSize降到 32先确认整个流程能跑通再决定是否扩大规模。5. 用测试集算准确率、画混淆矩阵再把模型保存成 .mat5.1 测试集上的 classify 和准确率训练完成后不能用训练集准确率作为最终结果应该用完全没有参与训练的testImagesYPred classify(net, testImages); testCats categorical(testLabels); accuracy mean(YPred testCats); fprintf(Test accuracy: %.2f%%\n, accuracy * 100);classify返回与输入样本数相同长度的 categorical 向量。YPred testCats生成逻辑向量mean直接计算出正确率。这一步跑出来的结果才是写进毕设摘要里的数字。上面那个三层卷积结构一般能到 98% 到 99% 之间。5.2 混淆矩阵该看哪些格子figure; cm confusionchart(testCats, YPred);confusionchart会自动生成带颜色深浅的混淆矩阵图。答辩时不要只看对角线数值要看容易被错分的格子比如4和9以及7和2经常互相混淆这是因为这些数字的左上角笔画和底部收笔结构相似。如果某个类别的召回率明显偏低通常说明训练数据里该数字的书写变体不够或者分类器对该类别的特征不够敏感。5.3 保存网络并写一个 predictDigit 函数训练好的net可以直接存成.mat文件方便后续做界面或者继续实验save(mnist_cnn_net.mat, net);下次使用时用load(mnist_cnn_net.mat, net)恢复。如果毕设要求做一个手写输入的小界面最方便的做法是先封装一个预测函数function label predictDigit(net, I) if size(I, 3) 3 I rgb2gray(I); end I imresize(I, [28 28]); I single(I) / 255; label classify(net, I); end这个函数会把任意尺寸的图片压缩成 28×28再走一遍与训练时相同的数据预处理。需要注意方向MNIST 的表征是黑底白字如果输入的图片是白底黑字预测前要先取反I 255 - I否则准确率会明显下降。把predictDigit接到 App Designer 的回调函数里就能输入任意一张手写数字图片并实时显示识别结果毕业设计也能顺势落地成可演示的交互程序。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门