简介:这份资源面向时间序列预测的学习者与研究人员,提供一套基于VMD-Attention-LSTM的完整Python实现方案,可用于金融、气象、电力负荷等场景的建模练习与课题复现。压缩包共17个文件,约5.25MB,包含6个py源码文件、4个pyc编译文件、3个npy数据文件,以及checkpoint模型权重、TensorFlow索引与数据分片和一份md说明文档,覆盖从数据到模型落地的完整链路。源码中VMD.py负责变模分解,vmd_attention_lstm.py搭建网络结构,训练、验证与预测脚本相互配套,并配有详细注释,便于理解每个模块的作用。已有181人学习,读者可据此掌握VMD分解降噪、LSTM长期依赖建模与Attention加权组合的整合思路,同时借助报告了解实验设置与结果分析,提升Python编程与数据分析能力。
1. 从一份 VMD-Attention-LSTM 源码包说起:它到底能解决什么
如果你做过电力负荷、风速或股价这类单变量时间序列预测,大概率遇到过同一个问题:原始序列噪声大、非平稳,直接把窗口喂给 LSTM,验证集 loss 能降,但测试集一到突变点就崩。这份基于 VMD-Attention-LSTM 的时间序列预测模型 Python 源码包,就是冲着这个痛点来的。它把变分模态分解(VMD)、注意力机制和 LSTM 串成一条完整链路,附带训练脚本、checkpoint、npy 数据集和一份项目报告,属于那种拿到手就能跑、跑完能对着报告核对结果的资源。适合两类人:一是正在做毕设或论文复现、需要一套可解释基线的研究者;二是想把分解+深度模型这套组合拳落到实际预测任务里的工程师。它不解决"预测一定准"的问题,但能让你清楚看到每个环节在干什么。
2. VMD 分解与数据准备:把非平稳序列拆成可学的模态
2.1 为什么先做 VMD 而不是直接上 LSTM
时间序列预测里有个绕不开的现实:真实序列往往是多个物理过程叠加的结果。电力负荷里混着趋势项、周期项和随机扰动,风速序列里低频天气过程和高频湍流搅在一起。LSTM 的门控机制擅长捕捉长期依赖,但它对"输入本身是不是干净"没有免疫力。噪声和模态混叠会让网络把精力浪费在拟合高频抖动上,表现为训练集拟合很好、测试集一遇拐点就偏。
VMD 的思路是把原始信号分解成 K 个中心频率不同的本征模态函数(IMF),每个模态带宽受限、彼此近似正交。相比经验模态分解(EMD),VMD 在数学上更稳,不容易出现模态混叠,端点效应也轻一些。常见做法是:先对整段序列做 VMD,得到 K 个模态分量,再对每个分量单独构造滑动窗口,分别送进模型预测,最后把各分量的预测结果叠加重构。这份源码里的utilt/VMD.py就是干这件事的,train_vmd_af.npy和test_vmd_af.npy是分解后存下来的模态数据,省得你每次训练都重跑分解。
需要提醒的是,VMD 有两个关键参数:模态数 K 和惩罚因子 alpha。K 选小了,不同频率成分还挤在一个模态里;K 选大了,会出现中心频率接近的冗余模态,反而给模型加噪声。源码里默认值能跑通,但换数据集时这两个参数必须重新调。
2.2 数据准备与 VMD 分解的可执行步骤
先看数据组织。资源包里原数据目录放原始序列,train_vmd_af.npy、test_vmd_af.npy是分解后的训练/测试模态数组,pred_hubei_set.npy是湖北某数据集的预测结果存档。整个流程是:原始序列 → VMD 分解 → 按模态切分训练/测试 → 归一化 → 构造监督样本。
下面这段代码演示如何用源码里的 VMD 模块对一条序列做分解,并保存成 npy,逻辑和utilt/VMD.py一致:
import numpy as np from utilt.VMD import VMD # 源码内的 VMD 实现 # 1. 读取原始单变量序列,形状 (T,) raw = np.load('原数据/load.npy').flatten() # 2. 设置 VMD 参数:K 模态数,alpha 带宽约束,tau 噪声容限 K = 5 alpha = 2000 tau = 0 DC = 0 # 不含直流分量 init = 1 # 中心频率初始化方式 tol = 1e-7 # 收敛容差 u, u_hat, omega = VMD(raw, alpha, tau, K, DC, init, tol) # u 形状 (K, T),每行是一个模态分量 # 3. 按 8:2 切分训练/测试,注意按时间顺序切,不能 shuffle split = int(len(raw) * 0.8) train_modes = u[:, :split] test_modes = u[:, split:] np.save('train_vmd_af.npy', train_modes) np.save('test_vmd_af.npy', test_modes) print('模态数:', u.shape[0], '训练长度:', train_modes.shape[1])逻辑说明:VMD 返回的u是 K 个模态在时间轴上的取值,后续每个模态独立建模。参数上,alpha控制带宽,越大模态越窄、越平滑;K是最需要试的,一般从 3 到 8 之间网格搜。切分必须按时间顺序,时间序列一旦 shuffle 就是数据泄漏,这是血泪经验。
提示:分解要在归一化之前做,还是之后做,社区里两种做法都有。源码走的是先分解再对各模态分别归一化,这样每个模态的动态范围独立,训练更稳。如果你先对原始序列归一化再分解,高频模态的幅值会被压得很小,反而不好学。
2.3 监督样本构造与归一化
分解完只是半成品,还要把每个模态转成 (样本数, 时间步, 特征数) 的三维张量。常见做法是用滑动窗口,窗口长度 lookback 决定模型能看多远,预测步长 horizon 决定预测未来几个点。源码里这部分逻辑在val_models_train.py的数据加载段。
def make_windows(series, lookback=24, horizon=1): X, y = [], [] for i in range(len(series) - lookback - horizon + 1): X.append(series[i:i+lookback]) y.append(series[i+lookback:i+lookback+horizon]) return np.array(X), np.array(y) # 对每个模态分别构造,再在特征维拼接 X_list, y_list = [], [] for k in range(train_modes.shape[0]): xk, yk = make_windows(train_modes[k], lookback=24, horizon=1) X_list.append(xk) y_list.append(yk) X = np.stack(X_list, axis=-1) # (N, lookback, K) y = y_list[0] # 各模态预测目标一致,取第一个即可 print('样本形状:', X.shape, '标签形状:', y.shape)参数说明:lookback=24表示用过去 24 个点预测下一个点,电力负荷日周期场景常用 24;horizon=1是单步预测,改大就是多步。X的最后一维是模态数 K,这样 Attention-LSTM 可以同时看到所有模态,也可以拆成 K 个独立模型分别训。源码走的是前者,把模态当特征通道,Attention 在时间步上做加权。
3. Attention-LSTM 模型搭建:从vmd_attention_lstm.py拆开看
3.1 LSTM 与 Attention 的分工
LSTM 负责在时间轴上传递记忆,三个门控决定哪些信息留、哪些丢。但标准 LSTM 的输出是最后一个时间步的隐状态,或者所有时间步隐状态的平均,这两种聚合方式对"哪些时间点更重要"是无差别的。Attention 补的就是这一环:给每个时间步的隐状态算一个权重,加权求和后再送全连接层。对负荷预测来说,早高峰和晚高峰的时间点权重自然会被抬高,凌晨平稳段权重压低,这比让 LSTM 自己硬记要高效。
源码里models/vmd_attention_lstm.py定义的就是这套结构。典型实现是:LSTM 输出所有时间步的 hidden state,接一个加性 Attention(Bahdanau 式)或点积 Attention,得到上下文向量,再过一个 Dense 输出预测值。下面给出与源码结构一致的最小实现:
import tensorflow as tf from tensorflow.keras import layers, Model class AttentionLayer(layers.Layer): def __init__(self, units): super().__init__() self.W = layers.Dense(units) # 对隐状态做变换 self.V = layers.Dense(1) # 打分 def call(self, hidden): # hidden: (batch, timesteps, units) score = self.V(tf.nn.tanh(self.W(hidden))) # (batch, timesteps, 1) weights = tf.nn.softmax(score, axis=1) # 时间步归一化 context = tf.reduce_sum(weights * hidden, axis=1) return context, weights def build_model(lookback, n_modes, lstm_units=64): inp = layers.Input(shape=(lookback, n_modes)) h = layers.LSTM(lstm_units, return_sequences=True)(inp) h = layers.Dropout(0.2)(h) ctx, w = AttentionLayer(lstm_units)(h) out = layers.Dense(1)(ctx) return Model(inp, out), w逻辑说明:return_sequences=True是 Attention 能工作的前提,必须保留每个时间步的隐状态。AttentionLayer里先做 tanh 变换再打分,softmax 在时间维归一化,context是加权和。Dropout(0.2)放在 LSTM 之后、Attention 之前,是源码里的做法,用来抑制过拟合。参数上lstm_units=64是常见起点,序列长、模态多可以加到 128,但要注意显存。
3.2 训练脚本val_models_train.py的关键配置
训练入口在val_models_train.py,它负责加载 npy、构造窗口、编译模型、跑 epoch、存 checkpoint。checkpoint 目录里my_model.ckpt.index和my_model.ckpt.data-00000-of-00001是 TensorFlow 的权重存档,.index存变量索引,.data存实际数值,恢复时两者要配套。
import numpy as np import tensorflow as tf from models.vmd_attention_lstm import build_model X = np.load('train_vmd_af.npy') # 若已构造窗口则直接读 y = np.load('train_label.npy') model, attn = build_model(lookback=X.shape[1], n_modes=X.shape[2]) model.compile(optimizer=tf.keras.optimizers.Adam(1e-3), loss='mse', metrics=['mae']) ckpt = tf.train.Checkpoint(model=model) manager = tf.train.CheckpointManager(ckpt, './checkpoint/my_model.ckpt', max_to_keep=3) history = model.fit(X, y, validation_split=0.2, epochs=100, batch_size=64, verbose=1, callbacks=[tf.keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)]) manager.save() print('最优 val_loss:', min(history.history['val_loss']))参数说明:Adam(1e-3)是时间序列回归的稳妥起点,loss 用 MSE 对应点预测。EarlyStopping(patience=10)防止过拟合,restore_best_weights=True保证存下来的是验证集最优权重,不是最后一个 epoch 的。batch_size=64对几千到几万样本都合适,样本少就降到 32。checkpoint 用CheckpointManager管理,max_to_keep=3只留最近三个,避免磁盘堆满。
注意:源码里 checkpoint 路径写的是相对路径,换机器跑之前先确认工作目录,否则会出现"训练完了但找不到权重"的翻车现场。恢复时用
ckpt.restore(manager.latest_checkpoint),再model就能直接推理。
3.3 验证与预测脚本val_models_pred.py
预测脚本负责加载 checkpoint、对测试集模态做前向、把各模态预测叠加重构回原始尺度。这一步最容易出错的是归一化逆变换:训练时对每个模态单独归一化,预测完必须用对应的均值和方差还原,否则叠加出来的曲线幅值完全不对。
import numpy as np import tensorflow as tf from models.vmd_attention_lstm import build_model X_test = np.load('test_vmd_af.npy') model, _ = build_model(lookback=X_test.shape[1], n_modes=X_test.shape[2]) ckpt = tf.train.Checkpoint(model=model) ckpt.restore(tf.train.latest_checkpoint('./checkpoint')) print('权重恢复自:', tf.train.latest_checkpoint('./checkpoint')) pred = model.predict(X_test) # (N, 1) # 逆归一化:用训练阶段保存的 scaler 参数逐模态还原 # 再按 VMD 重构公式把各模态预测相加,得到最终序列 final = pred.flatten() np.save('pred_hubei_set.npy', final)逻辑说明:tf.train.latest_checkpoint自动找最新存档,省得手写文件名。pred是归一化尺度下的预测,逆变换参数必须和训练时一致,建议把 scaler 的 mean/std 一起存进 npy,别只存模型。重构时如果模型是"模态当特征通道"的联合建模,输出已经是叠加后的结果;如果是分模态独立建模,才需要手动相加。源码走的是联合建模,所以pred_hubei_set.npy直接就是最终预测。
4. 避坑与排查:这套源码最容易翻车的五个地方
4.1 现象:测试集曲线整体平移,形状对但幅值差一截
原因:归一化和逆变换不匹配。训练时对每个模态单独算 mean/std,预测时用了全局 mean/std,或者干脆忘了逆变换。VMD 分解后各模态幅值差异很大,低频模态幅值可能是高频的几十倍,用错 scaler 直接导致重构曲线整体偏移。
解决:把训练阶段的 scaler 参数(每个模态的 mean、std)单独存成scaler.npz,预测脚本里严格按模态索引取用。逆变换公式是x = pred * std + mean,逐模态做,做完再叠加。
4.2 现象:验证 loss 很低,测试集一塌糊涂
原因:滑动窗口构造时 shuffle 了,或者训练/测试切分点选在了序列中间做随机划分。时间序列的样本之间有强自相关,随机划分会让相邻窗口分别落进训练和测试,等于变相泄漏。
解决:严格按时间顺序切,前 80% 训练、后 20% 测试,中间不留重叠。构造窗口时也不要在切分后再 shuffle,model.fit里的shuffle=True对时间序列要慎用,源码默认没开。
4.3 现象:VMD 分解报错或模态数对不上
原因:K设得比信号有效频率成分还多,或者输入序列太短。VMD 要求序列长度远大于 K,几百个点的序列硬拆 10 个模态,数值上会不收敛。
解决:序列长度至少是 K 的 20 倍以上。K 从 3 开始试,看各模态中心频率是否分离,如果两个模态中心频率挨得很近,说明 K 大了。alpha默认 2000,噪声大的序列可以加到 3000 到 5000。
4.4 现象:checkpoint 恢复失败,报 shape mismatch
原因:改了lstm_units或n_modes之后重新建模型,但恢复的是旧 checkpoint,权重形状对不上。或者 TensorFlow 版本差异导致变量命名变化。
解决:恢复前先确认模型结构和存档时一致,build_model的参数从配置文件读,别硬编码。换 TF 版本时建议重新训练,跨版本恢复 checkpoint 是玄学,能避就避。
4.5 现象:预测结果全是均值附近的平线
原因:Attention 权重塌缩,所有时间步权重接近均匀,模型退化成对输入的简单平均。常见于学习率太大、训练初期就过拟合,或者 LSTM 隐状态维度太小表达力不够。
解决:把学习率降到 5e-4 或 1e-4,lstm_units加到 128,Attention 前加 LayerNorm 稳定分布。训练时把 Attention 权重打出来看,正常应该是少数时间步权重明显高,如果是一条平线就是塌了。
5. 进阶用法:把 VMD-Attention-LSTM 用到自己的数据上
拿到这套源码,真正有价值的是把它迁移到自己的序列上。我的习惯是分三步走,先验证分解是否合理,再验证模型能否过拟合小样本,最后才上全量数据。
第一步,换数据后先单独跑 VMD,把 K 个模态画出来看。如果某个模态几乎是直线,说明 K 大了;如果所有模态都还在剧烈抖动,说明 K 小了。这一步不涉及模型,纯信号层面判断,能省掉后面大量无效训练。第二步,取前 500 个点做小样本过拟合测试,正常情况训练 loss 应该能降到接近 0,如果降不下去,问题在模型结构或学习率,不在数据量。第三步,全量训练时用EarlyStopping加ModelCheckpoint双保险,验证集 loss 连续 10 个 epoch 不降就停。
参数迁移上,lookback跟着数据的周期走:日周期数据用 24,周周期用 168,没有明显周期就从 12 开始试。horizon单步预测用 1,多步预测建议改成 seq2seq 结构,别硬把 horizon 调大后还用单输出 Dense。模态数 K 和lstm_units是一对需要联合调的参数,K 大时lstm_units也要相应加大,否则每个模态分到的表达容量不够。
验证方法上,除了 MSE、MAE,建议加一个方向准确率(预测涨跌方向对不对),单看 MSE 会被大幅值样本主导。把预测曲线和真实曲线叠在一起画出来,重点看突变点附近的表现,那才是模型真实能力的试金石。
最后说个我自己的教训:早期做这类分解+深度模型的项目,我总想一步到位把 K、alpha、lookback、units 全网格搜一遍,结果跑了两天两夜,最后发现最优组合还不如手动试出来的三组。从那以后我每次换数据集,都强制先跑一遍 VMD 可视化、再做小样本过拟合、最后才全量训练,三步不走完不开大网格。这套流程帮我省下的算力,比任何调参技巧都实在。希望帮到你。
本文还有配套的精品资源,点击获取