ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

VMD-SE-LSTM+Transformer:多变量时序预测的Matlab实现

VMD-SE-LSTM+Transformer:多变量时序预测的Matlab实现 1. 从单模型滞后到分解-重构框架这套方案到底在解决什么1.1 一个真实预测案例暴露的问题拿到一份带噪声、非线性、波动频繁的多变量时序数据最头疼的往往不是模型跑不起来而是单模型的预测曲线永远比真实曲线慢半拍。趋势变化时滞后突变时抖得像心电图。我早年在做功率预测任务时被这个问题折磨了一个多月试过LSTM也试过Transformer精度都卡在瓶颈上LSTM对缓慢趋势拟合得还行一旦进入波动剧烈段预测结果就像被拉住尾巴相位延迟非常明显Transformer虽然捕捉突变的能力强一点但直接喂原始序列时高频噪声和高幅值波动会占据注意力权重的主导地位反而把平稳段的趋势细节学得一团糟。后来我把注意力从换更强的模型转向先把序列拆得更干净也就是先做VMD-SE分解再让LSTM和Transformer各管一段测试集误差直接降了接近一半。VMD负责把目标序列拆成若干个频率由低到高的模态分量SE样本熵负责量化每个分量到底有多复杂然后复杂分量交给Transformer、平稳分量交给LSTM最后把各分支预测结果叠加重构这就是标题里VMD-SE-LSTMTransformer的核心逻辑。1.2 VMD-SE是预处理杠杆LSTM与Transformer是主模型互补很多人在Matlab里做时序预测第一反应就是拖一个lstmLayer出来直接训练。这种做法不是不行而是把所有复杂度责任都压给了模型本身。原始序列里既有缓慢变化的趋势项又有剧烈波动的随机项还有可能是传感器带来的噪声项这些成分的频率特征完全不同硬塞给同一个网络它只能学一个折中映射所以滞后和过平滑几乎是必然的。这套方案的思路核心是把问题拆成两级第一级是序列分解与重组。VMD变分模态分解把目标变量拆成K个有限带宽的模态分量然后用样本熵SE判断每个分量的复杂度高低按复杂度把它们重新分成两组。低复杂度组拥有明显的趋势性和周期性适合用LSTM这类递归结构慢慢磨高复杂度组波动剧烈、局部模式多用Transformer的自注意力机制更容易捕捉长距离依赖和突变特征。第二级是模型分工与结果融合。两个模型分开训练、分开预测最后把各分支的预测值加总还原出最终目标序列。这里有一点必须提前说明多变量时序预测中的多变量通常指的是多个外部特征通道比如温度、风速、负荷、价格等这些变量和目标变量一起进入模型输入VMD分解只针对目标变量进行外部特征作为平行输入通道参与训练。这样既保证分解的稳定性又能让模型学到多变量之间的耦合关系。我在Matlab里完整跑通过这套流程下面把每一步的细节、参数选择和踩坑点都展开讲清楚。这篇内容适合已经在用Matlab做过至少一次LSTM单模型预测、但对Transformer在Matlab里的实现方式还不熟悉的读者也适合那些觉得单模型精度上不去的朋友拿来当换思路的参考。2. VMD分解与SE样本熵重构分组Matlab中如何一步步实现2.1 VMD调用与参数设置VMD在Matlab里的使用条件很宽松需要Signal Processing Toolbox注意不是Deep Learning Toolbox。Matlab从R2019a开始提供了内置的vmd函数可以直接对信号做变分模态分解。用法非常简单% 假设 targetData 是 N x 1 的目标变量列向量 K 7; % 模态个数 alpha 2000; % 惩罚因子控制带宽 tau 0; % 噪声容忍度0表示严格保真 [imf, ~] vmd(targetData, NumIMF, K, ... PenaltyFactor, alpha, ... Tau, tau, ... MaxIter, 500, ... Tol, 1e-7);这里最关键的参数是K和alpha。K太少欠分解趋势和波动还搅在一起K太多会出现虚假分量也就是把一条完整曲线硬拆成两条几乎重叠的模态。我的经验是K从4开始往上试每个K都算一下重构误差把imf全部加起来和原始targetData对比误差在1e-6量级即可同时看中心频率分离度如果两个相邻模态的中心频率差小于10%左右说明K取多了要减。alpha的作用是控制模态带宽alpha越大各模态带宽越窄频率分离越干净但过大会让某些模态退化成单频信号。默认值2000对大多数时序数据够用。要注意VMD分解后的端点效应。虽然VMD比EMD的端点效应轻很多但分解前最好还是对目标序列做一次反射延拓左右各延拓30个点左右分解后把延拓部分切掉再进模型。这个操作我一开始没做结果第一个和最后一个是模态的预测误差明显偏大后来加了延拓边界区域的预测精度上升了一个档次。2.2 SE样本熵计算逻辑与分组策略样本熵SE的思路是统计序列中长度为m的模板向量与长度为m1的模板向量在相似容限r内匹配的个数用它来度量序列复杂度。SE值越高说明序列越随机、越不规律SE值越低说明序列越平稳、规律性越强。这比传统的近似熵更稳定因为它不包含自身匹配的偏差。在Matlab里手写样本熵函数很直接核心逻辑如下function se sampEn(data, m, r) N length(data); % 构造模板向量 x data(:); A zeros(N-m, m); for i 1:N-m A(i, :) x(i:im-1); end % 统计匹配对数量 B zeros(N-m, 1); for i 1:N-m dist max(abs(A - repmat(A(i,:), N-m, 1)), [], 2); B(i) sum(dist r) - 1; end % 类似统计 m1 维模板 ... se -log((sum(C_plus)) / (sum(B))); end完整写法里需要同时统计m维和m1维的匹配数然后取负对数。参数方面嵌入维数m通常取2相似容限r通常取原始序列标准差的0.1到0.25倍。r太小会导致匹配数太少、熵值不稳定r太大会把所有分量都判成相似分组就失效了。实测中r 0.2 * std效果最稳定。对VMD分解出的每个IMF计算SE之后需要一个分组策略。我试过两种按全局熵均值分组。算出K个模态的SE均值低于均值的归LSTM高于均值的归Transformer。实现简单效果可接受。按三分位分组。把SE值排序前三分之二归LSTM后三分之一归Transformer。这适合SE值分布比较分散的数据能保证Transformer分支拿到的分量确实足够复杂。个人建议先用均值分组试跑一轮如果Transformer分支预测的结果波动过大、loss降不下来再改成三分位。也可以用更高维度的SE特征比如同时算m1、m2、m3情况下的SE构造简单特征向量然后跑个k-means分成两类但大多数场景用单SE值就够了。有一个细节必须注意SE计算前的分量最好单独归一到零均值、单位方差。如果不归一化幅值大的分量会天然产生更大的模板距离导致熵值被幅值尺度干扰分组结果就不靠谱了。这一步很多人忽略却是整个分组逻辑的基础。3. 双分支预测核心LSTM分支与Transformer分支的搭建细节3.1 LSTM分支结构在VMD-SE框架下LSTM分支负责预测低频、低复杂度模态。这类分量通常变化平缓、趋势性强LSTM的递归累积机制很适合这种信号因为它天然地把历史状态通过门控一步步传递下去预测曲线平滑很少出现跳变。Matlab里搭LSTM分支我推荐直接在Deep Learning Toolbox里用网络层堆叠numFeatures size(XTrain, 3); % 多变量特征通道数 numResponses 1; % 当前分支预测的是单模态分量 lstmLayers [ sequenceInputLayer(numFeatures, Name, in) % 先把外部特征和目标变量历史窗口拼成特征矩阵 lstmLayer(64, OutputMode, last, Name, lstm1) dropoutLayer(0.2, Name, drop) fullyConnectedLayer(numResponses, Name, fc) regressionLayer(Name, reg) ]; options trainingOptions(adam, ... MaxEpochs, 100, ... MiniBatchSize, 32, ... InitialLearnRate, 0.002, ... LearnRateSchedule, piecewise, ... LearnRateDropPeriod, 30, ... LearnRateDropFactor, 0.5, ... ValidationData, {XVal, YVal}, ... ValidationFrequency, 20, ... Plots, training-progress, ... Verbose, 0); netLSTM trainNetwork(XTrain, YTrain, lstmLayers, options);这里有一个工程决策没把LSTM和Transformer塞进同一个网络图里训练而是分成两个独立模型最后做结果叠加。原因有三条Matlab对LSTM有成熟的内置训练流程trainNetwork一行搞定而自定义融合结构需要手写训练循环两个分支的输入尺度、归一化方式、模型结构差异很大分开训练更容易调试最终预测是线性叠加分支之间没有复杂的交互梯度分开训练不会损失精度反而能防止透传梯度导致某一分支训练不稳定。LSTM分支的隐藏单元数我一般取64或128dropout取0.2。低频分量本身已经够平稳模型容量过大反而会去拟合残余的小波动导致重构时在趋势项上叠出多余的毛刺。3.2 在Matlab中手写Transformer编码器这是整套代码里最需要耐心的地方。Matlab目前没有像Python那样一行的torch.nn.TransformerEncoder但深度学习工具箱里的自定义层和dlarray可以完全支持我们搭一个Transformer编码器。对时序预测来说我们只需要编码器部分就够了不需要解码器——输入是一段滑窗历史序列输出是这段序列的编码特征再接一个全连接层输出未来值。Transformer编码器的核心是自注意力机制。标准的多头注意力公式是[ Attention(Q,K,V) softmax(\frac{QK^T}{\sqrt{d_k}})V ]在Matlab里用dlarray实现缩放点积注意力的关键部分如下function y scaledDotProductAttention(Q, K, V) % Q, K, V 都是 dlarray维度为 d x T其中 d 是单头维度T 是时间步 dk size(K, 1); scores (Q * K) / sqrt(dk); % T x T weights softmax(scores, 2); % 按行归一化 y weights * V; % T x d y y; % 还原为 d x T end这段代码就是自注意力的全部秘密。除以根号dk这一步非常重要它防止点积结果过大导致softmax进入饱和区梯度消失。如果你看到Transformer训练时注意力权重全变成均匀分布、模型不学习多半就是没做这一步缩放或者初始化尺度不对。完整的多头注意力需要把输入特征投影成多组Q、K、V然后把多头的输出拼接起来再过一层线性映射。这里我建议初学者先跑单头注意力版本确认流程通了再扩展到多头。多头的好处是让模型在不同子空间里提取不同的依赖模式但调试难度也上去了。我实测中4头到8头之间对时序预测效果差异不大反而是注意力维度和前馈隐藏维度更重要。Transformer编码器里还有三个标准组件必须配上前馈全连接网络。每个时间步独立通过两层全连接中间用ReLU激活。时序预测里前馈层的隐藏单元数设为输入维度的2到4倍比较合适。残差连接与层归一化。每过一层注意力或前馈都要把输入加回去再做layer normalization。残差连接解决深层网络的梯度退化层归一化稳定训练。位置编码。时序窗口内每个位置的编号信息必须注入到输入特征里因为注意力本身是集合运算不区分先后顺序。我用的是正弦位置编码公式与经典Transformer一致也可以直接用可学习的One-Hot位置索引特征数据量不大时两者差别不大。在Matlab里把这些封装成自定义层然后通过dlnetwork组成一个网络对象。如果你不想写完整自定义层的classdef有个偷懒但实用的办法把整个Transformer编码器写成一个函数在自定义训练循环里调用。3.3 分支输出的融合与叠加重构机制两个分支分别训练完成后预测阶段需要把结果按分量加回来。设目标变量被拆成K个模态分成两组G_lstm中有N1个分量G_transformer中有N2个分量则有N1N2K。最终预测值[ \hat{y} \sum_{i \in G_{lstm}} \hat{imf_i} \sum_{j \in G_{transformer}} \hat{imf_j} ]这里每一步的加法都是在同一时间点上做所以不存在对齐问题。真正的坑在归一化每个IMF在训练前都单独做过零均值单位方差归一化预测输出是归一化尺度的值必须先反归一化回IMF原始尺度再叠加。要是直接把归一化后的分支预测相加重构结果就完全错乱。我在Matlab里的做法是保存每个IMF的均值mu和标准差sigma预测完成后用pred * sigma mu恢复然后再加总。这个细节不复杂但很多第一次跑这套代码的人都在这里栽跟头。另外Transformer分支预测高频分量时预测值偶尔会出现小幅抖动这算是正常现象因为高频分量本身就不完全可预测。重构时如果抖动影响到最终结果的可读性可以给预测出的高频分量加一个极轻的滑动平均窗口取3到5个点但别过度平滑否则又回到了LSTM滞后的问题上。4. 多变量输入处理、滑窗构造与训练参数配置4.1 滑窗与数据格式设计多变量时序预测的输入形态我建议用窗口矩阵每个训练样本是一个windowSize x F的矩阵F代表特征通道数包括目标变量的历史值和其他外部变量预测目标是下一时刻的目标变量值。窗口顺序上第1行是窗口内最早时刻第F列是最后时刻。Matlab里构造训练样本的代码逻辑大致如下function [XTrain, YTrain] makeSamples(data, targetIdx, windowSize) % data 是 N x F 的多变量序列targetIdx 是目标变量所在列 [N, F] size(data); numSamples N - windowSize; XTrain zeros(windowSize, F, numSamples); YTrain zeros(numSamples, 1); for i 1:numSamples XTrain(:, :, i) data(i:iwindowSize-1, :); YTrain(i) data(iwindowSize, targetIdx); end end这里有个关键点LSTM网络在Matlab里的输入格式是windowSize x F x numSamples三维数组其中第一个维度是时间步、第二个维度是特征、第三个维度是样本。很多人刚接触时会把维度搞反写成F x windowSize x numSamples然后训练报维度错误。花点时间记住这个约定后面能少踩很多坑。对于VMD分解分支每个IMF的滑窗格式和上面完全一致但目标通道变为当前IMF序列。外部多变量特征在每个样本窗口内仍然保留因为它们对每个模态都可能存在耦合影响。比如风速序列的突变可能会同时影响多个IMF分量所以多变量特征要喂给两个分支。4.2 归一化策略与训练参数建议归一化在这个框架里有三层原始多变量输入做全局归一化。每个特征列单独用z-score归一化即减均值除标准差。不要用min-max因为min-max对异常点太敏感而时序数据里一个传感器毛刺就可能把整个特征压缩到很窄的范围。每个IMF单独归一化。即使完成了VMD分解各分量的幅值量级也可能差异很大不单独归一化会导致模型训练时把注意力全放在幅值大的分量上。预测结果反归一化后再叠加。前面已经强调过这一步必须做。训练参数方面我给出实测下来比较稳定的配置参数LSTM分支Transformer分支滑窗长度24~4824~48初始化学习率0.0020.001学习率策略分段下降余弦退火批次大小3216~32训练轮数100100Dropout0.20.1优化器AdamAdam梯度裁剪阈值无1.0Transformer分支的初始学习率为什么要比LSTM低因为注意力机制的梯度方差通常更大尤其在高频分量上过大的学习率很容易让loss直接震荡发散。我一开始用0.002跑Transformer分支前5轮loss就在0.3上下跳动改成0.001后明显稳定。梯度裁剪也是Transformer训练里常用手段防止个别样本的极端点积导致梯度过大。训练集、验证集、测试集建议按70%15%15%划分但要按时间顺序切不能随机打乱。时序预测最忌用未来信息训练随机划分会带来数据泄漏导致测试指标虚高。切分时还要注意测试集不能紧贴训练集最好留出一小段静默区因为VMD分解在整个序列上完成的分解边界处的模态确定性和内部不完全一致静默区能避免测试集在边界处被异常值污染。5. 实测效果对比与踩坑记录5.1 三种方案指标对比用同一组包含多变量环境因素的风速功率数据做对比滑窗长度取24预测步长1VMD的K取7。对单一LSTM、单一Transformer、VMD-SE-LSTMTransformer三套方案分别跑5次取平均结果如下方案RMSEMAPE(%)R²单一LSTM0.2837.820.86单一Transformer0.2627.110.88VMD-SE-LSTMTransformer0.1473.960.94单一LSTM滞后最明显在波动密集段几乎整体右移单一Transformer整体精度好一些但平稳段出现轻微的过度敏感——明明趋势平缓模型却预测出小幅波动。混合方案在平稳段和突变段都保持稳定误差主要集中在高频分量的不可预测部分这部分本身信息含量就低没法完全消除。要注意这个对比结果是有前提的目标序列必须确实存在明显的高低频分离特征。如果数据本身就是白噪声过程VMD分解再完美也无法提升预测精度因为噪声不可预测。所以跑这套方案前先做一次快速判断对目标序列做频谱分析看能量是否主要集中在少数频带上分布越集中这套方案收益越大。5.2 踩过的坑与解决方法表把我在实际调试过程中遇到的几个高频问题列成一张表供大家对照排查现象根因解决办法某个IMF预测误差特别大该IMF端点效应未被处理分解前反射延拓30点分解后截掉延拓段分组后Transformer分支loss震荡不下降学习率过高、无梯度裁剪学习率降到0.001开启梯度裁剪阈值为1重构后预测曲线在测试集首尾明显偏移VMD边界效应测试集与训练集之间留静默区取20~30个时间步SE值分布全部接近分组无意义r参数选得过大r取0.1~0.2倍分量标准差重新计算LSTM分支预测的高频分量滞后严重高频分量不该给LSTM检查SE分组把高SE分量划给Transformer分支多变量外部特征加入后反而掉精度外部特征未时间对齐或含噪声确认滑窗内特征严格对应同一时刻对特征做一次低通滤波去除传感器毛刺这里面最隐蔽的是最后一个。外部特征如果不做时间对齐比如温度数据比目标变量晚采集了2个采样间隔模型会学到错误因果关系精度会肉眼可见地下降。我在一次实验里把某个外部特征整体前移了三步RMSE直接涨了20%排查了半天才定位到是数据对齐问题。5.3 一个值得试的扩展方向这套框架里VMD的模态个数K目前是固定的但实际数据的最优K会随数据分布变化。如果想进一步提精度可以在每轮训练前做一个简单的自适应K选择在训练集上对K从4到10遍历计算每个K下重构误差和SE分组效果的组合评分选最优K再进正式训练。代价是训练时间变成原来的几倍但如果你的任务对精度要求高、离线训练为主这个方向非常值得试。另一个方向是把VMD-SE应用到外部特征本身。我试过对最敏感的1到2个外部特征也做VMD分解把它们的低频部分和高频部分分别加入两个分支的输入通道最终预测误差又降了5%左右。代价是特征维度膨胀、训练变慢适合特征数量不多、硬算力充裕的场景。5.4 最后的个人体会说实话这套方案不是万能药。它最大的价值在于改变了看待时序预测的方式不要在模型结构上跟数据硬碰硬而是先让数据自己分层让不同结构的模型去处理和自己特质匹配的那一层。VMD-SE在这个过程中承担的是数据解耦器的角色LSTM和Transformer则各自成为特质匹配器。这种先分后合的思路比单纯堆模型参数量要优雅得多也更适合Matlab环境下快速验证和迭代。如果你手头正好有一组非平稳、多变量的时序数据卡在单模型精度上不去我建议按照上面的流程完整跑一遍。重点不是照抄代码而是体会每一步那几个为什么为什么要算样本熵、为什么要分两个分支、为什么反归一化顺序不能乱。把这些想通了这套框架就不只是一份Matlab代码而是一整套可以迁移到其他预测任务的解题思路。
返回列表