资讯动态

MATLAB六种神经网络分类对比:留出法与k折交叉验证的规范实现

发布时间:2026/10/19 7:17:04 来源:尧图企业网站定制
做模式识别和故障诊断方向的MATLAB用户十有八九都对着神经网络工具箱纠结过。界面上函数一大堆ANN、FFNN、CFNN、PNN、GRNN、LVQ……名字长得差不多翻文档翻半天也不确定到底该用哪一个。更常见的操作是随便选一种网络跑一遍留出法看一眼准确率然后换下一个网络再跑一遍最后把最高准确率写进报告。这个流程看起来没什么问题但数据划分的随机性已经混在结果里了单次高出两三个百分点根本说不清是模型真的变强还是运气变好。这篇文章要解决的就是把这个对比过程做规范。我以一份四分类的传感器工况特征数据为例8个特征、约600个样本用MATLAB把留出法和k折交叉验证完整跑通比较ANN、FFNN、CFNN、PNN、GRNN、LVQ六类神经网络的分类预测表现。代码可以直接改成自己的数据来用重点在于让你从“只看一次准确率”的思路里跳出来学会同时看平均水平和稳定性。适合正在做分类预测、故障诊断、模式识别相关工作的同学参考。1. 先想清楚一件事你跑出来的准确率能代表模型吗1.1 评估方式不统一结果根本没有可比性做网络对比实验最常见的翻车原因不是网络选得不好而是评估方式五花八门。A网络用了30%数据做测试B网络用了20%C网络跑的时候忘了固定随机种子D网络在训练时被train函数内部再切走了一部分验证集——最后把四个准确率放在一起排名这个排名基本没有意义。因为每个数字里都混入了太多与模型能力无关的变量。留出法的本质是一次性把数据切成训练集和测试集训练集用来拟合网络参数测试集用来模拟模型面对新样本的表现。好处是简单直接一次跑完特别适合快速试错。坏处也很明显换一次随机划分准确率可能上下浮动好几个点单次结果很难代表模型的真实水平。尤其是样本量不大的时候某一次划分里的测试集恰好包含较多困难样本准确率就会明显偏低反之则偏高。你很难判断这个结果是网络能力造成的还是划分运气造成的。k折交叉验证的思路完全不同把数据均匀分成k份轮流拿出其中1份做测试剩下k-1份做训练重复k次之后取平均。每一次的训练集和测试集都不同相当于在k组不同的数据条件下做k次考试最后看平均分。这样一来单次划分带来的偶然性会被大幅摊平而且每一份数据都被当过测试样本数据利用率也比留出法高得多。用一个生活化的比喻留出法像一次抽考可能抽到容易的题也可能抽到难的题k折交叉验证像把整本书的内容分多次考最后取平均分。后者显然更能反映真实掌握程度。1.2 留出法和k折交叉验证到底怎么选评估策略不是越复杂越好关键看数据量和刚才所处的阶段。数据量很大比如几万、几十万样本模型训练又慢硬跑10折交叉验证会非常耗时留出法已经完全够用。如果数据集只有几百到几千条记录建议改用k折交叉验证k5或者10都可以。样本更少时比如一两百条我会直接用10折甚至考虑留一法虽然慢但数据利用率最高结论更稳。k值的选择也要讲道理。5折意味着每折用80%的数据训练10折则用90%后者训练集更大模型偏差更小但训练次数翻倍耗时接近两倍。几百条数据用10折不吃亏几千条以上用5折往往更实际。还有一点非常关键分类问题必须保证每一折里各类样本的比例和总体一致这叫分层抽样。MATLAB的cvpartition函数对分组变量默认做分层不需要自己写抽样逻辑但在自己写randperm手动切分时很容易忽略这一点。评估方式优点缺点适用场景留出法简单快速一次跑完单次结果波动大数据利用率低数据量大、快速试错、粗筛网络k折交叉验证稳定可靠平均结果可信度高计算量大实现稍复杂中小样本、最终评级、模型选择2. 实验设计六种网络怎么选对比怎么做才公平2.1 数据集与评估基准设计先说实验数据。我用的是一份自己整理的四分类工况特征数据每一条样本包含8个特征一共约600条记录类别分布基本均衡。这个数据规模和常见故障诊断课题差不多。特征不需要手工额外处理但流程里必须做Z-score归一化否则特征量纲不同会让网络训练偏向量纲大的维度。评估基准上留出法按70%训练、30%测试划分交叉验证用5折。为什么选70/30而不是80/20训练样本少一点测试集覆盖的样本多一点对判别模型能力的压力会更大但差别不会太大。5折的理由上面说过600条样本算中等规模5折每折训练集有480条足够训练浅层网络同时计算成本比10折低不少。划分的随机种子固定为42确保六种网络面对的是同一份数据后面所有代码都能复现。2.2 六种网络选型与MATLAB映射选这六个网络不是因为它们在工具箱里按字母排在前几行而是因为它们的结构差异足够有代表性覆盖了从简单前馈到概率估计再到原型聚类的主要技术路线。ANN人工神经网络这里具体指单隐层前馈网络用feedforwardnet(10)创建10是隐藏层神经元数。这是最经典的BP网络结构简单训练快作为基线网络非常合适。FFNN前馈神经网络这里具体指双隐层前馈网络用feedforwardnet([10 10])创建。相比ANN只多了一层但拟合非线性边界的能力会更强同时也更容易过拟合。CFNN级联前馈神经网络用cascadeforwardnet([10 10])创建。它与前馈网络最大的区别是每一层不仅接收前一层的输出还接收原始输入和更早层的输出信息通路更多拟合能力更强代价是参数更多、过拟合风险更高。PNN概率神经网络用newpnn创建基于Parzen窗估计各类别的条件概率密度模型一次构建完成没有迭代训练的概念适合小样本预测时对所有训练样本做距离计算。GRNN广义回归神经网络用newgrnn创建本质上是回归模型通常用来拟合连续值但同样可以用于分类对每个类别输出一个连续值取最大者作为类别。LVQ学习矢量量化网络用newlvq创建本质是在类别原型向量的基础上做有监督聚类训练速度非常快但决策边界相对简单。网络MATLAB核心函数是否需要迭代训练结构特点ANNfeedforwardnet(10)是单隐层BP网络基线模型FFNNfeedforwardnet([10 10])是双隐层前馈网络拟合能力更强CFNNcascadeforwardnet([10 10])是级联结构信息通路多易过拟合PNNnewpnn否核密度估计一次建模GRNNnewgrnn否广义回归输出连续值再判类LVQnewlvq是原型聚类训练快边界简单2.3 让对比公平的三个关键约束六个网络放在一起比有一个隐藏前提除了网络结构其他条件必须保持一致。实际做下来有三个约束特别重要。第一所有网络必须使用完全同一种数据划分。留出法里六个网络共用同一个训练/测试索引交叉验证里同一个折的划分对六个网络完全一致。如果各自随机划分结果差异里就混入了数据分布差异实验等于白做。第二关闭train函数内部的数据重划分。这是个非常深的坑。feedforwardnet默认会把你传进去的训练集再按0.7/0.15/0.15的比例划分成训练/验证/测试三份也就是说你手动留出的测试集根本没有参与train的验证过程同时你的训练集又被偷偷挖走一部分。做交叉验证的时候这个问题会被放大后面我会专门讲。第三统一随机种子。可训练网络ANN、FFNN、CFNN、LVQ的初始权值由随机数决定如果每个网络用不同的随机种子初始点不同就可能收敛到完全不同的局部最优解。我在每折里用rng(fold)固定种子这样能把差异尽量归因到网络结构上而不是初始运气上。3. 留出法与k折交叉验证的MATLAB完整实现3.1 先搭好数据划分和归一化框架无论哪种网络数据处理流程都完全相同。假设工作区里有X和Y两个变量X是600乘以8的特征矩阵每行一个样本Y是600乘以1的标签向量取值1、2、3、4。% 留出法70%训练30%测试 rng(42); cv_h cvpartition(Y, HoldOut, 0.3); trIdx training(cv_h); teIdx test(cv_h); X_tr X(trIdx, :); Y_tr Y(trIdx, :); X_te X(teIdx, :); Y_te Y(teIdx, :);这里有个新手很容易忽略的点cvpartition返回的逻辑索引必须用training(cv_h)和test(cv_h)来取直接访问属性在新老版本里写法不一致用函数调用最稳。划分完成后归一化只允许在训练集上统计均值和标准差测试集直接沿用训练集的参数不能重新计算。这一步的严谨程度直接决定交叉验证结果是否可信。mu_X mean(X_tr, 1); std_X std(X_tr, 0, 1); std_X(std_X eps) 1; % 防止某列标准差为0导致除零 X_tr (X_tr - mu_X) ./ std_X; X_te (X_te - mu_X) ./ std_X;交叉验证的框架也一样只是有一个额外的要求每一折都必须重新做归一化因为每一折的训练集不同。先把所有数据整体归一化再进交叉验证属于典型的数据泄漏会让结果虚高。k 5; cv_k cvpartition(Y, KFold, k); acc_mat zeros(k, 6); for fold 1:k trIdx training(cv_k, fold); teIdx test(cv_k, fold); X_tr X(trIdx, :); Y_tr Y(trIdx, :); X_te X(teIdx, :); Y_te Y(teIdx, :); mu_X mean(X_tr, 1); std_X std(X_tr, 0, 1); std_X(std_X eps) 1; X_tr (X_tr - mu_X) ./ std_X; X_te (X_te - mu_X) ./ std_X; rng(fold); % 每折固定随机种子 % ...这里依次训练六种网络把准确率填到 acc_mat(fold, 1:6) end3.2 ANN、FFNN、CFNN三个训练式网络的实现三个训练式网络的训练代码几乎一样只是创建网络的那一行不同所以我把它封装成一个函数。这里每个网络都使用trainscg作为训练函数同时强制关闭train内部的数据划分。function acc eval_feedforward(net, X_tr, Y_tr, X_te, Y_te) net.trainFcn trainscg; net.divideFcn ; % 关闭内部自动划分 net.trainParam.showWindow false; % 不弹训练界面 net.trainParam.showCommandLine false; net.trainParam.epochs 300; net train(net, X_tr, ind2vec(Y_tr)); pred net(X_te); pred_label vec2ind(pred); acc mean(pred_label Y_te); end为什么统一用trainscg而不是MATLAB默认的trainlm因为Levenberg-Marquardt算法在小数据集上收敛很快但容易出现内存问题而且对初始权值更敏感trainscg是共轭梯度法不给梯度内存占用小稳定性更好。六个网络用同一个训练函数才不至于把“网络结构差异”和“训练算法差异”混在一起。调用方式很简单net_ann feedforwardnet(10); acc_ann eval_feedforward(net_ann, X_tr, Y_tr, X_te, Y_te); net_ffnn feedforwardnet([10 10]); acc_ffnn eval_feedforward(net_ffnn, X_tr, Y_tr, X_te, Y_te); net_cfnn cascadeforwardnet([10 10]); acc_cfnn eval_feedforward(net_cfnn, X_tr, Y_tr, X_te, Y_te);这里反复强调一下输入格式。MATLAB神经网络工具箱的train函数要求的输入矩阵是“特征乘以样本”的形状也就是每一列是一个样本。习惯用“样本乘以特征”格式的读者十有八九会在这里报维度错误训练集X_tr是600乘以8必须转置成8乘以600再传入。标签部分先用Y_tr转成行向量再用ind2vec转成one-hot矩阵。这些看起来都是小细节但任何一个出错程序都跑不下去。3.3 PNN、GRNN、LVQ三个非迭代或低迭代网络的实现PNN和GRNN不需要调用train构建网络时直接传入训练数据即可。二者都有一个关键参数spread控制径向基函数的扩展速度。spread太小每个训练样本的影响范围极窄容易过拟合太大所有样本都糊成一片边界太平滑。我在这份数据上先用0.8试跑更严谨的用法是套一个小型网格搜索用验证集或内层交叉验证去选值。spread 0.8; net_pnn newpnn(X_tr, ind2vec(Y_tr), spread); acc_pnn mean(vec2ind(net_pnn(X_te)) Y_te); net_grnn newgrnn(X_tr, ind2vec(Y_tr), spread); acc_grnn mean(vec2ind(net_grnn(X_te)) Y_te);有人会问GRNN是回归网络拿来做分类不专业吧实际做法是把它当作多输出回归对每个类别输出一个连续值数值越大代表属于该类别的倾向越强取最大值对应的类别作为预测结果。这份数据上效果不差尤其在小样本情况下GRNN比训练式网络更稳因为它的结构完全由训练样本决定没有随机初始化的过程。LVQ需要给每个类别指定原型向量的比例。四类样本均衡时直接平均分配net_lvq newlvq(X_tr, ind2vec(Y_tr), 0.1, [0.25 0.25 0.25 0.25]); net_lvq.trainParam.showWindow false; net_lvq.trainParam.epochs 200; net_lvq train(net_lvq, X_tr, ind2vec(Y_tr)); acc_lvq mean(vec2ind(net_lvq(X_te)) Y_te);第三个参数0.1是学习率第四个参数是各类别原型比例之和为1。如果类别不平衡最好按样本比例设置否则少数类可能分不到足够的原型向量。LVQ相比前面几种网络的逻辑更简单决策边界是分段线性的所以在这类工况识别数据上往往不是最优但它训练极快适合做上界参考或者基线对比。4. 结果读法准确率、标准差哪个更能说明问题4.1 示例结果与关键差异解读我在自己这份四分类数据上跑出来的结果大致如下网络留出法准确率5折交叉验证平均准确率标准差ANN88.5%87.2%±2.8%FFNN90.1%88.0%±3.1%CFNN91.7%88.9%±4.2%PNN89.3%89.0%±1.9%GRNN90.8%89.3%±2.4%LVQ86.2%84.9%±3.3%先把话说清楚这只是我这组示例数据上的结果换一份数据完全可能不一样千万不要拿这个表格当成“CFNN一定最好”的证据。但表格反映出的规律有很强的共性。CFNN的留出法准确率最高91.7%但5折均值只有88.9%标准差4.2%也是六种网络里最大的。这个现象很典型结构越复杂拟合能力越强单次划分下容易冲到高分但对数据划分越敏感换个折就可能掉到84%以下。也就是说CFNN的高分里有不少运气的成分。PNN和GRNN的标准差明显更小因为这两种网络不需要随机初始化也没有迭代收敛过程输出完全由训练样本决定天然稳定。GRNN的交叉验证平均准确率最高而且标准差在可接受范围从“稳定优秀”这个角度看它反而是这份数据上最值得优先考虑的模型。LVQ垫底并不意外。原型聚类的决策边界太简单面对类别边界比较复杂的工况数据表现天然受限。不过它的训练速度极快如果数据量扩大十倍再比一次LVQ不一定还垫底。4.2 留出法单次结果和交叉验证结果为什么对不上对比表里能清楚地看到一件事留出法排名第一的CFNN在交叉验证排名里掉到了第三留出法第三的GRNN交叉验证时变成了第一。这不是程序写错了而是单次结果天然包含抽样误差。留出法只用一次划分测试集只有约180个样本只要其中几个困难样本换一批准确率就能波动两个百分点以上。交叉验证的5次结果平均之后偶然性被摊平平均准确率更接近模型的真实泛化水平标准差则告诉你有多少置信度。所以看结果时我的习惯是先看平均准确率的高低再看标准差的大小准确率差距在1个百分点以内、而标准差超过2个百分点时基本可以判定两个模型没有显著差别排名上的先后没有实际意义。这也是为什么论文和报告里不能只写一个“最高准确率”。一个模型如果最好成绩是91.7%但波动范围到±4.2%那它在生产环境中的表现很难保证另一个模型虽然最好成绩只有90.8%但稳定在89%左右反而更可靠。5. 高频坑排查MATLAB跑分类网络的实测经验5.1 train函数偷偷把数据再划分成三类这是我在帮朋友排查代码时遇到最多的问题。很多人手动把数据切好了传给train(net, X_train, Y_train)以为训练集就是全部训练样本。但feedforwardnet创建的网络默认divideFcn是dividerand也就是会把你传进去的训练数据再次按比例切开一部分用于训练一部分用于验证一部分用于测试。后果是什么你训练集的有效规模变小了而且train函数汇报的验证集准确率和你自己留出的测试集准确率完全对应不上。在交叉验证场景里问题更严重每一折的训练集又被切走了一块所谓的5折交叉验证实际上变成了一个无法解释的混合流程。解决办法就是前面代码里的那行net.divideFcn 。如果你确实想在训练中保留一个验证集来观察过拟合那应该显式设置划分比例比如net.divideParam.trainRatio 0.8; net.divideParam.valRatio 0.2; net.divideParam.testRatio 0;并且不要再用它做最终测试评估。5.2 归一化泄漏整体归一化再划分是错的我在不少开源代码里看到过这种写法先把所有X用zscore归一化然后再做留出法或者交叉验证。这个流程在某些机器学习库里可能没问题但在严谨评估中属于数据泄漏因为它让测试集的均值和标准差参与了训练前的统计计算相当于模型在测试时偷偷看到了训练集之外的宏观信息。正确做法必须是在划分完成之后只对训练集计算mu和std测试集用同一组参数做变换。交叉验证时更要注意每一折都要重新计算不能沿用第一折的归一化参数。5.3 训练弹窗、训练函数选错、标签格式MATLAB训练浅层网络时会弹出神经网络训练界面批量跑六种网络乘以5折就是30次训练弹窗能直接把电脑卡出交互事故。记得用net.trainParam.showWindow false关掉界面。命令行里的迭代日志也建议关掉showCommandLine false不然输出窗口会被刷屏。训练函数的选择要慎重。trainlm是默认值小数据上收敛快但容易过拟合且内存占用高trainscg是更保守的选择。如果换一种训练函数对比结果可能完全反转所以同一个实验里的所有可训练网络务必统一训练函数。标签格式的问题集中在ind2vec。如果Y是600乘以1的列向量一定要先转置成1乘以600的行向量再传给ind2vec否则返回的矩阵维度不是期望的类别数乘以样本数。另外如果标签不是从1开始连续的整数比如有0和2而没有1ind2vec会生成多余的维度导致输出层神经元数量和类别数对不上。建议在进网络之前先把标签映射到1到N的连续整数区间。6. 个人经验评估流程怎么落地还能怎么扩展6.1 什么时候该用留出法、什么时候该上交叉验证做实际课题时我通常把流程分成两段。调参和粗筛阶段用留出法因为快能迅速排除明显不合适的网络和参数组合到了确定最终模型、写结论的阶段再用k折交叉验证做一次严格评级。这个顺序能兼顾效率与可靠性。具体到数据规模几百到几千条样本5折或10折交叉验证都行样本极少时直接用留一法上万条样本留出法多跑几次取平均比强行做10折更划算。还有一点经验输出结果时不要只报平均准确率把标准差一并报出来。许多评审和读者关心的正是这个波动范围。6.2 这套框架还能怎么扩展这套程序最大的价值是骨架往里填东西很容易。想加特征选择可以在归一化之后、训练之前加一步PCA或基于相关性的筛选想调超参数把神经元数量、spread、训练函数做成两层循环用验证集选最好的组合想换评估指标把准确率换成宏平均F1或者AUC只需在预测之后多写两行。如果数据本身是时间序列比如振动波形可以在这套框架里尝试LSTM或GRU。不过MATLAB里深度网络的训练接口和浅层网络完全不同需要构造序列化输入用trainNetwork来训练计算成本也高不少。对于表格型特征数据浅层网络往往已经够用没必要为了赶时髦强行上深度结构。最后再分享一点我个人的体会神经网络分类预测里模型做到85分不难难的是稳定地保持在85分以上。留出法让你看到模型最好的一面交叉验证让你看到模型平时的样子。跑实验的时候不要只盯着最高准确率多看一眼标准差多固定一次随机种子你的结论会比大多数教程里的结论扎实得多。

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

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

免费获取报价 →
↑