VMD-SE-BiLSTM+Transformer组合模型:时序预测中的分工与实现

发布时间:2026/10/21 23:28:43
VMD-SE-BiLSTM+Transformer组合模型:时序预测中的分工与实现 做时间序列预测的朋友看到“VMD-SE-BiLSTMTransformer”这个一长串名字大概都会有一种“又来了一个组合怪”的感觉。但拆开来逐个看这个组合确实不是随便拼的VMD负责信号分解SE负责决定怎么重组BiLSTM擅长捕捉双向时序依赖Transformer再对全局关系做一轮注意力建模每条链路都有明确分工。这篇文章就拿我实际跑过的多变量预测任务为例风电功率预测输入风速、风向、温度、历史功率等参与建模把整套思路、Matlab实现细节、超参数设置、踩过的坑都写清楚。写给那些准备做时序预测实验、看到这类组合论文或开源代码想复现、但又觉得无从下手的读者。看完你不仅能看懂这套东西为什么有效也能自己搭出一套能跑的Matlab代码。1. 解题前先拆题这个模型名字的每一段都在干什么1.1 多变量时序预测真正难在哪单变量预测是“一条曲线外推”多变量预测则是“看着很多条曲线算其中一条”。难点不只是特征变多了而是这些特征之间有时间错位、有耦合关系、还有大量噪声。比如预测风电场未来1小时功率风速本身就有强烈的随机波动温度和气压的变化还会滞后影响风速直接把这些原始序列拼在一起丢进神经网络网络需要自己去学这些复杂的交叉关系数据量大、噪声强时就很容易学不动。这是多变量预测的第一个核心问题原始输入信号太“脏”了。第二个问题则是时间尺度的差异。风速的快速波动、温度的缓慢漂移这两个时间尺度可能差了几个量级同一个网络结构很难同时把它们都表达好。很多人一开始会想“堆更深的网络”实测下来往往会过拟合真正的问题其实出在输入端信号没有做好预处理。1.2 为什么纯深度学习预测不够用纯BiLSTM也好纯Transformer也好都默认一个问题你喂给它的原始序列是相对平稳、噪声可控的。但真实的风速功率、交通流量、负荷数据全是非平稳信号常用手段有两种差分和分解。差分只能处理一定程度的趋势和季节性对强随机波动效果一般分解则能把原始序列拆成不同频段的分量其中随机波动就被单拎出来低频趋势、周期成分各自变得更容易建模。VMD变分模态分解就是分解家族里很能打的一个。它能把信号分解成一组围绕特定中心频率的模态分量IMF每个分量都有明确的窄带特性。分出来的IMF序列比原始序列平稳得多用网络分别拟合再叠加重构误差通常远低于直接预测原始序列。这是这套组合模型比“BiLSTMTransformer裸结构”效果更稳的主要原因。1.3 熵SE防止拆得太碎不过VMD有个毛病它不会告诉你拆成几个分量效果最好如果K设大了会分出一堆熵值接近、物理意义相似的伪分量。这个时候就需要一个客观指标来“合并同类项”样本熵Sample Entropy, SE就是常用的工具。SE计算的是序列的复杂度一个序列的样本熵越大说明它越随机、越不可预测熵值小说明它更有规律。VMD拆完以后把各IMF的SE全部算一遍结果会发现总有几项复杂度非常接近把它们合并成一组就是SE在这个模型里干的活。很多人不知道这一步的意义以为VMD分解完直接全部输入模型就行这就失去了SE省计算量、降冗余的作用。1.4 网络BiLSTMTransformer时序记忆加全局注意力BiLSTM在这里负责局部时序依赖前向、后向两个方向把上下文都看一遍擅长捕捉风速这种短时突变Transformer的自注意力机制则负责长程依赖比如温度变化要滞后几小时才影响功率这种跨时间的关联靠LSTM的记忆单元虽然理论上能记住但实际训练中非常容易忘。组合的逻辑很简单BiLSTM每个时间步都能输出一个隐状态这些隐状态作为Transformer的输入序列让注意力层在“经过双向编码之后”的特征上再寻找全局关联。直接对原始序列做注意力其实很吃数据量经过BiLSTM的压缩后注意力机制的收敛速度和效果都明显更好。我在实验里测过单独用BiLSTM时后期长程误差偏高单独用Transformer时前期波动大的区域拟合不到位组合起来正好互补。2. 整体流程如何串起来从原始数据到预测结果2.1 一看就懂的pipeline整套模型的运行Pipeline按执行顺序是多变量原始数据 → VMD分解 → 计算样本熵 → 分量合并重构 → 对每个重构分量构造训练集 → BiLSTMTransformer预测模型分别建模 → 各分量预测结果求和 → 反归一化 → 得到最终预测。这段流程里最影响精度的其实不是模型结构而是VMD分解质量和SE重构策略。我的建议是在动手调网络之前先花大量时间把前三步做扎实因为网络再强也很难弥补输入端序列质量差的缺陷。具体到风电功率预测场景我的输入特征通常选5个平均风速、最大风速、风向正弦分量、温度、历史功率。其中历史功率和平均风速是主特征其他是辅助特征。VMD不是对全部特征一起做而是对需要预测的目标序列也就是历史功率序列做分解辅助特征保持原样直接进入网络。很多人初始化时搞混了这个点会把所有特征都拿去分解这么做会导致输入维度和物理对应关系全乱套。目标序列分出来的每个分量再和辅助特征拼成多变量输入矩阵分别建模。这里需要注意VMD的输入问题。Matlab自身有VMD函数但版本不同函数参数不太一样有些老版本需要下载开源的vmd.m例如网上流传很广的某开源版本就可以直接用。我在本文示例代码里就按常见VMD函数来写重点讲清楚参数怎么配不会因为某个函数细节卡住你。2.2 VMDSE重构的细节VMD的关键参数有三个模态数K、惩罚因子alpha也叫平衡参数、容差tol。K设太大会把噪声单独拆成一个分量导致该分量SE极大、很难预测最终重构时反而增大误差K设太小又达不到平稳化的效果。工程上可以先从K3或4开始之后逐次增加并对比分解后各IMF的频带是否有明显重叠如果出现两个相邻IMF的中心频率几乎重合说明K已经过大了。SE计算时的分组原则通常是把样本熵比较接近的模态归为一组。举个例子VMD分解出IMFs计算得到的SE值分别为0.15、0.12、0.31、0.29、0.78那么IMF1和IMF2、IMF3和IMF4可以分别合并IMF5波动过大可以单独建模或直接并入噪声处理。在实际实现中我习惯将原始序列分解后分为高频重组分量、低频趋势分量以及残余分量三组再做预测最后相加。这种“三分法”比逐模态建立大量模型更省时间预测精度也不会差太多我实测对比最多差2%的RMSE但训练时间能少一半以上。为什么要用SE做合并而不是直接按频率合并因为频率接近不代表复杂度接近两个频率相差较大的分量可能SE值非常接近合并后既能降维又不破坏整体时序规律反之两个频率接近但波动复杂度不同的分量强行合并反而会把随机性带入半平稳分量把低频组的数据质量拉低。SE本质是数据驱动的一种复杂度聚类指标比人工看频谱确定分组合并要靠谱得多。2.3 预测值是怎么合并回来的多分量预测的最后一步是重构。每个分量都会独立训练一个BiLSTMTransformer模型输出对应的预测结果把所有分量的预测值按时间点相加就得到原始的预测序列。这里有一个非常容易踩的坑VMD在分解时每个分量的幅值尺度差别可能很大如果只对输入做归一化而把预测输出直接加起来可能会出现高幅值分量误差被低幅值分量误差抵消的假象也就是预测曲线看起来漂亮但逐点误差很大。正确的做法是对每个分量单独做标准化然后保存每个分量归一化时用的均值和标准差重构时必须用同一组参数进行反归一化再相加。我在早期复现时图方便直接对全部序列做一个全局归一化结果低频分量被压得很小预测误差被高频分量主导后来改成逐分量归一化效果才正常。3. Matlab代码实现与关键设计3.1 目录结构与依赖先把Matlab实现方案说清楚。这套代码依赖三块东西VMD分解函数、自注意力层Transformer部分、deep learning toolbox。如果装了较新版本的Matlab可以用自带函数例如transformerLayer或者selfAttentionLayer不过这类层在多序列输入下有些限制也不太好控制输出维度。更稳的方案是自写一个简单的multi-head self-attention层代码量不大可控性高。在实际项目中我建议自己写一个基于dlnetwork的自注意力层后续调整激活函数、丢弃率、注意力头数都方便。我习惯的工程目录是这样的main.m负责读数据和跑全流程vmd.m放分解函数SampEn.m放计算样本熵的函数models文件夹放网络定义utils文件夹放归一化、反归一化、滑动窗口处理。名字可以随意但结构要清楚因为实验往往要跑十几组组合对比结构混乱会让你想改一个参数都要翻半天代码。3.2 VMD分解核心代码假设我们的历史功率序列是列向量x采样间隔为delta_t典型调用是这样的K 4; % 模态数 alpha 2000; % 惩罚因子 tol 1e-6; % 相对容忍度 IMF vmd(x, K, alpha, tol); % 每列是一个模态分量如果用的是Matlab内置函数注意参数名不一样需要改成[IMF, ~] vmd(x, NumIMF, K, PenaltyFactor, alpha, Tol, tolerance);两段代码几乎等价只是参数名差异。这里我有一个经验惩罚因子alpha尽量不要小于1000太小的话模态中心频率会互相污染分解出来的分量频带重叠严重alpha在1500到2500之间比较稳具体值可以按数据波动幅度微调。波动特别大的数据可以上调到3000以上。分解完成后强烈建议画一个频谱图看一眼比如画出x和每个IMF的时域曲线以及FFT幅频曲线肉眼确认分解结果是否合理如果某个IMF的中心频率明显和原始序列主频不搭说明K设得不对或者是alpha太极端。这一步几十秒就能完成但能省掉后面调模型的几个小时。3.3 SE样本熵计算与分组代码样本熵代码在很多教材里都有写成简洁的Matlab函数大概是这样function sae SampEn(data, m, r) N length(data); data (data - mean(data)) / std(data); % 可选标准化 % 构造m维向量 A zeros(N-m, m1); for i 1:N-m A(i,:) data(i:im); end % 计算匹配对数 B 0; A_count 0; for i 1:N-m for j i1:N-m dist max(abs(A(i,:) - A(j,:))); if dist r B B 1; if max(abs(A(i,1:m) - A(j,1:m))) r A_count A_count 1; end end end end sae -log(A_count / B); end这里需要强调的是样本熵的结果高度依赖嵌入维数m和相似容差r。经验上m取2或3r取原始序列标准差的0.1到0.25倍。如果r取得太小匹配对数几乎为0熵值直接变成无穷大如果取得太大又区分不出序列差异。在多次实验中m2、r0.2*std比较稳定。分组代码则可用这样实现seVals zeros(size(IMF,2),1); for i 1:size(IMF,2) seVals(i) SampEn(IMF(:,i), 2, 0.2*std(IMF(:,i))); end % 按熵值差异做简单分组 [~, idx] sort(seVals); group1 sum(IMF(:,idx(1:2)), 2); % 熵最小的两个合并 group2 sum(IMF(:,idx(3:end)), 2); % 其余作为一种这里的分组是示意实际项目中可以根据熵值的具体数值聚类也可以人工选取阈值。3.4 BiLSTMTransformer网络搭建Matlab里搭建网络最简单的方式是用深度网络设计器也可以用代码定义。我用代码定义一次后续改参数更快。BiLSTM部分用这个结构numFeatures size(XTrain,1); % 特征数 numHiddenUnits 128; layers [ sequenceInputLayer(numFeatures, Name, input) bilstmLayer(numHiddenUnits, OutputMode, sequence, Name, bilstm) dropoutLayer(0.2, Name, dropout1) ];Transformer部分我建议自己定义一个multi-head self-attention层然后通过connectLayers接在BiLSTM输出后面。用dlnetwork实现注意力层的思路是输入X经过全连接层分别得到Q、K、V注意力输出为softmax(Q*K/sqrt(d)) * V。这里写一个自定义层的关键骨架classdef AttentionLayer nnet.layer.Layer properties (Learnable) Wq; Wk; Wv; Wo end properties NumHeads; DModel end methods function layer AttentionLayer(name, dmodel, heads) layer.Name name; layer.NumHeads heads; layer.DModel dmodel; layer.Wq dlarray(randn(dmodel,dmodel) / sqrt(dmodel)); layer.Wk dlarray(randn(dmodel,dmodel) / sqrt(dmodel)); layer.Wv dlarray(randn(dmodel,dmodel) / sqrt(dmodel)); layer.Wo dlarray(randn(dmodel,dmodel) / sqrt(dmodel)); end function Y predict(layer, X) [T, N, C] size(X); % T:时间步,N:batch,C:通道 Q pagemtimes(X, layer.Wq); K pagemtimes(X, layer.Wk); V pagemtimes(X, layer.Wv); % 多注意力头拆分与缩放点积 ... end end end代码里用到的pagemtimes是Matlab R2020b及以上版本支持的按页矩阵乘法如果你的版本比较旧需要用循环处理batch维度不然会报错。再说Transformer层的输出维度。BiLSTM输出序列长度等于输入时间步数进入注意力层后序列长度不变再经过一个flatten层或者加一个fullyConnected层把维度压到预测输出维度得到最终结果。预测输出维度和你想预测多少个未来步有关比如用过去10个时刻预测未来1个时刻输出维度就是1。3.5 训练与预测循环要点训练这套网络我习惯用mini-batch梯度下降配合Adamoptions trainingOptions(adam, ... MaxEpochs, 100, ... MiniBatchSize, 32, ... InitialLearnRate, 0.001, ... LearnRateSchedule, piecewise, ... LearnRateDropPeriod, 30, ... LearnRateDropFactor, 0.5, ... ValidationData, {XValidation, YValidation}, ... ValidationFrequency, 20, ... Plots, training-progress);这里尤其要注意的是Matlab训练循环判断训练数据时时序数据默认把行当作特征、列当作时间步如果直接拿普通矩阵喂进去很容易搞混维度。我通常在创建数据后先用一个测试样本跑一次预测流程确认输入输出维度都对再开始正式训练。另外每个VMD分量建议单独保存对应的训练好的网络用一个cell数组存起来nets cell(numGroups, 1); for i 1:numGroups nets{i} trainNetwork(XTrainGroup{i}, YTrainGroup{i}, layers, options); end预测时把每个分量的预测结果反归一化后相加就是最终预测序列。4. 超参数、数据预处理与评估4.1 数据预处理与滑动窗口构造多变量时序预测中滑动窗口的长度直接决定模型能看到的“视野”。窗口太短比如只取过去3个时刻模型根本看不出趋势窗口太长比如取50个时刻注意力矩阵规模变大训练变慢而且可能引入过多无关信息。我的做法是结合数据的自相关性来确定。先画出目标序列的自相关函数图观察自相关系数衰减到0.05以下的时间点这个时间点附近就是窗口长度的参考值。以风电功率为例通常采样间隔为15分钟时窗口取24到32个历史时刻即过去6到8小时就能获得比较平衡的效果如果采样间隔更小窗口可以相应加大。数据归一化方法方面如果用的是LSTM最稳妥的是每个特征分别归一化到0到1或做Z-score标准化。注意不要用整个数据集统计而只用在训练集上计算均值和标准差否则验证集和测试集的信息会提前泄漏到训练过程中导致结果虚高。很多人在这个细节上翻了车拿到的指标高得吓人一旦放到新数据上就原形毕露。4.2 超参数设置速查表下面直接用表格列出我实验中表现比较稳的参数组合方便参考参数项常用取值说明VMD模态数K3~5过大会拆出伪IMFs过小平稳化不足VMD惩罚因子alpha2000左右1500~3000之间调样本熵m值21太粗糙3以上对噪声敏感样本熵r值0.2*std0.1~0.25倍标准差滑动窗口长度24历史点看采样间隔和自相关BiLSTM隐藏单元数128数据量少可以降到64Transformer注意力头数8隐藏单元128时8~16头均可丢弃率0.1~0.2过高会让训练不收敛初始学习率0.001Adam配合高则震荡MiniBatchSize32~64数据量小取32MaxEpochs100~150配合early stopping需要说明的是这个表是我的基准配置不是万能配置。如果你换到金融序列或者交通流量序列采样频率差很远可能需要把窗口长度和注意力头数都调整一遍。真正有效的调参流程一定是从小数据、小模型开始逐步放大而不是一上来就堆大模型。4.3 评价指标对比与消融实验这套模型跑出来的效果不能只看预测曲线漂亮不漂亮得拿指标说话。一般时序预测必备四个指标RMSE均方根误差、MAE平均绝对误差、MAPE平均绝对百分比误差、决定系数R²。Matlab里一行就能算rmse sqrt(mean((y_pred - y_true).^2)); mae mean(abs(y_pred - y_true)); mape mean(abs((y_pred - y_true) ./ y_true)) * 100; r2 1 - sum((y_true - y_pred).^2) / sum((y_true - mean(y_true)).^2);要证明VMD-SE-BiLSTMTransformer这套组合有效只跑一组结果没有说服力。至少要做一个消融对比裸BiLSTM、BiLSTMTransformer、VMDBiLSTMTransformer、完整模型四组都跑同样数据、同样的训练集测试集划分记录指标。我在实验里看到的典型结果是完整模型比裸BiLSTM的RMSE下降20%到30%其中VMD的贡献大于Transformer的贡献因为信号预处理带来的提升往往比网络结构升级更明显。这个结果其实在提醒大家如果不适合用深度学习解决的问题先想着改进输入信号收益可能比换网络结构大得多。还要特别注意一个细节不同分量预测结果的误差不会完全抵消所以最终指标不是所有分量指标的平均值一定要把预测结果相加成完整序列后再算总体误差。很多人写论文时逐模态算指标最后把指标加总平均这样既不符合统一量纲也反映不了真实预测效果。5. 从实际跑实验吐槽问题与避坑5.1 VMD模态数K怎么选眼睛比穷举靠谱K这个参数是最玄学的网上很多论文都是拍脑袋定的然后告诉你好用。我一开始也试过用不同K值反复跑模型找最优代价非常大。后来发现的更高效方法是先对原始序列做频谱分析数出明显的主峰个数这基本就是K的下限然后把K从下限开始往上加1、2个看分解的IMF频带是否仍然清晰分离。如果多加一个K会出现两个IMF的中心频率几乎重合这个K就过大了。中心频率怎么看VMD函数通常返回模态分量可以自行做FFT也可以直接计算IMF间的相关系数相关系数超过0.6基本上说明分解已经重复了。用这个办法K的调整只需要几次就能固定下来不用跑完整模型。5.2 信息泄漏问题归一化和时间划分必须提前信息泄漏是时序预测最隐蔽的问题。除了前面提到的归一化统计只能用训练集之外时间序列的划分也必须是连续的训练集用前70%验证集用中间的10%测试集用最后20%不能像普通机器学习那样随机打乱那样会把未来的信息混进训练集模型指标会好到不真实。我在复现过程中还撞到过一个更坑的情况Matlab训练模型时会默认对输入特征做均值和标准差的离线统计如果我们把归一化后的数据再手动归一化一次就会造成双重标准化特征值全被压到接近0模型直接不收敛loss曲线平得像一条直线。这种问题不算难排查但真的很浪费时间因为你可能会怀疑是网络结构的问题实际上只是数据预处理多写了一句代码。5.3 网络训练发散或loss不下降训练过程中常见的Lu症状有两个一是loss震荡太大始终降不下来二是loss直接变成NaN。第一个情况通常是因为学习率太高或batch size太小把学习率从0.001降到0.0005或者把batch size从16提高到64往往能解决。第二个情况有可能是因为梯度爆炸LSTM处理长序列时这个概率不低比较好的解决方法是开启梯度裁剪训练选项里设置GradientThreshold为1左右。另外要注意如果数据中存在NaN值训练过程会直接崩。我遇到过数据采集端出问题某一小时的数据显示885.0实际上是某设备故障产生的默认“坏数”如果不对数据做粗差探测模型loss直接爆表。建议训练前先执行一遍isnan和isinf检查把异常值插值掉。5.4 自注意力层的版本兼容问题这里专门提一下Transformer部分的版本坑。Matlab的深度学习工具箱对transformer层支持不算特别成熟不同版本调用方式差距很大。如果直接用自带transformerLayer有时会报“未定义”之类的函数错误。我目前验证过的是R2023a用自写AttentionLayer配合pagemtimes较稳定如果你的版本更老建议不要硬套自带层用自定义层更稳。如果训练过程中报出维度不匹配的错多半是因为Matlab时序数据默认排列是“特征×时间步”而注意力层里通常先把数据当作“时间步×特征”来操作中间需要一次permute调整维度。这个报错信息有时候不明显提示在某层维度不匹配但实际原因就在于Q、K、V的dmodel维度和序列时间步搞反了。遇到这类问题建议写个小demo构造随机张量过一遍网络打印每一层的size比翻报错日志快得多。5.5 跑慢和内存问题这套模型参数量不大但训练时间仍然可能很长尤其是VMD分解后如果分组多每个分量都要训练一遍网络。第一次实验时我用逐模态训练8个网络整个过程跑了一晚上后来改成重组为3组训练时间减少60%精度基本不变。内存方面最大的风险是训练数据一次性全部读入成为cell数组后又因为序列长度不一致而触发填充策略导致内存溢出。建议把MiniBatchSize调低或者用padding策略调成可变的保证同一batch内的序列长度一致。如果还是内存不足可以考虑用单精度single代替double精度损失很小但内存直接减半。6. 一点个人经验这套VMD-SE-BiLSTMTransformer组合模型本质上是在“信号分解”和“深度预测”之间搭了一条桥。VMD让复杂信号变得可分解SE让分解结果变得可理解BiLSTM和Transformer则分别处理短期和长期的依赖关系。整套流程跑下来我最大的体会是模型的复杂度并不等于效果的上限真正决定上限的是每一步细节的处理方式。信号分解得干净、数据没有泄漏、参数匹配到合适范围一个并不复杂的网络也能达到很高精度。如果你准备把代码应用到自己领域的数据上第一个建议就是不要直接套参数。先做频谱分析再做自相关分析确定窗口再确定K值再按SE策略分组然后训练模型整个过程是串行决策的每个环节都要有数据依据。第二个建议是保存好每一步的中间结果VMD分解结果、SE值、分组方案、每组网络的权重全部存下来。实验过程中你会频繁想对比不同分组方案的效果没有中间结果就只能全部重跑。这篇文章基本把自己在复现过程中走过的路、踩过的坑都交代了一遍。后续你拿到一个可用的基线之后可以继续往这个框架上加东西比如把注意力输出再接一个卷积层、或者在VMD后加一个数据增强环节思路都是通的。按照这套流程先跑通一个最小版本然后逐步加料我相信你也能把看起来复杂的组合模型跑出稳定效果。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询