尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

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

基于MATLAB的LSTM回归预测与SHAP可解释性分析实战

基于MATLAB的LSTM回归预测与SHAP可解释性分析实战 简介本资源是一套面向机器学习与智能算法研究者的MATLAB实战代码包聚焦LSTM回归预测模型的可解释性增强特别适用于能源负荷预测、环境参数建模、工业时序回归等需兼顾精度与决策可信度的工程场景。压缩包共6个文件713KB含2个核心MATLAB脚本main_shap.m主流程shapley_function.m自定义SHAP计算、1个原始Excel数据集回归数据.xlsx、2张关键可视化结果图预测对比与SHAP蜂群图及1份输出说明txt覆盖从数据预处理、LSTM建模训练、多指标评估到SHAP值计算与双维度可视化全局重要性条形图局部贡献蜂群图的完整技术链。已有49人学习下载提供开箱即用的完整实现无需修改即可运行全流程包含归一化参数保存、cell格式自动转换、反归一化预测还原、RMSE/R²/MAE三指标输出及符合学术规范的图表生成显著降低SHAP在时序深度模型中落地的理解与实现门槛。 做回归预测的LSTM代码网上一搜一大把但大部分教程都停在了“训练完画个真实值-预测值对比图”这一步。真正拿到项目里客户问一句“你这预测值为什么偏高是哪个输入参数影响的”基本就答不上来了。这个项目从一开始就没打算只做一个黑盒回归模型而是把LSTM回归预测和SHAP可解释分析一起做了完整闭环MATLAB训练好LSTM模型预测出连续数值再用SHAP算出每个输入特征对预测结果的正负贡献。整条链路都有完整代码和测试数据适合刚接触深度回归的工程师、想做可解释性研究的学生以及被“领导追问模型依据”折磨的数据分析人员。1. 项目整体设计与技术选型思路1.1 为什么回归预测场景下LSTM比BP和CNN更合适回归预测任务有很多模型可选传统BP神经网络、卷积神经网络、支持向量回归都能做但这类任务的数据往往是带时间先后顺序的多变量观测。比如设备运行参数逐小时记录、能耗逐日统计、金融指标连续采样。数据一旦有了时间先后关系普通BP网络的问题就暴露了它把所有输入特征当作独立的平铺向量完全丢失了时间顺序信息。CNN虽然可以通过一维卷积捕捉局部模式受限于卷积核的感受野对于长距离的时序依赖往往要堆很多层才能覆盖参数量和训练难度都会上升。LSTM则天生是处理序列结构的循环网络通过输入门、遗忘门、输出门三个门控结构让网络能自主决定哪些历史信息要记住、哪些要丢弃。就拿预测设备关键参数来说它会自动学到“30个时间步之前的异常振动是否对当前输出有影响”这种长期依赖建模能力是BP和CNN难以直接替代的。不过这也不代表LSTM在所有时序任务里都是最优解。样本量特别少几百条、序列非常短、或者数据本身没有明显时序依赖时简单模型往往效果更好。我在实际项目中见过有人非要在几百条数据上硬上LSTM结果过拟合得一塌糊涂。这个项目里用LSTM前提是你手里的数据是典型的多变量周期性或趋势性序列样本量在千条以上序列长度在10到100之间比较合适。1.2 SHAP对黑盒回归模型的价值不止“特征重要性”很多工具能输出特征重要性排序比如置换重要性、决策树的Gini重要性但它们只能告诉你“哪些特征对预测结果有影响”回答不了“这个特征是把预测值往高推还是往低拉”。SHAP不一样它来源于博弈论里的Shapley值每个输入特征被当作一个“玩家”预测值就是所有玩家合作得到的“总收益”。SHAP把总收益公平地分配给每个特征得到一个带方向的贡献值。正数代表该特征的当前取值把预测结果往上推负数代表往下拉。这个信息在回归预测里非常实用。比如你预测设备的某个关键指标偏高SHAP结果会直接显示“是入口温度这个特征贡献了3.2压力贡献了-1.1”定位原因一步到位。相比于LIME这类局部解释方法SHAP在数学上没有对特征独立性做不切实际的假设不同样本的解释结果也具备一致性不会出现A样本解释和B样本解释互相矛盾的情况。1.3 整体流程从原始时序数据到可解释结论整个项目流程可以拆成四段。第一段是数据准备原始数据按时间顺序切分成训练集、验证集、测试集同时做归一化把多变量序列切成长度为“时间窗口”的样本块。第二段是LSTM网络搭建用MATLAB Deep Learning Toolbox的sequenceInputLayer、lstmLayer、fullyConnectedLayer、regressionLayer搭一个多对一的回归网络。第三段是训练与评估用trainNetwork训练用测试集预测后反归一化计算RMSE、MAE、R²。第四段是SHAP可解释分析由于MATLAB没有现成的SHAP工具箱我基于Kernel SHAP原理写了一套模型无关的解释器直接调用训练好的LSTM网络做黑盒预测最终输出特征贡献排序和正负方向。2. 数据准备与LSTM网络搭建实操2.1 数据组织方式时间窗口切分是所有步骤的地基这个项目采用了“多输入单输出”的回归形式用过去P个时间步的N个变量预测未来一个时刻的目标值。P就是时间窗口长度N是输入变量数。数据组织上原始表格通常长这样每行是一个时间戳每列是一个监测变量最后一列是待预测目标。MATLAB的trainNetwork要求LSTM回归的输入是元胞数组或数值数组。处理不定长序列时用元胞数组每个元胞放一个样本的序列这个项目里所有序列长度固定为P所以可以用数值数组维度是特征数×时间步×样本数。代码里我习惯先写一个切窗函数把原始时间矩阵转为样本块function [X, Y] createSequences(data, targetIdx, P) % data: 每行一个时刻每列一个变量 % targetIdx: 目标变量所在列 % P: 时间窗口长度 numSamples size(data, 1) - P; numFeatures size(data, 2) - 1; % 去掉目标列或按需保留目标列滞后值 X zeros(numFeatures, P, numSamples); Y zeros(numSamples, 1); for i 1:numSamples X(:, :, i) data(i:iP-1, [1:targetIdx-1, targetIdx1:end]); Y(i) data(iP, targetIdx); end end注意几点一是切窗时窗口不能跨数据段如果原始数据是分段测量的要在每段内部单独切二是目标列如果也作为输入特征使用比如用历史目标值预测未来目标值那么输入里应包含目标列的滞后序列代码里的numFeatures需要相应调整三是窗口P的选取要结合数据采样频率和自相关性来试不能拍脑袋定。2.2 归一化这步做错LSTM大概率不收敛LSTM内部用sigmoid和tanh作为激活函数输入如果绝对值过大或尺度差异悬殊很容易让门控饱和梯度更新变得极慢甚至完全不更新。所以对所有输入特征和目标值都要归一化。我习惯把数据先按列统计均值和标准差再做z-score标准化到零均值单位方差。投影到预测阶段后再反归一化。这里有个常见错误直接用全量数据算归一化的均值和方差这会引入未来信息泄漏——测试集的均值方差信息被提前用到了训练阶段。正确做法是只用训练集的统计量应用到验证集和测试集% 只从训练集计算归一化参数 muX mean(XTrainRaw, [1, 3]); % 每个特征的均值维度1×numFeatures stdX std(XTrainRaw, 0, [1, 3]); % XTrainRaw 维度numFeatures×P×numSamples XTrain (XTrainRaw - muX) ./ stdX; % 测试集使用同样的均值和方差 XTest (XTestRaw - muX) ./ stdX; % 目标值同理 muY mean(YTrainRaw); stdY std(YTrainRaw); YTrain (YTrainRaw - muY) / stdY;MATLAB的std函数对三维数组按维度求标准差时[1,3]表示沿特征维和时间维计算得到每行特征一个值。这里维度容易搞混写完代码建议用size确认一遍。如果某列标准差为0常数值直接除会得到NaN我在预处理时会把方差接近0的特征直接删掉否则后面SHAP计算也会跟着出错。2.3 LSTM网络结构从序列输入到回归输出序列数据进入LSTM网络时特征维必须放在第一维。对于每个样本LSTM按时间步依次展开输入序列最后一个时间步的隐含状态由OutputMode参数决定是否输出。做回归预测时通常用最后一步的隐含状态接一个全连接层输出一个连续值。我推荐一个比较稳妥的基线网络numFeatures size(XTrain, 1); numHiddenUnits 64; layers [ sequenceInputLayer(numFeatures, Name, input) lstmLayer(numHiddenUnits, OutputMode, last, Name, lstm1) dropoutLayer(0.2, Name, dropout1) fullyConnectedLayer(32, Name, fc1) reluLayer(Name, relu1) fullyConnectedLayer(1, Name, fc2) regressionLayer(Name, output) ];几个设计点第一lstmLayer的OutputMode要设为last只返回最后一个时间步的输出这样接全连接层才能得到单个预测值如果设成sequence输出就是每个时间步的预测序列那用途就是序列到序列回归了。第二dropout层放在LSTM之后、全连接之前可以有效抑制过拟合训练时随机丢弃20%的神经元预测时自动不丢弃。第三全连接层加一个32维的隐藏层再接relu可以增加网络非线性表达能力。numHiddenUnits取多少我一般从32开始试按64、128递加同时观察验证集误差。隐藏单元数太小拟合能力不足太大训练变慢且容易过拟合。对这个项目几十个特征的规模来说64是一个很不错的起点。3. 模型训练、预测与评估的完整流程3.1 训练选项怎么调学习率、批次与早停的关键取舍MATLAB的trainingOptions配置直接决定了能不能训练出一个可用的模型。我的默认配置如下options trainingOptions(adam, ... MaxEpochs, 200, ... MiniBatchSize, 32, ... InitialLearnRate, 0.005, ... GradientThreshold, 1, ... Shuffle, every-epoch, ... ValidationData, {XValidation, YValidation}, ... ValidationFrequency, 10, ... Verbose, true, ... Plots, training-progress, ... OutputFcn, (info)stopIfValidationLossNotImproving(info, 10));adam优化器对学习率的敏感性相对较低适合LSTM这种非凸优化问题。InitialLearnRate设置在0.001到0.01之间比较安全太高容易震荡发散太低收敛慢到怀疑人生。GradientThreshold设为1是为了防止梯度爆炸这是LSTM训练里很常见的问题时序长、网络深的时候梯度累乘容易膨胀加了裁剪能稳定很多。MiniBatchSize的选择要权衡。32是一个常用起点如果训练集很大可以试64或128。批次太大虽然训练速度快但收敛效果会下降批次太小则梯度噪声大。早期训练时打开Plots观察损失曲线如果训练损失不断下降但验证损失上升就是过拟合信号应该加大dropout、加L2正则或者提前停止。OutputFcn里的停止条件是一个自定义回调函数连续10次验证损失不下降就中止训练能省下大量时间。3.2 实际训练中必须盯住的三个信号训练LSTM不像训练普通回归模型那么“秒出结果”200个epoch跑下来少说几分钟多则几十分钟。等待过程中要盯住三个信号。第一个信号是训练损失曲线的形态。正常情况应该是前几十个epoch快速下降然后平缓收敛。如果损失一路锯齿剧烈震荡大概率是学习率过大调低一个数量级再试。如果前几十个epoch损失几乎没变化可能是学习率太低或者归一化出了问题先检查数据是否存在NaN。第二个信号是验证损失和训练损失的剪刀差。两者都在下降是健康的验证损失开始回头就是过拟合开始了。这时候优先调节dropout比例从0.2升到0.3甚至0.5同时可以铺一个全连接层的L2正则。第三个信号是最终预测值的滞后现象。回归预测结果如果画出来和真实值几乎完全平行只是整体往右偏移了一个时间步说明模型学到的是“把上一个时刻的值复制过来”也就是倾向于用最近的历史值做懒惰预测。出现这种情况时可以尝试删除输入变量中的目标变量滞后项或者缩短时间窗口长度。3.3 测试集评估R²别只看数值大小训练完成后用测试集做最终评估。由于前面归一到标准空间预测结果需要还原到原始尺度再计算指标YPredRaw predict(net, XTest); YPred YPredRaw * stdY muY; YTest YTestRaw; % 原始测试目标值 rmse sqrt(mean((YPred - YTest).^2)); mae mean(abs(YPred - YTest)); r2 1 - sum((YPred - YTest).^2) / sum((YTest - mean(YTest)).^2);R²越接近1说明拟合程度越高但要警惕伪高R²。当目标变量自身具有很强的自相关性时哪怕模型输出几乎是目标值的滞后拷贝R²也可能很高。所以我要强调最终判断模型好不好不能脱离业务场景看指标。如果预测结果是要用于预警那么对异常波动的捕捉能力比整体R²更重要。建议额外画一张“预测误差随时间分布”的图看看误差是不是集中在某些时间段这往往能暴露出数据里的隐式分段或周期性规律。4. SHAP可解释性分析的MATLAB完整实现4.1 为什么不用现成工具而是自己写Kernel SHAPMATLAB没有官方SHAP工具箱常见的替代做法是训练后在MATLAB里导出ONNX模型再扔给Python的shap库去分析。这在工程上多了一层环境依赖要在机器上装Python环境、装TensorFlow或PyTorch、装shap库对很多用MATLAB为主力工具的人来说并不友好。Kernel SHAP是一种模型无关的Shapley值近似方法只要求能对任意输入样本调用预测函数不关心模型内部结构。既然LSTM模型训练完已经是一个黑盒预测函数完全可以在MATLAB内部实现Kernel SHAP不需要任何额外工具链。这个方案的好处是纯MATLAB闭环换数据、换模型都能复用同一套SHAP代码。缺点是大批量计算时速度会慢但通过合理设置采样数和并行计算可以接受。4.2 Kernel SHAP核心原理如何把预测值分给每个特征假设一个样本有M个特征把这M个特征看成M个玩家模型预测值就是合作产生的总收益。Shapley值给出每个玩家的边际贡献需要在所有特征子集上计算边际贡献的组合平均。精确计算需要对2^M个子集全部枚举特征数一多就不可行。Kernel SHAP的核心思路是随机采样若干个特征子集用加权线性回归来近似Shapley值。具体做法是对每个采样子集S让被选中的特征保留带解释样本的值未被选中的特征用背景数据集的对应值填充生成扰动样本并送进模型预测。这个预测值会偏离原始预测值偏离量就记录该子集的“贡献效果”。对所有采样子集以SHAP核为权重解一个带L2正则的线性回归得到每个特征的Shapley值近似解。SHAP核的权重公式是特征子集S的权重 (M-1) / (C(M, |S|) * |S| * (M-|S|))其中C(M, |S|)是组合数|S|是子集中被选中的特征个数。直观理解是只选1个特征和选择M-1个特征几乎全选的子集包含的信息量最大所以权重最高而中间大小的子集权重较低。4.3 适配LSTM的扰动生成策略这里有一个时间序列特有的细节要处理LSTM的输入是一段时间窗口的多变量序列每个样本的输入形状是特征数×时间步。如果按传统Kernel SHAP的做法“特征”指的是单个标量特征还是整个变量序列我在项目里的做法是把每个输入变量当成一个“逻辑特征”。比如输入里有温度、压力、振动三个变量窗口长度24那么M3掩码就是3维的0/1向量。某个变量的掩码为0时就把这个变量的整个时间序列24个点的值替换成背景样本里对应变量的序列。这样既能保持LSTM输入结构的完整性又能回答“温度变量整体对预测结果贡献是正还是负”这个业务问题。背景数据的选取也有讲究。理论上扰动样本应该从条件分布中采样实际项目里简化做法是从训练集中随机抽取几百个样本作为背景库用它们的特征序列来填充被遮蔽变量。背景库数量太少填充值会不稳定我的经验是至少取100个训练样本。4.4 MATLAB代码实现一个可直接套用的Kernel SHAP函数下面给出一个可以在项目里直接改用的Kernel SHAP实现框架function shapVals kernelSHAP_LSTM(net, xSample, XBackground, numFeatures, P, nSampleMasks) % net: 训练好的LSTM网络 % xSample: 待解释样本维度 numFeatures×P % XBackground: 背景样本维度 numFeatures×P×numBg % numFeatures: 逻辑特征数输入变量数 % P: 时间窗口长度 % nSampleMasks: 采样掩码数量建议至少 2*numFeatures2 numBg size(XBackground, 3); M numFeatures; % 预存所有背景样本的展开形式加速填充 XBgFlat reshape(XBackground, M * P, numBg); XSampleFlat reshape(xSample, M * P, 1); masks zeros(nSampleMasks, M); weights zeros(nSampleMasks, 1); targetY zeros(nSampleMasks, 1); origPred predict(net, {xSample}); origPred origPred(:); for k 1:nSampleMasks % 随机采样掩码避免全0和全1 mask randi([0 1], 1, M); while sum(mask) 0 || sum(mask) M mask randi([0 1], 1, M); end masks(k, :) mask; % 根据掩码生成扰动样本 % 选中的特征保留原始样本未选中的从背景随机抽取填充 z XSampleFlat; s sum(mask); idx randi(numBg, 1); bgSample XBgFlat(:, idx); for f 1:M if mask(f) 0 rows (f-1)*P 1 : f*P; z(rows) bgSample(rows); end end xPert reshape(z, M, P); yPert predict(net, {xPert}); targetY(k) yPert - origPred; % 计算SHAP核权重 if s 0 || s M weights(k) inf; else comb nchoosek(M, s); weights(k) (M - 1) / (comb * s * (M - s)); end end % 去掉权重为inf的掩码全0全1用于约束实际用正则处理 finiteIdx isfinite(weights); masks masks(finiteIdx, :); weights weights(finiteIdx); targetY targetY(finiteIdx); % 加权线性回归求解 Shapley 值 % 模型targetY masks * shapVals intercept % 使用岭回归lambda取一个小值 lambda 1e-4; A masks * diag(weights) * masks lambda * eye(M); b masks * diag(weights) * targetY; shapVals A \ b; end代码中几个工程细节需要说明。第一预测函数predict(net, {xSample})的输入是元胞数组单个样本的序列矩阵放进去即可MATLAB会自动区分单样本和多样本预测。第二掩码采样时过滤掉全0和全1这两类掩码在SHAP核权重公式里是无穷大也就是用于约束回归必须经过原始预测点的边界条件实际处理时要么显式加入约束要么直接忽略并依赖正则项。项目里我选择忽略边界点用岭回归收敛到合理解。第三循环内对每个掩码重新随机抽一个背景样本扰动样本的随机性既能保证多样性又不会让填充值之间产生相关性。4.5 SHAP结果可视化与业务解读计算完一个测试样本的SHAP值后首先要看的是所有测试样本的汇总统计。我习惯输出两类图第一类是全局平均绝对SHAP值柱状图按特征重要程度降序排列第二类是SHAP依赖散点图横轴是某个特征的原始取值纵轴是对应SHAP值观察特征取值变化如何影响预测方向。% 对多个测试样本计算SHAP得到一个 numSamples×numFeatures 矩阵 % 平均绝对SHAP值排序 meanAbsShap mean(abs(ShapAll), 1); [~, sortIdx] sort(meanAbsShap, descend); figure; barh(meanAbsShap(sortIdx)); set(gca, YTickLabel, featureNames(sortIdx), YTick, 1:numFeatures);解读时有一个容易踩的坑SHAP贡献值是在归一化空间里计算的它反映的是相对推动方向不是原始目标变量的绝对数值。比如某个特征SHAP值是正数只说明当前样本里这个特征的取值相对于背景分布会把预测结果往高推但不能直接读成“给预测值加了2度”。如果要业务人员直读可以把SHAP值和归一化前的预测值做一次放缩在解释页面上同时展示原始预测值和特征原始值。另一个更贴近业务场景的做法是挑一个异常预测样本做单样本分解。比如设备预警系统报警了把该样本的SHAP值按正负分成两类就能得到“温度偏高是主因贡献了1.38压力正常略有抑制贡献了-0.42”的结论。这让模型从“黑盒报警”升级成了“带证据链的预警”在项目验收时非常加分。5. 常见问题与排查方向5.1 数据阶段时间顺序混乱和归一化泄漏LSTM对训练样本的先后顺序很敏感但也有点矛盾训练时每个epoch会重新打乱mini-batch的顺序这不影响模型学习序列内部的时间依赖模型学习的是每个样本内部的时序结构。真正会出问题的是你把原始时间序列按错误顺序喂给了createSequences比如把数据按日期排序时文本排序导致“10月”排在“9月”前面。这种问题在日志型数据里很隐蔽跑出来的图形上会看到周期性错乱。建议切窗前先检查时间戳的单调性。归一化泄漏是一个更常见的隐患。全量数据算均值和标准差虽然能让训练集测试集分布高度统一但测试集的统计信息已经被模型间接“见过”了评估结果会偏乐观。我在2.2节强调过只允许使用训练集的统计量。实际排查时有个快速方法把训练集、测试集归一化后的均值方差分别打印出来如果测试集的均值和训练集差异过大说明两段数据的分布本身就不一致这时候就要考虑分段建模或增量学习。5.2 训练阶段损失不降、梯度爆炸、过拟合损失完全不降时先检查归一化和NaN很多问题都出在输入数据带了NaN或Inf。LSTM对这类异常值几乎没有容错能力输入一旦出现NaN梯度也会跟着变成NaN训练损失直接飘成NaN。其次是学习率把InitialLearnRate从0.005调到0.001或者0.0005分别试一轮基本能判断方向。梯度爆炸的典型表现是训练到中途损失突然变成一个极大值然后恢复或者干脆变成NaN。这时候把GradientThreshold从1降到0.5并且打开梯度裁剪问题通常能缓解。过拟合和欠拟合的判断方法我在3.2节说过这里补充一个经验阈值当验证集R²比训练集R²低0.15以上时优先加dropout和L2正则当验证集误差在多个epoch完全不下降考虑降低模型复杂度比如把numHiddenUnits从128减到64。参数调整建议速查表 现象A训练损失高且不下降 调整方案检查数据学习率 ×0.1检查归一化 现象B训练损失下降但验证损失上升 调整方案dropout 0.2→0.5加L2正则减少隐藏单元 现象C训练损失锯齿剧烈震荡 调整方案学习率 ×0.1增大MiniBatchSize 现象D验证损失平台期训练损失还在降 调整方案加大ValidationPatience检查是否需要更多训练数据5.3 SHAP阶段计算慢、结果不稳定、方向与直觉不符SHAP计算慢是MATLAB用户最常吐槽的点。LSTM的predict对单个样本调用有固定开销而Kernel SHAP要对每个掩码生成扰动样本再单独预测一次。如果测试集有几百个样本、特征有10个、掩码采样数设置成2M2这么保守总预测次数也在几千次级别跑起来确实慢。我的优化手段有三个。一是对每个掩码采样生成多个扰动样本后取平均预测值然后一次性并行预测先在循环里把所有扰动样本收集到元胞数组再用parfor或者单个predict大数组一次算完能压缩不少时间。二是从测试集里随机抽取少量代表性样本做SHAP分析比如抽50个这对于排名稳定性已经足够。三是固定随机种子确保可复现同时把nSampleMasks调到200到500之间太多时间开销大太少结果方差大。如果SHAP结果的方向和业务直觉相反先别急着质疑模型先检查背景数据是否合理。背景样本应该能代表该特征的真实分布如果背景库里的温度特征只有一个窄区间而当前样本温度远高于区间上限SHAP会认为“高温”是异常值贡献方向自然被拉向极端。这时候把背景库改成分层采样保留更多分布尾部的样本结果会更稳定。5.4 一套完整的模型验证流程建议整个项目跑完我建议在交付前把验证流程固定下来先用随机种子固定数据切分和网络初始化记录训练集RMSE和测试集RMSE然后独立跑3次取平均避免单次随机性影响判断。如果三次结果波动很大说明模型方差过高优先减少隐藏单元或增加正则化。模型确定后再跑SHAP同一测试集上跑两次SHAP对比一致性如果同一特征的SHAP值在不同运行间正负翻转说明采样数不够加大nSampleMasks。我在实际项目里踩过最大的坑是第一次跑通LSTM回归后就直接把模型上线了结果预测准确率尚可但我完全没有办法回答“为什么这个样本预测值突然跳高”最终又回来补做SHAP那部分。后来想明白了模型阶段的指标只是一半能解释清楚预测依据才算真正闭环。这个MATLAB版本的LSTM回归预测加SHAP解释流程目前在我自己的设备状态预测和能耗预测项目里复用了很多次换数据集、换网络结构核心代码基本不用动。本文还有配套的精品资源点击获取
返回列表