简介:这是一份面向多维时间序列预测任务的MATLAB代码资源,核心思路是采用变分模态分解(VMD)对原始数据降噪分解,再通过麻雀搜索算法(SSA)优化长短期记忆网络(LSTM)的超参数,最终给出LSTM、VMD-LSTM、VMD-SSA-LSTM三种模型的完整对比实现。资源共包含31个文件,其中22个.m脚本用于模型构建、参数优化、结果评估与绘图,6个.mat文件保存训练结果和中间变量,3个.xlsx文件存放实验数据,压缩包仅247KB,轻量易用。代码覆盖了数据读取、VMD分解、SSA寻优、LSTM训练以及R2、NSE等指标计算的全流程,并附有各模型的结果文件,便于直接复现和对比预测性能。目前已有237人学习下载,适合从事风速、电价、交通流等复杂多维序列预测研究的科研人员和工程师,可作为实验对比基线或算法改进的参考。同时提供结果对比图绘制脚本,方便直观分析。
1. VMD-SSA-LSTM到底解决什么问题:多维时间序列预测的“拆-选-学”组合拳
先跟你掏个底:多维时间序列预测,不是把几个变量塞进长短期记忆网络就能出结果的。我做过的负荷和风速预测里,原始序列同时带着趋势漂移、日周期、突变毛刺和多变量之间的耦合关系,直接训练LSTM,指标永远卡在某个瓶颈上,曲线看似跟着走,峰值却总是滞后一拍。变分模态分解把原始序列按频段拆开,让每一路LSTM面对相对规整的输入;麻雀算法去搜索LSTM的隐含层数、学习率、正则系数这些让人挠头的超参数。这套VMD-SSA-LSTM的组合,适合已经跑通基础LSTM、但精度上不去的人,也适合被多变量特征和时间步长折磨到怀疑模型有问题的人。后面给到的MATLAB代码,照着改数据就能复现整条链路。
2. VMD分解:先把非平稳序列拆成可以“各个击破”的模态
长短期记忆网络擅长学时序依赖,但非平稳序列会让它很难办。一个序列如果同时包含年趋势、日周期、不规则脉冲和测量噪声,LSTM的门控单元要在一个循环结构里同时拟合这么多频率成分,结果往往是低频趋势跟得上,高频毛刺被当噪声滤掉,或者为了拟合格外出现的尖峰而牺牲整体泛化。VMD在组合里的角色,就是把原始序列按中心频率自适应切分成若干个固有模态函数,让每个模态内部的波动尺度相对统一,再交给LSTM去逐个建模。
2.1 VMD的原理和在组合里的定位
VMD求解的是一个变分问题:给定输入信号,分解出K个模态,目标是让所有模态的估计带宽之和最小,约束条件则是各模态叠加后等于原信号。求解过程中交替更新模态函数和中心频率,所以它能自适应地把信息分配到不同频段。和EMD画包络、逐次筛分的思路不同,VMD一次迭代就能得到全部模态,模态个数K是显式指定的,而且分解结果对噪声的敏感度更低,这对后续按模态串行训练LSTM非常有利。
在VMD-SSA-LSTM这套流程里,VMD只负责“拆”。常见做法是:目标变量和辅助变量分别做VMD,然后把序号相同、中心频率区间接近的模态拼成一组多变量输入,训练一个LSTM。每个模态代表一个频段的物理过程,例如低频模态对应长期趋势,中频模态对应日周期,高频模态对应随机扰动。这样拆分之后,LSTM的数量从1个变成K个,但每个网络的学习任务都更单纯,整体预测精度通常会明显优于直接端到端训练。
2.2 MATLAB里执行VMD的最小代码与参数入口
不同来源的MATLAB工程里,VMD函数入口顺序不完全一样,有的把alpha写在第二参数,有的把K写在第二参数。我一般先读函数头再调用,下面这段是按多数代码包通用入口写的,适用于一维数值序列。
% 读取原始序列并强制转成列向量 raw = load('load_series.mat'); X = raw.series(:); % 列向量,长度 N % VMD 入口参数 alpha = 2000; % 带宽惩罚因子,越大各模态频带越窄 tau = 0; % 噪声容忍度,0 表示强去噪模式 K = 5; % 模态个数,需要结合数据和业务预设后调试 DC = 0; % 0 表示不强迫第一模态为直流分量 init = 1; % 中心频率初始化为均匀分布 tol = 1e-7; % 迭代容差 % 执行分解 [u, u_hat, omega] = VMD(X, alpha, tau, K, DC, init, tol); % 打印收敛后的中心频率,用于判断 K 是否合理 for k = 1:K fprintf('IMF %d 中心频率: %.4f\n', k, omega(end, k)); end % 绘制各模态时域波形,观察是否有模态混叠 figure; for k = 1:K subplot(K,1,k); plot(u(:,k)); title(['IMF ', num2str(k)]); end这段代码的逻辑是:把序列转成列向量后,用VMD分解得到K个模态列向量u,omega保存了每一轮迭代的各模态中心频率,我们只需要看最后一行,也就是收敛后的中心频率。绘制各模态时域图能直观看出有没有两个模态长得几乎一样,那就是模态混叠了。
参数上,alpha=2000是绝大多数序列的起步值。序列噪声大、毛刺密集时,alpha可以调到3000~4000,让每个模态带宽更窄,避免噪声被摊到多个模态里;但如果调得过大,模态会被削成近似正弦波,反而丢失局部形态信息。tau=0是强去噪模式,适用于大多数工业数据;如果数据本身平滑且你怀疑分解过程把有效信息当噪声滤掉了,可以把tau设为0.1~0.3试试。init=1表示中心频率均匀初始化,这个选项在大多数场景下省心且收敛快,不建议一上来就用随机初始化的init=0。
2.3 怎么判断K和alpha设得合不合理
判断K是否合理,第一看中心频率的排列。K从3逐步增加到6,如果某个omega和相邻模态的omega挤在几乎同一个值附近,说明两个模态在争抢同一个频段,那就要把K退回小一档。第二看残差结构:VMD虽然理论上保真,但实际数值分解后,原信号减去所有模态之和会有一个很小的残差。如果残差里还残留明显的周期性波动,通常是alpha偏小导致模态没有完全分离,或者K不足导致某些频段被遗漏。
还有个容易忽略的点:模态个数K尽量和控制变量数配合。如果辅助变量有3个,每个都设K=5,那后面要训练5个LSTM但每个LSTM的输入维度是3,这可以接受;但如果你把每个变量分解成不同数量的模态再强行拼接,输入矩阵就会失去对齐关系,预测效果会明显变差。我的习惯是,先只对目标变量做中心频率分析,用这个方法粗定K,再让辅助变量统一用同一个K,避免模态序号错位。
3. SSA优化LSTM超参数:把“试出来的参数”变成可搜索的最优解
LSTM自身的训练是靠反向传播完成的,但网络结构超参数,比如隐含神经元数量、初始学习率、L2正则系数、批次大小、训练轮数,这些仍然需要人工指定。多维时间序列的LSTM还要额外决定时间窗长度和输入特征组合方式,搜索空间大幅膨胀。网格搜索在这种场景下完全不现实,SSA的价值就是用一小群“麻雀”在这个多维参数空间里自动搜索,用验证集误差作为每个个体的生存指标。
3.1 LSTM的超参数为什么难调
LSTM在时间序列预测上的表现对超参数极其敏感。隐含神经元太少,记忆容量不够,趋势和周期学不完整;神经元太多,训练集上拟合很好,一旦预测未来几步,误差被放大得特别快。初始学习率更是让人头疼,设大了loss震荡,设小了迭代几百轮还在原地,而SSA迭代中每评估一个个体都要完整训练一次LSTM,这个成本决定了我们不能盲目扩大搜索范围。
还有一个非常隐蔽的问题:同一种超参数组合,在不同随机种子下训练出来的LSTM,预测误差本身就存在不小的波动。这意味着适应度函数不是一个光滑函数,SSA在搜索过程中会把这些波动当成“地形成分”,结果容易把最优个体选在了一个恰好运气好的点上。解决这个问题的标准做法是,适应度评估时用固定随机种子,或者每个个体训练两次取平均误差,代价是训练时间翻倍,但最终选出来的参数更可靠。
3.2 麻雀个体编码与适应度函数
我把麻雀个体的位置向量设计成一组LSTM超参数,维度是5:隐含神经元数量、初始学习率、L2正则系数、MiniBatchSize、最大训练轮数。输入时间窗长度和时间步长一般根据业务经验固定,不参与搜索,因为把窗长也纳入优化会显著增加训练时间。下面给出适应度函数框架。
function rmse = fitness_LSTM(x, XTrain, YTrain, XVal, YVal) % 将麻雀个体位置解码为超参数 numHidden = round(x(1)); % 隐含神经元个数,取整 lr = x(2); % 初始学习率 l2 = x(3); % L2 正则系数 batchSize = round(x(4)); % 批次大小,取整 maxEpochs = round(x(5)); % 最大训练轮数 % 构建 LSTM 网络结构 layers = [ sequenceInputLayer(size(XTrain,1)) lstmLayer(numHidden, 'OutputMode', 'last') fullyConnectedLayer(size(YTrain,1)) regressionLayer]; options = trainingOptions('adam', ... 'InitialLearnRate', lr, ... 'L2Regularization', l2, ... 'MiniBatchSize', batchSize, ... 'MaxEpochs', maxEpochs, ... 'Verbose', 0, ... 'Plots', 'none'); % 训练并计算验证集 RMSE net = trainNetwork(XTrain, YTrain, layers, options); YPred = predict(net, XVal); rmse = sqrt(mean((YPred - YVal).^2, 'all')); end这个函数的核心逻辑是:麻雀算法把个体位置传给fitness_LSTM,函数负责构造网络、训练一次、用验证集算一次RMSE并返回。注意,训练数据的格式是CTO格式,即细胞数组,每个细胞里是一个时间步的输入序列,维度为特征数×序列长度。如果你习惯用矩阵滑窗,可以先用一个转换函数把矩阵变成细胞数组再传入。
训练开销是这个函数最大的现实约束。我的血泪经验是:SSA刚开始搜索时,把maxEpochs的搜索范围设小一点,比如从30到200,让前几代快速淘汰明显差的个体;等到迭代后期,再围绕当前最优解做一次小范围精搜。如果一开始就允许个体训练500轮,一个种群10只麻雀跑15代,那一共要训练150个LSTM,普通CPU机器根本跑不动。
3.3 SSA主循环:参数设置和收敛监控
SSA主循环的调用方式和种群设置如下。
% 参数边界:维度5,分别对应 hidden, lr, l2, batchSize, epochs lb = [20, 1e-3, 1e-6, 16, 50]; ub = [150, 1e-1, 1e-2, 128, 400]; % 麻雀搜索算法初始化 dim = 5; % 搜索维度 pop = 10; % 种群数量,别贪大 maxIter = 15; % 最大迭代次数 % 构造适应度函数句柄 fitnessFunc = @(x) fitness_LSTM(x, XTrain, YTrain, XVal, YVal); % 调用 SSA 主函数 [bestPos, bestFitness, curve] = SSA(fitnessFunc, lb, ub, dim, pop, maxIter); % 输出最优参数 fprintf('最优 neuronal: %d\n', round(bestPos(1))); fprintf('最优 lr: %.4f\n', bestPos(2)); fprintf('最优 l2: %.6f\n', bestPos(3)); fprintf('最优 batch: %d\n', round(bestPos(4))); fprintf('最优 epochs: %d\n', round(bestPos(5))); % 绘制收敛曲线 figure; plot(curve, 'LineWidth', 2); title('SSA 收敛曲线'); xlabel('迭代次数'); ylabel('最佳适应度');pop=10和maxIter=15是我在多个项目里试出来的折中值。种群太小,比如4~5只,搜索空间覆盖不足,容易掉进局部最优;种群太大,每代要训练10~20个LSTM,时间成本不划算。adaptation曲线如果到第8代左右就平了,说明搜索已经收敛;如果一直锯齿状抖动不下降,优先检查LSTM训练是否发散,比如学习率范围取值过大导致个体训练崩溃。
4. 完整预测流程:从原始数据到最终预测值要怎么串
VMD、SSA、LSTM三个模块单独跑通只是第一步,真正让这套组合能落地的是把数据组织、逐模态训练和重构环节串起来。很多人在这一步翻车,最常见的是模态拼接错位和归一化信息泄漏。
4.1 多维时间序列的数据组织方式
多维时间序列预测的前提是变量对齐:所有变量在同一个时间轴上采样,比如每小时采一次,那每一行就是同一时刻的各变量值。数据处理我一般遵守一个硬规则:先切分,后归一化。也就是先把数据集按时间顺序切成训练段、验证段、测试段,然后只用训练段的最大值和最小值去归一化训练、验证、测试三段,不能先整体归一化再切分,否则测试段的分布信息会被带进归一化统计量里,属于数据泄漏,预测结果虚高。
滑窗构造样本时,时间步长timeStep和预测步长step是两个业务参数。比如用过去12个时刻预测未来3个时刻,那么每个样本的输入是12×特征数,输出是3×预测变量数。构造时要记住:样本之间不能重叠采样。如果滑窗每次移动1个时间点,相邻样本会有11个时间点重复,模型会泄漏,训练集和测试集的样本之间也存在相关性,这会让测试集误差表现得比真实在线部署小很多。我通常让滑窗移动步长等于预测步长,让样本之间保持独立。
4.2 逐模态训练LSTM与整体重构
整个流程的代码骨架如下。
% 主流程:VMD + 逐模态 SSA-LSTM + 重构 % 1. 读取并整理多维序列,每一列是一条变量 raw = load('multivariate_data.mat'); series = raw.data; % N x varNum target = series(:, 1); % 要预测的主变量 numVars = size(series, 2); % 2. 对每一列变量分别做 VMD,保存为 cell K = 5; alpha = 2000; mus = cell(numVars, 1); for v = 1:numVars [mus{v}, ~, omega] = VMD(series(:, v), alpha, 0, K, 0, 1, 1e-7); end % 3. 按模态组构造滑窗样本 timeStep = 12; step = 3; [XAll, YAll] = buildSamples(mus, timeStep, step, K); % 4. 按时间顺序切分 70% / 15% / 15% n = size(XAll, 1); nTrain = round(n * 0.7); nVal = round(n * 0.15); % XAll 的维度是 [总样本数, 模态数*变量数, timeStep],要转成细胞数组这里我特意强调了转成细胞数组,因为MATLAB的LSTM训练接口要求输入是N×1的细胞数组,每个细胞是一个numFeatures×timeStep的矩阵。如果你用普通三维数组直接喂给trainNetwork,会因为维度不匹配报错。buildSamples函数做的事就是:把每个变量的每个模态按时间窗取出,组合同一模态的多个变量作为输入特征,输出则是目标变量对应的预测模态。
逐模态训练时,K个LSTM可以顺序训练,也可以利用MATLAB的parfor加速,但要注意parfor每次迭代都要重新初始化网络,内存占用会比较大。重构时,把K个模态的预测结果直接相加,理论上就能还原整体的预测值。不过在相加之前,确认每个模态的预测输出和原本VMD分解的模态在相位上没有偏移。VMD本身没有相位调整环节,但滑窗训练引入的预测延迟会在叠加后体现为整体滞后,这是后面避坑章节要重点处理的。
4.3 预测结果评价:RMSE、MAE、MAPE之外的判断
RMSE、MAE、MAPE是标配,但多维时间序列预测里,我还会强制看三个附加指标。第一个是峰值时刻误差,也就是预测曲线和真实曲线的峰值出现时间差,LSTM在时序预测里最常见的病就是峰值滞后,按周期分解之后,这个滞后会集中出现在中频模态,单独看每个模态的滞后能定位问题。第二个是趋势方向准确率,也就是相邻两点变化方向的预测正确率,这个指标对业务决策很重要,MAPE很低但方向错一半,模型也是不可用的。第三个是误差的频谱分布,把预测误差残差做一次FFT,如果在某个频率区间出现明显能量峰,说明该频段的模态没有被LSTM学好,需要重点检查对应模态的输入特征是否充分。
5. VMD-SSA-LSTM的避坑记录:现象、原因与解决
这里把我在模拟项目X上反复踩过的五个坑整理出来,每一条都是现象、原因、解决连在一起写的,希望你别再烧这些时间。
5.1 预测前段飞点严重,曲线开头突然拉出一个大尖峰
现象:整体指标还行,但预测序列的前5~10个点偏离真实值非常大,后面慢慢恢复正常。 原因:VMD分解时,信号两端因为延拓方式不同会产生边界效应,模态在开头和结尾的取值不完全保真。LSTM学到的模态形态在边界处与实际模态不匹配,一旦前段预测被边界失真影响,误差会顺着LSTM循环结构向后传递几层。 解决:训练和预测时,把分解后每个模态的首尾各切掉若干个点,比如切掉序列总长度的1%~2%。如果不想损失样本,可以把原始序列两端先做镜像延拓,分解后再截掉延拓部分。第二种办法更优雅,推荐优先用。
5.2 SSA收敛曲线一直不下降,每代最优适应度原地抖动
现象:SSA迭代了10代以上,curve曲线完全没有下降趋势,在某个值附近反复波动。 原因:LSTM训练本身有随机性,同一个超参数在不同随机种子下训练结果差异可能达到10%以上。SSA把这种随机抖动当成了搜索地形,导致麻雀在不断“试运气”而非真正逼近最优。 解决:给fitness_LSTM加固定随机种子,如rng(42),确保同一个个体每次评估结果一致。如果还想再稳一步,每个个体评估两次取平均RMSE,虽然耗时翻倍,但最终选择出来的参数在真正测试时表现更稳定。
5.3 K设得太大,某个模态完全过拟合
现象:分解层数调到8以上,训练好的LSTM在训练集上误差极小,测试集误差反而比K=5时还大,而且某个高频模态几乎学不到有效结构。 原因:K过小时模态混叠,K过大时高频区段被切得过碎,某些模态主要包含噪声。SSA搜索时,LSTM会把噪声当成有效结构硬学,测试时噪声模式变化,预测自然崩掉。 解决:不要只看VMD重构误差。把每个模态的能量占比、零交叉率、中心频率间距打出来,能量占比低于1%的模态直接去掉或合并到相邻模态。我一般把K控制在3~6之间,再用中心频率间距判断是否收敛。
5.4 逆归一化之后预测值整体偏离真实值一个固定比例
现象:预测曲线形态完全正确,但幅值整体偏大或偏小,像乘了一个系数。 原因:某个模态归一化时用的最大值是整条序列的统计值,切分训练验证集之后,训练集内可能没覆盖到该模态的最大值。逆归一化时,预测值被放回到了训练集区间,但真实值却超出了这个区间,幅值被整体压缩或扩张。 解决:切分数据后再统计训练段的最大值和最小值。如果训练段最大值远小于测试段最大值,预测本身的泛化能力本来就存疑,优先检查是不是模态分解边界引起的非平稳点,而不是单纯调归一化代码。
5.5 多变量输入时模态序号错位导致特征对齐错误
现象:辅助变量和目标变量各自分解后,输入矩阵按照模态序号拼接,但测试时某个辅助变量的预测误差突然剧增,单独看每个模态都觉得没问题。 原因:不同变量在同一模态序号下对应的中心频率区间并不一致,尤其当两个变量一个变化平缓、一个剧烈波动时,它们的IMF 3可能一个落在日周期频段,另一个落在半日周期频段,强行拼接等于把不同物理过程的特征喂给了同一个LSTM输入层。 解决:在分解完成后,按中心频率区间重新排序对齐各变量的模态,而不是死守分解顺序。每得到一个新的模态组,打印中心频率对比表格,确认组内各模态频率接近再训练。如果频率差太大,说明这两个变量在对应频段没有耦合关系,可以考虑减少输入变量数。
6. 从能跑到能用:让VMD-SSA-LSTM更稳的三个技巧
第一,K值不只看误差,还要看omega的排列形态。我习惯在SSA之前先把各变量的omega画在一张图上,如果某个频率区间明显稀疏,说明那个频段的信息没有被单独建模,K可以加1;如果两个omega几乎重叠,K已经偏大。用这个判断代替“跑完再看测试误差”,能省下大量训练时间。
第二,多步预测时优先采用滚动修正,而不是一次输出所有未来步。LSTM在一步步递归预测时误差会累积,把时间窗末端的值替换成实际观测值再继续预测,能有效压低误差。但如果预测步长较长,比如未来24小时,一下全部输出,中频模态的相位滞后会在叠加时被放大。常见做法是:先训练单步预测模型,部署时滚动迭代,每次迭代把新观测值加入输入窗。注意,此时每个模态的输入变量都需要同时更新,不能只更新目标变量。
第三,用一块钱的成本验证组合的收益。跑VMD-SSA-LSTM之前,先用同样的滑窗和训练参数跑一个不加VMD的LSTM,计算同样的评价指标。如果VMD带来的RMSE提升不到10%,就要考虑是不是数据本身已经足够平稳,或者K、alpha设置有问题。我见过不少项目,VMD加了还不如不加,问题不是组合不好,而是模态被拆得太碎,LSTM在各模态上反复过拟合。先用简单基线锚定,再判断每个环节的边际收益,这个习惯能帮你把精力集中在真正有效的方向上。
MATLAB代码跑通一套组合不难,难的是让每个模块各司其职。我现在每接到一个多维时间序列预测的活,都会先要求自己复现一套最小全程脚本:读数据、VMD分解、SSA搜索、逐模态训练、重构、多指标评价,全部跑通再谈优化。中间哪个环节出现指标不进反退,就用上面这些排查点逐个过一遍,绝大多数问题都能定位到边界效应或参数错位。希望帮到你。
本文还有配套的精品资源,点击获取