MATLAB实现BP神经网络多输入多输出回归预测与SHAP可解释性分析

发布时间:2026/8/31 16:54:28
MATLAB实现BP神经网络多输入多输出回归预测与SHAP可解释性分析 简介本资源面向机器学习初学者与MATLAB工程实践者聚焦多输入多输出回归建模中的预测性能与决策可解释性双重需求提供一套完整、即用的BP神经网络SHAP分析技术方案。资源包含8个文件4个核心MATLAB脚本、3个Excel数据集、1个运行说明文本总大小仅55KB结构精炼main.m实现端到端回归训练与预测main_shap.m调用shapley_function.m完成特征贡献度量化分析newpre.m支持新样本批量预测配套数据集涵盖多维输入与多维连续输出场景。已有91人学习下载适用于时序回归、工业参数建模、环境预测等需兼顾精度与归因解释的实际任务。读者可直接运行获得预测结果、SHAP力图与特征重要性排序并基于Excel模板快速替换自有数据开展复现与拓展。 做多输入多输出的回归预测最怕的不是精度上不去而是模型训练完了还是没法交差。别人问“这几个特征到底对结果有多大影响”你答不上来来了一批新数据要批量预测又得重新翻代码。这套基于MATLAB的BP神经网络多输入多输出回归预测方案把三件事一次性做完训练网络、SHAP可解释性分析、新数据预测。项目里包含完整的MATLAB源码和数据示例照着复现就能用。适合刚接触机器学习建模的工程师、研究生以及需要在论文或项目里同时完成“预测精度”和“可解释性”两块硬指标的从业者。1. 项目整体设计与技术选型1.1 多输入多输出回归预测的核心难点传统回归问题大多是单输出也就是多个特征预测一个值。多输入多输出则换成多个特征同时预测多个目标比如根据设备的温度、压力、振动频率同时预测寿命剩余时长和故障等级。难点主要在三块。第一是输出之间的相关性。多个输出往往不是相互独立的比如电机的转速和扭矩之间存在物理耦合。如果拆成多个单输出模型分别训练等于强行切断这种关联单个模型精度可能还行但放到实际场景里多输出联合预测的一致性会变差。BP神经网络天然支持多个输出神经元在同一个网络里通过共享隐藏层把输出之间的隐含关系一起学进去这是它适合这类任务的重要原因。第二是量纲和尺度差异。输入特征可能是0到1的百分比也可能是几百上千的数值输出变量之间的量级也可能差了好几个数量级。这种差异会让BP网络在训练时偏向数值大的变量导致损失函数被大数值输出主导。所以归一化必须在数据预处理阶段做好而且训练、验证、测试以及后续的新数据都要用同一套归一化参数这一点后面会专门讲。第三是评估指标的复杂性。多输出场景不能只看一个RMSE需要对每个输出分别计算R2、RMSE、MAE再综合判断模型是否可用。很多初学者只贴一个总损失值这在多输出回归里说服力是不够的。1.2 为什么选BP神经网络加SHAP组合BP神经网络本身并不算新但在中小样本的回归预测场景它依然是非常能打的方案。相比线性回归它能拟合非线性关系相比LSTM、TCN这类时序模型它结构简单、训练快、对数据量要求低相比XGBoost、LightGBM这类树模型它不需要复杂的特征工程输出多目标时也更自然。但BP神经网络的麻烦在于“黑盒”。训练完的网络是一堆权重和偏置你没法直接说“特征A每增加一个单位输出B就增加多少”。这就是为什么需要SHAP。SHAP基于博弈论中的Shapley值把一次预测结果拆解成每个特征的贡献之和既能回答“哪个特征最重要”也能针对某一条具体预测解释“为什么这个样本的输出值偏高”。组合起来就是一套完整的交付逻辑BP网络负责把预测精度做上去SHAP负责把模型行为讲清楚新数据预测模块负责落地应用。这个组合在工业项目、学术论文、竞赛答辩里都能经得起追问。1.3 整体流程与文件结构整个项目可以分成四个模块数据预处理、模型训练、可解释性分析、新数据预测。我习惯把代码拆成独立脚本每个脚本只干一件事这样后续替换数据或调整参数时不用在几百行代码里找逻辑。推荐的文件结构大概是这样的main_train.m主训练脚本负责加载数据、归一化、划分训练测试集、训练BP网络、输出指标和图表。net_bp.mat训练完成后保存的网络对象包含训练好的权重、偏置和结构。ps_in.mat、ps_out.mat保存输入和输出的归一化参数新数据预测时要加载它们。shap_analysis.m可解释性分析脚本调用SHAP计算特征贡献绘图。main_predict.m新数据预测脚本读取待预测数据输出预测结果到Excel或表格。ProcessData.m数据加载与整理函数统一处理列名、缺失值和原始数据格式。这样设计的好处是训练只跑一次之后的SHAP分析和新数据预测都不用重新训练加载已保存的网络即可。下面逐个模块展开。2. BP神经网络多输入多输出回归预测实现2.1 输入输出数据组织与归一化处理先明确数据格式。输入矩阵X的维度是“样本数×特征数”比如有1000个样本、6个特征X就是1000×6。输出矩阵Y的维度是“样本数×输出变量数”比如同时预测2个目标Y就是1000×2。这是MATLAB中fitrgp、feedforwardnet等模型通用的一种约定。数据读入后第一件事不是直接训练而是先看数值范围。我习惯用mapminmax做归一化它把每一行数据映射到[-1,1]区间。注意这里容易踩坑mapminmax默认是按行处理的而我们的输入X是“样本×特征”所以需要先转置让每个特征占一行再传给函数。示例数据归一化代码如下% 读取原始数据假设data.xlsx中前6列是特征后2列是输出 rawData readmatrix(data.xlsx); X rawData(:, 1:6); % 输入特征 Y rawData(:, 7:8); % 输出目标 % 划分训练集和测试集这里按顺序划分80%/20% n size(X, 1); trainIdx 1:round(0.8*n); testIdx (round(0.8*n)1):n; % 归一化注意转置ps_in保存归一化参数 [X_train_norm, ps_in] mapminmax(X(trainIdx,:)); [Y_train_norm, ps_out] mapminmax(Y(trainIdx,:)); % 对测试集使用同一套归一化参数不能重新计算 X_test_norm mapminmax(apply, X(testIdx,:), ps_in); Y_test_norm mapminmax(apply, Y(testIdx,:), ps_out);这里有个关键点归一化参数只能在训练集上计算然后应用到测试集。很多人图省事先把全部X归一化再划分训练集和测试集这会造成数据泄露。因为测试集的均值和范围已经被模型“看见”了算出来的指标会虚高换到真实新数据时表现会明显下降。训练完成后网络输出的是归一化后的预测值必须用ps_out反归一化才能得到真实尺度。这两组归一化参数一定要保存新数据预测时还需要用到。2.2 网络结构设计与训练参数选取BP神经网络的默认结构是单隐藏层。对大多数回归问题单隐藏层已经够用增加隐藏层数会提高拟合能力但也更容易过拟合收敛也更慢。我的建议是先固定一个隐藏层把节点数调到足够好再考虑是否需要加深。隐藏层节点数没有绝对公式常用的经验参考是sqrt(输入特征数 输出目标数) a其中a取1到10之间的整数。也可以从较小的节点数开始比如4个节点逐步倍增观察测试集误差的变化。节点数太少会欠拟合训练集和测试集误差都高节点数太多会过拟合训练集误差很低但测试集误差升高。训练算法的选择也很重要。MATLAB的feedforwardnet默认使用Levenberg-Marquardt算法trainlm收敛快、精度高适合中小型数据集。如果数据量较大且内存有限可以换成trainscg如果明显过拟合可以考虑trainbr贝叶斯正则化它能在训练过程中自动惩罚过大的权重比手动加正则项更方便。不过trainbr速度较慢样本量特别大时不推荐。下面是创建BP网络的典型代码% 创建前馈BP网络隐藏层10个节点 hiddenSize 10; net feedforwardnet(hiddenSize); % 设置训练算法和参数 net.trainFcn trainlm; % 使用LM算法 net.trainParam.epochs 1000; % 最大训练次数 net.trainParam.goal 1e-5; % 目标误差 net.trainParam.min_grad 1e-6; % 最小梯度 net.trainParam.max_fail 6; % 最大验证失败次数 % 数据集划分比例训练/验证/测试 net.divideParam.trainRatio 0.7; net.divideParam.valRatio 0.15; net.divideParam.testRatio 0.15; % 训练网络 [X_train_norm, Y_train_norm] ... % 已经准备好的归一化数据 net train(net, X_train_norm, Y_train_norm);feedforwardnet创建的网络默认输出层是线性激活函数适合回归任务隐藏层默认是tansig。如果数据量很小可以删掉自动划分的验证集用所有训练样本训练但这样无法判断是否过拟合建议保留验证集。2.3 模型训练代码与评估指标训练完成后的第一件事是看看拟合效果。测试集的归一化预测结果反归一化后与真实值对比计算每个输出变量的指标。% 测试集预测 Y_pred_norm net(X_test_norm); Y_pred mapminmax(reverse, Y_pred_norm, ps_out); % 原始真实输出 Y_true Y(testIdx, :); % 对每个输出计算R2、RMSE、MAE for i 1:size(Y_true,2) y_true Y_true(:, i); y_pred Y_pred(:, i); ss_res sum((y_true - y_pred).^2); ss_tot sum((y_true - mean(y_true)).^2); r2 1 - ss_res/ss_tot; rmse sqrt(mean((y_true - y_pred).^2)); mae mean(abs(y_true - y_pred)); fprintf(输出%dR2%.4f, RMSE%.4f, MAE%.4f\n, i, r2, rmse, mae); end这里有个容易被忽视的点R2在输出变量方差很小的时候不太适用。比如某个输出值基本上恒定在10附近波动只有0.01此时R2可能很低但实际误差很小。所以要多看RMSE和MAE结合业务判断。训练完成后保存模型后面所有分析都直接加载这个网络对象save(net_bp.mat, net, ps_in, ps_out);可视化部分我一般画三张图一是测试集每个输出的真实值和预测值对比折线图二是拟合回归图理想情况下散点落在45度对角线上三是误差分布直方图。这些图既能帮自己判断模型状况也是项目报告里必不可少的素材。3. SHAP可解释性分析落地3.1 MATLAB里做SHAP的两种路径SHAP在Python生态里已经非常成熟但项目要求MATLAB完整源码所以需要了解MATLAB环境下的实现方案。我试过两条路都有可行性。第一条路是MATLAB自带的shapley函数从R2021a开始出现在Statistics and Machine Learning Toolbox中。它可以对大多数机器学习模型计算Shapley值而且支持传入自定义预测函数。这意味着我们不必是特定模型对象只要写一个复杂的函数句柄把BP网络包装进去就能计算特征贡献。缺点是对非线性复杂模型shapley计算速度较慢样本量大时可能要跑很久而且它的结果可视化风格和Python SHAP不完全一样需要适应。第二条路是通过MATLAB的Python接口调用Python的shap库。这需要本机安装Python并配置pyenv。优点是功能全、图好看、社区方案成熟缺点是需要维护两套环境而且要把BP网络的预测逻辑从MATLAB搬到Python有些绕。从项目完整性角度我更推荐第一条路因为它不依赖外部Python环境代码全在MATLAB里别人拿到就能跑。下面以第一条路为例展开。3.2 基于predictFcn的SHAP计算示例shapley函数需要两个核心输入预测函数和待解释的特征数据。预测函数必须接受一个特征矩阵返回每个样本的预测值。因为我们的BP网络是“多输入多输出”而shapley默认针对单输出解释所以要对每个输出分别做一次SHAP分析。也就是说输出1做一次、输出2做一次各自得到一组特征贡献。假设网络已经保存写一个包装函数预测输出变量1的值function pred predict_output1(X_raw, net, ps_in, ps_out) % X_raw原始特征矩阵每一行是一个样本 % 先归一化 X_norm mapminmax(apply, X_raw, ps_in); % 网络预测输出是归一化值 Y_norm net(X_norm); % 反归一化取第一行输出变量1 Y_raw mapminmax(reverse, Y_norm, ps_out); pred Y_raw(1,:); % 返回列为样本数 end然后调用shapley% 加载模型 load(net_bp.mat, net, ps_in, ps_out); % 使用测试集部分样本计算SHAP样本太多会很慢 sampleIdx 1:50; X_sample X(sampleIdx, :); % 定义预测函数 predictFcn (X) predict_output1(X, net, ps_in, ps_out); % 计算shapley值 explainer shapley(predictFcn, X_sample);explainer对象里包含每个特征在每个样本上的Shapley值变量名和特征名称需要自己设置。如果原始数据没有列名可以手动指定explainer shapley(predictFcn, X_sample, QueryPoints, X_sample, ... Data, table(X_sample(:,1), X_sample(:,2), ..., VariableNames, {温度,压力,转速}));细节可以根据自己的数据结构调整。核心是预测函数写好shapley就能工作。注意shapley函数底层会生成大量扰动样本BP网络每预测一批样本都要做一次归一化和反归一化所以计算时间会比较长。建议先用几十个样本跑通流程再按需扩大到全测试集。3.3 SHAP结果怎么读、怎么用到报告里计算完成后MATLAB的shapley对象支持几个常用绘图函数plot(explainer)可以画单个样本的特征贡献瀑布图summary(explainer)可以汇总所有样本的特征重要性排序。对于多输入多输出模型我建议先做一张所有输出的“特征平均绝对SHAP值”汇总表。这张表能直观看到对输出变量1来说哪个特征贡献最大对输出变量2来说哪个特征贡献最大。通常业务上最关心的就是这类问题。我习惯把结果整理成这样一张表格| 特征名 | 输出变量1的平均|SHAP| | 输出变量2的平均|SHAP| | 综合排序 | |--------|--------|--------| | 温度 | 0.85 | 0.32 | 1 | | 压力 | 0.41 | 1.02 | 2 | | 转速 | 0.16 | 0.55 | 3 |绘制这张表其实很简单拿到explainer中的Shapley值矩阵后用mean(abs(shapValues))即可。真正费时间的是解读SHAP值为正说明该特征在当前样本中推高预测值为负则拉低预测值绝对值越大影响越强。在论文或项目报告里我一般写三句话第一句说明用SHAP进行了特征归因分析第二句给出各特征重要性排序第三句结合领域知识解释为什么这个特征最重要。这样既严谨又有说服力。4. 新数据预测模块4.1 新数据格式与预处理模型训练完后真正要交付给业务方使用的通常不是训练代码而是“新数据预测”模块。业务方会丢来一张Excel表里面只有特征没有输出我们要返回带预测结果的表格。新数据的第一坑是列顺序。训练时用的特征顺序是第1列温度、第2列压力、第3列转速那新数据也必须严格按这个顺序排列。如果新数据列名是中文建议读取后先重命名或者按固定索引取数不要直接整表塞进模型。第二坑是归一化参数。新数据必须使用训练时保存的ps_in、ps_out直接在原数据上调用mapminmax(apply, X_new, ps_in)。这一步看起来简单但我在实际项目里遇到过有人对每批新数据都重新做一次归一化导致预测结果完全错乱。最后一点新数据中有缺失值或异常值应该在预测前清理。如果业务方的Excel里有个别空单元格读取后的矩阵会出现NaN直接用网络预测会报错。最简单的办法是读取后检查any(isnan(X_new))有缺失就先做均值填充或剔除。4.2 预测核心代码与结果输出新数据预测脚本的核心逻辑只有几步加载模型、加载归一化参数、读取新数据、归一化、预测、反归一化、输出。% 加载训练好的模型 load(net_bp.mat, net, ps_in, ps_out); % 读取新数据特征列与训练数据一致 newData readmatrix(new_data.xlsx); X_new newData; % 假设已经是纯特征矩阵 % 检查缺失值 if any(isnan(X_new(:))) error(新数据包含缺失值请先处理); end % 归一化 X_new_norm mapminmax(apply, X_new, ps_in); % 预测输出是归一化值 Y_new_norm net(X_new_norm); % 反归一化得到真实预测值 Y_new mapminmax(reverse, Y_new_norm, ps_out); % 输出到Excel并附带原特征一起保存 result [X_new, Y_new]; writematrix(result, prediction_result.xlsx);这里注意Y_new_norm的行是输出变量数列是样本数所以反归一化后需要转置拼接时Y_new才能和X_new行数对齐。如果希望结果更友好可以用table指定列名T array2table([X_new, Y_new], ... VariableNames, {温度,压力,转速,预测值1,预测值2}); writetable(T, prediction_result.xlsx);批量预测很直接。如果只预测单条新数据代码也一样只是矩阵只有一行。为了保持维度可以显式将待预测数据写成一行向量或者用X_new data(:);转成行。5. 常见问题与避坑指南5.1 训练波动大、精度不稳定BP网络的初始权重是随机的每次训练结果都可能不同。这是很多初学者问“为什么我两次跑出来的结果不一样”的主要原因。解决方案是固定随机种子rng(42);在创建网络之前设置rng这样每次训练初始权重一致结果可以复现。但如果网络结构差异很大即使固定种子也不意味着最优解唯一。所以更稳妥的做法是多次训练保存测试集指标最优的那个模型。可以用一个简单的循环在每次训练后记录RMSE保留最佳网络避免被某一次的偶然结果误导。5.2 归一化泄露导致测试结果虚高归一化泄露是这类项目里最隐蔽的问题。如果先在全部数据上计算均值和范围再划分训练测试集测试集的分布信息就已经影响了归一化参数。这样在测试集上看到的精度会比真实应用中高很多。正确做法是先在训练集上做mapminmax得到ps_in然后对测试集、验证集、新数据都只调用mapminmax(apply, data, ps_in)。写代码时可以把测试集数据的归一化放在训练集划分之后并且养成保存ps_in的习惯。5.3 SHAP计算失败或结果全为0如果shapley报错最常见的原因是预测函数输入输出的维度没对齐。shapley默认期望输入是一个矩阵每行一个样本每列一个特征期望输出是一个列向量每个元素对应一个样本的预测值。如果你的预测函数返回行向量或者内部对矩阵做了转置就容易维度不匹配。另一个常见问题是结果全为0。这往往不是网络本身的问题而是归一化后预测值变化范围太小或者SHAP采样时没有覆盖到有效特征区间。可以检查一下预测函数在随机扰动样本上是否还有响应如果某些特征列在原始数据中方差很小SHAP很难机械地捕捉到贡献。还有一点不要对全部测试样本直接跑SHAP会非常慢。我一般先随机抽50到100个有代表性的样本或者按预测误差分层抽样既能保证解释稳定又能控制计算时间。5.4 多输出解释结果的呈现技巧多输出模型每次只能对一个输出变量做SHAP所以最终会得到多张特征重要性图。怎么呈现在PPT或论文里是个问题。我建议不要每个输出单独画一张瀑布图而是做一张“特征重要性热力图”或“平均贡献对比条形图”行是特征列是输出变量格子颜色代表SHAP平均值大小。这样一眼就能看出特征A对输出1影响大特征B对输出2影响大。如果业务需要逐样本解释比如某个异常样本预测值很高可以用plot(explainer)画瀑布图展示该样本中每个特征是推高了还是拉低了预测值。注意这里的SHAP值是基于当前样本的特征值和基线值对比得到的解释时一定要结合业务背景不要机械地说“压力重要就肯定是压力导致故障”还要看压力值的实际方向。最后再分享一个我在实际项目中养成的习惯拿到一个新数据集后第一轮先不要急着调网络结构而是用默认参数跑一遍然后看测试集误差和SHAP结果。如果某个特征SHAP贡献始终为0且该特征方差接近0直接删掉如果所有特征贡献都很平均先检查数据是否存在多重共线性。这样先粗筛一轮后面调参和解释都会省很多事。这套流程熟练之后从拿到数据到输出带解释的预测结果半天时间基本足够。本文还有配套的精品资源点击获取