简介:面向需要开展时序预测的Python开发者与研究者,这份资源提供基于TensorFlow框架的VMD-LSTM模型完整实现,将变分模态分解与长短期记忆网络结合,适用于电力负荷等非线性、非平稳数据的单步与多步预测。代码中文注释清晰,支持单输入单步、单输入多步、多输入单步、多输入多步四种组合模式,并内置MSE、RMSE、R2、MAE、MAPE等常用评估指标,方便横向对比模型效果。资源包内含9个文件,包括Python主程序与数据预处理脚本、CSV与Excel格式的测试数据集、使用说明与依赖库文档,以及3张分解效果示意图,压缩包仅654KB。已有54人学习,适合希望快速上手VMD-LSTM组合模型、替换自有数据完成预测的读者,附带的说明文档和依赖清单可显著降低环境配置与复现门槛。
1. VMD-LSTM 到底是什么:一个先分解、再预测的时序建模组合
做负荷预测或者金融时序预测时,最头疼的不是模型不够深,而是拿到的原始序列根本不听话:趋势项、周期项、随机噪声搅在一起,还带着尖峰和突变。把这样的原始序列直接喂给 LSTM,结果往往是 loss 下降很慢、预测曲线跟着真实值走但总慢半拍。VMD-LSTM 的思路很直接:先用变分模态分解(VMD)把序列按频率拆成几个相对平稳的分量,再对每个分量单独做 LSTM 预测,最后把预测值合起来。这个组合解决的是"非平稳序列难学"的问题,适合已经会用 TensorFlow 搭 LSTM、但卡在特征预处理和精度提升上的从业者。需要提醒的是,VMD-LSTM 不是新模型,本质是把信号分解当成特征工程,它有效,但也不是万能药。
2. 为什么选 VMD 而不是 EMD:分解原理与两个关键参数
2.1 VMD 与 EMD 的本质差别:从模态混叠说起
经验模态分解(EMD)在时序预测里更早被用,但它的毛病在工程上一眼就能看出来:EMD 是递归筛分的,先抽最高频分量,再对剩余信号继续筛,这个过程对极值点分布极其敏感,采样率一变、噪声一大,同一个信号分解出的 IMF 就不一样。更烦人的是模态混叠——一个频率成分被劈成两半,分属两个 IMF,后面喂给 LSTM 的两个通道其实是同一个信号的碎片,模型自然学得乱七八糟。
VMD 换了个思路:不递归筛分,而是直接把"把信号分解成 K 个模态"定义成一个约束变分问题。具体说,它假设每个模态 u_k 都是围绕中心频率 ω_k 的调幅调频信号,整个分解的目标是让所有模态的带宽之和最小,同时所有模态加起来等于原始信号。带宽用梯度范数来估计,整个问题靠拉格朗日乘子和 ADMM 迭代求解。这样一次迭代就把 K 个模态和它们的中心频率一起解出来,没有级联误差,对采样和噪声的容忍度也高得多。
这个数学设定直接带来了两个工程红利。第一,分解结果稳定,同一段数据跑两遍结果一致,不会像 EMD 那样随机抖动。第二,模态之间的区分度可控,因为带宽是被显式惩罚的,控制惩罚强度就能控制每个模态的频率范围。LSTM 拿到的每个输入通道都是"频率成分相对干净、平稳性更好"的子序列,学习难度比硬啃原始序列低一个量级。
2.2 K 和 alpha 怎么定:中心频率观察法
VMD 最核心的两个参数是模态数 K 和惩罚因子 alpha。K 决定把序列拆成几份,alpha 决定每个模态的带宽。
alpha 的理解方式我习惯这样说:它是对模态带宽的二次惩罚系数,alpha 越大,模态的带宽就被压得越窄,每个模态越"纯粹",但也越容易把同一频率带里的有效信息切成碎片;alpha 越小,模态带宽越宽,容忍度高了,但相邻模态容易交叠。工程经验值是 2000 附近起步,日频或小时频的负荷、价格序列,500 到 3000 之间都值得网格搜索一遍。
K 的选取没有解析解,最实用的办法是看中心频率。跑完 VMD 后把 K 个模态的中心频率排个序,如果相邻两个频率靠得太近(比如相差不到一个数量级),说明 K 选大了,存在过分解;如果最高频的那个模态波形还是很复杂的多峰形状,说明 K 偏小,高频信息没拆干净。我用这个规则把 K 从 2 试到 10,通常 5 到 7 就能覆盖大部分场景。
注意:K 不是越大越好。过分解会把一个连续频谱切成若干窄带尖峰,每个模态都只学到了一小段频率里的噪音,反而拉低预测精度。
2.3 用 Python 跑通一次 VMD 分解:最小代码与输出解读
VMD 在 Python 里最常见的实现是 vmdpy 库,安装完成后按下面这段操作即可。以一段 2000 点的模拟非平稳序列为例:
import numpy as np from vmdpy import VMD # 生成一段模拟序列:趋势 + 正弦 + 噪声,模拟真实非平稳数据 t = np.linspace(0, 1, 2000) series = 5 * t + np.sin(20 * np.pi * t) + 0.2 * np.sin(60 * np.pi * t) series += np.random.normal(0, 0.1, len(series)) f = series.astype(float) # alpha=2000: 带宽惩罚因子;tau=0: 噪声容限;K=5: 模态个数 # DC=0 表示不单独分离直流分量;init=1 表示中心频率均匀初始化 u, u_hat, omega = VMD(f, 2000, 0, 5, 0, 1, 1e-7) # u 的形状是 (K, N),每一行是一个模态分量 print("模态数:", u.shape) print("各模态中心频率:", omega)VMD 的返回值里,u 是分解后的模态分量数组,每一行对应一个 IMF 子序列:低频的趋势成分通常在 u[0],最高频的噪声成分通常在最后一行。omega 是迭代收敛后的中心频率,单位是归一化频率,用它来检查 K 是否合适。
用序列的总长度和模态波形验证分解质量:把 u 的所有行加起来,应该能几乎完美还原原始序列 f,差出来的部分来自 tol 收敛精度。如果还原误差超过原始信号幅度的 1%,优先检查 tau 是不是被设成了非零值,以及 alpha 是否小到让模态带宽过宽。
我一般会在分解后画一张模态堆叠图,肉眼扫一遍。如果某个模态和另一个模态波形高度相似,直接回到 K 减一;如果某个模态像被刀切过一样陡峭跳变,那是 alpha 太大了,降到 1000 以下重新跑。
3. 把分解结果接到 TensorFlow LSTM:数据包装与模型搭建
3.1 滑窗样本构造:别让未来数据提前进特征
分解是前置步骤,接下来要把每个模态分量变成 LSTM 能吃的样本。LSTM 的输入形状要求是 (样本数, 时间步长, 特征数),时间窗口 window 决定模型每次看多长的历史,horizon 决定一步预测多远。
构造滑窗样本时最常见的翻车点是窗口越界和未来数据泄漏。下面这个函数用纯索引控制,把 window 和 horizon 分开处理:
import numpy as np def make_samples(component, window=24, horizon=1): # component: 一维数组,某个模态的完整序列 # window: 输入时间步数;horizon: 预测未来步数 X, y = [], [] length = len(component) - window - horizon + 1 for i in range(length): X.append(component[i:i + window]) y.append(component[i + window:i + window + horizon]) return np.array(X), np.array(y).reshape(-1, horizon)核心逻辑是样本 i 的特征区间是 [i, i+window),标签区间是 [i+window, i+window+horizon),两个区间不重叠。length 的边界计算保证标签索引不越界。这个函数返回的 X 是二维数组,后面要 reshape 成 (样本数, window, 1),因为单个模态的特征维度是 1。
这里的坑在于,如果你的原始序列在分解前就已经把全序列都拿来做过 VMD 了,那"未来信息泄漏"其实已经发生,这个没有让它提前泄露,只是把它变成样本而已。更彻底的严防泄漏做法见第 5 章。
实际业务中 window 的选择要看数据粒度。15 分钟粒度的负荷数据,window 取 96 能捕捉一天周期;日频数据 24 到 30 就够。注意,window 越大,模型容量需求越高,训练时间越长,不要为了"多看历史"盲目堆到几百。
3.2 一个能直接跑的 LSTM 模型:Sequential 三层结构
分解出的每个模态分量单独训练一个 LSTM,模型结构不需要复杂,三层的 Sequential 足够覆盖多数单变量预测需求:
import tensorflow as tf window, horizon = 24, 1 def build_lstm(window, horizon): model = tf.keras.Sequential([ # 第一层 LSTM 返回完整序列,供第二层继续提取时间特征 tf.keras.layers.LSTM(64, return_sequences=True, input_shape=(window, 1)), tf.keras.layers.Dropout(0.2), # 第二层只返回最后时刻的输出,接 Dense 出预测值 tf.keras.layers.LSTM(32, return_sequences=False), tf.keras.layers.Dense(horizon) ]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='mse', metrics=['mae'] ) return model第一层 LSTM 用 return_sequences=True 是因为需要把完整的时间维输出传给第二层,如果只有一个 LSTM 层,直接设 return_sequences=False 即可。Dropout 在两层之间,只对激活起作用,不改变输入形状。
units 的起步值我固定用 64 和 32,原因很朴素:单变量模态序列的信息量不大,太宽的隐藏层只会让模型记住训练集的噪声,对验证集无益。输入输出维度上,input_shape 的最后一维是 1,因为每个模态是一个单通道特征;如果后面改用多模态堆叠输入,才把这个维度改成模态个数。
编译参数里 loss 用 mse,是因为 VMD 后的模态是连续数值,mse 对异常值敏感,能逼着模型把大误差压下来。metrics 带一个 mae 只是用来日常看进度,评估还是留到最后统一算。
3.3 多分量预测怎么合并:并联训练 vs 特征通道
分解后 K 个模态有两种接法:一是每个模态一个独立 LSTM,各自预测再相加,这是论文里最常见的做法;二是把 K 个模态在特征维度上叠起来,变成一个输入形状为 (window, K) 的多变量回归问题,只训练一个 LSTM。
并联训练的好处是每个模型只学一个窄带频率成分,任务简单、收敛快;坏处是要维护 K 个模型,且每个模型的误差在求和时可能会同向叠加。单模型多特征通道的做法把"每个模态之间的相关性"也交给 LSTM 学,但代价是模型输入的频率成分互相干扰,如果分解质量不好,多通道反而让模型更糊涂。
我实际项目中的选择标准是看 K 的大小:K 小于等于 4 时用并联训练,每个模态的预测误差单独看,方便定位是哪个频段拖了整体精度;K 超过 6 时用单模型多特征通道,否则要训练 6 个以上的 LSTM,维护成本和过拟合风险都控制不住。融合层这个选项也很实用,把各模态的预测值拼起来再接一个 Dense 重新加权求和,这等于让模型自己学"谁更可信",能有效缓解误差累加问题。
4. 训练配置与调参顺序:先让 loss 正常下降,再谈精度
4.1 归一化、时间顺序切分、早停:训练前必做的三件事
分解后的模态虽然平稳性提升了,但幅值范围差异依然很大:低频趋势分量可能是几十到几百的数值,高频噪声分量可能只有小数点后两位。不归一化直接训练,LSTM 的权值更新会被大数值的分量主导,小分量的梯度被淹没,典型表现是 loss 降到某个平台就再也下不去。
每个模态必须单独做 MinMaxScaler,且只能用训练段的统计量去 transform 验证段和测试段:
from sklearn.preprocessing import MinMaxScaler def scale_component(component, train_len): scaler = MinMaxScaler(feature_range=(0, 1)) # train_part = component[:train_len] 只用来 fit,防止验证/测试信息进入 scaler scaler.fit(component[:train_len].reshape(-1, 1)) scaled = scaler.transform(component.reshape(-1, 1)).flatten() return scaled, scalerfit 只发生在 train_len 之前的区间,这是时序预测里不能破的红线。如果 full fit 全序列,scaler 已经"见过"未来的最大最小值,预测时一上线就会因为真实新数据的数值范围超出训练时见过的范围而表现崩坏。预测完成后的还原同样用这个 scaler 的 inverse_transform,而不是重新 fit。
时间顺序切分也在此一并处理:训练集占前 70%,验证集占中间 20%,测试集占最后 10%,切分时直接用索引切片,禁止随机打乱。时序数据一旦 shuffle,验证集就混入了训练集之前的时间段,模型等于在"借未来的数据验证昨天的预测",评估指标完全失真。
早停是控制过拟合的主力。我习惯给足耐心但保留恢复能力:
early_stop = tf.keras.callbacks.EarlyStopping( monitor='val_loss', patience=15, restore_best_weights=True ) lr_reduce = tf.keras.callbacks.ReduceLROnPlateau( monitor='val_loss', factor=0.5, patience=8, min_lr=1e-6 ) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=200, batch_size=32, callbacks=[early_stop, lr_reduce], verbose=1 )patience 设为 15 的意思是如果验证集 loss 连续 15 个 epoch 不创新低,就停。restore_best_weights 是关键参数,保证停在最优权重而不是最后一步。ReduceLROnPlateau 在 loss 挂住时把学习率减半,给模型一次重新下降的机会。这套组合能让训练稳定,不会出现 loss 曲线一路飙升到 NaN 的失控场面。
4.2 学习率、batch size、units 的调整顺序
调参最忌讳一上来就同时动所有旋钮。我的固定顺序是先调学习率,再调网络宽度,最后才碰 batch size。
学习率从 0.001 起步。训练过程中观察 loss 曲线:如果 loss 在前 5 个 epoch 内剧烈震荡,说明学习率偏大,降到 0.0005 或 0.0003;如果 loss 下降得像蜗牛爬,50 个 epoch 里只降了几个百分点,升到 0.003 试试。Adam 优化器对学习率的容忍度相对高,但 0.01 以上基本都会翻车。
units 的调整看的是模型容量是否匹配序列复杂度。先固定 64/32 跑一遍,如果训练集 loss 和验证集 loss 都徘徊在一个高水平,把第一层加到 128;如果训练集 loss 很低但验证集 loss 很高,是过拟合,优先加 Dropout 而不是减小网络。记住一个原则:VMD 已经把序列拆简单了,units 需求通常比直接用原始序列训练小得多。
batch size 的调整放在最后,因为它的作用最微妙。32 是个稳健的起点,样本量少的时候(几千条)用 16 能让梯度更新更频繁,收敛更稳;样本量超过十万再考虑 64 或 128,用空间换速度。batch size 改完如果验证集指标抖动变明显,说明噪声梯度变大了,退回去就行。
5. VMD-LSTM 常见问题避坑:我踩过的五个坑
5.1 坑一:分解阶段混入全序列信息,测试集结果虚高
现象:训练时验证集和测试集的误差都很小,模型表现堪称完美,一部署到线上预测新数据,误差立刻翻倍。
原因:做 VMD 分解时直接对整条序列做了分解,然后再切训练集和测试集。VMD 的约束变分求解是全局迭代的,每个时刻的模态值都会受到整段序列的影响,测试阶段的信息通过分解过程流进了训练用的模态序列里。模型在训练时就"见过了"未来,测试误差自然虚低。
解决:离线研究场景下全序列分解然后切分还能接受,但要在报告里说明这是离线实验;工程上线必须改成训练段分解、测试段用固定滤波器组来提取模态。具体做法是用训练段分解出的中心频率 omega,构造对应频段的带通滤波器,测试段新数据每条都过这个滤波器,得到模态分量后再交给训练好的 LSTM。代价是滤波器的频率响应和 VMD 的模态不是完全等价,精度会有损失,但这是诚实评估模型泛化能力的唯一正路。
5.2 坑二:K 选太大导致过分解,预测曲线一截一截的
现象:把 K 设成 10,分解出来的模态里有两个波形几乎长得一样,预测结果的曲线像被截断过一样,一段平滑一段跳动。
原因:K 过大时,VMD 会把一个物理上连续存在的频率带硬切成多个窄带模态,每个模态只分到一小段频谱。LSTM 对每个窄带模态学到的规律被噪声主导,合成预测时多个弱模型拼出畸变。
解决:回到第 2 章的中心频率观察法,检查 omega 数组。只要发现相邻中心频率的差值有异常小的,就把 K 往下调。更省事的做法是 K 用 3、4、5、6 做网格搜索,对每个 K 都跑一遍完整的训练评估流程,直接比较验证集 RMSE,选最小的那个。K 的搜索成本不高,因为 VMD 分解本身是毫秒级的,真正花时间的是后面每个 K 都要训练 LSTM。
5.3 坑三:alpha 凭感觉乱调,模态带宽完全失控
现象:alpha 从默认 2000 调到 10000 之后,分解出的高频模态变得非常尖锐,几乎只剩一个尖峰,而中频模态里出现明显的高频抖动。
原因:alpha 控制模态带宽,alpha 越大,模态的频率范围越窄。过大的 alpha 会把信号的关键频率成分削掉,造成模态失真;过小的 alpha 则让模态互相交叠,分解失去意义。
解决:alpha 不轻易离开 500 到 3000 这个区间。调参时观察一个指标:分解后的每个模态是否还能覆盖一个连续的频带。如果模态波形出现异常陡峭的窄尖峰,降 alpha;如果多个模态的波形高度相关,升 alpha。和 K 一样,alpha 也适合做网格搜索,但优先级低于 K,因为它的影响不像 K 那样直接改变模态数量。
5.4 坑四:LSTM 输入没归一化,loss 直接变 NaN
现象:训练跑了两三个 epoch,loss 输出变成 nan,或者从一个大得离谱的数字开始,怎么也降不下来。
原因:这个坑最直白——某个模态的幅值达到成千上万,输入到 LSTM 后经过多个时间步的矩阵乘法,数值直接溢出。尤其当学习率偏大时,梯度爆炸会让权重更新一步迈到无穷大。
解决:先看是不是所有模态都做了 MinMaxScaler,再确认 scaler 的 fit 区间是训练段。然后检查数据里有没有 inf 或 NaN,VMD 分解偶发不收敛时会产出空值,这种情况需要先对分解结果做 np.isnan 检查。最后给 Adam 加一个 clipnorm 参数,把梯度的 L2 范数限制在 1.0,防止梯度爆炸。代码上就是一行:
optimizer=tf.keras.optimizers.Adam(learning_rate=0.001, clipnorm=1.0)clipnorm 是做事后兜底,不是偷懒借口。归一化和数据清洗做好之后,这个参数一般用不上,但保留它能让训练过程更皮实。
5.5 坑五:多模态误差累加,直接相加预测不如融合层
现象:每个模态单独预测的 RMSE 都不高,但把所有模态的预测值直接相加后,整体 RMSE 比预期高出一大截,误差像商量好了似的往同一个方向偏。
原因:各模态的 LSTM 模型独立训练,每个模型的误差方向不受控。如果多个模态同时高估或同时低估,相加后误差是累加的,不是抵消的。平稳性假设在真实数据里从来不是完美成立的,模态之间的相关性让误差出现了同向偏置。
解决:把相加改成加权求和,权重交给模型去学。在 K 个模态的预测值之后接一个 Dense 层,输出唯一一个预测值,这样模型可以学到各个模态的可信度。融合层的输入是 K 个模态的预测值,输出是最终预测。
# concat_pred: 形状 (None, K),每个模态的预测结果并列摆放 fuse = tf.keras.layers.Dense(1, use_bias=True)(concat_pred)这个改法会引入少量额外参数,但换来的是误差累加问题的有效缓解。如果融合后提升不明显,再检查一下是不是某个模态本身预测质量太差,先剔除掉最差的模态再说。
6. 从单步到多步预测:一个能落到业务的验证方法
单步预测的 RMSE 好看,不代表业务可用。多数真实预测需求都是要往后看好几个时间步的,多步预测里误差会随着步数累积,第一步预测作为第二步的输入,误差滚雪球一样越滚越大。验证一个 VMD-LSTM 模型能不能落地,我习惯先看多步预测的误差曲线发散到多快。
递归多步预测是最直接的验证方式:把模型上一时刻的输出作为下一时刻的输入,逐点迭代。
def recursive_predict(model, init_window, steps): # init_window: 形状 (window, 1) 的历史输入 # steps: 需要预测的未来步数 preds = [] current_input = init_window.reshape(1, window, 1) for _ in range(steps): next_val = model.predict(current_input, verbose=0)[0, 0] preds.append(next_val) # 把新预测值推进窗口,丢掉最早的历史点 current_input = np.concatenate( [current_input[:, 1:, :], np.array([[[next_val]]])], axis=1 ) return np.array(preds)逻辑说明:每次迭代模型输入 current_input,输出预测值;然后把预测值追加到窗口末尾、去掉窗口最前面的一个时间点,形成下一轮的输入。这种递归方式实现简单,但误差会累加,预测几步之后曲线往往偏向平稳,这是模型把不确定性吸收掉了的表现。
更稳的做法是直接多步输出,把最后一层 Dense 的神经元数设为 horizon 而不是 1,让模型一次吐出未来 horizon 步的预测向量。直接多步输出的代价是单步不再共享时序递推,但误差不会同向累积。我通常两种都跑,如果直接多步输出明显优于递归,说明模型对短期模式的提取更有效,这在业务上更有价值。
评估指标只用 RMSE 不够,要把按步数拆开看。用递归预测得到 24 步预测结果,分别计算第 1、第 6、第 12、第 24 步的 RMSE,能清楚看到模型在哪里开始崩。如果第 12 步的 RMSE 已经是第 1 步的三倍,那这个模型只适合短周期预测,强行拿去预测长期只会翻车。
我现在的习惯是拿到一条新的非平稳序列,先花十分钟观察频谱,再跑一遍 VMD 看中心频率分布,用中心频率的间隔来决定 K 和 alpha,之后才碰 LSTM。VMD-LSTM 这套组合最迷人的地方在于,分解质量是可见的、可调试的,它让深度学习时序预测不再是黑匣子,每一步都有后悔药可吃。希望帮到你。
本文还有配套的精品资源,点击获取