
简介这是一份面向Python开发者的EEMD-LSTM时间序列预测完整源码与数据集项目将集成经验模态分解和长短时记忆网络相结合可有效处理非平稳、非线性时间序列数据适用于风速、负荷、交通流量等预测场景。压缩包内共3个文件包括1个主程序文件以及2个csv格式的数据文件整体大小仅47KB结构精简但完整。代码采用参数化编程几乎每一行都配有注释清晰展示数据读取、EEMD分解、LSTM模型构建、训练与预测全过程方便小白对照学习环境基于Anaconda、PyCharm和TensorFlow搭建修改参数或替换数据即可迁移到其他实际任务。该资源适合计算机、电子信息、数学等专业学生用于课程设计、期末大作业或毕业设计也适合入门深度学习时序建模的读者目前已有383人学习下载轻量而实用具有很好的参考与扩展价值。1. 为什么 EEMD-LSTM 会比单一 LSTM 更能打拿焦作.csv 这种以小时为粒度的监测序列直接喂给 LSTM收敛慢预测到突变点时基本是跟着上一时刻的值平移峰值明显滞后。原因是趋势、周期、随机扰动三种成分叠在一起门控单元难以同时拟合多尺度信息梯度反而被高频抖动干扰。EEMD 先把序列拆成从高频到低频的多个 IMF 和一个残差趋势项每个分量尺度相对单一再交给 LSTM 逐个建模最后把各分量预测叠加还原。这套分解-预测-重构结构在时间序列预测里很通用适合课程设计、期末大作业和毕业设计负荷预测、流量预测都能复用同一套代码骨架。源码包里除了 EEMD-LSTM.py 还有两份 CSV 数据。入门的人看逐行注释就能跑通有经验的人重点盯分解参数、窗口构造和重构对齐三处这套方案最容易被改坏的地方全在这。2. EEMD 分解原理与 PyEMD 实战先把非平稳序列拆干净2.1 模态混叠是怎么来的EEMD 为什么有效EMD 的核心操作是迭代地寻找上下包络、取包络均值、从原信号中筛出本征模态函数 IMF分解结果天然按频率从高到低排列。但原始 EMD 有一个被反复吐槽的缺陷模态混叠。一个 IMF 里同时出现时间尺度相差很大的成分或者同一个尺度被切碎散落在多个 IMF 里根源是原始信号里存在间歇性扰动包络拟合被局部突变带偏。EEMD 的思路很直接单次分解对扰动敏感那就人为加白噪声重复分解再平均。白噪声频谱在整个频带均匀分布相当于给间歇性成分一个均匀的底座多次试验后噪声在平均中相互抵消剩下的是稳定的模态。正是这一步让 EEMD 在非平稳时间序列预测中大量替代了普通 EMD也是这个源码把 EEMD 放在 LSTM 前面的原因。两个核心参数决定分解质量。trials 是集成次数也就是加噪后重复做 EMD 的次数常见取值 50~200次数太低噪声消不干净每次运行得到的 IMF 都不一样。noise_width 是白噪声标准差与原始信号标准差的比值取 0.05~0.2 比较常见太小起不到抗混叠作用太大会把白噪声残留在分解结果里。具体影响看下面这张表参数常见范围偏小的后果偏大的后果trials50~200模态不稳定结果不可复现训练耗时成倍增长收益递减noise_width0.05~0.2抗混叠效果不足IMF 纯度变差白噪声残留在 IMF 中重构误差变大2.2 PyEMD 安装与核心分解代码环境是 Anaconda PyCharm Python TensorFlow。装 PyEMD 时有个坑pip install pyemd装的是另一个完全不同的库正确包名是EMD-signal引入路径才是PyEMD。安装命令pip install EMD-signal下面这段就是把焦作.csv 拆成 IMF 的完整逻辑# -*- coding: utf-8 -*- import numpy as np import pandas as pd from PyEMD import EEMD # 读取数据焦作.csv 第一列是时间第二列是要预测的监测值 df pd.read_csv(焦作.csv, encodingutf-8) series df.iloc[:, 1].values.astype(float) # 构造 EEMD 分解器 eemd EEMD(trials100, noise_width0.08) # trials100做 100 次加噪 EMD 再平均模态更稳定 # noise_width0.08白噪声幅度取原信号标准差的 8% imfs eemd.eemd(series) # 返回二维矩阵形状为 (n_imf, len(series)) # 最后一行是残差趋势项不是严格意义上的 IMF np.save(imfs.npy, imfs) print(IMFs shape:, imfs.shape)读数据时建议先把 DataFrame 的列名打出来确认目标变量在第几列有些 CSV 的列顺序和预期不一致会直接导致分解结果变成垃圾。eemd()返回的二维数组第一维是模态数量第二维和原始序列等长最后一行是残差。把残差也当 IMF 送进 LSTM 不是不行但残差接近单调趋势用 LSTM 建模收益很低后面会专门说怎么处理。2.3 IMF 筛选不是每个分量都值得建模分解完先别急着训练。把每个 IMF 的方差贡献率算一遍能量占比极低的高频分量大多是噪声主导预测它们既增加训练时间又把抖动误差叠加进最终重构结果。我一般先筛掉贡献率低于 1% 的分量# 计算每个 IMF 的方差贡献率 energy np.var(imfs, axis1) ratio energy / energy.sum() keep_idx [k for k, r in enumerate(ratio) if r 0.01] imfs_used imfs[keep_idx] print(保留的 IMF 序号:, keep_idx)筛选之后残差单独处理。常见做法是残差用一阶差分加线性外推或者也走一遍 LSTM如果训练集只有几百条直接忽略残差只保留能量高的几个 IMF 也能接受代价是趋势段的预测会略微偏移。先算方差贡献率再决定保留哪些分量比凭眼睛看波形靠谱得多。3. LSTM 预测器设计与 TensorFlow 参数细节3.1 为什么分解之后仍然选 LSTM而不是 ARIMA分解把多尺度混叠拆开了但每个 IMF 仍然是非平稳的调幅振荡。高频 IMF 的方差随时间变化低频 IMF 的周期也在缓慢漂移ARIMA 这类线性模型对调幅振荡的适应性很差。LSTM 的遗忘门在每个时间步决定保留多少历史信息对幅值在变、局部有规律的信号拟合效率比纯线性模型高。在 EEMD-LSTM 源码里每个保留的 IMF 对应一个结构完全相同的 LSTM 模型只是输入输出的尺度不同。这种同构模型、独立训练的设计优点是训练快、可以顺序跑不抢显存也不存在多任务学习里常见的梯度相互干扰。每个模型学到的只是自己那个频段的时间依赖最后相加时互不污染。3.2 滑动窗口构造与归一化的顺序LSTM 的输入是一个固定长度的窗口窗口长度 lookback 决定模型能看到多长的历史。小时级数据取 12 或 24能覆盖半天到一天的周期如果是低频 IMF可以适当加大。构造窗口的代码def make_windows(data, lookback): X, y [], [] for i in range(lookback, len(data)): X.append(data[i - lookback:i]) # 前 lookback 个点作为特征 y.append(data[i]) # 当前点作为标签 return np.array(X), np.array(y)归一化放在建窗之前并且分 IMF 单独做。每个 IMF 的幅值差异很大共用一个 MinMaxScaler 会让高频分量在数值上被压制。更关键的是fit只能用训练段数据from sklearn.preprocessing import MinMaxScaler split 2400 # 训练/测试切分点按行索引 scaler MinMaxScaler() scaler.fit(imf[:split].reshape(-1, 1)) # 只用训练段算 min/max scaled scaler.transform(imf.reshape(-1, 1)) # 再变换整段序列 X, y make_windows(scaled.flatten(), lookback)注意fit和transform的对象不能反过来。先在全序列上 fit 再切训练测试等于把测试集的数值范围提前泄露给了 scaler测试误差会被系统性低估。这种顺序问题在时间序列预测的复现里是最常见的失分点代码能跑、损失在降但实验结论不成立。3.3 模型结构与参数速查表每个 IMF 的模型用两层 LSTM 加 dropout第一层返回完整序列给第二层第二层只输出最后一个时间步的特征最后接 Dense(1) 输出预测值from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def build_lstm(lookback): model Sequential([ LSTM(64, activationtanh, return_sequencesTrue, input_shape(lookback, 1)), Dropout(0.2), LSTM(32, activationtanh, return_sequencesFalse), Dropout(0.2), Dense(1) ]) model.compile(optimizeradam, lossmse, metrics[mae]) return model第一层return_sequencesTrue是为了把每个时间步的隐状态都传给第二层第二层return_sequencesFalse只保留最后一步正好对应预测目标。激活函数保持 tanh 默认LSTM 的循环计算对 tanh 的输出范围依赖很强改成 relu 容易出现数值爆炸。参数推荐值调整方向lookback12数据周期明显时取 24 或 48第一层单元数64训练量小于 500 条时降到 32第二层单元数32跟随第一层同比例缩放dropout0.2过拟合时提到 0.3~0.5batch_size16序列长、显存够时提到 32epochs30~50配早停不要硬编码固定值优化器学习率adam 默认 1e-3不收敛时降到 5e-4注意每个 IMF 训练前都要重新初始化权重不能复用上一个模态训练好的模型。不同 IMF 的统计特性差异很大复用权重会把低频分量学到的时间依赖错误地带到高频分量上。4. 焦作数据集的完整训练流程分解、预测、重构4.1 两份 CSV 的分工与数据预处理压缩包里给了焦作.csv 和焦作全.csv 两份数据。常见分工是全量文件用于 EEMD 分解短文件用于训练和测试。EEMD 存在端点效应序列越短两端越不稳定用全量序列做分解能明显减轻这个问题分解完成后按训练集的行范围截取对应的 IMF 片段再训练。预处理先做缺失值和时间对齐。CSV 数据里偶尔有空值和重复时间戳稳妥的做法是按时间列排序再用前后两个有效值的线性插值填充空缺df df.sort_values(time).reset_index(dropTrue) df.iloc[:, 1] df.iloc[:, 1].interpolate(methodlinear)interpolate默认就是线性插值对监测类数据足够。不要用填充常数的方式补缺失那会引入人为的阶梯LSTM 会把阶梯当成真实的趋势去学习预测段会出现台阶状误差。4.2 分解—训练—预测主循环把前面的逻辑串起来就是 EEMD-LSTM.py 的主循环。对每个保留的 IMF 独立完成归一化、建窗、训练、预测最后把预测结果叠加lookback 12 pred_parts [] for k, imf in enumerate(imfs_used): # 每个 IMF 单独归一化避免幅值差异互相压制 scaler MinMaxScaler() scaler.fit(imf[:split].reshape(-1, 1)) scaled scaler.transform(imf.reshape(-1, 1)).flatten() X, y make_windows(scaled, lookback) # X[i] 用区间 [i-lookback, i-1] 预测第 i 个时刻 X_train, y_train X[:split - lookback], y[:split - lookback] X_test, y_test X[split - lookback:], y[split - lookback:] model build_lstm(lookback) model.fit(X_train, y_train, epochs40, batch_size16, validation_split0.1, verbose0) model.save(flstm_imf_{k}.h5) # 每个 IMF 的模型单独存盘 pred_scaled model.predict(X_test) pred scaler.inverse_transform(pred_scaled).flatten() pred_parts.append(pred) print(fIMF {k}: 训练完成) # 重构各分量预测值直接相加 final_pred np.sum(pred_parts, axis0) y_true y[split - lookback:]注意切分索引。make_windows返回的 X[i] 对应的标签是 y[i]也就是第 i 个时刻的真实值。训练段取到split - lookback之前保证标签全部落在训练区间内测试段从split - lookback开始第一个测试窗口恰好使用训练段末尾的 lookback 个点这是一步预测的正常用法不算数据泄露。4.3 重构结果的误差评估叠加完成后用三个指标衡量RMSE 对大幅误差敏感MAE 反映平均偏差R² 看整体拟合度from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score rmse float(np.sqrt(mean_squared_error(y_true, final_pred))) mae float(mean_absolute_error(y_true, final_pred)) r2 float(r2_score(y_true, final_pred)) print(fRMSE{rmse:.4f} MAE{mae:.4f} R2{r2:.4f})在焦作这类监测数据上EEMD-LSTM 相对单一 LSTM 的改进主要体现在 RMSE 上因为大幅度的趋势项被单独建模峰值位置的误差比单一模型小。常见结果大致落在下面这个区间具体数值随数据长度和划分方式浮动方案RMSE 相对变化MAE 相对变化说明单一 LSTM基准基准峰值滞后约 1~2 步EEMD-LSTM 保留全部 IMF下降 15%~30%下降 10%~25%模型数量多训练时间长EEMD-LSTM 筛掉低能量 IMF与上一行接近略降训练时间明显减少如果跑出来的改进不明显先检查是不是跳过了 IMF 筛选。残差项的预测误差如果很大叠加时会污染所有高频分量的成果这是复现 EEMD-LSTM 效果不佳最常见的原因。提示分解步骤耗时且带随机性imfs_used存成 .npy 后在调模型参数阶段就不用重新分解了。5. 调参技巧与三种验证方法调参时先动分解参数还是模型参数我的答案很明确先动分解。下面三个验证手段能帮你在动手训练之前就把问题定位到具体环节。5.1 分解参数的两个坑noise_width 取到 0.2 以上时白噪声会残留在 IMF 里分解结果看起来平滑但重构回去和原序列的误差变大。我判断噪声是否残留的方法是把全部 IMF 加回来看重构误差如果和原序列的 RMSE 超过原序列标准差的 2%说明噪声占比偏高把 noise_width 往 0.05~0.1 调。trials 低于 50 时两次运行的 IMF 数量可能不一致结果不可复现正式实验用 100 起步时间允许就上 200。5.2 验证一重构残差检查分解是否彻底分解完先做一次重构验证再谈模型reconstruct np.sum(imfs_used, axis0) residual series[:len(reconstruct)] - reconstruct print(重构 RMSE:, np.sqrt(np.mean(residual ** 2)))如果重构误差已经很大后面所有预测分析都没有意义问题一定出在分解阶段而不是 LSTM。这一步 10 秒跑完能省下大量排查模型的时间。5.3 验证二单步预测的逐点稳定性模型跑通后把每个 IMF 的预测按时间画子图观察高频 IMF 的预测线是围绕真实线小幅抖动还是整体偏移。如果某个 IMF 的误差远大于其他 IMF单独对这个分量加大 lookback 或降低学习率重训不要动全局参数。高频分量对 lookback 更敏感低频分量对学习率更敏感这个规律在分解粒度越细时越明显。5.4 验证三固定随机种子做可复现对比对比 EEMD-LSTM 和单一 LSTM 时先用tf.random.set_seed(42)固定种子再跑三次取平均。LSTM 初始化是随机的不加种子得到的对比结论很可能是噪声。另外把每个分量的预测结果存成单独一列 CSV最后对比单个分量的 RMSE能快速定位误差集中在哪个频率段再针对性调整那个分量对应的模型参数。EEMD-LSTM 的调参优先级是分解参数先于模型参数IMF 筛选先于层数堆叠。多数项目的问题不是 LSTM 不够深而是分解质量不够干净。本文还有配套的精品资源点击获取