资讯动态

Matlab实现TCN+SHAP:多变量时间序列分类与可解释性分析

发布时间:2026/10/21 17:34:53 来源:尧图企业网站定制
做过多变量时间序列分类预测的朋友估计都遇到过同一个尴尬模型确实能跑准确率也还凑合可一旦要解释为什么把这条样本判成了故障或者要给业务方讲清楚哪些传感器通道起了主要作用传统LSTM那一套黑箱玩法就会当场露怯。注意力权重勉强能看但离严谨归因差了十万八千里。所以我这两年做这类任务基本固定用一套组合TCN做多变量分类预测再用SHAP做可解释性分析整套流程在Matlab里完整落地。今天这篇就把从数据窗口化、TCN模型搭建到SHAP特征归因和可视化的完整链路一次讲透代码思路也尽量给到可以直接抄作业的程度。适合那些正在用Matlab做深度学习分类、又被解释性卡住的朋友参考。1. 为什么是TCN与SHAP多变量分类的两块拼图1.1 TCN凭什么替代LSTM多变量时间序列分类说白了就是多个传感器/通道并行采集数据每个时间点有一个向量模型要判断整段序列属于哪个类别。这类任务以前的主流方案是LSTM和GRU但LSTM有几个麻烦串行计算慢、长序列记忆衰减、调优门槛高。TCN时间卷积网络用纯卷积结构处理序列核心是三件事因果卷积、膨胀卷积、残差连接。因果卷积保证输出t时刻只依赖输入t时刻及之前的信息不会偷看未来膨胀卷积用dilation1的间隔采样让一个小卷积核也能覆盖很长的历史范围残差连接避免网络加深后梯度消失。我用一个生活化类比LSTM像是批改试卷时从头往后逐题看必须处理完上一题才能看下一题TCN则像是不同老师分头批改每位老师在自己的视野范围内同时看若干道题通过分层配合覆盖整张卷子。所以TCN在GPU上并行效率高训练速度快加上滑动窗口做得足够宽时它捕捉时序依赖的能力并不比LSTM差。1.2 SHAP解决信不信的问题模型做得再准落不了地也是白搭。工业设备要上预测性维护医生要接受辅助诊断都得先回答一个问题模型凭什么这么判断SHAPShapley Additive Explanations是目前解释性分析里理论基础最扎实的方法。它来源于博弈论里的Shapley值核心思想是把每个特征当成一个玩家把模型预测值当成合作收益然后计算每个玩家对收益的边际贡献。它满足可加性、对称性、虚拟性等公理给出的归因结果是唯一且公平的。简单理解三个同事一起做完一个项目拿到奖金如何分配才能让每个人都觉得公平Shapley值就是那个唯一让所有人无话可说的分配方案。1.3 整体技术路线我的方案在Matlab里跑通的具体路线如下多通道原始数据做滑动窗口切片窗口化形成样本集按 8:2 划分训练集和测试集Z-score 归一化搭建TCN残差块网络设置膨胀率递增序列实现序列到标签分类训练并评估输出准确率、F1、混淆矩阵对测试集代表性样本做SHAP分析先在通道级做枚举式归因再细化到通道×时间段级做采样式归因用条形图和瀑布图输出结果这套路线的好处是每个环节都能独立验证不会出现模型跑完了但不知道哪一步出了问题的情况。下面我按步骤细拆。2. 数据准备与窗口化预处理Matlab实现2.1 多变量数据规范化与训练/测试划分假设你的数据是rawData维度是时间点数 × 变量数。我一般先做标准化但标准化必须在划分训练集之后、只统计训练集均值方差否则测试集信息泄漏后面SHAP算出来的重要度全是假的。% 假设 rawData: nTime × nVars标签 labelVec: nTime × 1 % 划分比例 80/20 cv cvpartition(length(labelVec), HoldOut, 0.2); trainIdx training(cv); testIdx test(cv); % 只基于训练集计算均值和标准差 mu mean(rawData(trainIdx, :)); sigma std(rawData(trainIdx, :)); dataNorm (rawData - mu) ./ sigma;这里有三个容易踩的细节如果原始数据里后续还有新批次数据进来要保存mu和sigma供线上推理使用不要每次重新计算否则特征尺度总在变。类别不平衡问题一定要在划分之后再看先看训练集的类别分布。少数类样本少到一定程度后面的TCN分类就基本可以放弃了先过采样或加权损失函数处理。标签如果是字符串类别如故障A故障B先把它们映射成1到n分类的整数或 categoricalMatlab的分类层要求标签必须是分类变量。2.2 滑动窗口构造张量多变量时间序列不能直接整段扔进分类网络通常要切窗口。窗口长度我初期选windowLen 80个时间点步长step 20这样窗口之间有重叠能扩充样本量。窗口切完的样本数约等于 (总点数 - 64) / 20动手前算一下别切完才发现样本数不够用。windowLen 80; step 20; n length(labelVec); numSamples floor((n - windowLen) / step) 1; X zeros(windowLen, nVars, numSamples); % 时间×通道×样本 Y categorical(zeros(numSamples, 1)); for i 1:numSamples s (i-1)*step 1; e s windowLen - 1; X(:, :, i) dataNorm(s:e, :); % 用窗口末尾时刻的标签作为该窗口的标签这是一个常见约定 Y(i) labelCategorical(e); end标签对齐方式建议用窗口末尾的标签因为分类预测关心的是当前时刻的状态窗口前面的部分是判断依据。如果你做的是事件检测窗口中心标签更合理但那种情况下窗口设计本身要调整这里不展开。有一点必须强调不要把切好的窗口全部随机打乱后再划分训练集测试集。时间序列相邻窗口强相关随机打乱会出现训练集和测试集的信息重叠看起来准确率很高一上真实数据就崩。正确做法是先用原始时间轴划分连续区间再在每个区间内窗口化。有些朋友在这块吃过亏模型在测试集上跑到98%换新数据掉到70%就是这个原因。2.3 数据增强与类别不平衡的补充窗口重叠本身就是一种弱数据增强。如果少数类仍然不足可以对少数类样本做小幅抖动在窗口数据上叠加高斯噪声标准差取该通道标准差的10%~20%标号不变。操作上只扩充训练集测试集保持原样这点不需要过多解释但效果立竿见影。另外Matlab里可以直接用augmentedImageDatastore或者transform搭配自定义函数做在线增强省内存不用提前把所有增强样本都放进内存。3. TCN模型搭建完整流程3.1 TCN残差块原理与层结构TCN的基本单元是残差块每个块里有两条路径主路径先做膨胀因果卷积、批归一化、ReLU、Dropout再重复一组跳跃连接把输入直接加到输出上。如果输入输出通道数不一致就在跳跃连接上加一个1×1卷积做通道对齐。膨胀率的选择很关键。常见的做法是按1, 2, 4, 8, 16指数递增叠加4到5个残差块。有效感受野的理论值大约为感受野 ≈ 1 (卷积核大小 - 1) × Σ(每层的膨胀率)例如卷积核为3、5个残差块、每块两层卷积、膨胀率1,2,4,8,16总感受野大约是1 2×(3-1)×(124816) 1 4×31 125个时间点。如果窗口长度只有80感受野超过窗口长度网络利用率不足此时可以减少残差块数量或缩小膨胀率。这是一个很实在的调参依据我在初版搭建时习惯先把感受野算清楚再定网络深度而不是盲目堆层数。3.2 Matlab层图构建与超参数设置用Matlab的深度学习工具箱构建TCN核心函数是convolution1dLayer和additionLayer。以4个残差块、每块两层卷积为例核心层图代码如下filterSize 3; numChannels 8; % 输入通道数 numFilters 32; % 第一块输出通道数 dilations [1 2 4 8]; lgraph layerGraph(); % 输入层 tempLayers [sequenceInputLayer(numChannels, Normalization, none, Name, input)]; lgraph addLayers(lgraph, tempLayers); % 依次添加残差块 lastLayer input; for blockIdx 1:length(dilations) d dilations(blockIdx); blockPrefix [block num2str(blockIdx)]; % 主路径两层膨胀因果卷积 convLayers [ convolution1dLayer(filterSize, numFilters, ... Dilation, d, Padding, causal, Name, [blockPrefix _conv1]) batchNormalizationLayer(Name, [blockPrefix _bn1]) reluLayer(Name, [blockPrefix _relu1]) dropoutLayer(0.1, Name, [blockPrefix _drop1]) convolution1dLayer(filterSize, numFilters, ... Dilation, d, Padding, causal, Name, [blockPrefix _conv2]) batchNormalizationLayer(Name, [blockPrefix _bn2]) reluLayer(Name, [blockPrefix _relu2]) dropoutLayer(0.1, Name, [blockPrefix _drop2]) ]; lgraph addLayers(lgraph, convLayers); % 跳跃连接通道对齐 skipLayers [ convolution1dLayer(1, numFilters, Name, [blockPrefix _skipConv]) ]; lgraph addLayers(lgraph, skipLayers); % 加法层 addLayer additionLayer(2, Name, [blockPrefix _add]); lgraph addLayers(lgraph, addLayer); % 连接 lgraph connectLayers(lgraph, lastLayer, [blockPrefix _conv1]); lgraph connectLayers(lgraph, lastLayer, [blockPrefix _skipConv]); lgraph connectLayers(lgraph, [blockPrefix _relu2], [blockPrefix _add/in1]); lgraph connectLayers(lgraph, [blockPrefix _skipConv], [blockPrefix _add/in2]); lastLayer [blockPrefix _add]; end % 分类头全局平均池化 全连接 softmax headLayers [ globalAveragePooling1dLayer(Name, gap) fullyConnectedLayer(numClasses, Name, fc) softmaxLayer(Name, softmax) classificationLayer(Name, classification) ]; lgraph addLayers(lgraph, headLayers); lgraph connectLayers(lgraph, lastLayer, gap);这里有几个值得注意的点convolution1dLayer的Paddingcausal在较新的Matlab版本里直接可用如果你的版本不支持就用Paddingsame再加一层crop或者手动截掉末尾多出的部分来保证因果性。每个残差块我加了两次Dropout中间层的Dropout率0.1到0.2都合理。数据量越小Dropout越要保守否则训练不足。第一个块的输入通道数来自sequenceInputLayer的numChannels参数通常等于你的传感器通道数。后续块之间通道数保持一致时skipConv其实也可以省掉直接连接加法层但为了通用性都保留。3.3 训练配置与性能评估训练配置我固定推荐这几个选项Adam优化器、初始学习率0.001或者0.002超过0.005基本就发散、MiniBatchSize选32或64、GradientThreshold设为1或2、验证频率按迭代次数走。options trainingOptions(adam, ... InitialLearnRate, 0.001, ... MaxEpochs, 60, ... MiniBatchSize, 32, ... GradientThreshold, 2, ... Shuffle, every-epoch, ... ValidationData, {XValid, YValid}, ... ValidationFrequency, 10, ... Plots, training-progress, ... Verbose, false); net trainnet(lgraph, XTrain, YTrain, options);我在某模拟的多通道传感数据上跑完这套配置分类准确率大约93%测试集F1在0.92左右。关键指标不只是准确率还要看混淆矩阵里的类别间混淆情况尤其是两类故障类样本的区分度。多变量分类里如果类别A容易被判成类别B往往意味着这两个类在通道模式上本身就接近后续SHAP分析可以进一步揭示它们到底靠哪个通道区分开。4. SHAP可解释性分析Matlab实现4.1 Shapley值与KernelSHAP原理SHAP把模型预测值分配到各个特征上样本预测值 基线值 各特征SHAP值的和。从数学上说特征j的Shapley值是它在所有可能的特征子集联盟中加入前后预测变化的加权平均。KernelSHAP是SHAP家族的实用变体。它把这个组合爆炸的排列计算问题转化为一个加权线性回归问题核心公式是g(z) φ0 Σ(φj × zj)其中z是联盟向量取1表示特征在联盟中0表示不在。把大量联盟的预测值收集起来用SHAP核权重做带权最小二乘回归回归系数就是各特征的SHAP值。这个思路巧妙在不需要对模型做任何结构上的改动只要模型能对任意特征子集给出预测就能算出归因。这也是它能套在TCN这种复杂模型头上的原因。4.2 通道级SHAP枚举式KernelSHAP实现对多变量时间序列分类最自然的解释粒度首先是通道。把每个传感器通道当作一个特征解释时把某个通道的整条时间序列替换成背景均值序列相当于把这个传感器关掉看预测变化。如果你的输入窗口是8通道×80时间点通道级特征只有8个完整枚举所有联盟只需2^8 256次模型预测在当前算力条件下完全可以接受。% 假设 trainedNet 已训练XSample 是窗口数据8×80 % backgroundX 是背景参照取训练集逐通道均值序列 nChannels 8; numSubsets 2^nChannels; % 生成所有联盟向量 subsets 1:numSubsets; Zmask dec2bin(subsets-1) - 0; % numSubsets × nChannels % 权重计算 w zeros(numSubsets, 1); for i 1:numSubsets s sum(Zmask(i, :)); if s 0 || s nChannels w(i) 1e6; % 空集和全集给极大权重 else w(i) (nChannels - 1) / (nchoosek(nChannels, s) * s * (nChannels - s)); end end % 逐联盟预测 scores zeros(numSubsets, 1); for i 1:numSubsets Xmasked XSample; zeroCols find(Zmask(i, :) 0); Xmasked(zeroCols, :) backgroundX(zeroCols, :); % 注意predict需要单样本维度按你的数据层结构调整 p predict(trainedNet, Xmasked); % 取该样本真实类别的概率或者目标类别的概率 scores(i) p(1, targetClass); end % 加权最小二乘解 Z [ones(numSubsets, 1), Zmask]; W diag(1 ./ w); phi (Z * W * Z) \ (Z * W * scores); % phi(1)是基线phi(2:end)是各通道SHAP值这段代码关键在于predict对单个样本的形状要求以及targetClass的选择。我一般对多分类问题统一解释目标类概率也就是该样本被分到的那个类别如果你想看为什么不是某个特定类别就把目标类换成那个类别SHAP值代表各个通道对错判概率的贡献。4.3 细化到通道×时间段的采样式SHAP通道级解释粒度够用但略粗因为没法回答这个通道的前半段还是后半段起作用。这时候可以把窗口按时间切成4段每段聚合出均值、斜率、方差等统计量再把通道×时间段当作一个特征特征数可能来到几十个。特征数超过20以后枚举所有联盟就不现实了改用采样式KernelSHAP随机抽取一部分联盟做预测再回归。Matlab里没有现成的shapley工具箱但你可以自己实现一个简易采样版工具函数。% 采样版KernelSHAP随机抽 maxSubsets 个联盟 maxSubsets 2000; M size(featureMask, 1); % 总特征数 % 先按SHAP权重分布采样优先采样联盟大小在1和M-1附近的 % 简单做法是均匀随机采样后按权重加权回归 for iter 1:maxSubsets z randi([0, 1], M, 1); % 计算联盟预测存入向量... end这里提醒一句KernelSHAP的采样结果有一定随机性同一批测试样本跑两次特征排序可能在边缘处有小幅抖动。解决办法是固定随机数种子rng(42)或者对多个采样种子取平均结果。SHAP分析追求的是相对稳定的贡献排序不是小数点级别的精确数值。4.4 特征贡献可视化全局特征的排列图比较简单直接把每个特征在所有测试样本上的平均|SHAP|值排个序用条形图展示。平均绝对值反映了特征的平均影响力正负符号则说明特征往哪个方向推。% meanAbsShap: 每个特征的平均|SHAP| [vals, idx] sort(meanAbsShap, descend); figure; barh(vals); set(gca, YTickLabel, featureNames(idx)); xlabel(平均绝对值SHAP值); title(全局特征重要度排序);样本级瀑布图更能说明为什么这条样本被判成X类。从模型基线值出发每个特征对应一个箭头向上表示推高预测概率向下表示压低概率最终堆积到实际预测值。% shaps: 某样本各特征SHAP值 baseValue phi(1); figure; % 简单瀑布堆积示意 cumVal baseValue; for i 1:length(shaps) yLine cumVal shaps(i); plot([i-1, i], [cumVal, yLine], b-, LineWidth, 2); cumVal yLine; end实际应用中我不追求Matlab里做出Python那种交互式力导向图条形图和瀑布图足够写进实验报告和技术方案。将来如果要给非技术背景的人看可以把每个特征翻译成业务语言比如2通道前段方差贡献0.13是模型判定为故障A的最大依据。5. 踩坑记录与排查技巧5.1 TCN训练中的常见坑第一个坑是因果填充问题。低版本Matlab不支持Paddingcausal时我用same凑合结果训练误差降不下去后来发现是卷积输出序列在时间对齐上错位了。当时排查了很久最后在验证集上按时间顺序逐点对比预测结果才看出来。所以优先升级环境或检查文档宁可先算清楚填充量也不要硬用same掩盖错误。第二个坑是感受野不够。有个数据集窗口长度120我只堆了3个残差块膨胀率1,2,4感受野只有1(3-1)×(124)15远小于窗口长度模型几乎只能看到开头一小段信息。发现测试集对长周期样本的区分度特别差后把膨胀率改成1,2,4,8,16准确率直接涨了近10个百分点。TCN调参第一件事永远先算感受野覆盖度这点我怎么强调都不为过。第三个坑是训练后期过拟合。TCN数据量少时训练集准确率能跑到99%测试集只有80%。解决办法是提前分配10%训练集做早停监控配合ValidationPatience在验证损失连续多轮不降时终止训练。加上Dropout率适当调到0.2一般都能压住过拟合。5.2 SHAP计算中的坑做SHAP时最容易出问题的是背景替换策略。如果你把某些通道替换成整段序列的全局均值模型可能因为输入分布整体偏移而给出不正常的预测特别是通道均值本身有慢漂移趋势时。更稳妥的做法是逐通道减去该通道均值、加上背景参照值保持单通道内的波动结构不变只是把水平拉回背景。实操中也可以直接对训练集随机抽一批样本的通道序列取平均作为静态背景。第二个坑是SHAP分析的特征粒度和模型输入不一致导致解释失效。有人直接在80×8160维的扁平特征上算SHAP行数爆炸不说维度间高度相关算出来的SHAP值相互抵消排序完全失真。正确姿态是像我前面讲的那样先在通道或语义特征层面定义特征集再计算SHAP。解释性分析的第一步是定义什么是一个特征这比跑代码重要得多。第三个坑是采样随机性带来的结论不稳定。我踩过一次上午分析某特征重要度第一下午重跑变成第二差点以为自己改了数据。后来把随机种子固定并且对同一测试集每个特征的平均SHAP值计算标准误边缘排序的特征直接标注差异不显著不再勉强解读。5.3 几个实操心得先说结论TCN的稳定性和速度确实比LSTM好尤其适合长窗口的多通道数据。但TCN调参的入口比较特殊如果你刚上手强烈建议先在小规模窗口数据上做一个感受野-准确率曲线找到窗口和膨胀率的匹配区间再做大模型省下的时间足够你多跑两轮实验。SHAP这边的核心心得是模型结构和解释性设计要同时想不要等模型训完再来补解释。我在搭数据管线时就会先想好最终要解释的粒度是通道还是通道×时间段让输入特征的组织方式与解释粒度一致后面做SHAP几乎零成本。反过来如果你等模型训练完成才琢磨解释往往要么特征粒度不对要么特征数目太大只能返工重建特征管道。另外建议每次实验都把SHAP结果输出CSV存档包含特征名、SHAP值、样本ID、预测类别方便后续对比不同版本的模型解释差异。我试过几次这种做法在排查模型学到了奇怪的规律时特别有用。最后分享一个小习惯所有SHAP分析固定R2023a版本环境固定随机种子并且在后处理时保留置信区间。这样每次实验的可解释性结论都可以被复核对论文、报告或者技术评审来说这一步能让整个深度学习模型的可信度上一个台阶。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑