ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Matlab实战:用BP神经网络快速实现数据分类预测

Matlab实战:用BP神经网络快速实现数据分类预测 做数据分类这件事很多人第一反应是上Python、搭环境、装sklearn一套操作下来光折腾库就花了一下午。其实如果你的工作环境里本来就有Matlab或者你读研期间的课题组一直用Matlab做算法验证那么用BP神经网络做数据分类预测完全可以几分钟就把整条链路跑通。BP神经网络这个经典结构放到今天看依然很能打它不挑数据类型表格型特征拿来就能训练它也不需要显存和CUDA纯CPU跑几百上千个样本毫无压力Matlab的神经网络工具箱把前向传播、反向传播、梯度更新这些底层细节都封装好了你要做的只是把数据摆成它认识的形状。这篇文章不打算念教材。我会从原理讲起但重点放在怎么用Matlab把这个模型真正跑起来数据怎么整理、归一化怎么做、网络怎么建、训练完怎么看指标、遇到问题怎么排查。适合刚接触机器学习的Matlab用户也适合想在毕业论文或项目里快速出一个可解释分类结果的科研党。你可以直接照抄里面的代码骨架换成自己的数据就能用。1. BP神经网络凭什么能做分类预测1.1 从一次实际需求说起先想象一个具体场景你手里有500条设备运行记录每条记录包含温度、振动幅度、运行时长三个特征每条记录已经标注了设备状态是正常还是异常。现在来了50条新记录只有特征没有标注你要预测这些新设备的状态。这就是典型的有监督分类问题用带标签的历史数据训练模型让模型学会特征和标签之间的映射关系然后用它预测新数据。BP神经网络解决的就是这个映射问题而且它对这种表格型数据非常擅长不需要你手动设计复杂的判定规则。我最早接触BP网络是在一门模式识别课上当时觉得它玄乎后来自己动手写了一遍前向计算和反向求导才发现它本质上就是一堆加权求和套非线性函数然后通过误差反推每一层权重该往哪个方向调。Matlab里连这些都不用你写工具箱直接给你封装好了但你得知道背后发生了什么否则调参的时候会一头雾水。1.2 网络结构拆解三层网络是怎么完成分类的标准BP神经网络通常有三层输入层、隐藏层、输出层。输入层的节点数等于你每条数据的特征个数比如上面例子里的温度、振动、运行时长那就是3个输入节点。输出层的节点数取决于类别数量二分类可以只输出一个节点用一个阈值做判定多分类通常是每个类别一个节点最后取得分最高的那个。隐藏层是BP网络最核心的部分它的作用是把输入特征进行组合和非线性变换。你可以这么理解单个特征可能无法区分正常和异常设备比如温度高不一定坏振动大也不一定坏但温度高且振动大大概率就是坏了。隐藏层的神经元做的就是这种特征组合的工作层数越多、节点越多它能表达的组合方式就越复杂但也越容易把训练数据死记硬背下来这就是后话的过拟合问题。每个神经元内部做的事情很简单把上一层传过来的所有输出分别乘以对应权重再求和加上一个偏置然后扔进一个激活函数。分类问题里隐藏层常用tansig双曲正切或ReLU输出层如果是二分类会用logsigSigmoid多分类则用softmax把输出变成各类别的概率。用数学写出来就是z w1x1 w2x2 ba f(z)其中f就是激活函数。神经网络能拟合任意复杂函数靠的就是这些非线性激活函数一层层套下去。1.3 训练的本质误差反向传播在做什么网络初始化时权重是随机的预测结果当然一塌糊涂。训练过程就是不断调整权重让预测越来越准。具体分两步前向传播算预测结果算出预测和真实标签的误差反向传播根据误差计算每个权重的梯度然后用梯度下降更新权重。打个比方你站在山坡上要下山梯度就是当前脚下最陡的方向梯度下降就是朝着最陡的下坡方向迈一小步然后重新算梯度再迈步直到走到谷底。BP神经网络里的梯度是通过链式法则从输出层一层层往前传的这就是它名字里反向传播的由来。Matlab里训练函数trainlm、trainscg这些做的就是这件事往复迭代直到误差收敛或达到最大迭代次数。这里有个初学者常踩的坑分类问题该用什么误差函数。有人习惯性用了均方误差MSE但分类问题的输出是概率分布交叉熵误差函数在梯度上比MSE更匹配softmax输出层训练收敛更快、分类效果也更好。Matlab的patternnet网络默认帮你配好了交叉熵和softmax这也是我推荐用patternnet而不是自己拿feedforwardnet硬改的原因。2. Matlab环境准备与方案选型2.1 为什么用Matlab做BP神经网络我知道现在Python很流行但Matlab在神经网络原型验证上有一个不可替代的优势开箱即用。你不需要pip install任何东西Deep Learning Toolbox以前叫Neural Network Toolbox装好Matlab就有。数据是矩阵Matlab本身就是矩阵语言归一化用mapminmax一行搞定画图用plot、scatter、confusionchart都是现成的。另外Matlab的神经网络训练过程是可视化的训练窗口会实时显示梯度、误差变化、验证集表现这对理解模型训练状态特别有帮助。我在项目里经常用Matlab快速验证一个分类思路能不能走通确认可行之后再考虑要不要用Python做工程化部署。工具没有高下之分场景合适就是好工具。2.2 工具箱、版本与API怎么选做BP神经网络分类你需要确认你的Matlab装了Deep Learning Toolbox。在命令行输入ver能看到已安装的工具箱列表或者直接输入patternnet如果返回函数句柄说明工具箱可用。工具箱里跟BP相关的函数有好几个很多老教程还在用newff这是老版本遗留的语法新版本依然兼容但已经不推荐了。新版本推荐用feedforwardnet做通用前馈网络用patternnet专门做模式识别与分类。两者的区别在于patternnet会自动配置输出层的softmax和交叉熵损失适合分类feedforwardnet默认用MSE更适合回归拟合。所以做分类预测直接用patternnet是最省心的选择。注意如果你的Matlab版本比较老比如R2015a之前的版本有些新函数可能不存在这时可以用newff代替但数据格式和训练方式基本一致。2.3 分类问题的数据格式约定这一步是新手最容易懵的地方。Matlab神经网络工具箱对数据格式有严格约定输入矩阵的每一列是一个样本每一行是一个特征目标矩阵的每一列是一个样本的标签每一行是一个类别。举个例子如果你有100个样本每个样本3个特征那输入矩阵X的大小应该是3×100而不是100×3。很多从Python转过来的人习惯行是样本在这里直接翻车。目标矩阵T的大小是类别数×样本数二分类就是2×100每一列是类似[1;0]或[0;1]的形式这叫one-hot编码。手动写one-hot比较麻烦Matlab提供了ind2vec和vec2ind这对工具函数。ind2vec把类别索引向量转成one-hot矩阵vec2ind把网络输出的one-hot矩阵转回类别索引向量。比如你有100个样本的标签存储在向量label里取值是1或2那目标矩阵就是T ind2vec(label)。预测完得到输出矩阵Y再把类别索引取回来pred vec2ind(Y)。整个流程就是ind2vec进去vec2ind出来对称且好记。3. 完整代码实战从数据准备到结果评估下面进入正题。我以一个人造的二分类数据集为例完整演示一遍BP神经网络分类预测的流程。这个数据集有两个特征、两类样本方便可视化也能直观看到分类效果。你实际使用的时候把数据读入部分换成你自己的导入逻辑特征列和标签列对上即可。3.1 生成数据并划分训练集、验证集、测试集为了可复现先用rng设置随机种子。我用一个服从正态分布的两类点集第一类中心在(2,2)第二类中心在(-2,-2)每类300个点这样两类在二维平面上基本可分但边界有重叠比较接近真实场景。% 固定随机种子保证结果可复现 rng(2025); % 生成两类样本每类300个每个样本2个特征 numEachClass 300; X1 randn(numEachClass, 2) 2; % 第一类中心在(2,2) X2 randn(numEachClass, 2) - 2; % 第二类中心在(-2,-2) X [X1; X2]; % 600×2注意这里行是样本 label [ones(numEachClass,1); 2*ones(numEachClass,1)]; % 类别标签1和2 % 转置成工具箱要求的格式每列一个样本 X X; % 2×600 label label; % 1×600 % 随机划分训练集、验证集、测试集 [trainInd, valInd, testInd] dividerand(size(X, 2), 0.7, 0.15, 0.15);这里有个技巧不需要自己写打乱逻辑dividerand会随机划分索引。训练集70%验证集和测试集各15%。验证集的作用是训练过程中监测是否过拟合测试集则是训练全部结束后做最终评估测试集在训练过程中不能碰。3.2 归一化处理与标签编码神经网络对输入特征的尺度非常敏感。如果特征A取值范围是0到1特征B取值范围是100到10000那训练时B会主导梯度更新A可能直接被忽略。归一化把所有特征压到同一个尺度让每个特征对初始梯度的贡献大致相当。Matlab里mapminmax是首选它默认把数据映射到[-1,1]区间。这里有一个特别重要的坑归一化只能基于训练集的统计量最小值和最大值测试集归一化时必须复用训练集的统计量不能拿测试集自己的最小最大值重新算。否则相当于测试集信息泄露进了训练流程评估结果会虚高。% 用训练集拟合归一化参数再应用到训练集、验证集、测试集 [Xtrain, ps] mapminmax(X(:, trainInd)); Xval mapminmax(apply, X(:, valInd), ps); Xtest mapminmax(apply, X(:, testInd), ps); % 标签转成one-hot编码 Ttrain ind2vec(label(:, trainInd)); Tval ind2vec(label(:, valInd)); Ttest ind2vec(label(:, testInd));ps是保存下来的归一化参数包含每行的min和range预测新数据的时候也要用同一个ps做apply这一条记牢了能省很多排查时间。3.3 构建网络、设置参数并训练我选patternnet隐藏层用两个隐藏层节点数分别为10和5。为什么用两个隐藏层两个特征的问题其实一个隐藏层就够我用两层是为了演示多隐藏层的写法也方便你后面迁移到更复杂的数据。隐藏层节点数我在后面第4节单独讲怎么调。% 创建模式识别网络两个隐藏层节点数10和5 net patternnet([10 5]); % 训练函数选择 net.trainFcn trainscg; % 缩放共轭梯度法内存占用小中小数据集够用 % 关闭工具箱自带的随机划分改用我们手动划分的索引 net.divideFcn divideind; net.divideParam.trainInd trainInd; net.divideParam.valInd valInd; net.divideParam.testInd testInd; % 训练 [net, tr] train(net, X, ind2vec(label), useParallel, no);注意这里train函数的输入我直接用了原始X和原始label的one-hot编码这是因为divideind会在内部按trainInd等索引去取对应列。这个做法更简洁归一化和不归一化的数据在工具箱内部是等价的只要保证一致性就行。不过我个人的习惯是像3.2那样干脆手动把三个集合都拆出来然后用net train(net, Xtrain, Ttrain)直接训练这样最容易理解也方便打印中间结果。两种写法都对你按自己习惯来。设置net.trainParam可以控制迭代次数和误差阈值net.trainParam.epochs 1000; % 最大迭代次数 net.trainParam.goal 1e-5; % 目标误差 net.trainParam.min_grad 1e-6; % 最小梯度train完以后tr结构体里保存了训练过程中的误差变化、验证集表现、迭代次数等信息tr.bestEpoch是最佳迭代次数后面看训练曲线会用到。3.4 测试集预测与性能指标训练结束用sim或直接调用网络对象对测试集做预测。新版本更推荐用net(Xtest)这种函数式调用sim是老写法但依然可用。拿到输出Ytest后每一列是网络判断的各类别概率用vec2ind取概率最大的类别索引作为预测标签。% 对测试集做预测 Ytest net(Xtest); predLabel vec2ind(Ytest); trueLabel vec2ind(Ttest); % 计算准确率 accuracy sum(predLabel trueLabel) / numel(trueLabel) * 100; fprintf(测试集准确率%.2f%%\n, accuracy);准确率是最直观的指标但它只是开始。二分类问题建议再看混淆矩阵因为类别不平衡时准确率会骗人。比如95%都是负类模型全预测负类也有95%准确率但实际毫无意义。混淆矩阵能告诉你每个类别的查准率、查全率以及具体哪些样本被分错了。% 混淆矩阵 C confusionmat(trueLabel, predLabel); confusionchart(C, {类别1, 类别2});confusionchart会弹出一个图形窗口矩阵对角线是正确分类的数量非对角线是错误分类的数量一目了然。如果想看更细的指标比如每个类别的查准率、查全率、F1值可以用confusionmat结合公式自己算也可以直接用roc曲线看分类阈值的影响。工具箱里的plotroc可以画ROC曲线曲线下面积AUC越接近1说明分类器区分能力越强。代码很简单% 绘制ROC曲线 plotroc(net, Ttest, Ytest);3.5 训练过程与分类结果可视化训练结束后Matlab会自己弹训练窗口显示误差收敛曲线。但如果你想把这个图保存下来放到报告里可以用plotperform重新绘制% 训练误差、验证误差、测试误差随迭代次数的变化 plotperform(tr);这条曲线能直观地告诉你训练是否收敛。理想情况是三条曲线同步下降然后趋于平缓如果训练误差一直降、验证误差反而上升说明过拟合了这时候就该减少隐藏层节点数或提前停止训练。对于二维数据我强烈建议画一个分类效果散点图把测试集真实类别和预测类别分别标出来。这个图虽然不直接算指标但能让你一眼看出错误分类的样本长什么样是边界附近的模糊点还是离群点。% 可视化测试集分类结果 figure; gscatter(Xtest(1,:), Xtest(2,:), trueLabel, br, o); hold on; % 标出预测错误的样本 wrongIdx find(predLabel ~ trueLabel); plot(Xtest(1, wrongIdx), Xtest(2, wrongIdx), kx, MarkerSize, 12, LineWidth, 2); legend(真实类别1, 真实类别2, 预测错误);如果只追求一个最终数值那准确率一个指标就够了。但实际项目里可视化能帮你发现数据质量问题比如标签是不是标错了、特征是不是有噪声点这些在数值指标里很难察觉。把上面3.1到3.5的代码块按顺序拼到一个脚本里直接就能运行。我完整的跑了一次测试集准确率通常稳定在95%以上因为这两类数据的分布本身有交叠这也是一个符合实际的结果不是越高越真实95%就说明模型学到了数据规律且有少量不可分样本非常正常。4. 调参与避坑实录4.1 常见问题速查表我把自己用Matlab做BP分类时遇到的典型问题整理成了一张表。这些问题里有一半以上是我在帮别人看代码时发现的很多是数据格式问题而不是模型问题。问题现象常见原因解决方案提示未定义函数patternnet没装Deep Learning Toolbox或版本过老用ver检查工具箱老版本改用newff准确率一直在50%上下标签one-hot编码错误/归一化范围混用检查T矩阵每列是否只有一个1确认测试集用了训练集的ps训练误差很低测试误差很高过拟合减少隐藏层节点数增加验证集提前停止加大训练数据每次运行结果不一样权重随机初始化训练前用rng固定种子以复现实验结果预测结果全是同一个类别类别严重不平衡或特征无区分度检查标签分布考虑类别加权采样先画散点图看数据有没有分开训练不收敛误差反复震荡学习率过大调小学习率net.trainParam.lr换trainscg4.2 隐层节点数到底怎么定这是被问得最多的一个问题。很遗憾没有公式能一步算对因为最优节点数和数据本身的复杂度、样本量、特征数都有关。但有几个经验法则可以先顶着用单隐藏层节点数一般取输入特征数和输出类别数的中间值再往上加一点常见经验公式H sqrt(m n) a其中m是输入节点数n是输出节点数a取1到10之间的整数如果数据量不大几百个样本隐藏层节点数别贪多从3到5个试起逐渐往上加。我的实际做法是先用一个隐藏层节点数从3开始每次加2训练一轮看验证集准确率画一条节点数-准确率的曲线找到拐点。拐点之前准确率上升拐点之后开始过拟合准确率反而下降就取拐点附近的值。隐藏层数量也是同理。绝大多数表格型分类问题一个隐藏层就够用两个隐藏层能拟合更复杂的决策边界但需要更多数据。两个特征的玩具数据用多层没什么意义特征很多、样本量很大的场景才值得尝试深一点的网络。4.3 训练算法的选择与局部极小值patternnet默认的trainFcn是trainlmLevenberg-Marquardt这个算法收敛快、精度高但需要存储近似的二阶导数矩阵数据量大时内存消耗很恐怖。几千个样本以内trainlm表现很好样本一旦上了几万建议换成trainscg缩放共轭梯度法它只用一阶梯度信息内存占用小收敛速度也不慢。如果数据噪声大或者训练过程中验证误差反复横跳可以试试trainbr贝叶斯正则化。这个算法会自动约束权重的大小能有效抑制过拟合代价是训练时间更长。我在处理高维小样本数据时比较喜欢用它效果往往比手动调隐藏层节点数更省心。局部极小值是BP网络的经典痛点。梯度下降是贪心算法只能保证找到当前山头的谷底不保证是全局最低谷。实际表现就是同一份数据、同样的网络结构换一个随机种子最终准确率可能差几个点。解决办法很朴素多跑几次取验证集表现最好的那一次作为最终模型。我习惯写个循环对多个随机种子各训一次把测试集准确率都记下来取平均作为模型的真实水平而不是只报最好的一次。4.4 从二分类扩展到多分类与回归二分类跑通之后你很自然会遇到多分类问题。好消息是patternnet天然支持多分类只要类别数大于2输出层节点数就等于类别数标签用ind2vec生成对应的one-hot矩阵其他代码几乎不用改。唯一的变化是vec2ind返回的是1到K的索引K是类别总数。而如果任务从分类变成回归预测——比如预测设备的剩余寿命输出是连续值而不是类别——那就不能用patternnet了改回feedforwardnet输出层不用softmax误差函数用MSE最后预测出来的也是连续数值。做的事情和分类本质上一样只是输出层的“翻译方式”不同。再往后可以尝试把BP网络跟特征工程结合比如先用主成分分析PCA降维再进BP网络在特征冗余的情况下往往能提升泛化能力。Matlab里pca函数直接能用降维后的特征矩阵作为网络输入代码上的改动就是多一行而已。这个方法在处理高维表格数据时值得一试有时候效果提升比调网络结构还明显。BP神经网络在Matlab里做分类预测真正值钱的部分不是网络结构那几行代码而是你对数据的理解和对训练过程的判断。数据格式对不对、归一化一致性有没有保证、验证集有没有被偷看、过拟合有没有及时止损这些才是决定模型能不能用的关键。我见过有人把代码跑通就完事结果在真实数据上一塌糊涂回头排查发现是训练集和测试集的归一化参数混用了。我也见过有人花大量时间调网络结构最后发现是标签编码错位白白浪费了两天。所以我的建议很直白第一次跑通这篇文章的完整代码之后不要急着换数据集先把训练曲线反复看几遍把混淆矩阵里分错的样本找出来看看到底长什么样再手动改一改归一化方式、节点数、训练函数观察这些变化如何影响最终指标。这个过程走一遍你对BP神经网络的理解会比看十篇教程都管用。Matlab的好处是这些操作全部可视、可交互本身就是最好的学习环境。
返回列表