简介一套基于径向基RBF神经网络的Matlab分类预测代码集成了SHAP可解释性分析方法适用于科研实验、工程应用或课程设计中的特征贡献分析。资源覆盖数据打乱与分层划分、归一化、神经网络建模、分类准确率计算以及预测对比图、混淆矩阵生成可进一步计算SHAP值并绘制特征重要性图、依赖关系图直观呈现各特征对预测结果的影响帮助研究者理解模型决策依据。包体共8个文件其中6个.m脚本实现主流程、SHAP计算与绘图1个xlsx为实验数据集1个txt为使用说明压缩包仅78KB轻量易上手。目前已有93人学习下载适合需要快速开展RBF分类与可解释性分析的用户参考。读者可基于完整代码直接运行或二次开发免去从零搭建的繁琐过程也能通过依赖图洞察特征作用方向与幅度为模型优化和结论解释提供支撑。1. RBF神经网络做分类预测为什么还要配一套SHAP分析做工业状态识别、信贷风险评分或者医疗辅助筛查的同行大概率都遇到过这个场景你用 RBF神经网络分类预测 跑出一张测试集准确率 92% 的报表领导却追问一句“那到底是哪几个特征把它分出来的”。准确率只能证明“分对了”而 SHAPShapley Additive Explanations能回答“每个特征在这个过程中贡献了多少、往哪个方向推”。在 Matlab 里把 RBF 分类模型和 SHAP 分析串成一条流水线数据、人员、汇报都齐了这就是标题里“完整代码和数据”落到工程上的含义——一张干净的多特征表格一份能直接改参数再跑的脚本。适合谁适合已经有表格数据、会基础 Matlab 操作、想让黑匣子模型变成可汇报结果的分析师和工程师。2. 在Matlab里先立住RBF模型数据组织、newrb参数与交叉验证2.1 数据和标签的输入规范行样本、列特征类别不能直接当数值RBF 网络的工具箱函数全是按“样本在列”来设计的多数人用readmatrix读进来的 CSV 却是“样本在行”。这一步方向一错后面所有 SHAP 值都会像照镜子一样特征顺序全部对不上。我一般先把数据整理成size(X) N×DN是样本数、D是特征数然后按需转置交给newrb。% 以鸢尾花数据集演示150 个样本、4 个特征、3 个类别 load fisheriris X meas; % 150×4行是样本列是特征 Y species; % 150×1元胞数组存类别名 % 类别名转数值标签 1、2、3 [~, Ynum] ismember(Y, unique(Y)); % 转成 1-of-K 目标矩阵3 类就变成 3 行输出RBF 网络用多输出做分类 T full(ind2vec(Ynum)); % 先把 1×N 转成 K×N再转回 N×K % 归一化RBF 的径向基函数本质是欧氏距离不归一化时量纲大的特征会主导距离 Xnorm mapminmax(X, 0, 1); % mapminmax 按列归一化所以先转置 % 分层划分训练/测试集保证各类别比例与原数据一致 cv cvpartition(Ynum, HoldOut, 0.3); Xtr Xnorm(training(cv), :); Xte Xnorm(test(cv), :); Ytr Ynum(training(cv)); Yte Ynum(test(cv)); Ttr full(ind2vec(Ytr)); % 训练目标矩阵Ntr×K代码逻辑拆开说ismember(Y, unique(Y))把类别名映射成 1 到 K 的整数ind2vec再把这些索引变成 0/1 指示矩阵这一步是为了配合newrb的多输出分类形式。归一化里最容易出错的是mapminmax按列处理你给它的矩阵必须是“特征×样本”否则它会把每个样本当成一条特征去缩放结果就是从根上错了。cvpartition用HoldOut保证训练和测试集合里每一类占比都接近原始数据类别不平衡时这一层尤其重要。我见过不少直接在原始数据上训练 RBF 的工程代码准确率能看但一查 SHAP 就露馅某个量纲很大的特征因为数值天生大SHAP 值也虚高。这不是模型学得好是距离计算被量纲绑架了。规范的数据组织是整个流程的地基后面每一步都依赖这里的列顺序。2.2 newrb和newrbe怎么选spread、goal、maxNeuron三个参数决定成败Matlab 里 RBF 有两个入口newrbe是精确设计每个训练样本分配一个中心样本一多网络巨大而且对噪声一点不客气我基本只在样本量小于 50 时才用它newrb是增量构建每步加一个神经元、看一次误差直到达到目标误差或神经元上限工程落地优先选newrb。% 用 newrb 训练目标均方误差 0.05spread 先给 1.0神经元上限 60 net newrb(Xtr, Ttr, 0.05, 1.0, 60, 25); % 预测网络输出是连续分数vec2ind 把它换算回类别索引 Ypred vec2ind(net(Xte)); acc mean(Ypred Yte) * 100; fprintf(测试集准确率%.2f%%网络神经元数%d\n, acc, numel(net.b{1}));参数含义和踩坑点我整理成了表方便你对照调参参数含义经验取值坑在哪GOAL目标均方误差0.01~0.1设成 0 会建满神经元纯记忆训练集SPREAD径向基函数宽度0.5~2.0太小是过拟合元凶太大则全部样本糊成一团MN最大神经元数50~100上限不够会欠拟合但建满本身就是红旗DF显示频率25调试时设 1 看进度正式跑不用管这段代码里最值得盯的是spread。它控制径向基函数的“作用半径”spread小基函数峰又尖又窄每个神经元只覆盖自己周围极小区域训练误差降得极快但测试点稍微偏离训练样本分布就直接“没人管”预测跌得惨烈spread太大所有神经元的重叠区域变大曲线过度平滑训练误差都降不下去。经验做法是先给 1.0 跑一轮再在 0.2~2.0 之间网格搜索。2.3 训练后先别急着算SHAP用交叉验证压一压不稳定因素SHAP 分析有个隐含前提模型本身是稳定的。如果模型换一个训练集划分预测结果就大幅抖动那 SHAP 算出的“特征贡献”也会跟着抖你根本分不清是特征真的重要还是模型在碰运气。所以我在算 SHAP 前一定会做一轮交叉验证选参数。% 网格搜索 spread用 5 折交叉验证比较泛化能力 spreadList 0.2:0.2:2.0; for s 1:numel(spreadList) cv2 cvpartition(Ytr, KFold, 5); accFold zeros(cv2.NumTestSets, 1); for f 1:cv2.NumTestSets netf newrb(Xtr(training(cv2,f),:), Ttr(:,training(cv2,f)), ... 0.05, spreadList(s), 60, 25); Ypf vec2ind(netf(Xtr(test(cv2,f),:))); accFold(f) mean(Ypf Ytr(test(cv2,f))); end fprintf(spread%.1f平均准确率%.2f%%±%.2f%%\n, ... spreadList(s), mean(accFold)*100, std(accFold)*100); end这层循环解决两件事一是选 spread二是看标准差。如果某个 spread 下平均准确率还行但标准差到了 5% 以上说明这个配置对训练集划分敏感换个划分成绩就大变这类模型不适合拿去做 SHAP 解释。真实项目里我会把“准确率最高”和“标准差最小”两个指标放一起权衡宁可让平均准确率降 1 个百分点也要换一个稳定模型因为解释的可靠性比性能数字更重要。3. SHAP特征贡献在Matlab的两条实现路径内置shapley与自写KernelSHAP3.1 SHAP到底在算什么特征子集上的边际贡献SHAP 的思想来自博弈论里的 Shapley 值。放在机器学习里它回答的问题是对于某个样本特征 i 的贡献等于“把 i 加到各种特征子集 S 中后预测值变化量的加权平均”。数学上写成phi_i sum over S [ ( |S|!(D-|S|-1)! / D! ) × ( f(S ∪ {i}) - f(S) ) ]这个公式保证三条性质也是 SHAP 和 LIME 这类方法拉开差距的地方局部准确性各特征贡献加起来等于预测值与基线之差、一致性模型变化方向与贡献变化方向一致、虚假性不起作用的特征贡献为零。工程上不用手算这个组合爆炸的公式而是用 KernelSHAP 采样逼近。如果用的 Matlab 版本较新Statistics Toolbox 提供shapley对象直接吃黑盒函数和背景数据用法和官方文档一致但 RBF 网络不是工具箱里的标准分类器shapley对模型类型的兼容有限包装成函数句柄时经常因为输入矩阵维度对不上而报错。我的经验是在没有标准分类模型可传时自己按 KernelSHAP 逻辑写一个反而更透明不受 toolbox 版本限制。下面给出的是我常用的实现。3.2 KernelSHAP的Matlab实现带权最小二乘替代组合爆炸KernelSHAP 的核心是把每个特征子集 S 表示成一个 0/1 向量 z1 代表特征保留0 代表被替换成背景值然后对预测结果 f(z) 做一次带权线性回归回归系数就是 SHAP 值。权重要按 Shapley 核来给这样才能保证回归结果收敛到真正的 Shapley 值。function sv kernelshap(x0, Xbkg, predFun, nsample) % x0 : 1xD 待解释样本 % Xbkg : MxD 背景数据用于把“被删掉”的特征替换成随机背景值 % predFun : 函数句柄输入 NxD输出 Nx1 的连续分数越大越像目标类 % nsample : 随机抽样子集个数建议 100~1000 D size(x0, 2); M size(Xbkg, 1); Z []; W []; Y []; for t 1:nsample S rand(1, D) 0.5; % S1 表示特征留在子集中 if ~any(S) || all(S), continue; end % 空集与全集跳过交给回归基线 z x0; z(~S) Xbkg(randi(M), ~S); % 被排除的特征用随机背景样本替换 fz predFun(z); ns_ sum(S); % Shapley 核权重子集越小或越大权重越高 w (D-1) / (nchoosek(D, ns_) * ns_ * (D-ns_)); Z [Z; S]; W [W; w]; Y [Y; fz]; end A [ones(size(Z,1),1), Z]; beta lscov(A, Y, W); % 带权最小二乘beta0 是基线 sv beta(2:end); % 每个特征一个 SHAP 值 end这段代码里有三个关键设计。第一z(~S) Xbkg(randi(M), ~S)是在做“特征删除”的近似真正的 Shapley 值要求对没选中的特征求条件期望工程上就用背景数据随机替换来近似背景数据一般从训练集抽 100 行左右。第二Shapley 核权重公式里nchoosek(D, ns_)是子集数量ns_和D-ns_分别惩罚小集合和大集合这样回归才能还原 Shapley 值而不是普通线性近似的系数。第三lscov是带权最小二乘权重向量 W 直接参与求解比手动乘到设计矩阵里更稳。3.3 计算单个样本的特征贡献并画出排序图有了kernelshap函数剩下的就是把它接到 RBF 网络上。这里要决定解释哪个目标类多分类模型有 K 个输出SHAP 只能针对其中一个类解释“哪些特征把这个样本推向这一类”。% 背景数据从训练集随机抽 100 行代表数据整体分布 rng(7); bkgIdx randsample(size(Xtr,1), 100); Xbkg Xtr(bkgIdx, :); % 解释第一个测试样本为什么被判成第 2 类 k 2; predScore (Xs) net(Xs)(:, k); % 取 RBF 第 k 个输出的分值 sv kernelshap(Xte(1,:), Xbkg, predScore, 300); disp(sv); % 画水平条形图正贡献在右负贡献在左 [svSort, idx] sort(sv, descend); figure(Position, [100 100 560 340]); barh(svSort); set(gca, YTick, 1:numel(svSort), YTickLabel, featureNames(idx)); xlabel(SHAP value); title(sprintf(样本 %d 的 SHAP 特征贡献目标类 %d, 1, k));predScore只取第 k 列输出意味着你解释的“目标类”不同得到的 SHAP 值也不同这是多分类场景下必须接受的设定。sort(sv, descend)把贡献从大到小排列同时保留原索引barh画横条图横轴是 SHAP 值正负分别表示把样本推向目标类或推离目标类。全局特征重要性则是把这个过程对每个测试样本都算一遍然后取平均绝对值mean(abs(sv_all), 1)再用同样的方式排序画图。提示比较两个特征的贡献大小时优先比较绝对值。SHAP 值 0.1 和 -0.05 放在一起不能说前者“更不重要”它只是方向不同。4. SHAP结果解读的三个容易翻车点排序、符号和类别不平衡4.1 全局排序和局部排序不是一回事别拿“平均贡献第一”解释单个样本全局特征重要性排序是把所有测试样本的 |SHAP| 求平均后得到的。它回答的是“整体上哪个特征最常起大作用”。但具体到某一个样本贡献排名第一的特征可能和全局第一完全不一样。我做过一次滚动轴承故障诊断全局排名第一的是“轴温”但某一个被判为“内圈故障”的样本里贡献最大的是“高频振动幅值”轴温的贡献排到第四。这不是 bug而是模型在这个样本上确实依赖了不同的证据。报告里常见的错误是把全局排序直接抄到单样本结论里说“这个故障主要是轴温导致的”但单样本的 SHAP 图明明显示是振动幅值。正确做法是汇报全局排序时明确说“整体上”汇报单样本时只看该样本自己的 SHAP 向量。如果领导要看“为什么这一条被拒贷”你必须展示局部 SHAP而不是那一张平均重要性条形图。4.2 SHAP符号翻转是RBF的径向结构不是模型坏了线性模型里每个特征只有一个固定的系数符号不会变。RBF 网络不是这样径向基函数在中心点附近响应强、远离中心响应弱特征某个区间内会把样本推向第 2 类另一个区间内却推向第 3 类。于是你会看到同一个特征在不同样本上的 SHAP 符号翻转甚至在同一批样本里既有 0.3 又有 -0.4。很多第一次接触 SHAP 的工程师看到符号翻转就怀疑模型或代码出错先别急。正确验证方法是画该特征与预测分数的散点图看趋势是否呈 U 型或倒 U 型。如果特征值低段的时候 SHAP 为正、高段变负说明模型学到了非单调关系这恰恰是 RBF 相对线性模型的优势。把这个图放进报告比单独放 SHAP 条形图更有说服力。4.3 多分类不平衡时SHAP必须按目标类分开看三类样本数量是 100:50:10 这种结构时全局平均 |SHAP| 会被多数类主导。样本量最大的那类特征排序看起来理所当然但少数类真正依赖的特征可能被完全淹没。处理办法是两个习惯。第一做全局特征重要性时按类别分别聚合每个类别出一份排名而不是混在一起算总平均。第二解释单样本时明确目标类 k只取网络第 k 个输出做解释K 个输出对应 K 套 SHAP 值。输出层的第 k 个神经元本身代表“这个样本属于第 k 类”的证据强度这和第 k 个 SHAP 解释天然对齐。5. 避坑指南RBFSHAP在Matlab里的高频故障与排查5.1 SHAP值之和与预测分对不上时先查三个位置现象单个样本的 SHAP 值加总后是 0.35但网络预测概率是 0.78对不上账。原因有三个按概率排一是predFun直接取了net(Xs)的原始网络输出没有从基线角度对齐分支之间的差异天然不同二是背景数据分布与测试样本差异太大抽样替代引入噪声三是采样次数太少回归未收敛。解决先确认基线公式——SHAP 值的总和应当约等于f(x0) - mean(predFun(Xbkg))也就是该样本预测分数减去背景数据集平均预测分数。如果偏差很大把predFun换成第 k 类输出的分数本身不要额外做 softmax 或概率缩放再增加采样次数到 1000重新检查对账结果。5.2 newrb训练误差降得快测试集却大面积翻车现象训练过程显示误差一路降到 0.001测试集准确率反而只有七成不到比默认参数的模型还差。原因绝大多数情况是spread太小。每个 RBF 神经元只对训练样本附近一小片区域有响应新样本落不进任何神经元的有效范围网络输出就接近随机。另一个常见诱因是GOAL设成 0神经元数量直接建满模型变成训练集的查表器。解决把spread拉回 1.0~2.0 区间重新网格搜索GOAL设为 0.03~0.05不要追求训练误差归零。如果交叉验证显示训练准确率和测试准确率差距超过 5 个百分点优先怀疑径向基宽度而不是数据质量。5.3 SHAP值每次运行排名都不一样结果像玄学现象同一个样本、同一个模型连续跑两次kernelshap前三名特征排序不同。原因KernelSHAP 是采样算法特征子集是随机抽的背景样本也是随机抽的。没固定随机种子时两次结果自然有波动采样次数太少时波动会大到影响排序。解决第一步固定随机种子rng(1)之后跑一次kernelshap保证当前结果可复现。第二步把nsample从 200 提到 1000第三步检查背景数据行数如果背景只有几十行随机替换的方差会很大抽到不同的行结果就差很多。这三步做完排序基本稳定。5.4 内置shapley对象报错或不支持自定义网络现象调用shapley时报模型类型不支持或者传入函数句柄后说输入维度有问题脚本直接中断。原因shapley对象对标准分类模型决策树、集成模型等支持最顺但 RBF 网络不是工具箱原生分类器只能走黑盒函数路径。黑盒函数对输入输出格式有要求很多人的包装函数返回了行向量而不是列向量维度检查直接不过。解决有两个方向。一个方向是把 RBF 预测包装成标准黑盒函数确保输入是 N×D 矩阵、输出是 N×1 列向量再传给shapley另一个方向是直接使用上一章自写的kernelshap函数绕开版本和类型限制逻辑还更透明。自写方案是我对神经网络类模型的默认选择。5.5 条形图上特征标签错位特征和名字对不上号现象画完barh后发现第 1 个条对应的特征名显示成了第 3 列的名字排序完全错乱。原因sort(sv)对 SHAP 值排序后直接丢掉了原始索引set(gca,YTickLabel, ...)填名字时用了没排序的原始featureNames两边的顺序不一致。解决排序时用[svSort, idx] sort(sv, descend)同时保留索引填标签时取featureNames(idx)。另一个隐蔽点featureNames的存储顺序必须和训练矩阵 X 的列顺序完全一致从第 2 章开始就要约定好这个顺序中间加特征、删特征时同步维护名字列表否则排查起来很费劲。6. 把SHAP从“能出图”调到“能用于交付”三个验证习惯6.1 用独立测试集或验证集算SHAP别用训练集训练集上的 SHAP 反应的是模型记忆到的规律不是对新样本的预测依据。我会一律在测试集上挑样本算 SHAP并且多说一句测试集 SHAP 的分布如果和训练集差异很大说明模型泛化边界很敏感这时优先回去调模型而不是调解释。6.2 把单个样本的SHAP与单特征扰动曲线对照SHAP 说某个特征贡献 0.4我会手动把这个特征从样本里抽出等间隔取值其余特征保持不变让predScore输出一条扰动曲线。如果曲线的变化趋势和 SHAP 的符号方向一致说明解释可信如果曲线平得像地板而 SHAP 却给了大值那多半是 KernelSHAP 的随机采样在那一轮出了问题需要重新跑。6.3 重复跑两次SHAP用Spearman秩相关检查稳定性SHAP 排序是交付报告里最常被读的部分排序不稳定等于报告不可信。我的交付前最后一关是这个% 同一解释样本跑两次比较排序稳定性 rng(1); sv1 kernelshap(Xte(1,:), Xbkg, predScore, 500); rng(2); sv2 kernelshap(Xte(1,:), Xbkg, predScore, 500); rho corr(sv1, sv2, Type, Spearman); if rho 0.9 fprintf(排序不稳定建议把 nsample 加到 1000并检查背景数据量\n); endSpearman 相关系数低于 0.9 时我不会出图而是加采样次数、加背景数据行数直到两次排序稳定。这三条习惯是从“能出图”到“能用于决策”的分水岭也是我这几年交付特征贡献分析报告前必过的关卡。踩坑踩得多了才明白SHAP 不是画图工具是审计工具——它审计的是模型是否在拿真正的证据做判断。希望帮到你。本文还有配套的精品资源点击获取