☰
VMD-SE-LSTM+Transformer:多变量时序预测的Matlab实现
2026/9/26 8:48:20 网站建设 项目流程

1. 从单模型滞后到"分解-重构"框架:这套方案到底在解决什么

1.1 一个真实预测案例暴露的问题

拿到一份带噪声、非线性、波动频繁的多变量时序数据,最头疼的往往不是模型跑不起来,而是单模型的预测曲线永远比真实曲线慢半拍。趋势变化时滞后,突变时抖得像心电图。我早年在做功率预测任务时被这个问题折磨了一个多月,试过LSTM,也试过Transformer,精度都卡在瓶颈上:LSTM对缓慢趋势拟合得还行,一旦进入波动剧烈段,预测结果就像被"拉住尾巴",相位延迟非常明显;Transformer虽然捕捉突变的能力强一点,但直接喂原始序列时,高频噪声和高幅值波动会占据注意力权重的主导地位,反而把平稳段的趋势细节学得一团糟。

后来我把注意力从"换更强的模型"转向"先把序列拆得更干净",也就是先做VMD-SE分解,再让LSTM和Transformer各管一段,测试集误差直接降了接近一半。VMD负责把目标序列拆成若干个频率由低到高的模态分量,SE样本熵负责量化每个分量到底有多"复杂",然后复杂分量交给Transformer、平稳分量交给LSTM,最后把各分支预测结果叠加重构,这就是标题里VMD-SE-LSTM+Transformer的核心逻辑。

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的模板向量与长度为m+1的模板向量在相似容限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:i+m-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 % 类似统计 m+1 维模板 ... se = -log((sum(C_plus)) / (sum(B))); end

完整写法里需要同时统计m维和m+1维的匹配数,然后取负对数。参数方面,嵌入维数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特征(比如同时算m=1、m=2、m=3情况下的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或128,dropout取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个分量,则有N1+N2=K。最终预测值:

[ \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:i+windowSize-1, :); YTrain(i) = data(i+windowSize, 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
训练轮数100100
Dropout0.20.1
优化器AdamAdam
梯度裁剪阈值无1.0

Transformer分支的初始学习率为什么要比LSTM低?因为注意力机制的梯度方差通常更大,尤其在高频分量上,过大的学习率很容易让loss直接震荡发散。我一开始用0.002跑Transformer分支,前5轮loss就在0.3上下跳动,改成0.001后明显稳定。梯度裁剪也是Transformer训练里常用手段,防止个别样本的极端点积导致梯度过大。

训练集、验证集、测试集建议按70%:15%:15%划分,但要按时间顺序切,不能随机打乱。时序预测最忌用未来信息训练,随机划分会带来数据泄漏,导致测试指标虚高。切分时还要注意测试集不能紧贴训练集,最好留出一小段"静默区",因为VMD分解在整个序列上完成的,分解边界处的模态确定性和内部不完全一致,静默区能避免测试集在边界处被异常值污染。

5. 实测效果对比与踩坑记录

5.1 三种方案指标对比

用同一组包含多变量环境因素的风速功率数据做对比,滑窗长度取24,预测步长1,VMD的K取7。对单一LSTM、单一Transformer、VMD-SE-LSTM+Transformer三套方案分别跑5次取平均,结果如下:

方案RMSEMAPE(%)R²
单一LSTM0.2837.820.86
单一Transformer0.2627.110.88
VMD-SE-LSTM+Transformer0.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代码,而是一整套可以迁移到其他预测任务的解题思路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询