
简介这套EMD-LSTM时间序列预测Python实现包含完整源码与两份CSV数据文件面向计算机、电子信息工程、数学等专业学生适用于课程设计、期末大作业或毕业设计场景也适合深度学习初学者用作实战入门。代码基于TensorFlow编写在Anaconda和PyCharm环境下运行采用参数化编程与保姆级注释几乎一行一注释详细展示经验模态分解EMD与长短期记忆网络LSTM相结合的数据处理、训练及预测流程。资源包共三个文件其中一份可直接运行的Python脚本和两份csv格式数据集压缩包大小约四十七KB结构简单清晰下载后便于快速部署和修改参数。目前已有四百七十五人学习下载作者为资深算法工程师长期从事神经网络预测与信号处理方向代码思路清晰、注释细致方便读者理解与二次开发。通过这份资源读者可掌握从EMD分解、LSTM模型搭建到结果输出的完整流程获得可直接运行的工程基线为课程答辩或项目实践提供有力支撑。1. 先拆信号再进网络EMD-LSTM 到底在解决什么问题如果你尝试过把一条非线性、非平稳的时间序列直接丢给 LSTM 做预测大概率见过这样的结果训练集 loss 一路往下掉验证集曲线却明显滞后于真实值。问题多半不在 LSTM 本身而是输入序列的成分太杂。EMD-LSTM 的思路是先用经验模态分解Empirical Mode DecompositionEMD把原始序列拆成若干个本征模态函数IMF再用 LSTM 对每个分量分别建模预测最后求和重构。这套组合在电力负荷、风速、交通流量等波动剧烈的序列上被反复验证过精度往往优于直接端到端预测。本文给出的完整 Python 源码和数据组织方式能让你从分解、训练到重构一气呵成地跑通并绕开常见的边界效应和数据泄露问题。2. EMD 拆出 IMF、LSTM 抓时间依赖两段式预测为什么更稳2.1 EMD 分解出 IMF非线性非平稳信号是怎么被逐级拆开的经验模态分解的核心思想是把任意一条序列 x(t) 看作若干个本征模态函数IMF与一个残差项的叠加。它不需要预设基函数完全根据信号自身的极值点分布逐层剥离分量。这一点和傅里叶变换、小波分解有本质区别傅里叶变换要求信号平稳小波需要人为选择小波基和分解层数而 EMD 是数据自适应的这也是它被大量用于非线性、非平稳信号处理的原因。筛分sifting的过程分几步先找到整条序列上所有局部极大值点用三次样条插值连成上包络线再找到所有局部极小值点连成下包络线计算上下包络线的均值 m1(t)用原序列减去均值得到候选分量 h1(t)。此时要检查 h1(t) 是否满足 IMF 的两个条件一是极值点数目与过零点数目相等或最多相差一个二是上下包络均值局部为零。如果满足h1(t) 就是第一个 IMF如果不满足就对 h1(t) 继续做同样的筛分直到满足条件。随后把第一个 IMF 从原序列中剔除对残余序列重复上述过程直到残差单调或幅度足够小。实际运行 PyEMD 时会发现分解出的 IMF 数量并不固定主要取决于极值点分布和默认筛分次数上限。高频噪声多的序列第一个 IMF 经常就是噪声分量幅度小但波动密集。我一般会在分解后打印每个 IMF 的形状和方差占比先看一遍再决定保留还是剔除。PyEMD 库最常用的类有两个EMD 是标准经验模态分解CEEMDAN 是带自适应噪声的完全集合经验模态分解通过多次加入白噪声求平均来抑制模态混叠对端点效应更稳健但计算量明显增加。序列有几万点时建议先用普通 EMD 跑通逻辑再决定要不要换 CEEMDAN。这里有个容易混淆的细节PyEMD 是包名导入语句是from PyEMD import EMD大小写不能写错。装了包却 import 失败十有八九是把包名和类名搞混了。另外分解结果的行数就是分量个数不是固定值后续按分量循环训练时不要写死行数。2.2 LSTM 在单个 IMF 上做预测为什么选循环结构拆出来的 IMF 相对原始序列要平稳得多但依然是时间序列有短程自相关和周期波动。这时为什么不用 ARIMA 直接建模ARIMA 本质上是线性模型对 IMF 中幅度变化和周期漂移这类非线性模式拟合能力有限。LSTM 的门控机制能在一个滑动窗口内自动学习哪些历史信息需要记住、哪些可以丢弃比固定阶数的线性模型灵活得多。LSTM 单元内部有输入门、遗忘门和输出门。训练时模型通过反向传播学习这三组门的权重决定上一时刻的记忆状态保留多少、当前输入写入多少、输出多少。这个结构让它在遇到局部突变时不会清空全部记忆也能在长期没有有效信息的区段保持状态不衰减。相比普通 RNNLSTM 用门控绕开了梯度消失问题所以单变量序列预测里 LSTM 是最常用的循环结构。用 LSTM 做单变量预测时输入不是点对点而是滑动窗口把过去 lookback 个点作为输入预测下一个点。窗口长度既决定模型能看到的视野也决定训练样本数量。窗口过长会让模型关注过多无关历史窗口过短则学不到周期特征。我常用的策略是先计算序列自相关找到自相关系数降到 0.3 以下的滞后期把它作为 lookback 的参考值具体做法在第四章展开。2.3 原始序列直接预测 vs 先分解后预测差距从哪来直接用原始序列训练 LSTM 的问题在于趋势项、周期项和噪声混在一起量级差异可能很大。比如趋势项幅度是几百周期性波动幅度是几十噪声只有几。LSTM 在拟合时会把主要容量用在幅度最大的趋势项上周期项被当成次要特征噪声则被部分忽略结果预测曲线常常滞后于真实曲线。先分解后预测的结构让不同尺度的模式分到不同 IMF 中。趋势残差项接近于单调函数用一个很浅的 LSTM 甚至线性模型就能拟合高频 IMF 虽然噪声成分多、单点预测误差大但绝对幅度很小对最终重构的贡献有限中频 IMF 承载主要周期特征是 LSTM 最应该发挥作用的地方。把分量分开建模相当于让每个模型只解决自己擅长的问题。但「分解必定提升精度」并不成立。如果原始序列已经接近白噪声或者信噪比很低EMD 也提取不出可预测的结构这时两段式模型只会因误差累积变得更差。判断该不该用 EMD-LSTM最可靠的方式是拿自己的数据做一次对照实验也就是第三章讲完实现之后第六章再细说怎么比较。3. 用 Python 实现 EMD-LSTM完整源码、运行命令与关键参数3.1 安装依赖PyEMD、TensorFlow 与数据处理库先把依赖装齐。下面这几行命令会自动把 EMD 分解、LSTM 训练、数据读取和指标计算需要的库都装上。pip install PyEMD pip install tensorflow pip install pandas numpy scikit-learn matplotlibPyEMD 负责经验模态分解TensorFlow 负责 LSTM 训练pandas 负责读数据sklearn 提供 MinMaxScalermatplotlib 用来画预测对比图。注意 PyEMD 的包名是 PyEMD导入时是from PyEMD import EMD而不是from pyemd import emd很多人第一行代码就在这翻车。如果你的环境里 TensorFlow 版本较新Keras 已经作为内置模块集成直接from keras.models import Sequential通常可用如果报错就改成from tensorflow.keras.models import Sequential这两条路径在不同版本下常见。3.2 从原始序列到预测结果完整实现源码下面是完整实现按「读取数据 → EMD 分解 → 逐分量训练 LSTM → 滚动预测 → 求和重构」的顺序组织。import numpy as np import pandas as pd from PyEMD import EMD from sklearn.preprocessing import MinMaxScaler from keras.models import Sequential from keras.layers import LSTM, Dense, Dropout from keras.callbacks import EarlyStopping def read_series(path): # CSV 至少包含两列date 和 value # value 是待预测的单变量序列 df pd.read_csv(path, parse_dates[date]) df df.set_index(date).sort_index() return df[value].values.astype(float) def emd_decompose(series): # 返回二维数组 imfs # imfs.shape (分量个数, 序列长度) emd EMD() imfs emd.emd(series) return imfs def make_windows(data, lookback): 把一维序列转换成监督学习样本 X, y [], [] for i in range(len(data) - lookback): X.append(data[i:i lookback]) y.append(data[i lookback]) return np.array(X), np.array(y) def build_lstm(lookback): model Sequential() # 单变量输入所以 input_shape 为 (lookback, 1) model.add(LSTM(64, return_sequencesTrue, input_shape(lookback, 1))) model.add(Dropout(0.2)) model.add(LSTM(32, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizeradam, lossmse) return model def train_one_component(data, lookback, epochs, batch_size): 对单个 IMF 归一化、构造样本、训练 LSTM scaler MinMaxScaler() scaled scaler.fit_transform(data.reshape(-1, 1)).flatten() X, y make_windows(scaled, lookback) split int(len(X) * 0.8) X_train, X_val X[:split], X[split:] y_train, y_val y[:split], y[split:] X_train X_train.reshape((X_train.shape[0], lookback, 1)) X_val X_val.reshape((X_val.shape[0], lookback, 1)) model build_lstm(lookback) early_stop EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) model.fit( X_train, y_train, validation_data(X_val, y_val), epochsepochs, batch_sizebatch_size, callbacks[early_stop], verbose0 ) return model, scaler def predict_component_future(component, model, scaler, lookback, n_steps): 从序列末尾开始滚动预测未来 n_steps 个点 scaled scaler.transform(component.reshape(-1, 1)).flatten() seed scaled[-lookback:].reshape(1, lookback, 1) preds [] for _ in range(n_steps): pred model.predict(seed, verbose0)[0, 0] preds.append(pred) # 把新预测值挤进窗口尾部丢掉窗口最前一个点 seed np.append(seed[:, 1:, :], [[[pred]]], axis1) return scaler.inverse_transform(np.array(preds).reshape(-1, 1)).flatten() def main(data_path, lookback24, epochs80, batch_size64, n_steps30): series read_series(data_path) imfs emd_decompose(series) print(fEMD 分解得到 {imfs.shape[0]} 个分量) total_pred np.zeros(n_steps) for i in range(imfs.shape[0]): component imfs[i, :] model, scaler train_one_component( component, lookback, epochs, batch_size ) pred predict_component_future( component, model, scaler, lookback, n_steps ) total_pred pred return total_pred if __name__ __main__: result main(data.csv, lookback24, n_steps30) np.save(pred.npy, result)这段代码的流程是先读入单变量序列用 EMD 分解为若干 IMF然后对每个 IMF 独立归一化、构造成滑动窗口样本、训练一个双层 LSTM预测时用序列末尾的 lookback 个点作初始窗口逐步滚动预测到未来 n_steps 个点最后把所有分量的预测结果求和得到整体预测。三个最关键的参数值得留意。lookbackLSTM 看见的历史窗口长度。序列有明显周期时至少覆盖一个完整周期周期不明显时从 24 开始尝试。epochs / patienceEarlyStopping 的 patience10 表示验证集误差连续 10 轮不下降即停止训练restore_best_weightsTrue 会把权重回滚到验证集误差最低时防止过拟合。n_steps多步预测步数。滚动预测误差会累积n_steps 越大后半段预测越不可靠业务只需要一步时直接设 1误差会小得多。3.3 数据文件怎么组织CSV 格式、字段命名与预处理代码里假定 CSV 至少有两列一列叫 date一列叫 value。date 列要能被 pandas 解析建议用YYYY-MM-DD HH:MM:SS这类标准格式避免带时区、带中文月份等特殊格式。value 列必须是数值不能有 NaN 或字符串。如果原始数据有空值不要留在后续流程里先用 fillna 或前向填充处理。我不建议用纯序号替代日期列。时间序列预测里日期不仅是一个序号它决定样本之间的先后顺序也是最后做可视化对比的横轴。保留日期列完整调试时能随时对齐预测值与真实值省掉很多确认「这个点到底是哪一天」的麻烦。数据量不大时文件可以直接放在项目根目录命名为 data.csv数据量大到几十万行时建议改用 parquet 或 h5 格式读取速度差很多。3.4 为什么对每个 IMF 单独归一化代码里对每个 IMF 分别做了一次 MinMaxScaler这是有意设计。不同 IMF 的幅度差异可以达到几个数量级残差项范围可能是 0 到 10000而高频 IMF 的范围只有 -0.5 到 0.5。如果所有分量共用同一个归一化参数高频分量会被压缩成一个几乎不可分辨的常量LSTM 学到的是噪声。每个分量独立归一化、独立逆归一化重构求和时幅度自然对齐。这样做还有一个调试上的好处分量之间互不影响某个分量的训练失败了不会污染其他分量。我在调试时会把每个 IMF 的 min、max、方差打出来看一遍凡是方差占比低于 1% 的分量优先考虑在重构时丢弃或降低权重这个手法在第五章会讲到。4. 构造 EMD-LSTM 训练数据滑动窗口、时间切分与归一化顺序4.1 滑动窗口和 lookback 的选择样本数量与自相关之间的平衡时间序列预测的数据集构造方式和普通机器学习不一样。普通监督学习可以随机采样行样本时间序列必须保持时间顺序。最常用做法是用一个长度为 lookback 的窗口在序列上滑动窗口内的值作为输入特征窗口右侧紧邻的下一个值作为标签。窗口每向右滑动一个时间步就生成一个新样本。样本数量的公式是 N - lookback。序列长度 N 是 5000lookback 是 24能生成 4976 个样本lookback 增大到 100样本数变成 4900差别很小。但如果序列只有 300 点lookback 100 就只剩 200 个样本训练双层 LSTM 已经很吃紧。短序列上不要盲目加大窗口。选 lookback 的实用做法是看自相关。对序列计算不同滞后期的自相关系数找到自相关系数下降到 0.3 以下的那个滞后期把它作为 lookback 的参考值。这个方法比凭经验设 24、48 更可靠因为它量化了「多久以前的历史对当前还有预测力」。业务上有明确周期时比如日度数据有周度周期就把 lookback 设为 7再根据自相关结果往上微调。4.2 训练集、验证集切分时间顺序不能乱把 sklearn 的 train_test_split 直接拿来做时间序列切分是很容易犯的错误。默认参数下它会随机打乱样本这在大数据分类任务里没问题但在时间序列里是数据泄露。随机打乱后验证集里混入了训练集之后才出现的时间段模型相当于看见了未来验证集误差会异常低于真实水平。一旦投入实际预测误差立刻回到正常甚至更差。正确做法是按时间顺序切分前 80% 做训练、后 20% 做验证。更严格的方式是时间序列交叉验证把数据按时间顺序分成 k 段用前 i 段训练、第 i1 段验证i 从 1 增加到 k-1最终取各轮验证误差的均值。这样能看到模型在多个时间段上的稳定性而不是只赌一次切分。这里有一个容易被忽略的细节切分顺序与归一化的先后。正确的顺序是先切分再在训练集上 fit scaler然后用训练集的 scaler 转换验证集。如果先在整个序列上 fit scalermin/max 已经包含验证集信息同样是信息泄露。代码里用 split int(len(X) * 0.8) 手工切分而不是用 validation_split就是不想让 Keras 内部用随机方式选验证集。训练阶段的 shuffle 参数设置为 True 没问题它只在训练时打乱样本顺序不改变样本本身的时间语义但构造数据集阶段绝不能打乱顺序。4.3 归一化放在分解前还是分解后顺序不同结果会有差别。EMD 基于极值点包络如果先对整条序列做 MinMaxScaler序列形态不变、极值点位置不变分解出的 IMF 结构基本不受影响。但有一个例外序列里有极端大值时MinMaxScaler 会把其他所有值压缩到很小的区间噪声分量的相对幅度被放大分解结果不稳定。所以更稳妥的做法是先分解、后归一化也就是上一章代码里写的顺序。这样做还有一个额外好处每个 IMF 独立归一化后数值范围一致LSTM 的收敛速度和稳定性都更好。需要记住的原则是归一化参数永远只能从训练集统计验证集只调用 transform不能重新 fit。5. EMD-LSTM 常见问题与避坑5 个真实翻车点5.1 现象预测起点滞后前几个点严重偏离原因EMD 的边界效应。序列两端极值点不完整三次样条包络在端点附近会漂移端点处的 IMF 值不准确。预测的起点恰恰在序列末端正好落在受影响区间起点偏离是必然的。解决分解前做端点延拓。PyEMD 没有直接暴露延拓参数常见做法是用镜像延拓把序列两端各补一段分解后再裁剪掉多余部分。另一个折中方案是改用 CEEMDAN它对端点效应比普通 EMD 稳健但要额外忍受数倍的计算时间。我一般先看起点偏差有多大如果只影响前 3 到 5 个点业务能接受就直接用如果偏差影响整个预测窗口就必须处理。实际排查时把分解后的第一个 IMF 和原始序列末端画在同一张图上能立刻看出端点漂移有多严重。5.2 现象验证集误差很低实际预测误差翻倍原因数据泄露。两类最常见的情况一是先在全序列上做归一化再切分min/max 包含验证集信息二是构造数据集时用了随机打乱或 random split。两种情况都让模型在验证时「作弊」误差自然好看。解决严格按时间顺序先切分再在训练集上 fit scaler验证集只 transform。构造窗口样本时保证时间顺序训练阶段才允许打乱。如果验证集误差比真实回测误差低很多第一反应不是窃喜而是检查有没有泄露。检查时最简单的方法是把验证集误差和训练集误差画出来两者差距过大就优先怀疑归一化参数或者切分逻辑出问题了。5.3 现象重构结果和真实值幅度对不上原因某个或某几个 IMF 的预测结果偏离真实幅度。最常见的诱因是高频 IMF 方差极小归一化后有效信息接近数值精度下限LSTM 实际上在拟合噪声。解决分解后打印每个 IMF 的方差占比。如果某个高频 IMF 方差低于总方差 1%重构时直接丢弃它或者用它的均值代替预测结果。丢弃噪声分量对整体预测精度的提升往往比保留它更好。这个手法在工程里很常见不要觉得少用一个分量是在偷工减料。还有一种情况是残差项幅度极大预测出现整体偏移这时只需要把残差项当作一条单调曲线用最后一个历史值做水平外推比用 LSTM 预测更稳。5.4 现象EMD 分解结果不稳定每次运行 IMF 数量或顺序不同原因普通 EMD 本身是确定性的但 CEEMDAN 会加入随机白噪声不设随机种子时结果就不可复现。另一个原因是数据量太少、极值点过稀包络线不稳定。解决CEEMDAN 必须设置随机种子PyEMD 的接口里对应参数是 random_state。对极值点少于二十个的短序列先做平滑预处理或直接放弃 EMD-LSTM改用纯 LSTM。可复现性是工程上线的前提种子固定能省掉大量排查时间。跑实验时把分解出的 IMF 数量也记录下来如果同一个数据集在不同随机种子下分解结果差异很大说明数据本身不适合 EMD。5.5 现象训练 loss 下降验证 loss 不动或震荡原因学习率偏大或网络结构偏深。IMF 分量相对原始序列平滑信息量有限双层 LSTM 前几层很容易学不到有效特征只剩后层在拟合验证集自然不稳定。小数据集上这个问题尤其突出样本量越少越明显。解决把双层 LSTM 改成单层隐藏单元数从 64 降到 32学习率从默认的 0.001 降到 0.0005。对高频 IMF 可以进一步缩小 lookback因为噪声分量对未来信息的依赖弱长窗口只会引入无关历史。按照分量的频率从高到低逐步调整参数是调优这套模型最直观的路径。如果调整后验证 loss 仍然震荡检查是不是归一化参数在验证集上被重新 fit 了这个原因比学习率更常见。6. 验证 EMD-LSTM 预测效果基线对比与三个评估指标6.1 三种基线同台ARIMA、原始 LSTM、EMD-LSTM判断 EMD-LSTM 值不值得上线不能只看它自己的误差曲线。我会同时跑三个模型做对照ARIMA 用来判断序列是否有足够强的线性自相关结构原始 LSTM 用来判断深度学习是否必要EMD-LSTM 用来判断分解是否带来真实增益。三者的对比关系大致如下。对比结论判断ARIMA 优于原始 LSTM序列偏平稳线性深度学习收益低不建议上 EMD-LSTM原始 LSTM 与 EMD-LSTM 接近序列多尺度结构不明显两段式复杂度是浪费EMD-LSTM 显著更优序列含明显多尺度成分分解引入真实增益这个对照实验的成本不高把同一份数据分别跑三个模型比较同一时间段上的验证误差即可。一顿饭的时间换一个明确的架构决定很划算。6.2 回放测试与指标取舍RMSE、MAE、MAPE验证多步预测效果我习惯做回放测试把训练好的模型拿到训练序列的最后一段用真实历史值作为种子预测未来 n_steps 个点再和这段时间的真实值比较。这样能在模型上线前就看到滚动预测的真实水平。# 假设 series_all 是完整序列train_end 是训练段终点 # 把训练段末尾的 lookback 个真实值作为种子预测未来 n_steps 点 seed np.array(series_all[train_end - lookback:train_end]) pred [] for _ in range(n_steps): p model.predict(seed.reshape(1, lookback, 1), verbose0)[0, 0] pred.append(p) seed np.roll(seed, -1) seed[-1] p true series_all[train_end:train_end n_steps]评估指标不能只看一个。RMSE 对极端值敏感MAE 反映平均偏离程度MAPE 反映相对误差。三个指标一起看能避免被单一指标误导。序列值接近零时 MAPE 会变得很大甚至无穷这时改用 sMAPE 或 MASE。滚动预测还要把前段和后段误差分开统计如果误差从第 10 步开始急剧增大说明模型内在误差累积过快业务预测窗口就该收缩到 10 步以内。我现在每个模型上线前都会拿最近三个时间窗做回放测试误差曲线不稳定就不上。这个习惯帮我挡掉了不止一次看起来很美的实验结果希望帮到你。本文还有配套的精品资源点击获取