ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MATLAB实现BP神经网络回归预测:从数据归一化到模型评估

MATLAB实现BP神经网络回归预测:从数据归一化到模型评估 你把标题丢给我的时候我第一反应是又一位被BP神经网络四个字吓住的初学者。实际上这东西在MATLAB里做回归预测真没有传说中那么玄乎。很多教程一上来就甩出一堆公式和晦涩术语弄得大家以为要先把反向传播的数学推导背下来才能跑通一个模型。其实完全没有必要MATLAB把大部分底层细节都封装好了你要做的是理解数据怎么进、模型怎么配、结果怎么看。这篇东西就是给你用的。只要你会写最基础的MATLAB脚本哪怕没写过一行神经网络代码只要按着下面的流程走一遍就能在几十分钟内跑通一个属于自己的BP回归预测模型。我尽量把每一步为什么这么做讲清楚而不是让你死记硬背代码。1. 为什么从BP网络开始做回归预测MATLAB又是怎么帮你省事的1.1 BP网络在回归预测里的定位是什么BP神经网络的BP指的是反向传播也就是把预测误差从输出端一层层传回网络中间层不断调整各层之间的连接权重。它在回归任务里做的事情说白了就是做一个非线性映射给你一批输入特征和对应的输出值它自己学着拟合两者之间的复杂关系。比如你输入温度、湿度、风速想让模型预测光伏发电功率这就是一个典型的回归预测问题。回归预测和分类最大的区别在于输出值。分类的标签是离散的比如猫还是狗回归的输出则是连续数值比如明天的销量是105.3件或者电压是220.5V。很多人第一次用BP网络时会把分类任务的思路直接套在回归上最后发现结果怎么都不对原因多半就出在对输出层的设计上。回归任务的输出层通常只需要一个节点如果是单变量预测并且不需要softmax这类东西直接输出预测数值即可。1.2 为什么初学者优先考虑MATLAB而不是Python我不否认Python在深度学习领域的生态很强大但对于第一次接触BP网络、又不想折腾环境配置的初学者来说MATLAB有一个极其突出的优势数据集和模型之间的接口被简化到了接近傻瓜操作的程度。你不需要自己手写反向传播的矩阵求导不需要纠结PyTorch版本和CUDA版本是否匹配也不用在Anaconda里新建环境配依赖。MATLAB自带的神经网络工具箱把训练过程封装成了一个相对完整的流程输入数据、设定隐藏层规模、选择训练算法、拿到训练结果绘图。整个过程只需要一行一行的脚本代码每行做什么都看得见摸得着特别适合建立直觉。等你真的跑通了一个模型再回去看BP的公式推导你会发现以前的困惑大多迎刃而解。我一直建议初学阶段选这种封装度高的工具去建立全局观而不是一上来就钻进底层细节里出不来。用MATLAB还有一个隐蔽的好处是调试成本低。网格搜索、数据切片、归一化处理、绘图对比这些操作在MATLAB里的语法非常直观甚至某些操作可以比Python少写一半代码。对于仿真数据、小样本预测这类场景MATLAB在本机就能快速完成整个流程不需要依赖云服务器。2. 动手写代码之前先把这三件事想清楚2.1 数据预处理才是预测效果的半条命我见过太多新手拿到数据直接丢进train()就跑然后看着惨不忍睹的预测曲线来问我是不是网络结构有问题。多数情况下网络结构没什么问题问题出在数据没有做归一化或者数据的输入输出顺序不对。为什么要归一化BP网络里各层之间的连接权重在训练时靠的是梯度下降更新而梯度的大小和输入数据的量纲密切相关。如果你的第一维特征取值范围是0到1第二维特征取值范围是几百到上千那么训练过程中大数值特征会主导梯度的方向模型很难学到真正有用的规律。解决方法是把输入和输出数据都压缩到同一个范围最常用的是映射到[-1, 1]或[0, 1]。在MATLAB里归一化可以调用mapminmax这个函数也可以直接用简单的算术计算。比如把数据转换到[-1,1]公式是y_norm (y - min(y)) / (max(y) - min(y)) * 2 - 1;不过我更推荐用mapminmax因为它在训练完之后还能非常方便地把预测结果还原回原始数值范围不会出现自己写归一化公式时忘了保存最大值最小值的情况。数据预处理还有一件容易被忽略的事输入特征矩阵的排列方向。MATLAB神经网络工具箱对数据格式是有要求的它期望的输入矩阵通常是特征数 × 样本数也就是每一列是一个样本每一行是一个特征。很多人习惯把数据整理成样本数 × 特征数的表格直接喂进去就会报维度错误或者出莫名其妙的结果。这个坑我踩过不止一次每次调试半天才发现问题出在矩阵转置。2.2 隐藏层到底设几层、每层几个神经元先说结论再做解释对于绝大多数回归预测问题一个隐藏层就够用了。BP网络只要有足够多的神经元单隐藏层就可以拟合任意连续函数这是已经被证明过的万能逼近性质。初学者完全不需要一上来就堆两个隐藏层、三个隐藏层模型复杂度上去了训练难度和过拟合风险也会跟着上去。隐藏层神经元数量该怎么定大家都听过经验公式这个词但网上的公式版本多得能绕地球一圈。我自己常用的经验做法是先从输入特征数量的一倍到两倍开始试。比如你有5个输入特征那就先用10个隐藏层神经元跑一轮看测试集上的误差如果训练集拟合得很好但测试集误差大说明过拟合了把神经元数量调小如果训练集和测试集误差都大说明欠拟合把数量调大。需要注意的是隐藏层神经元数量不是越大越好。我有一次做仿真数据预测把隐藏层神经元从10个加到50个训练集误差倒是下降到几乎为0但一拿测试集验证预测曲线抖得跟地震仪似的。这就是典型的过拟合网络把训练数据里的噪声也当成规律记下来了。激活函数的选择也比较固定隐藏层一般用双曲正切函数tansig输出层在回归任务里用线性函数purelin。这个组合是回归预测场景中最稳的方案。原因在于输出层如果也用非线性激活函数就会强制限制输出范围你预测的数值可能落在[0,1]或[-1,1]区间内还得再还原多此一举直接用线性输出反而让网络可以拟合任意量纲的目标值。2.3 训练算法怎么选学习率怎么设MATLAB里常见的训练算法包括trainlmLevenberg-Marquardt、trainbr贝叶斯正则化、traingd梯度下降法。对于中等规模和小规模数据trainlm通常是默认首选它收敛速度快精度也高适合回归拟合。我个人的经验是样本量在几千条以内时trainlm都很好用。但trainlm有一个不那么友好的地方对内存的消耗比较大数据量如果到了几万条计算量会明显上涨。对于小样本仿真数据来说这完全不是问题。如果你做的是大数据量的预测可以考虑trainscg它收敛速度也不错内存占用更小。学习率在网络训练中是控制每一步更新权重的大小的。学习率太高权重更新步子迈得太大损失函数容易出现震荡就是训练误差忽高忽低学习率太低训练过程特别慢跑了半天误差还在原地打转。MATLAB里默认学习率是0.01这是很多场景都能用的安全值。如果你用trainlm这类算法其实不用太操心学习率问题因为它们内部有自适应的调整机制如果你用了传统梯度下降那就要自己多试几个数量级比如0.1、0.01、0.001各跑一轮对比训练曲线。3. MATLAB最容易上手的三种BP实现路径3.1 直接调用feedforwardnet五分钟跑通从R2010b之后MATLAB就开始推荐用feedforwardnet替代老旧的newff。如果你想快速验证一个想法feedforwardnet是最省事的方式。我拿一个典型例子演示一遍完整流程。假设你的数据存在inputData这个变量里每一列是一个样本outputData每一列是对应的目标值% 数据归一化 [inputNorm, inputPS] mapminmax(inputData, -1, 1); [outputNorm, outputPS] mapminmax(outputData, -1, 1); % 创建BP网络隐藏层设为10个神经元 net feedforwardnet(10, trainlm); % 设定隐藏层和输出层激活函数 net.layers{1}.transferFcn tansig; net.layers{2}.transferFcn purelin; % 划分训练集、验证集、测试集 net.divideParam.trainRatio 0.7; net.divideParam.valRatio 0.15; net.divideParam.testRatio 0.15; % 训练网络 [net, tr] train(net, inputNorm, outputNorm); % 用训练好的网络预测全部数据 predNorm net(inputNorm); % 反归一化 pred mapminmax(reverse, predNorm, outputPS); % 计算误差 error outputData - pred;你看核心就这几行。feedforwardnet(10, trainlm)里的第一个参数是隐藏层神经元数量第二个参数是训练算法。训练完之后的pred net(inputNorm)是网络的前向预测非常直观。我要特别提醒一下验证集的作用。trainlm在训练过程中会拿验证集误差来防止过拟合如果验证集误差连续上升若干次训练就会自动停止。这是MATLAB十分重要的一个内置机制。很多新手会问我不是只分训练集和测试集吗为什么训练函数会自动给我分三份——因为默认的divideParam设置了验证集比例。这个设计很合理因为神经网络训练时不能只盯着训练集调参否则很容易过拟合。3.2 老版本兼容写法newff与fitnet如果你用的是旧版本MATLAB或者网上拷贝的代码还是newff开头的也不用慌。newff的写法稍微传统一些但逻辑更直白net newff(inputNorm, outputNorm, [10], {tansig purelin}, trainlm);其中[10]表示一个隐藏层里有10个神经元{tansig purelin}是隐藏层和输出层的激活函数最后的trainlm是训练算法。还有一个我经常推荐给回归任务的函数是fitnet。它专门面向函数拟合和回归和feedforwardnet在核心结构上非常接近但默认参数在回归场景下更友好。如果你确定自己只需要做单输出的回归预测直接用fitnet就行net fitnet(10); [net, tr] train(net, inputNorm, outputNorm);fitnet和feedforwardnet的区别在实际使用中不大但fitnet对回归任务做了些默认配置上的优化比如输出的处理方式更贴合连续值的预测。不管是feedforwardnet还是fitnet训练完之后的预测和误差计算流程是一模一样的。3.3 想彻底理解BP自己动手把前向和反向写一遍工具箱用顺手之后我强烈建议你找时间手写一次BP的核心逻辑。这不是为了在生产环境里替代工具箱而是为了建立网络内部到底在算什么的直觉。我在学习阶段花了一个晚上用MATLAB手写了带一个隐藏层的BP回归代码从那以后工具箱里的那些参数设置对我而言就变成了一目了然的东西。核心逻辑其实就三块前向传播算预测值反向传播算梯度然后按梯度更新权重。前向传播在MATLAB里用矩阵乘法就能实现z1 W1 * input b1; % 隐藏层加权输入 a1 tansig(z1); % 隐藏层激活输出 z2 W2 * a1 b2; % 输出层加权输入 yPred z2; % 线性输出回归任务反向传播稍微复杂一点核心是用链式法则求损失函数对每一层权重的偏导数。如果你现在看不懂没问题这不影响你先把前面的工具箱流程跑通。但等你对预测结果有了一定的把握和理解再回头啃这些数学推导你就会明白为什么输出层用线性激活函数、隐藏层用tansig——因为这样设置之后梯度计算最简洁、训练最容易收敛。4. 训练到评估的完整闭环数据划分、指标计算与可视化4.1 数据划分策略随机划分和按时间划分不一样回归预测的数据划分方式取决于数据本身的来源。如果你的数据是独立的样本数据比如一批实验测量得到的不同条件下的结果那么随机划分三份就完全没问题。MATLAB默认的dividerand就是随机划分。但如果你的数据是时间序列数据比如用过去几天的温度序列去预测未来一天的温度就得格外小心了。随机划分时间序列数据会造成未来信息泄露比如用第100天的数据做训练第101天的数据做测试而模型在训练时已经偷看过第101天附近的统计学特征了测试结果会虚高。这时候需要按时间顺序划分前70%做训练中间15%做验证最后15%做测试。MATLAB里可以设置划分函数为divideblock这样它会按顺序取数据块而不是随机取样net.divideFcn divideblock; net.divideParam.trainRatio 0.7; net.divideParam.valRatio 0.15; net.divideParam.testRatio 0.15;很多初学者在这里踩坑拿时间序列数据用默认的随机划分测试集上表现好到离谱然后就以为自己做出了一个神级预测模型结果真正去预测未来数据时一塌糊涂。这不是网络不行是数据划分方式从一开始就不对。4.2 三个评估指标R²、RMSE、MAE怎么看训练跑完光看训练误差下降曲线是不够的。你需要评估模型在独立测试集上的泛化能力。我常用的指标有三个第一个是均方根误差RMSE。它把预测误差平方后取平均再开方数值越小代表预测越准。RMSE对较大误差非常敏感如果预测结果里偶尔出现一个离谱的偏差RMSE会立刻变大。第二个是平均绝对误差MAE。它直接计算误差绝对值的平均值不像RMSE那样对大误差给予过高的权重更贴近实际误差的平均情况。第三个是决定系数R²这个指标非常直观地告诉你模型解释了目标值多少比例的方差。R²等于1是最好的情况表示预测值完全等于真实值R²等于0表示模型还不如直接拿平均值去预测R²为负数就说明模型拟合效果比拿平均值预测还差。在MATLAB里计算这三个指标非常方便RMSE sqrt(mean((outputData - pred).^2)); MAE mean(abs(outputData - pred)); SS_res sum((outputData - pred).^2); SS_tot sum((outputData - mean(outputData)).^2); R2 1 - SS_res / SS_tot;我一般会在脚本里把这三个指标一起打印出来再画三张图预测值与真实值对比的散点图、误差分布直方图、还有训练过程的均方误差下降曲线。这三张图能让你对模型的状态有个全面的判断。预测值与真实值的散点图画法plot(outputData, pred, o); hold on; plot([min(outputData), max(outputData)], [min(outputData), max(outputData)], r-); xlabel(真实值); ylabel(预测值); axis equal;如果散点都紧密贴合那条45度参考线说明预测效果很好如果散点在一个方向系统性偏离比如真实值越大预测值越小说明模型可能存在欠拟合或者数据分布有问题。4.3 训练过程曲线怎么读训练完成后tr这个变量里保存了训练过程的所有信息。tr.perf是每一次迭代在训练集上的均方误差tr.vperf是验证集上的均方误差tr.tperf是测试集上的均方误差。画出来看plot(tr.perf, b-); hold on; plot(tr.vperf, r-); plot(tr.tperf, g-); legend(训练集, 验证集, 测试集); xlabel(迭代次数); ylabel(均方误差);正常情况下三条曲线是逐渐下降并趋于平稳的。如果你看到训练集误差一直下降但验证集误差下降一段时间后开始反弹上升这个拐点就是过拟合的开始。MATLAB的trainlm通常会在验证集误差连续上升6次时自动停止训练避免过拟合继续恶化这个默认机制非常有用。5. 常见报错和预测效果差的排查实录5.1 六个常见问题速查表我用表格整理一下初学者最常遇到的问题方便你对照排查。现象可能原因排查方法运行时报错输入数据维度不正确输入矩阵应为特征数×样本数被弄反了检查size用data转置预测值几乎是常数网络没有学到有效特征检查是否归一化出错、输入输出对应关系是否错位训练集效果很好测试集效果差过拟合减少神经元数量、增大数据量、用早停机制训练误差下降非常慢学习率太低或数据量纲差异太大改用trainlm检查归一化验证集误差震荡不收敛学习率太高或训练算法不合适调低学习率换trainscgR²为负数数据划分方式错误或模型严重欠拟合按时间顺序划分数据增加神经元数量5.2 我真实踩过的一个教训数据泄露问题有一次我拿一批仿真数据做预测数据本身是由某个物理公式生成的样本点我为了让训练集均匀覆盖整个输入空间特意用网格抽样的方式挑选了训练样本。结果测试集R²直接接近1我一度觉得自己调参天赋异禀。后来仔细一想问题出在训练样本和测试样本来自同一个网格分布训练集几乎已经把整个输入空间的规律背下来了测试集再怎么测都准得离谱。这个案例想说明的是回归预测模型评估的可信度完全取决于测试集是否真的独立。如果你的训练样本和测试样本存在强相关性任何模型的表现都是虚高的。在真实场景里做预测训练集和未来数据永远不可能完全同分布所以千万别被测试集上的漂亮数字冲昏头脑。我现在的习惯是评估模型时总会在测试集上留一部分极端工况的样本专门看模型在外推场景下的表现。5.3 隐藏层神经元数量的试错心得前文提到过隐藏层神经元数量不好定这里分享一个我常用的简单搜索流程。我从2个神经元开始依次按2倍往上试2、4、8、16、32、64。每次都用相同的训练集和测试集划分记录测试集R²。R²上升趋于平缓的拐点附近就是合适的数量范围。在MATLAB里这个循环写起来非常方便hiddenSizes [2, 4, 8, 16, 32, 64]; R2_results zeros(size(hiddenSizes)); for i 1:length(hiddenSizes) net fitnet(hiddenSizes(i)); net.divideFcn divideblock; net.divideParam.trainRatio 0.7; net.divideParam.valRatio 0.15; net.divideParam.testRatio 0.15; [net, ~] train(net, inputNorm, outputNorm); predNorm net(inputNorm); pred mapminmax(reverse, predNorm, outputPS); % 计算测试集或者全集的R2 SS_res sum((outputData - pred).^2); SS_tot sum((outputData - mean(outputData)).^2); R2_results(i) 1 - SS_res/SS_tot; end注意每次训练前用rng固定随机种子这样多次训练的结果才有可比性。我用的是rng(42)这类固定值让每次跑出来的初始权重一致才能公平对比不同神经元数量的效果。6. 小样本仿真数据的特殊处理和模型对比6.1 样本量少得可怜BP还适用吗仿真数据预测经常会遇到样本量很少的情况可能只有几十组数据。BP网络在这个场景下其实不太占优势因为它需要足够多的样本才能学出稳定规律。样本量太少时BP很容易把噪声当规律训练集上表现很好测试集上完全崩溃。但如果你非要用BP硬刚小样本问题有两个改进方向值得尝试。第一个是缩小网络规模隐藏层神经元控制在2到5个让网络根本没有能力去记住训练集的噪声细节。第二个是改用trainbr贝叶斯正则化算法它在损失函数里加了一个对权重的大小的惩罚项相当于自动帮网络瘦身能显著降低过拟合风险。这里有一段对比实验的想法用同一组小样本数据分别用trainlm和trainbr训练网络对比测试集性能。我实测过很多次样本量低于100时trainbr通常比trainlm稳得多。不过代价是训练时间更长权重更新过程需要迭代计算更复杂的概率模型对于小样本来说这个训练时间代价完全可以接受。6.2 别忽视高斯过程回归这类更强的替代方案做仿真数据预测的人应该对高斯过程回归GPR有所耳闻。它在处理小样本、非线性回归任务时的表现往往比BP网络更稳定。高斯过程回归的核心思想是直接去描述数据的概率分布而不是像BP那样不断地调整网络内部的连接权重。高斯过程回归最大的优势在两点一是它对小样本非常友好数据量小也能给出合理的预测和置信区间二是它几乎不太需要你操心网络结构问题不需要纠结隐藏层有几个神经元。MATLAB里调用高斯过程回归也非常直接一个fitrgp函数就能完成gprMdl fitrgp(inputData, outputData); pred predict(gprMdl, inputData);如果你的数据量在几百条以内而且你已经花了一下午调BP的网络结构还是不满意强烈建议直接把数据丢给fitrgp试试。它很可能在五分钟内给你一个比BP更好的结果。当然高斯过程回归的计算量随样本量增加会快速上升数据量到了几千条之后训练会明显变慢。我个人的建议是这样数据量小、非线性强、时间充裕——优先试高斯过程回归数据量较大、需要快速训练、你更熟悉BP的结构——用BP两者结合着用也没问题用BP先做一个基准结果再用高斯过程回归看看有没有提升空间。6.3 多输入单输出的典型仿真案例我拿一个最常见的仿真场景来说基于温度、压力、流速三个物理量预测某个设备的输出功率。假设你有300组仿真数据输入矩阵X是3×300输出向量Y是1×300。完整流程是这样的先对X和Y分别做归一化然后创建fitnet网络隐藏层设8个神经元按divideblock方式划分数据用trainlm训练最后计算测试集R²。300组样本不算多注意观察验证集曲线有没有出现过拟合拐点。如果R²低于0.9先不要急着加神经元而是检查数据质量和输入特征是否足够。很多时候预测效果差的根源不是模型不够复杂而是输入特征里缺少了关键的驱动变量。我在这类案例里还喜欢做一件事分别训练只用两个输入特征和用全部三个输入特征的模型对比R²的变化。这样能直观判断哪个输入特征对输出功率的贡献最大对后续做特征筛选也很有用。用MATLAB跑BP回归的几个基础建议总结先把这套基础流程跑通再谈更多的算法优化。我在实际使用中体会到新手最容易获得成就感的方式就是把一个最简单的BP模型从头到尾完整走一遍包括数据读取、归一化、训练、预测、反归一化、画图、计算误差指标。这个过程走通之后你对MATLAB神经网络的信心就有了再去看那些复杂的改进方法也就有底气了。最后再分享一个小技巧训练之前先rng固定随机种子每次训练的结果才可复现。不然同样的代码跑两次R²可能有明显波动你都不知道该信哪次结果。固定种子之后再对比不同参数设置才会有意义。BP网络训练本身就带随机性控制住随机性你才能更专注地观察结构和数据本身的影响。
返回列表