资讯动态

MATLAB卷积神经网络手写数字识别例程拆解与避坑

发布时间:2026/10/26 10:31:41 来源:尧图企业网站定制
简介面向深度学习入门者与MATLAB用户的卷积神经网络手写数字识别例程包围绕手写数字任务给出从数据加载、网络搭建到训练测试的完整实现帮助读者在MATLAB环境中理解卷积、池化、Softmax分类及反向传播等关键环节适合课程实验、自学练手与算法复现。资源共19个文件以16个.m脚本与函数为主体另含1个.mexw64与1个.cpp实现的池化模块、1份README说明压缩包约20KB体量轻便但结构较完整。该例程已有174人学习可作为小规模CNN代码级实践参考。其价值在于把数据扩增、前向传播、反向传播、梯度检查、参数更新和测试评估等流程串联起来并保留MaxPooling的C/MEX底层实现便于读者对照理解网络各层作用、调试训练过程并迁移到自己的手写识别实验。1. 先说结论一份能跑通的卷积神经网络手写数字识别 MATLAB 例程值不值当拆去年帮人看一个课程设计模型训练日志漂亮得不行训练集 99.6%验证集 99.1%损失曲线干净利落。结果拿手机拍了张手写数字 7 传进去输出 3再传 5还是 3。折腾到凌晨两点才定位到问题训练数据是黑底白字、像素已经压到 [0,1] 区间而他测试时把手机照片直接 resize 完丢进网络白底黑字一进去模型看到的其实是反相之后的分布。这类卷积神经网络手写数字识别的 MATLAB 例程真正的价值不在于证明“CNN 能认数字”而在于把数据怎么进、网络怎么搭、训练怎么调、结果怎么验这四件事拆成能逐行读、能逐行改的脚本。它适合两类人课程设计、毕业设计要交一份能跑通、能讲清原理的代码的人以及想把 MATLAB 里的训练流程接到自己工程上、后面还要导出到别的运行时的人。下面按我拆代码的顺序从目录结构一路讲到二次开发怎么下手。2. 项目全景这套 MATLAB 例程由哪几块拼起来把压缩包解开第一眼看到的通常不是一个大而全的框架而是五六个职责明确的.m文件加一个数据目录。这个组织方式很“教学例程”但对二次开发其实挺友好——每一段都能单独替换不用在几万行代码里找入口。先说清楚它依赖什么、由什么组成后面跑起来才不会卡在“函数不存在”上。2.1 技术底座Deep Learning Toolbox 是硬门槛这套例程的核心就一个函数trainNetwork。它是 Deep Learning Toolbox 提供的训练入口接受“数据 层数组 训练选项”三件套内部完成前向、反向、参数更新、验证、画训练曲线。卷积层、批归一化层、池化层、全连接层、softmax 层、分类层全部是工具箱自带的例程本身不实现任何算子所以整份代码能压到几百行。组件作用是否必需MATLAB R2020a 及以上运行环境trainNetwork与trainingOptions的主要参数在这些版本上稳定必需Deep Learning Toolbox提供层定义、训练、推理、confusionchart之外的网络 API必需Image Processing Toolboximread/rgb2gray/imresize/im2double必需图片路线Parallel Computing Toolbox把训练放到 GPU 上跑可选数据量大时几乎必需Deep Learning Toolbox Converter for ONNXexportONNXNetwork导出模型可选部署时用我一般先跑ver看工具箱再跑gpuDeviceCount看有没有卡。注意trainNetwork在 R2017a 就有了但早期版本对imageDatastore直接喂训练的支持、以及对dlnetwork风格自定义训练循环的支持差异很大。如果你拿到的例程里出现dlnetwork、dlfeval、dlgradient这些词说明它走的是自定义训练循环那条路版本要求会更靠后如果只有trainNetwork 层数组那就是最经典的那套兼容性最好。2.2 目录结构五个 .m 文件各管一段典型的一份手写数字识别例程目录大概长这样cnn-digit-demo/ ├── data/ │ ├── train/ # 按类别分子文件夹0/ 1/ ... 9/ │ └── test/ ├── src/ │ ├── prepareData.m # 数据读取、归一化、划分训练/验证 │ ├── buildNet.m # 返回一个 layer 数组 │ ├── trainNet.m # 组装 options调用 trainNetwork │ ├── evaluateNet.m # 混淆矩阵、准确率、错例回溯 │ └── predictOne.m # 单张图片的完整预处理 推理 ├── models/ # 训练好的 .mat ├── results/ # 混淆矩阵图、错例拼图 └── runAll.m # 一键串起上面五步prepareData.m只干一件事把磁盘上的图片或者.mat里的数组变成trainNetwork能吃的形态。buildNet.m里全是层定义改网络只动这一个文件。trainNet.m负责所有超参和trainingOptions也是我最常改的文件。evaluateNet.m独立出来很有必要因为评估要反复跑不该每次都重新训练。predictOne.m是最容易被忽略、又最容易翻车的一个——它里面的预处理必须和训练时严格一致否则就是我开头说的那个坑。这种拆法的好处是边界清晰想换数据集只改prepareData.m想加深网络只改buildNet.m想调学习率只改trainNet.m。坏处是变量在文件之间靠函数签名传递如果原作者图省事用了evalin/assignin或者全部写成脚本script 而非 function变量会污染 base 工作区第二次跑就容易拿到上一次的脏数据。拆的时候留意一下每个文件开头是function还是裸脚本裸脚本的要把输入输出显式化。2.3 三种跑法一键脚本、分步调试、可视化改网第一种是runAll.m一把梭适合确认环境没问题、想快速看到结果。第二种是分步执行先prepareData出 datastore再buildNet出 layers再trainNet中间每一步都能whos看变量维度——调 bug 基本都走这条路。第三种是用 Deep Network Designer 打开buildNet返回的层数组可视化地拖层、改通道数改完导出成代码再贴回来。三种方式里我建议新手从分步开始。一键脚本把错误栈埋在中间报错信息指向runAll.m第 12 行你根本不知道是数据的问题还是网络的问题。分步跑哪一步红了就是哪一步的问题定位成本差一个数量级。提示分步调试时养成习惯每步结束whos看一眼关键变量的 size 和 class。手写数字识别里 90% 的报错都能从“这个变量本该是 28×28×1×N 的 single结果是个 28×28 的 double”里找到线索。3. 核心链路拆解一张 28×28 灰度图怎么走到预测标签这一段是整份例程最值得逐行读的部分。从磁盘上的图片文件到屏幕上那个3中间经过四次形态变化文件 → 数值矩阵 → 4-D 张量 → 概率向量。每一次变化都有坑下面按顺序拆。3.1 数据入口把图片变成网络认得的 4-D 数组MATLAB 训练图像分类网络输入张量的维度顺序是高度 × 宽度 × 通道数 × 样本数也就是28×28×1×60000。这一点和很多其他框架的N×C×H×W完全相反是 MATLAB 侧最容易翻车的地方后面避坑章还会专门说。两条常见的读取路线选哪条取决于数据是什么形态。路线 A图片按类别分文件夹存放用imageDatastorefunction [imdsTrain, imdsVal, imdsTest] prepareData(dataRoot) % dataRoot 下应有 train/ 和 test/ 两个子目录各自再按 0~9 分子文件夹 % 标签由子文件夹名自动推断省掉手工维护 label 向量 imdsTrain imageDatastore(fullfile(dataRoot,train), ... IncludeSubfolders, true, ... % 递归进 0/ 1/ ... 9/ 这些子目录 LabelSource, foldernames); % 用文件夹名当类别标签 imdsTest imageDatastore(fullfile(dataRoot,test), ... IncludeSubfolders, true, ... LabelSource, foldernames); % 从训练集切 10% 出来做验证固定随机种子保证每次划分一致 rng(42); [imdsTrain, imdsVal] splitEachLabel(imdsTrain, 0.9, randomized); % 统一到 28x28 单通道训练/验证/测试必须用同一个目标尺寸 targetSize [28 28 1]; imdsTrain augmentedImageDatastore(targetSize, imdsTrain); imdsVal augmentedImageDatastore(targetSize, imdsVal); imdsTest augmentedImageDatastore(targetSize, imdsTest); endIncludeSubfolders决定要不要往下钻一层LabelSource决定标签从哪来。splitEachLabel的第二个参数我写0.9它表示每一类都按这个比例切不是全局切——这点很重要否则类别不平衡时验证集可能某一类一张都没有。augmentedImageDatastore在这里不是做增强只是做尺寸统一和通道统一名字容易让人误解。路线 B数据已经是.mat里的数值矩阵直接构造function [X, Y] loadMatData(matPath) % 假定 .mat 里有两个变量images 为 uint8 的 H×W×Nlabels 为 1×N 的整数 S load(matPath); X double(S.images) / 255; % 归一化到 [0,1]别留 uint8 X reshape(X, [size(X,1), size(X,2), 1, size(X,3)]); % 补上通道维 X single(X); % 单精度省一半显存 Y categorical(S.labels, 0:9); % 必须转 categorical endreshape那一行是关键原来H×W×N三维网络要的是四维中间插一个通道维。/255的归一化必须做uint8直接进网络卷积核权重初始值又小乘积之后数值尺度完全不对。categorical也不能省分类层的标签类型必须是 categorical用 double 会直接报错后面避坑章细说。3.2 网络搭建能到 99% 的最小 CNN 长什么样手写数字本身信息量不大28×28 的灰度图笔画就那么几根不需要很深的网络。我见过用 ResNet-50 跑这个数据的准确率确实高但训练时间和显存占用翻十几倍纯粹浪费。一个两层卷积加两层全连接的组合在常规数据集上就能摸到 99% 附近。function layers buildNet(numClasses, inputSize) % numClasses: 类别数手写数字是 10 % inputSize : 输入尺寸[28 28 1] layers [ imageInputLayer(inputSize, Normalization, none, Name, input) % 归一化已在 prepareData 里做过这里必须写 none否则会二次归一化 convolution2dLayer(3, 16, Padding, same, Name, conv1) batchNormalizationLayer(Name, bn1) % 稳定训练允许更大学习率 reluLayer(Name, relu1) maxPooling2dLayer(2, Stride, 2, Name, pool1) % 28x28 - 14x14 convolution2dLayer(3, 32, Padding, same, Name, conv2) batchNormalizationLayer(Name, bn2) reluLayer(Name, relu2) maxPooling2dLayer(2, Stride, 2, Name, pool2) % 14x14 - 7x7 fullyConnectedLayer(128, Name, fc1) reluLayer(Name, relu3) dropoutLayer(0.5, Name, drop1) % 防过拟合小数据集必备 fullyConnectedLayer(numClasses, Name, fc2) softmaxLayer(Name, softmax) classificationLayer(Name, output) ]; end逐层说几个参数。convolution2dLayer(3, 16, ...)的第一个 3 是卷积核边长第二是输出通道数Padding,same让输出尺寸和输入一致这样不用手工算每层之后的尺寸改网络时省心。batchNormalizationLayer放在卷积和激活之间作用是把每批激活值的分布拉回标准正态训练更稳学习率可以开大一点——反面影响是小 batch 时统计量不准MiniBatchSize低于 16 就会抖这个后面避坑章有具体现象。maxPooling2dLayer(2,Stride,2)把特征图边长减半两次池化 28→14→7最后全连接层拿到的是 7×7×32 1568 维。dropoutLayer(0.5)只在训练时生效推理时自动关闭所以不会影响线上预测放心加。classificationLayer不需要写类别数它会从训练数据里推断写错反而报错。3.3 训练参数决定成败的其实是这几个选项网络结构是骨架参数才是让它跑起来的东西。这一段我把trainingOptions拆开讲因为同一份网络换一组参数准确率能从 60% 涨到 99%。function options buildOptions(imdsVal) % imdsVal: 验证集 datastore用于每轮结束后评估 options trainingOptions(sgdm, ... Momentum, 0.9, ... % 动量抑制震荡、加速收敛 InitialLearnRate, 0.01, ... % 初始学习率CNN 常用 1e-2 量级 LearnRateSchedule, piecewise, ...% 分段衰减 LearnRateDropFactor, 0.1, ... % 每次衰减到原来的 10% LearnRateDropPeriod, 5, ... % 每 5 轮衰减一次 L2Regularization, 1e-4, ... % 权重衰减抑制过拟合 MaxEpochs, 15, ... % 训练轮数 MiniBatchSize, 128, ... % 每批样本数显存够就往上加 Shuffle, every-epoch, ... % 每轮打乱避免固定顺序带来的偏置 ValidationData, imdsVal, ... ValidationFrequency, 50, ... % 每 50 个迭代验证一次 ValidationPatience, 5, ... % 验证损失 5 次不降就早停 Verbose, false, ... % 命令行不刷屏 Plots, training-progress, ... % 弹训练窗口 ExecutionEnvironment, auto, ... % 有 GPU 用 GPU没有就 CPU CheckpointPath, models/ckpt); % 每轮存一个断点断电不白跑 endsgdm是带动量的随机梯度下降手写数字这种小网络用它足够收敛稳。adam也可以初始学习率要相应降到 1e-3 量级两者不要混着调。InitialLearnRate0.01 配上piecewise衰减是这类例程最常见的组合前 5 轮大步走之后每 5 轮步子缩到十分之一后期精修。ValidationPatience是很多人不写、但实际很有用的一个参数。验证损失连续 5 次不降就提前停避免在一个已经收敛的模型上继续跑、把训练集背下来。CheckpointPath是后悔药训练到第 12 轮机器崩了能从models/ckpt里的.mat恢复不用从头再来。3.4 评估与错误回溯混淆矩阵不是画着好看的训练完trainNetwork返回的net只是模型它到底行不行要看测试集。评估代码不长但每一行都对应一个排查动作function metrics evaluateNet(net, imdsTest, classNames) [YPred, scores] classify(net, imdsTest); % 一次性拿到标签和 10 维得分 YTrue imdsTest.Labels; accuracy mean(YPred YTrue); fprintf(测试集准确率: %.2f%%\n, accuracy * 100); % 混淆矩阵看哪个数字被误认成哪个比总准确率有信息量得多 figure; cm confusionchart(YTrue, YPred, ... Title, sprintf(测试集混淆矩阵 (acc%.2f%%), accuracy*100), ... RowSummary, row-normalized); % 按行归一化看召回率 % 把判错的样本找出来拼成一张图存盘方便肉眼找规律 wrongIdx find(YPred ~ YTrue); nShow min(16, numel(wrongIdx)); figure; for k 1:nShow subplot(4,4,k); img readimage(imdsTest, wrongIdx(k)); imshow(img); title(sprintf(%d→%d, YTrue(wrongIdx(k)), YPred(wrongIdx(k)))); end saveas(gcf, results/wrong_samples.png); metrics.accuracy accuracy; metrics.confusion confusionmat(YTrue, YPred); metrics.scores scores; endclassify同时返回标签和得分矩阵scores得分是 softmax 之后的概率做置信度阈值、做拒识置信度太低就判“不认识”都靠它。confusionchart的RowSummary,row-normalized很关键总准确率 99% 听着漂亮但如果数字 8 的召回只有 92%说明这一类系统性偏差得回头看数据里 8 的样本是不是偏少、或者写法差异大。错例拼图是我每次必看的一步。看十张错例往往能立刻判断是数据问题还是模型问题如果错例都是那种写得很潦草、人眼都认不出的那是数据本身的上限如果错例都是某一类被系统性地判成另一类比如 4 和 9那就是特征不够得加深网络或者加数据。4. 跑通一遍从环境自检到出混淆矩阵的完整命令前面拆的是部件这一段把部件串成一条能复现的路径。我按实际操作的顺序写每一步都给出可执行的命令和判断标准照着走一遍大概二十分钟能出第一版结果。4.1 环境自检与数据落盘先确认工具箱和硬件。这段代码建议单独存成checkEnv.m换机器时第一件事就是跑它function checkEnv() % 打印环境信息缺什么一目了然 v ver; names {v.Name}; need {Deep Learning Toolbox, Image Processing Toolbox}; for k 1:numel(need) if any(strcmp(names, need{k})) fprintf([OK] %s\n, need{k}); else fprintf([MISS] %s\n, need{k}); end end % GPU 情况gpuDeviceCount 为 0 表示只能 CPU 训练 nGpu gpuDeviceCount; fprintf(可用 GPU 数量: %d\n, nGpu); if nGpu 0 g gpuDevice; fprintf( 型号: %s, 显存: %.1f GB\n, g.Name, g.TotalMemory/1024^3); end fprintf(MATLAB 版本: %s\n, version); end数据落盘这一步很多人拿到的是几个压缩包或者.mat要先解到data/train和data/test下面并且按类别建子文件夹。如果原始数据是一堆平铺的图片加一个标签文件就得写几行循环搬运别偷懒直接在imageDatastore里硬读标签对不上会更麻烦。第一次跑之前先用小样本验证管线通不通% 只取每类 200 张跑 2 轮确认数据、网络、参数三者能对上 rng(42); [imdsTrain, imdsVal, imdsTest] prepareData(data); imdsSmall splitEachLabel(imdsTrain, 200, randomized); layers buildNet(10, [28 28 1]); options buildOptions(imdsVal); options.MaxEpochs 2; options.ValidationFrequency 20; net trainNetwork(imdsSmall, layers, options);这一步能在几分钟内完成跑通了说明整条链路没问题再上全量数据。我见过太多人直接上全量、跑了两小时才发现标签类型不对。4.2 训练、存模型、断点续训全量训练就是去掉样本数限制把轮数放回去。训练完立刻存盘别等评估完再存[imdsTrain, imdsVal, imdsTest] prepareData(data); layers buildNet(10, [28 28 1]); options buildOptions(imdsVal); net trainNetwork(imdsTrain, layers, options); % 存模型时把预处理参数一起存进去这是血泪经验 meta.inputSize [28 28 1]; meta.normalize divide255; meta.invertRule mean0.5; save(models/cnn_digit.mat, net, meta); % 评估 metrics evaluateNet(net, imdsTest, string(0:9));把meta和net一起存是因为单张推理时的预处理必须和训练时一致。半年后你或者别人拿到这个.mat只看net是猜不出当初是除以 255 还是除以 128、有没有做反色的。把规则写进meta推理函数读它能省掉一大堆“为什么线上不准”的排查。断点续训的入口在CheckpointPath存下的文件里% 从断点恢复checkpoint 是 struct含 net、info、iteration ckpt load(models/ckpt/net_checkpoint__1234__2024.mat); net ckpt.net; info ckpt.trainingInfo; % 用恢复出的 net 继续训练options 里记得把 MaxEpochs 设成剩余轮数 net trainNetwork(imdsTrain, net, options);注意恢复出来的net已经是DAGNetwork或SeriesNetwork对象直接作为trainNetwork的第二个参数传入即可继续训练层数组不用再给一遍。4.3 单张图片推理的完整预处理链这是最容易被写成三行、然后线上全错的函数。完整版应该是这样function [label, score] predictOne(imgPath, modelPath) % 单张图片推理预处理必须与训练严格一致 S load(modelPath); net S.net; meta S.meta; img imread(imgPath); if size(img, 3) 3 img rgb2gray(img); % 彩图转灰度 end img imresize(img, meta.inputSize(1:2)); % 缩放到 28x28 img im2double(img); % 转 double 并归一化到 [0,1] % 白底黑字 - 黑底白字训练集是什么极性这里就必须是什么极性 if strcmp(meta.invertRule, mean0.5) mean(img(:)) 0.5 img 1 - img; end img4d reshape(img, [meta.inputSize(1), meta.inputSize(2), 1, 1]); [label, score] classify(net, img4d); fprintf(预测: %s, 置信度: %.3f\n, string(label), max(score)); endim2double对uint8输入会自动除以 255这是 MATLAB 的既定行为不用再手动除一次——手动除两次会得到 [0, 0.004] 的输入网络直接失效。reshape补上通道维和样本维是因为classify对单张图要求四维输入。反色判断用均值大于 0.5 这个经验阈值对纯白底黑字的扫描件几乎百分百正确。4.4 CPU 和 GPU 的耗时预期用两层卷积这个小网络六万张 28×28 的训练集CPU 上训练 15 轮大概在十几分钟到半小时量级具体取决于核数和内存带宽中端 GPU 上通常压到一两分钟以内。这个差距主要来自卷积运算的并行度。判断标准很简单training-progress窗口里每次迭代的耗时Iteration 那一栏如果是几百毫秒基本在 CPU 上如果是几十毫秒说明在 GPU 上跑。如果gpuDeviceCount返回大于 0 但训练没变快检查ExecutionEnvironment是不是被硬编码成了cpu这个坑我在别人的例程里见过不止一次。5. 避坑与常见问题排查让准确率停在 10% 的那几个坑这一段是整份例程里最值钱的部分。前面所有代码都能从官方文档里拼出来但下面这些现象和原因得真跑过才写得出来。每条按“现象 → 原因 → 解决”走。5.1 现象训练跑完了测试准确率永远是 10%十类分类10% 就是瞎猜。这种情况如果训练损失也在降、只是降得很慢八成不是模型的问题是数据的问题。原因通常有三个。第一维度顺序错了。MATLAB 要求H×W×C×N如果从别的框架迁移过来很容易把N×C×H×W直接塞进去网络会把样本数当成高度最后全连接层的输入维度对不上或者勉强对上但语义完全错乱。第二标签没转categorical或者转的时候类别集合不对导致每个 batch 的标签全是同一类。第三输入没归一化uint8的 0–255 直接进网络卷积输出数值爆炸softmax 之后全是 0.1。解决方式先在prepareData末尾加一句whos X Y确认X是28×28×1×N single、Y是categorical再取一个 batch 的X打印min/max确认落在 [0,1]。这两步做完10% 的问题基本就没了。5.2 现象报错类别数不匹配或者分类层维度对不上报错信息通常是“最后一层输出与类别数不符”这一类。我遇到最多的是fullyConnectedLayer(10)写成了别的数比如从二分类例程改过来忘了改或者类别集合里实际有 11 个类比如加了一个“其他”。原因在于classificationLayer不显式接收类别数它从训练数据的标签集合推断推断出来是 11 类而前一层全连接只输出 10 维trainNetwork就会在构建网络的那一步报错。解决打印categories(imdsTrain.Labels)看实际类别数和顺序然后把fullyConnectedLayer的维度改成和它一致。同时检查categorical(labels, 0:9)里那个类别集合参数——如果数据里只有 0–8你写了0:9MATLAB 会认为第 9 类存在但样本数为 0某些版本会给出奇怪的警告不如老实用categories自动推断。5.3 现象损失不降或者降到一半来回震荡损失曲线是黑匣子打开的唯一窗口。不降和震荡是两个不同的问题。完全不降先看学习率。sgdm配 1e-5 会慢得看不出变化配 1 会直接发散adam的容错区间宽一些但也别超过 1e-2。其次是看批归一化是不是放在了激活之后——顺序应该是卷积 → BN → ReLU放反了 BN 的统计量会一直被 ReLU 截断效果打折。震荡则多和MiniBatchSize有关。BN 依赖 batch 内的均值方差batch 太小比如 8统计量噪声大损失会在一个区间里来回跳。把MiniBatchSize从 8 提到 64 或 128震荡通常会明显收敛。另外Shuffle一定要设成every-epoch如果数据是按类别排好序的不打乱就相当于每批全是同一类梯度方向会周期性地摆。5.4 现象训练到一半内存或显存爆掉报Out of memory或者系统直接卡死。这个和网络大小、batch 大小、以及是否把整个数据集读进内存三件事相关。如果走的是.mat路线路线 B六万张 28×28 的single大约 188MB本身不大但如果用了double就翻倍再叠加trainNetwork内部的中间激活缓存显存占用会到一个不小的量级。解决是先single()降精度再把MiniBatchSize从 256 降到 64 试。如果走的是imageDatastore路线它本身是流式读取内存压力小但augmentedImageDatastore如果开了预取也会占一部分。还有一个隐蔽的点CheckpointPath每轮存一个模型跑 100 轮就是 100 份权重文件磁盘写满之后 MATLAB 会以一种很不直观的方式报错。定期清理或者把CheckpointPath指向一个临时目录。5.5 现象换台机器 load 模型后预测结果全乱模型文件能load进来classify也不报错但输出全是同一个标签。这种情况多半不是模型坏了是输入预处理变了。原因通常是两份环境的数据极性或者数值范围不一致A 机器上训练时数据是黑底白字、除以 255B 机器上推理时图片是白底黑字、没归一化网络看到的输入分布完全偏移输出退化成某个先验概率最高的类。另一个可能是imageInputLayer的Normalization参数训练时写了none推理时如果重新构建网络忘了写默认的零中心化会把已经归一化到 [0,1] 的输入再减一次均值效果同样崩。解决方式就是 4.2 节里说的把预处理规则写进meta一起存推理函数从meta读参数而不是硬编码。这条习惯我从踩过那次坑之后就一直保持多花两行代码省掉一整晚。6. 二次开发换数据集、加深网络、导出部署东西跑通了接下来大概率要改成自己的任务。三个方向按改动成本从低到高排。6.1 换数据集从数字到字符、车牌、验证码改动集中在prepareData.m和两个常量。把numClasses从 10 改成实际类别数inputSize改成新数据的尺寸比如字符识别常用 32×32imresize的目标尺寸跟着变。如果新数据的类别不是文件夹名而是文件名里的前缀就把LabelSource换成自定义的readFcn在读取时解析文件名返回标签。要注意的是类别不平衡。手写数字十类样本量接近但换成字符集之后某些字符可能只有几百张另一些有几万张。这时候splitEachLabel按类切分会让稀有类的验证集只剩几十张classificationLayer建议加类别权重或者用imageDataAugmenter给稀有类做过采样。6.2 加深网络与数据增强网络加深最省事的做法是在buildNet.m里再叠一组“卷积 → BN → ReLU → 池化”但池化别加太多28×28 经过三次减半就剩 3×3特征图太小反而掉点。我的经验是 28×28 输入最多两次池化想加深就加卷积通道数而不是加池化层。数据增强对这条任务有用但有限因为手写数字的平移和轻微旋转是合理的大幅旋转就变成别的数字了6 和 9 别乱转aug imageDataAugmenter( ... RandRotation, [-8 8], ... % 小幅旋转别超过 10 度 RandXTranslation, [-2 2], ... % 像素级平移 RandYTranslation, [-2 2], ... RandScale, [0.95 1.05]); % 轻微缩放 imdsAug augmentedImageDatastore([28 28 1], imdsTrain, DataAugmentation, aug);6.3 导出部署ONNX、C 代码、独立函数exportONNXNetwork(net, digit.onnx)是最通用的出口导出的模型能进别的推理运行时。要在 MATLAB 生态里部署genFunction(net, digitPredict)会生成一个不依赖 Deep Learning Toolbox 的纯 MATLAB 函数配合 MATLAB Coder 可以生成 C/C如果再往上走就是打包成独立组件。导出前记得确认网络里没有 MATLAB 特有、目标运行时没有对应实现的层dropoutLayer和batchNormalizationLayer在导出时会被折叠或转换一般没问题但自定义层一定会卡住。6.4 一个验证方法固定种子跑三次改完超参之后别只看一次结果。rng(42)固定种子跑三次如果三次准确率方差超过 0.3 个百分点说明模型对初始化敏感这个改动不稳健别急着定稿。这条我吃过亏一次调完参数单跑出来 99.4%觉得成了结果换台机器复现只有 98.7%白高兴一场。从那以后我每次改超参都强制走一遍三次复现方差大的改动一律回退。希望这些能帮你少走点弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑