網(wǎng)絡(luò)多輸入多輸出回歸與SHAP可解釋分析實(shí)戰(zhàn))
簡介這份資源面向需要掌握多輸入多輸出回歸建模與模型可解釋性的機(jī)器學(xué)習(xí)學(xué)習(xí)者和工程人員提供一套基于MATLAB的完整實(shí)現(xiàn)方案。內(nèi)容圍繞BP神經(jīng)網(wǎng)絡(luò)展開覆蓋回歸預(yù)測(cè)、SHAP可解釋分析以及新數(shù)據(jù)預(yù)測(cè)三大環(huán)節(jié)配套Excel格式的多輸入多輸出數(shù)據(jù)集可直接替換數(shù)據(jù)復(fù)現(xiàn)實(shí)驗(yàn)。壓縮包共8個(gè)文件包含4個(gè)m腳本、3個(gè)xlsx數(shù)據(jù)表和1個(gè)txt說明文件整體約55KB腳本分別承擔(dān)主流程回歸、SHAP值計(jì)算、新樣本預(yù)測(cè)與核心函數(shù)封裝等職責(zé)數(shù)據(jù)表則提供訓(xùn)練與待預(yù)測(cè)樣本。已有92人學(xué)習(xí)關(guān)注。讀者可借此理解BP網(wǎng)絡(luò)在多輸出任務(wù)中的搭建與調(diào)參思路掌握SHAP方法對(duì)特征貢獻(xiàn)的量化解釋流程并學(xué)會(huì)將訓(xùn)練好的模型遷移到新數(shù)據(jù)上完成預(yù)測(cè)適合作為課程設(shè)計(jì)、論文實(shí)驗(yàn)或工程原型的參考模板。1. 從一張“黑箱”預(yù)測(cè)表說起B(yǎng)P神經(jīng)網(wǎng)絡(luò)多輸入多輸出回歸到底在解決什么你手里有一批實(shí)驗(yàn)數(shù)據(jù)輸入是 6 個(gè)工藝參數(shù)輸出是 3 個(gè)性能指標(biāo)領(lǐng)導(dǎo)要你“建個(gè)模型既能預(yù)測(cè)新樣本又能說清楚哪個(gè)輸入影響最大”。這時(shí)候單輸出模型要訓(xùn) 3 次每次還得單獨(dú)調(diào)參改一個(gè)輸入維度就得全部重跑。BP 神經(jīng)網(wǎng)絡(luò)多輸入多輸出回歸就是沖著這個(gè)場(chǎng)景來的一個(gè)網(wǎng)絡(luò)同時(shí)吐出多個(gè)目標(biāo)值共享隱含層特征訓(xùn)練一次搞定多目標(biāo)。但純 BP 的預(yù)測(cè)結(jié)果沒人敢信因?yàn)樗呛谙洹猄HAP 可解釋分析就是那把撬開黑箱的螺絲刀用博弈論里的 Shapley 值給每個(gè)輸入特征分配貢獻(xiàn)度讓你能對(duì)著圖說“第 3 個(gè)輸入對(duì)第 2 個(gè)輸出的影響占了 40%”。MATLAB 完整源碼和數(shù)據(jù)意味著你不用從零搭輪子改改數(shù)據(jù)接口就能跑自己的項(xiàng)目。這套組合適合做實(shí)驗(yàn)數(shù)據(jù)回歸、工藝參數(shù)優(yōu)化、多指標(biāo)預(yù)測(cè)的工程師尤其是樣本量在幾百到幾千條、輸入輸出維度不超過 20 的場(chǎng)景。下面我把從數(shù)據(jù)組織到 SHAP 解釋再到新數(shù)據(jù)預(yù)測(cè)的完整鏈路拆開講中間踩過的坑一并奉上。2. 多輸入多輸出 BP 網(wǎng)絡(luò)的數(shù)據(jù)組織與網(wǎng)絡(luò)搭建2.1 輸入輸出矩陣怎么擺MATLAB 里的行列約定BP 網(wǎng)絡(luò)在 MATLAB 里最容易被數(shù)據(jù)維度搞翻車。feedforwardnet或fitnet默認(rèn)把每一列當(dāng)作一個(gè)樣本每一行當(dāng)作一個(gè)特征。也就是說如果你有 500 個(gè)樣本、6 個(gè)輸入、3 個(gè)輸出輸入矩陣X應(yīng)該是 6×500輸出矩陣Y應(yīng)該是 3×500。很多人從 Excel 讀進(jìn)來是 500×6直接丟進(jìn)去訓(xùn)練結(jié)果網(wǎng)絡(luò)把 500 個(gè)特征、6 個(gè)樣本拿去學(xué)訓(xùn)練誤差看著降了預(yù)測(cè)全是垃圾。這是血淚經(jīng)驗(yàn)里排第一的坑。正確的數(shù)據(jù)組織方式如下% 假設(shè) rawData 是 500×9 的矩陣前6列輸入后3列輸出 rawData readmatrix(data.xlsx); % 500×9 X rawData(:, 1:6); % 轉(zhuǎn)置為 6×500每列一個(gè)樣本 Y rawData(:, 7:9); % 轉(zhuǎn)置為 3×500每列一個(gè)樣本 % 檢查維度 fprintf(輸入維度: %d×%d\n, size(X,1), size(X,2)); fprintf(輸出維度: %d×%d\n, size(Y,1), size(Y,2));這段代碼的核心就兩步讀數(shù)據(jù)、轉(zhuǎn)置。readmatrix是 MATLAB R2019a 之后推薦的表格讀取函數(shù)比xlsread干凈。轉(zhuǎn)置之后size(X,1)是特征數(shù)size(X,2)是樣本數(shù)后面所有操作都圍繞這個(gè)約定。如果你用的是.mat文件直接load進(jìn)來后檢查變量名別假設(shè)它一定叫data。提示轉(zhuǎn)置后一定用size打印確認(rèn)別憑感覺。我見過有人轉(zhuǎn)置了兩次等于沒轉(zhuǎn)訓(xùn)練了半小時(shí)才發(fā)現(xiàn)。2.2 網(wǎng)絡(luò)結(jié)構(gòu)選型隱含層節(jié)點(diǎn)數(shù)不是越多越好多輸入多輸出 BP 網(wǎng)絡(luò)的結(jié)構(gòu)設(shè)計(jì)核心就三個(gè)決策幾個(gè)隱含層、每層多少節(jié)點(diǎn)、用什么訓(xùn)練函數(shù)。對(duì)于輸入輸出維度都在 20 以內(nèi)、樣本量幾百到幾千的問題一個(gè)隱含層足夠。理論上有萬能逼近定理撐著兩個(gè)隱含層只在函數(shù)復(fù)雜度極高時(shí)才需要而且更容易過擬合。隱含層節(jié)點(diǎn)數(shù)的經(jīng)驗(yàn)公式有好幾個(gè)我一般用這個(gè)起步nInput size(X, 1); % 輸入維度比如 6 nOutput size(Y, 1); % 輸出維度比如 3 nSample size(X, 2); % 樣本數(shù)比如 500 % 經(jīng)驗(yàn)公式sqrt(輸入輸出) 調(diào)節(jié)項(xiàng) nHidden round(sqrt(nInput nOutput)) 5; % 約 8 % 或者用 2*輸入1 起步 nHidden_alt 2 * nInput 1; % 13 % 搭建網(wǎng)絡(luò) net feedforwardnet(nHidden, trainlm); net.trainParam.epochs 1000; net.trainParam.goal 1e-5; net.trainParam.lr 0.01; net.trainParam.showWindow false; % 批量跑的時(shí)候關(guān)掉窗口feedforwardnet的第一個(gè)參數(shù)是隱含層節(jié)點(diǎn)數(shù)第二個(gè)是訓(xùn)練函數(shù)。trainlm是 Levenberg-Marquardt 算法收斂快適合中小規(guī)模網(wǎng)絡(luò)但內(nèi)存占用比trainscg高。如果樣本超過一萬條換trainscg更穩(wěn)。epochs設(shè) 1000 是上限實(shí)際訓(xùn)練中如果驗(yàn)證集誤差連續(xù) 6 次不降trainlm會(huì)自動(dòng)早停這個(gè)默認(rèn)參數(shù)是net.trainParam.max_fail 6。goal設(shè) 1e-5 是目標(biāo)誤差別設(shè)太小否則容易過擬合。節(jié)點(diǎn)數(shù)怎么定我的做法是從sqrt(nInputnOutput)5開始跑三次不同隨機(jī)種子看驗(yàn)證集 MSE 的均值和方差。如果方差大說明節(jié)點(diǎn)數(shù)偏多減 2 到 3 個(gè)再試。如果均值高加 2 個(gè)。這個(gè)過程一般迭代 3 到 4 輪就能找到穩(wěn)定區(qū)間。別用遺傳算法或粒子群去優(yōu)化節(jié)點(diǎn)數(shù)對(duì)于這個(gè)規(guī)模的問題手動(dòng)試比自動(dòng)搜索快。2.3 數(shù)據(jù)劃分與歸一化別讓量綱差異毀了訓(xùn)練多輸入場(chǎng)景下不同輸入的量綱可能差幾個(gè)數(shù)量級(jí)。比如溫度是 200 到 800壓力是 0.1 到 0.5如果不歸一化梯度下降會(huì)被大量綱特征主導(dǎo)小量綱特征幾乎不更新。MATLAB 的feedforwardnet默認(rèn)在訓(xùn)練前自動(dòng)做mapminmax歸一化把數(shù)據(jù)映射到 [-1, 1]訓(xùn)練后再反歸一化輸出。但這個(gè)自動(dòng)處理有個(gè)坑它是在train函數(shù)內(nèi)部做的你拿到的net對(duì)象里歸一化參數(shù)存在net.inputs{1}.processSettings里新數(shù)據(jù)預(yù)測(cè)時(shí)必須手動(dòng)調(diào)用同樣的歸一化參數(shù)否則預(yù)測(cè)結(jié)果完全不對(duì)。% 手動(dòng)劃分訓(xùn)練/驗(yàn)證/測(cè)試集 net.divideParam.trainRatio 0.7; net.divideParam.valRatio 0.15; net.divideParam.testRatio 0.15; % 訓(xùn)練 [net, tr] train(net, X, Y); % 訓(xùn)練集預(yù)測(cè) Y_train_pred net(X(:, tr.trainInd)); % 測(cè)試集預(yù)測(cè) Y_test_pred net(X(:, tr.testInd)); % 計(jì)算測(cè)試集 MSE mse_test perform(net, Y(:, tr.testInd), Y_test_pred); fprintf(測(cè)試集 MSE: %.6f\n, mse_test);divideParam的三個(gè)比例加起來必須是 1。tr.trainInd、tr.valInd、tr.testInd是訓(xùn)練完成后tr結(jié)構(gòu)體里的索引直接拿來切數(shù)據(jù)最可靠。perform函數(shù)自動(dòng)處理了歸一化和反歸一化算出來的 MSE 是原始量綱下的。如果你自己手算mean((Y_pred - Y_true).^2)記得先反歸一化否則數(shù)值對(duì)不上。注意train函數(shù)每次調(diào)用會(huì)重新隨機(jī)劃分?jǐn)?shù)據(jù)想復(fù)現(xiàn)結(jié)果就在train之前設(shè)rng(42)固定種子。3. SHAP 可解釋分析從黑箱里挖出特征貢獻(xiàn)度3.1 SHAP 值在回歸問題里的數(shù)學(xué)含義SHAP 的核心思想來自合作博弈論里的 Shapley 值把每個(gè)特征看作一個(gè)“玩家”模型預(yù)測(cè)值看作“總收益”每個(gè)特征分到的收益就是它對(duì)預(yù)測(cè)結(jié)果的貢獻(xiàn)。對(duì)于回歸問題SHAP 值滿足三個(gè)性質(zhì)可加性所有特征 SHAP 值之和等于預(yù)測(cè)值減去基線值、對(duì)稱性兩個(gè)貢獻(xiàn)相同的特征 SHAP 值相同、一致性特征貢獻(xiàn)變大時(shí) SHAP 值不會(huì)減小。這些性質(zhì)保證了歸因的合理性比簡單的特征重要性排序靠譜得多。對(duì)于 BP 網(wǎng)絡(luò)這種非線性模型精確計(jì)算 SHAP 值需要遍歷所有特征子集計(jì)算量是 2 的 n 次方。實(shí)際用的是 KernelSHAP 或 DeepSHAP 近似算法。KernelSHAP 把 SHAP 值計(jì)算轉(zhuǎn)化為一個(gè)加權(quán)線性回歸問題對(duì)每個(gè)樣本采樣若干特征子集用模型預(yù)測(cè)值擬合。DeepSHAP 則利用神經(jīng)網(wǎng)絡(luò)的反向傳播把 SHAP 值分解到每一層效率更高但要求網(wǎng)絡(luò)結(jié)構(gòu)已知。在 MATLAB 里沒有官方 SHAP 工具箱常見做法有兩種一是調(diào)用 Python 的shap庫通過 MATLAB 的 Python 接口傳數(shù)據(jù)二是自己實(shí)現(xiàn) KernelSHAP 的核心邏輯。我一般用第一種因?yàn)?Python 的shap庫成熟穩(wěn)定MATLAB 只負(fù)責(zé)訓(xùn)練網(wǎng)絡(luò)和導(dǎo)出預(yù)測(cè)函數(shù)。3.2 用 MATLAB 訓(xùn)練網(wǎng)絡(luò)并導(dǎo)出預(yù)測(cè)接口要讓 Python 的 SHAP 庫能調(diào)用 MATLAB 訓(xùn)練好的網(wǎng)絡(luò)最干凈的方式是把網(wǎng)絡(luò)導(dǎo)出為可獨(dú)立調(diào)用的函數(shù)。MATLAB 提供了genFunction函數(shù)可以把訓(xùn)練好的網(wǎng)絡(luò)轉(zhuǎn)成純 MATLAB 代碼不依賴神經(jīng)網(wǎng)絡(luò)工具箱。% 訓(xùn)練完成后導(dǎo)出網(wǎng)絡(luò)為函數(shù) genFunction(net, bpNetPredict, MatrixOnly, yes); % 測(cè)試導(dǎo)出的函數(shù) Y_check bpNetPredict(X); fprintf(導(dǎo)出函數(shù)與網(wǎng)絡(luò)預(yù)測(cè)最大差異: %.2e\n, max(abs(Y_check(:) - net(X)(:))));genFunction生成的bpNetPredict.m文件包含了網(wǎng)絡(luò)的所有權(quán)重、偏置和歸一化參數(shù)輸入輸出都是矩陣格式。MatrixOnly設(shè)為yes表示只接受矩陣輸入不接受元胞數(shù)組這樣在 Python 里調(diào)用更方便。導(dǎo)出后一定要用max(abs(...))驗(yàn)證一下差異應(yīng)該在 1e-10 量級(jí)如果大了說明導(dǎo)出過程有問題。接下來在 Python 里通過matlab.engine調(diào)用這個(gè)函數(shù)import matlab.engine import numpy as np import shap # 啟動(dòng) MATLAB 引擎 eng matlab.engine.start_matlab() eng.cd(rC:\your_project_path, nargout0) # 準(zhǔn)備數(shù)據(jù)X_py 是 numpy 數(shù)組形狀 (n_samples, n_features) X_py np.load(X_for_shap.npy) X_matlab matlab.double(X_py.tolist()) # 調(diào)用 MATLAB 預(yù)測(cè)函數(shù) Y_pred eng.bpNetPredict(X_matlab) Y_pred np.array(Y_pred) # 用 KernelSHAP 解釋 # 注意這里需要一個(gè)包裝函數(shù)輸入 numpy 返回 numpy def model_predict(X): X_m matlab.double(X.tolist()) Y eng.bpNetPredict(X_m) return np.array(Y).T # 轉(zhuǎn)置為 (n_samples, n_outputs) # 對(duì)第一個(gè)輸出做 SHAP 分析 explainer shap.KernelExplainer( lambda x: model_predict(x)[:, 0], # 只取第一個(gè)輸出 shap.sample(X_py, 50) # 用 50 個(gè)背景樣本 ) shap_values explainer.shap_values(X_py[:100], nsamples200)這段代碼的關(guān)鍵點(diǎn)matlab.double把 numpy 數(shù)組轉(zhuǎn)成 MATLAB 能識(shí)別的雙精度矩陣model_predict包裝函數(shù)負(fù)責(zé)在 Python 和 MATLAB 之間轉(zhuǎn)換數(shù)據(jù)格式shap.KernelExplainer的第一個(gè)參數(shù)是預(yù)測(cè)函數(shù)第二個(gè)參數(shù)是背景數(shù)據(jù)集背景樣本數(shù)一般取 50 到 100太少會(huì)導(dǎo)致 SHAP 值方差大太多計(jì)算慢。nsamples200是每個(gè)樣本采樣的特征子集數(shù)越大越精確但計(jì)算時(shí)間線性增長。3.3 SHAP 圖怎么看從 summary plot 到 dependence plotSHAP 分析跑完后核心產(chǎn)出是三類圖summary plot、dependence plot 和 force plot。summary plot 把每個(gè)特征的 SHAP 值分布畫成蜂群圖橫軸是 SHAP 值縱軸是特征名顏色表示特征值高低。看這張圖能快速判斷哪些特征重要SHAP 絕對(duì)值大、影響方向是什么特征值高時(shí) SHAP 正還是負(fù)。import matplotlib.pyplot as plt # Summary plot shap.summary_plot(shap_values, X_py[:100], feature_names[fX{i1} for i in range(6)]) plt.savefig(shap_summary.png, dpi300, bbox_inchestight) # Dependence plot看第 3 個(gè)特征對(duì)第 1 個(gè)輸出的影響 shap.dependence_plot(2, shap_values, X_py[:100], feature_names[fX{i1} for i in range(6)]) plt.savefig(shap_dependence_X3.png, dpi300, bbox_inchestight)summary_plot的feature_names參數(shù)建議用有物理意義的名稱比如[溫度, 壓力, 流速, ...]別用X1、X2否則圖給領(lǐng)導(dǎo)看的時(shí)候還得解釋。dependence_plot的第一個(gè)參數(shù)是特征索引從 0 開始。這張圖能看出特征與 SHAP 值的關(guān)系是線性還是非線性如果散點(diǎn)呈現(xiàn)明顯的曲線說明 BP 網(wǎng)絡(luò)捕捉到了非線性效應(yīng)這正是用神經(jīng)網(wǎng)絡(luò)而不是線性回歸的理由。提示SHAP 值有正負(fù)正表示該特征把預(yù)測(cè)值推高負(fù)表示推低。summary plot 里如果某個(gè)特征的 SHAP 值集中在 0 附近說明這個(gè)特征對(duì)模型幾乎沒貢獻(xiàn)可以考慮剔除后重新訓(xùn)練簡化模型。4. 新數(shù)據(jù)預(yù)測(cè)從單條樣本到批量推理的完整鏈路4.1 新數(shù)據(jù)預(yù)處理的三個(gè)必須對(duì)齊新數(shù)據(jù)預(yù)測(cè)翻車十有八九是預(yù)處理沒對(duì)齊。訓(xùn)練時(shí)用的歸一化參數(shù)、缺失值處理方式、異常值截?cái)嚅撝翟谛聰?shù)據(jù)上必須一模一樣。MATLAB 的genFunction導(dǎo)出的函數(shù)已經(jīng)包含了訓(xùn)練時(shí)的歸一化參數(shù)所以只要新數(shù)據(jù)的原始量綱和訓(xùn)練數(shù)據(jù)一致直接調(diào)用就行。但如果你在訓(xùn)練前手動(dòng)做過缺失值填充或異常值替換新數(shù)據(jù)也得走同樣的流程。% 新數(shù)據(jù)一條樣本6 個(gè)輸入 newSample [350, 0.35, 12.5, 80, 2.1, 0.9]; % 直接調(diào)用導(dǎo)出的函數(shù) prediction bpNetPredict(newSample); fprintf(預(yù)測(cè)輸出: %.4f, %.4f, %.4f\n, prediction(1), prediction(2), prediction(3)); % 批量預(yù)測(cè)100 條新樣本 newBatch rand(100, 6) .* [500, 0.5, 20, 100, 3, 1.5]; % 模擬新數(shù)據(jù) predBatch bpNetPredict(newBatch); fprintf(批量預(yù)測(cè)維度: %d×%d\n, size(predBatch,1), size(predBatch,2));注意newSample是 1×6 的行向量轉(zhuǎn)置后變成 6×1 的列向量符合網(wǎng)絡(luò)輸入要求。predBatch是 3×100每列一個(gè)樣本的三個(gè)輸出。如果新數(shù)據(jù)的某個(gè)特征超出了訓(xùn)練數(shù)據(jù)的范圍BP 網(wǎng)絡(luò)會(huì)外推但外推可靠性隨超出程度增加而下降。我一般會(huì)檢查新數(shù)據(jù)每個(gè)特征是否在訓(xùn)練數(shù)據(jù)的 [min, max] 范圍內(nèi)超出 20% 以上的樣本標(biāo)記出來人工復(fù)核。4.2 預(yù)測(cè)結(jié)果的置信區(qū)間估計(jì)BP 網(wǎng)絡(luò)給出的是點(diǎn)預(yù)測(cè)沒有置信區(qū)間。但在工程決策里光有點(diǎn)預(yù)測(cè)不夠還需要知道預(yù)測(cè)的不確定性。常用做法是集成多個(gè)不同初始化的網(wǎng)絡(luò)用預(yù)測(cè)值的均值和標(biāo)準(zhǔn)差作為置信區(qū)間的近似。% 訓(xùn)練 10 個(gè)不同初始化的網(wǎng)絡(luò) nEnsemble 10; Y_ensemble zeros(nOutput, size(X_new, 2), nEnsemble); for i 1:nEnsemble rng(i * 100); % 不同隨機(jī)種子 net_i feedforwardnet(nHidden, trainlm); net_i.trainParam.showWindow false; net_i train(net_i, X, Y); genFunction(net_i, sprintf(bpNetPredict_%d, i), MatrixOnly, yes); Y_ensemble(:, :, i) feval(sprintf(bpNetPredict_%d, i), X_new); end % 計(jì)算均值和標(biāo)準(zhǔn)差 Y_mean mean(Y_ensemble, 3); Y_std std(Y_ensemble, 0, 3); % 95% 置信區(qū)間近似 Y_lower Y_mean - 1.96 * Y_std; Y_upper Y_mean 1.96 * Y_std; fprintf(第一個(gè)輸出的 95%% 置信區(qū)間寬度均值: %.4f\n, mean(Y_upper(1,:) - Y_lower(1,:)));這段代碼訓(xùn)練 10 個(gè)網(wǎng)絡(luò)每個(gè)用不同隨機(jī)種子預(yù)測(cè)結(jié)果取均值和標(biāo)準(zhǔn)差。std的第二個(gè)參數(shù) 0 表示按 N-1 歸一化第三個(gè)參數(shù) 3 表示沿第三維集成維度計(jì)算。置信區(qū)間寬度反映了模型在這個(gè)樣本上的不確定性寬度大的樣本建議人工復(fù)核。這個(gè)方法的計(jì)算成本是單網(wǎng)絡(luò)的 10 倍如果訓(xùn)練一個(gè)網(wǎng)絡(luò)要 5 分鐘集成就要 50 分鐘適合離線批量預(yù)測(cè)不適合實(shí)時(shí)推理。4.3 把預(yù)測(cè)和 SHAP 解釋串成一條流水線實(shí)際項(xiàng)目里新數(shù)據(jù)預(yù)測(cè)和 SHAP 解釋往往需要一起交付。比如給一批新樣本既要預(yù)測(cè)值又要知道每個(gè)樣本的預(yù)測(cè)主要受哪個(gè)特征驅(qū)動(dòng)。這時(shí)候可以把預(yù)測(cè)和 SHAP 分析串成一個(gè)腳本輸入原始數(shù)據(jù)輸出預(yù)測(cè)表加解釋圖。% 完整流水線新數(shù)據(jù)預(yù)測(cè) SHAP 解釋 function [Y_pred, shap_values] predictWithExplanation(X_new, model_path) % 加載導(dǎo)出的預(yù)測(cè)函數(shù) addpath(model_path); % 預(yù)測(cè) Y_pred bpNetPredict(X_new); % 導(dǎo)出新數(shù)據(jù)供 Python SHAP 使用 writematrix(X_new, X_new_for_shap.csv); writematrix(Y_pred, Y_pred_for_shap.csv); % 調(diào)用 Python 腳本做 SHAP 分析 system(python run_shap_analysis.py); % 讀取 SHAP 結(jié)果 shap_values readmatrix(shap_values.csv); fprintf(預(yù)測(cè)完成SHAP 分析完成\n); end這個(gè)函數(shù)把 MATLAB 預(yù)測(cè)和 Python SHAP 分析串起來中間用 CSV 文件交換數(shù)據(jù)。writematrix和readmatrix是 MATLAB 里最穩(wěn)定的 CSV 讀寫函數(shù)。system調(diào)用 Python 腳本時(shí)確保 Python 環(huán)境里裝了shap、numpy、matlab.engine等依賴。如果 Python 腳本報(bào)錯(cuò)MATLAB 這邊不會(huì)自動(dòng)捕獲建議在system調(diào)用后檢查返回狀態(tài)碼。注意system調(diào)用 Python 時(shí)工作目錄要和 Python 腳本里讀寫文件的路徑一致否則會(huì)找不到文件。我一般用絕對(duì)路徑省得排查路徑問題。5. 避坑與排查多輸入多輸出 BP SHAP 的五個(gè)高頻翻車點(diǎn)5.1 訓(xùn)練集 MSE 很低但測(cè)試集 MSE 爆炸現(xiàn)象訓(xùn)練完看tr.best_perf是 1e-6 量級(jí)但拿測(cè)試集一算 MSE 是 0.5差了五個(gè)數(shù)量級(jí)。原因過擬合。隱含層節(jié)點(diǎn)太多、訓(xùn)練輪數(shù)太多、樣本量太少三者占一個(gè)就會(huì)這樣。解決先減隱含層節(jié)點(diǎn)從sqrt(nInputnOutput)5減到sqrt(nInputnOutput)再把max_fail從 6 降到 4讓早停更激進(jìn)如果樣本確實(shí)少用trainbr貝葉斯正則化替代trainlm它自帶正則項(xiàng)抗過擬合能力強(qiáng)。5.2 SHAP 值全為正或全為負(fù)現(xiàn)象summary plot 里所有特征的 SHAP 值都在零線同一側(cè)看起來每個(gè)特征都在推高或推低預(yù)測(cè)。原因背景數(shù)據(jù)集選得不對(duì)。KernelSHAP 的基線是背景數(shù)據(jù)集的平均預(yù)測(cè)值如果背景數(shù)據(jù)集和解釋數(shù)據(jù)集分布差異大SHAP 值會(huì)整體偏移。解決背景數(shù)據(jù)集從訓(xùn)練集里隨機(jī)采樣別從測(cè)試集或新數(shù)據(jù)里采。樣本數(shù) 50 到 100 之間太少方差大太多計(jì)算慢。另外檢查model_predict函數(shù)返回的維度是否和shap_values期望的一致多輸出時(shí)只取一個(gè)輸出做解釋。5.3 新數(shù)據(jù)預(yù)測(cè)結(jié)果全是 NaN現(xiàn)象bpNetPredict(newSample)返回 NaN。原因新數(shù)據(jù)里有 NaN 或 Inf。BP 網(wǎng)絡(luò)的矩陣運(yùn)算遇到 NaN 會(huì)傳播到整個(gè)輸出。解決預(yù)測(cè)前檢查any(isnan(newSample))和any(isinf(newSample))有的話先填充或剔除。另外檢查新數(shù)據(jù)的量綱是否和訓(xùn)練數(shù)據(jù)一致如果訓(xùn)練時(shí)輸入是 0 到 1新數(shù)據(jù)是 0 到 100歸一化后可能超出 [-1, 1] 范圍但不會(huì)產(chǎn)生 NaN只會(huì)預(yù)測(cè)不準(zhǔn)。5.4 MATLAB 和 Python 數(shù)據(jù)交換時(shí)維度對(duì)不上現(xiàn)象Python 里matlab.double(X.tolist())傳過去后 MATLAB 報(bào)維度錯(cuò)誤。原因tolist()把 numpy 數(shù)組轉(zhuǎn)成嵌套列表matlab.double默認(rèn)按行優(yōu)先解釋而 MATLAB 是列優(yōu)先。如果 numpy 數(shù)組是 (n_samples, n_features)轉(zhuǎn)過去 MATLAB 看到的是 (n_features, n_samples)正好轉(zhuǎn)置了。解決在 Python 里轉(zhuǎn)置一下matlab.double(X.T.tolist())或者在 MATLAB 里再轉(zhuǎn)置一次。我一般約定 Python 端傳轉(zhuǎn)置后的數(shù)據(jù)MATLAB 端不再轉(zhuǎn)減少混亂。5.5 genFunction 導(dǎo)出的函數(shù)預(yù)測(cè)結(jié)果和原網(wǎng)絡(luò)不一致現(xiàn)象bpNetPredict(X)和net(X)的結(jié)果差很多。原因genFunction默認(rèn)不包含歸一化參數(shù)或者導(dǎo)出時(shí)MatrixOnly設(shè)成了no。解決導(dǎo)出時(shí)明確指定MatrixOnly, yes并且檢查生成的.m文件里是否有mapminmax_apply和mapminmax_reverse的調(diào)用。如果沒有說明歸一化沒導(dǎo)出需要手動(dòng)在genFunction之前設(shè)置net.inputs{1}.processFcns和net.outputs{2}.processFcns確保包含mapminmax。導(dǎo)出后必須用max(abs(...))驗(yàn)證差異大于 1e-8 就要查。6. 進(jìn)階技巧用 SHAP 交互值定位特征協(xié)同效應(yīng)單特征 SHAP 值只能告訴你每個(gè)特征獨(dú)立貢獻(xiàn)了多少但多輸入場(chǎng)景下特征之間的交互效應(yīng)往往才是關(guān)鍵。比如溫度高時(shí)壓力對(duì)輸出的影響可能比溫度低時(shí)大得多這種協(xié)同效應(yīng)單特征 SHAP 圖看不出來。SHAP 交互值SHAP interaction values能拆解出每對(duì)特征的聯(lián)合貢獻(xiàn)計(jì)算量是單特征 SHAP 的 n 倍n 是特征數(shù)6 個(gè)特征就是 6 倍還能接受。在 Python 的shap庫里有兩種方式算交互值。一是shap.TreeExplainer自帶shap_interaction_values方法但只支持樹模型。BP 網(wǎng)絡(luò)得用KernelExplainer加shap_interaction_values函數(shù)計(jì)算更慢但通用。# 計(jì)算 SHAP 交互值只對(duì)前 20 個(gè)樣本計(jì)算量大 shap_interaction explainer.shap_interaction_values(X_py[:20]) # shap_interaction 形狀: (n_samples, n_features, n_features) # 對(duì)角線是單特征 SHAP 值非對(duì)角線是交互值 # 提取第 0 個(gè)樣本的第 2 和第 4 個(gè)特征的交互值 interaction_2_4 shap_interaction[0, 2, 4] print(f特征3和特征5的交互 SHAP 值: {interaction_2_4:.4f}) # 畫交互熱力圖 import seaborn as sns mean_interaction np.mean(np.abs(shap_interaction), axis0) sns.heatmap(mean_interaction, annotTrue, fmt.3f, xticklabels[fX{i1} for i in range(6)], yticklabels[fX{i1} for i in range(6)]) plt.title(SHAP Interaction Heatmap) plt.savefig(shap_interaction.png, dpi300, bbox_inchestight)shap_interaction_values返回一個(gè)三維數(shù)組第一維是樣本第二維和第三維是特征對(duì)。對(duì)角線元素就是單特征 SHAP 值非對(duì)角線元素是交互值。熱力圖里顏色越深表示交互效應(yīng)越強(qiáng)。如果發(fā)現(xiàn)某對(duì)特征的交互值很大說明這兩個(gè)特征對(duì)輸出的影響不是簡單疊加而是有協(xié)同或拮抗。這時(shí)候可以在工藝上重點(diǎn)關(guān)注這兩個(gè)參數(shù)的匹配關(guān)系而不是單獨(dú)調(diào)一個(gè)。我一般會(huì)把這個(gè)熱力圖和工藝知識(shí)對(duì)照如果兩個(gè)特征在物理上確實(shí)有關(guān)聯(lián)比如溫度和壓力在熱力學(xué)上耦合那 SHAP 交互值大是合理的說明模型學(xué)到了真實(shí)規(guī)律如果兩個(gè)特征物理上無關(guān)但交互值大可能是數(shù)據(jù)里的偽相關(guān)需要檢查采樣過程是否有偏差。最后一個(gè)習(xí)慣每次跑完 SHAP 分析我都會(huì)把shap_values和原始數(shù)據(jù)一起存成.mat文件命名帶上日期和模型版本。因?yàn)?SHAP 計(jì)算耗時(shí)下次想復(fù)現(xiàn)某張圖不用重跑。這個(gè)習(xí)慣幫我省了至少幾十個(gè)小時(shí)的重復(fù)計(jì)算。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取