ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

EEMD-LSTM时间序列预测:从非平稳序列分解到多模型LSTM实战

2026/9/28 22:28:49 拓冰建站 浏览量
EEMD-LSTM时间序列预测:从非平稳序列分解到多模型LSTM实战 简介面向时间序列预测任务提供了完整的经验模态分解与长短期记忆网络EEMD-LSTM实现方案并附带可直接运行的数据集适合计算机、电子信息、数学等专业学生用于课程设计、期末大作业或毕业设计也适合刚接触深度学习的时间序列入门者。方案基于Anaconda、PyCharm与TensorFlow搭建覆盖经验模态分解和长短期记忆网络相结合进行预测的主要流程。压缩包内共有三个文件其中一个为Python脚本文件另外两个为CSV格式的数据文件代码与数据分离存放整体大小约47KB非常轻量。目前已有三百八十三人学习过。源码采用参数化编程几乎每一行都配有注释从数据读取、经验模态分解、长短期记忆网络建模、训练一直到预测输出整个链条清晰易懂各参数均支持灵活调整便于替换为自己的数据后快速复现实验是完成课程任务或初探时间序列预测的实用参考。1. EEMD-LSTM是什么为什么把序列拆开再预测反而更准直接用LSTM预测一条带趋势、周期和毛刺的非平稳序列调参调到头会发现效果像玄学lookback设短了抖动设长了滞后。EEMD-LSTM的做法是先做集合经验模态分解把原始序列拆成若干个本征模态函数IMF和一条残差再用LSTM对每个分量分别建模型最后把预测值叠加还原成最终结果。拆开以后每个分量平稳得多LSTM学的是“下一步相对当前怎么变化”而不是硬背整条波形。这套方案适合电力负荷、设备寿命预测、量化行情这类非平稳场景对会用Python基础库、想提升单变量时间序列预测准确率的开发者尤其实用。拿到源码和数据第一件事不是急着跑通模型而是把原始序列画出来看清趋势、周期、突变点分布后面所有决策都围绕这些特征展开。2. 从序列到IMFEEMD分解的原理与Python库选型2.1 EMD/EEMD到底在做什么为什么对LSTM友好经验模态分解EMD的核心假设是任何复杂时间序列都能看成若干本征模态函数IMF的叠加。一个合格的IMF要满足两个条件极值点数量和过零点数量相等或差1上下包络的均值趋近于0。分解过程用筛选法先找到序列全部局部极值用三次样条分别拟合上包络和下包络取包络均值从原始序列里减去这个均值再判断剩余部分是否满足IMF条件不满足就重复以上步骤直到残差变成接近单调的趋势项。这个筛选过程完全不依赖傅里叶基函数也不需要预设周期所以对非线性、非平稳序列特别有效。但EMD有个硬伤叫模态混叠一个IMF里同时混着不同频率尺度的成分比如一段低频趋势中间夹着几个高频毛刺筛选过程难以把两者分开。EEMD的解决思路很粗暴但有效每次分解前给原始序列加上白噪声重复多次后再对结果取平均。白噪声的频谱分布均匀会被筛选过程“摊”到各个IMF里而白噪声是随机的重复足够多次后平均值互相抵消真实信号分量保留下来模态混叠被明显压制。对时间序列预测来说EEMD带来的最大好处是让每个分量的动态范围变窄、波动模式变单纯。直接用LSTM拟合原始序列时模型需要在同一组权重里同时兼容秒级波动和小时级趋势梯度很容易被高频分量牵着走最后学成一个“复制上一时刻值”的平庸映射。拆开后每个LSTM面对的是一个相对平稳的窄带信号输入到输出的映射更简单收敛更快测试误差也更小。在设备寿命预测这种场景里振动信号经常是趋势项上叠加周期性冲击EEMD分解后冲击分量和退化趋势分开建模效果往往比直接上LSTM稳定得多。2.2 PyEMD库的最小复现代码安装、调用与结果检查先说环境。PyPI上的包名是EMD-signal但导入名是PyEMD很多人在python安装教程之外第一次遇到“安装包名和import名不一样”的情况。安装命令是pip install EMD-signal装完后用一套模拟信号把分解流程跑通。下面的代码构造一条“趋势正弦周期白噪声”信号正好对应EEMD能处理的典型形态# 先单独验证EEMD分解再进入LSTM阶段 import numpy as np from PyEMD import EEMD # 模拟信号线性趋势 5Hz正弦 白噪声 t np.linspace(0, 1, 500) signal 2.0 * t np.sin(2 * np.pi * 5 * t) 0.2 * np.random.randn(500) # trials加入白噪声后重复分解的次数建议50~200 # noise_width白噪声标准差与原始序列标准差的比例常见0.01~0.1 eemd EEMD(trials100, noise_width0.05) imfs eemd.eemd(signal) # imfs的shape是(分量个数, 序列长度)最后一行是残差 print(分解得到的分量数量:, imfs.shape[0]) print(每个分量的长度:, imfs.shape[1])eemd.eemd()返回一个二维数组每一行是一个分量顺序按频率从高到低排列最后一行是残差项。trials控制平均次数太小则白噪声抵消不充分分量不稳定太大则计算时间线性上升。noise_width是噪声强度太小时无法压制模态混叠太大又会把低频分量打散导致IMF数量膨胀。我的习惯是先固定trials100、noise_width0.05看分量数量是否落在5到12之间不对再调。分解完成后一定要把每个IMF画出来检查不要直接丢给LSTM。画图时观察三点靠前的分量频率高、过零点多靠后的分量频率低、形态平滑残差应该单调或接近直线。如果残差是锯齿状说明还有周期成分没被抽出来。这是整个项目里最容易被跳过的一步也是后面LSTM效果翻车的根源之一。2.3 如何判断分解质量模态混叠、端点效应与IMF数量判断分解质量有三个关键点对应三个常见问题。模态混叠如果某个IMF看起来“自带包络”也就是局部幅值忽大忽小、波形不是一个稳定频率说明它仍然混着多个尺度。处理办法是提高trials或者把noise_width调大一点再试。如果某条IMF明显成对出现比如两条波形几乎一样只是相位差一点这是EMD的常见“伪分量”现象可以考虑合并减少后续LSTM模型数量。端点效应三次样条拟合包络时序列两端缺少数据约束包络线容易发散导致IMF两端翘起或塌陷。EEMD比EMD轻一些但预测末尾那段仍然会受影响。常见做法是镜像延拓或者更省事分解完成后裁掉两端各20到50个点只保留中间可信段。如果测试集正好落在序列末端要注意对比真实值时跳过最后几个点否则误差会被端点效应主导。IMF数量常规EEMD分解出一份正常序列分量数量在5到12个之间。如果出现30个以上优先怀疑数据质量比如存在缺失值、跳变、离群点而不是急着调模型如果只有2到3个说明序列太平滑LSTM不一定比简单外推有优势。分量数量直接决定后面要训练多少个LSTM模型8个分量就8个模型训练时间大约翻8倍。对实时性要求高的场景常见做法是只对前几个高频IMF用LSTM残差用线性外推或直接取最后一个值牺牲一点精度换速度。完整复现源码数据时还是建议全部分量都走LSTM预测稳定性更好。3. 为每个分量单独建模LSTM网络结构与超参数设定3.1 为什么选多模型分治而不是把所有IMF拼成一个多变量LSTM拿到EEMD分解结果后很自然会想到把所有IMF拼成多变量输入喂给一个LSTM让网络自己学分量之间的关系。这个思路不是不行但在工程上缺点明显不同IMF的动态范围差几个数量级高频分量梯度强悍会主导整个网络的权重更新低频分量的特征很难被学到而且测试阶段做多步预测时需要同时给出所有分量在下一时刻的真实值部署非常麻烦。所以常见做法是“多模型分治”每个IMF单独建一个LSTM训练结束后把各分量预测值叠加成最终结果。这样有几个实际好处每个模型的结构可以独立调高频分量用短窗口、低频分量用长窗口某个分量训练崩了不影响其他分量调试时也可以单独看哪个分量的误差贡献最大。代价是要维护好几个模型和对应的标准化器代码里多一层循环。这套方案的本质是用“分而治之”降低每个子任务的难度。原始序列里既有慢变趋势又有快速波动单个LSTM学的是一个复杂映射分解后每个LSTM只需要拟合一条相对平稳的窄带信号模型容量可以更小训练更容易收敛测试集上的泛化也更稳。3.2 Keras建模最小代码单变量LSTM模板与滑窗构造单个IMF的建模逻辑很简单用滑窗把一维序列变成监督学习样本前lookback个时间步预测第lookback1步。模型就用一层LSTM接一层全连接不堆太多层分量数据量一般不大深网络容易过拟合。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from tensorflow.keras.callbacks import EarlyStopping def make_lstm_model(lookback, lstm_units64): # input_shape(lookback, 1)每个时间步只输入一个数值 model Sequential([ LSTM(lstm_units, activationtanh, input_shape(lookback, 1)), Dense(1) ]) model.compile(optimizeradam, lossmse) return model def make_samples(data, lookback): # data是一维IMF序列返回X和y 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) # 假设imf_train_scaled是某个IMF归一化后的训练段 X, y make_samples(imf_train_scaled, lookback12) X X.reshape((-1, X.shape[1], 1)) # 变成(样本数, 时间步, 特征数) model make_lstm_model(lookback12) early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) model.fit(X, y, validation_split0.1, epochs80, batch_size32, callbacks[early_stop], verbose0)make_samples用循环构造样本数据量大的时候可以改用np.lib.stride_tricks.sliding_window_view提速但循环版本可读性好适合移植改参数。模型输出层只有1个节点因为目标值是下一个时刻的分量值。EarlyStopping监控验证集损失patience10表示连续10轮不下降就停并回滚到最优权重防止过拟合。参数推荐起点调整方向lookback12~48分量波动快取小波动慢取大lstm_units32~64数据量小取32样本多可加大到128batch_size16~64样本少用16样本多可用64epochs50~100配合EarlyStopping不必纠结上限如果机器是CPU训练lstm_units不建议超过64否则每个分量都要等很久GPU环境下可以适当加大到128但分量模型的增益通常不大不如把算力花在调lookback上。3.3 归一化与反归一化每个IMF必须单独缩放这是EEMD-LSTM最容易踩坑的一步。很多免费python源码大里的LSTM模型代码只对原始序列做了一个全局MinMaxScaler然后直接喂给网络。放到EEMD框架下这样做会出两个问题一是低频IMF本来幅度很大和所有分量一起缩放后动态范围被压到很窄的区间LSTM学不到它的起伏二是如果scaler是在整段序列上fit的测试集的min和max信息已经提前进入标准化器属于数据泄漏测试指标会虚高。正确做法是每个IMF单独一个scaler并且只fit训练段。测试段用训练段的scaler做transform。反归一化时用同一个scaler把预测值拉回原始尺度然后各分量相加。from sklearn.preprocessing import MinMaxScaler def build_supervised(imf, lookback, scalerNone, fit_scalerTrue): # fit_scalerTrue时在训练段拟合False时沿用已保存的scaler if fit_scaler: scaler MinMaxScaler() imf_scaled scaler.fit_transform(imf.reshape(-1, 1)).flatten() else: imf_scaled scaler.transform(imf.reshape(-1, 1)).flatten() X, y [], [] for i in range(len(imf_scaled) - lookback): X.append(imf_scaled[i:i lookback]) y.append(imf_scaled[i lookback]) return np.array(X), np.array(y), scaler # 训练段调用 X_train, y_train, scaler build_supervised(imf_train, lookback12, fit_scalerTrue) # 测试段调用沿用训练段的scaler X_test, y_test, _ build_supervised(imf_test, lookback12, scalerscaler, fit_scalerFalse)训练段fit、测试段transform这个顺序不能乱。如果测试数据里某个值超出了训练段的min/max范围transform只是等比缩放出去不会崩反过来如果把测试段信息混进scalerRMSE立刻虚高。调试时可以打印每个分量的scaler.data_min_和data_max_确认训练段范围是否符合直觉这也是定位“预测曲线比原始LSTM还差”最快的检查点。4. 完整预测流水线数据切分、分量重构与评价指标4.1 切分顺序与数据泄漏先分解还是先切分时间序列预测的切分有一条铁律按时间顺序切不能随机shuffle。随机打乱会破坏时间依赖关系LSTM学到的映射在测试集上毫无意义。EEMD-LSTM里多了一个新问题先对整段序列做EEMD再按索引切分成训练集测试集还是先切分再对训练段分解工程上常见做法是先整段分解、再切分。理由很直白EEMD的包络拟合依赖整段数据先切分会把训练段末端的端点效应直接带到测试段开头测试段前几十个点的IMF形态会明显异常。先整段分解后训练段和测试段共享了同一套IMF形态预测稳定得多。代价是在严格对比实验里测试段的IMF包含了一部分未来信息。这个泄漏在学术场景下需要警惕但工程复现大多数项目都这么处理。我的建议是复现源码数据时先整段分解再切分做严肃的算法对比时改成“训练段分解、测试段用训练段包络外推”但实现复杂且不稳定需要额外标注清楚。切分代码很简单train_end int(len(signal) * 0.8) # 先对整段信号做EEMD分解 imfs eemd.eemd(signal) # 按时间索引切分各分量 train_imfs imfs[:, :train_end] test_imfs imfs[:, train_end:]切分前确认train_end大于lookback 1否则训练段样本数不够。测试集长度最好覆盖至少两个完整周期这样评价指标才有统计意义。量化交易策略里常见做法是7比3或8比2切分对日线行情8比2更常用分钟级行情可以适当拉高训练比例。4.2 从IMF预测到最终预测叠加重构与RMSE/MAE每个分量独立完成训练和测试段预测后最终预测是所有分量反归一化结果的和。下面的代码把整条流水线串起来展示每个分量在循环里的完整生命周期from sklearn.metrics import mean_squared_error, mean_absolute_error lookback 12 preds np.zeros(len(test_raw)) for i in range(imfs.shape[0]): train_imf imfs[i, :train_end] test_imf imfs[i, train_end:] # 每个分量单独拟合scaler只fit训练段 scaler MinMaxScaler() train_scaled scaler.fit_transform(train_imf.reshape(-1, 1)).flatten() test_scaled scaler.transform(test_imf.reshape(-1, 1)).flatten() # 滑窗构造训练样本 X_train, y_train make_samples(train_scaled, lookback) X_train X_train.reshape((-1, X_train.shape[1], 1)) # 训练当前分量自己的LSTM model make_lstm_model(lookback) model.fit(X_train, y_train, epochs50, batch_size32, verbose0) # 滚动预测测试段 test_pred np.zeros(len(test_scaled)) for t in range(len(test_scaled) - lookback): x_input test_scaled[t:t lookback].reshape(1, lookback, 1) test_pred[t lookback] model.predict(x_input, verbose0)[0, 0] # 反归一化后叠加到最终预测里 preds scaler.inverse_transform(test_pred.reshape(-1, 1)).flatten() # 前lookback个点没有预测值对比指标要跳过 valid_preds preds[lookback:] valid_truth test_raw[lookback:] rmse np.sqrt(mean_squared_error(valid_truth, valid_preds)) mae mean_absolute_error(valid_truth, valid_preds) print(fRMSE{rmse:.4f}, MAE{mae:.4f})这个循环会训练全部分量耗时与IMF数量线性相关。滚动预测那层循环是串行的每个分量每一步都要跑一次model.predict测试集几百个点时还能接受上万点会卡到怀疑人生。想提速可以先用model.predict(X_all_batches)一次性算出所有预测再按索引回填但要注意这样一步到位预测和滚动预测在结果上不完全等价批量预测用的是真实窗口滚动预测在后续步骤里用了自己的预测值误差累积特征不同。评价指标方面RMSE对大误差敏感MAE更抗离群点MAPE在序列接近0时会爆掉像电力负荷夜间时段可能接近0MAPE会出现几百倍的异常值不用为妙。判断EEMD-LSTM是否值得一个实用习惯是把直接LSTM在同样切分下的RMSE算出来对比如果下降幅度不到10%大概率是数据本身太简单或者分量模型没调好而不是方法失效。4.3 多步预测递归滚动还是直接多步到目前滚动预测都是单步每预测一个点下一步就把真实观测值填进窗口。这种评估的是模型单步能力误差不会积累。但很多需求是预测未来24小时或30天必须决定多步策略。递归多步预测是最简单的把上一步的预测值当成下一步窗口末尾的输入继续预测。实现成本低但误差会随步长累积高频IMF累积最快通常三步之后开始明显漂移。低频分量变化慢递归多步反而能撑更久。直接多步预测是另一个思路把输出层从Dense(1)改成Dense(horizon)一次输出未来horizon个值def make_multi_lstm_model(lookback, horizon, lstm_units64): model Sequential([ LSTM(lstm_units, activationtanh, input_shape(lookback, 1)), Dense(horizon) # 一次输出未来horizon个值 ]) model.compile(optimizeradam, lossmse) return model直接多步的缺点是预测步长固定如果需求从预报7天改成预报30天需要重新训练。折中方案是分块预测把horizon拆成几个小段每段用一个独立模型段接力。对EEMD-LSTM项目我的经验是单步预测用递归结构多步预测优先直接输出别混用——混用会让错误来源分不清是模型问题还是误差累积问题。5. 避坑指南EEMD-LSTM最常见的5个翻车现场5.1 数据预处理阶段的三类翻车翻车现场一预测结果比直接LSTM还差RMSE不降反升。原因是每个IMF都用了整段序列的scaler测试集信息提前泄漏进标准化参数训练时看着收敛很快测试时一加上没见过的动态范围就崩。解决严格按第3.3节方式每个分量单独scaler、只fit训练段、反归一化用同一个scaler。翻车现场二同一份数据两次运行结果差20%甚至每次EEMD分出来的IMF数量都不一样。EEMD本身加入了白噪声没有固定随机种子时每次分解结果都不同LSTM权重初始化也随机。解决程序入口固定np.random.seed(42)和tensorflow.random.set_seed(42)PyEMD内部调用np.random固定全局种子即可同时把trials提高到100以上让白噪声平均更充分。翻车现场三分解出几十个IMF训练时间从几十分钟暴涨到几小时。常见原因是noise_width太大把真实分量打散成碎片或者原始序列里存在缺失值和离群点样条包络被极端点干扰。解决先清洗数据缺失值用插值而不是直接删行离群点按分位数截断把noise_width调回0.01到0.05区间。记住一个经验IMF数量超过12个时先修数据别急着堆算力。5.2 训练与预测阶段的典型翻车翻车现场四测试集预测曲线中段变成近似直线像是把上一个值平移了一下。原因是LSTM学到了“复制最近观测值”的捷径。这种现象在lookback太短时尤其明显模型只要记住最后一位就能得到很低的训练损失。解决增大lookback让模型被迫看到周期结构或者减小lstm_units降低模型容量。判断方法很简单把预测值和真实值做差如果残差看起来像带噪声的白噪声说明只有零点漂移被预测了如果残差里还有明显周期性说明模型根本没学到周期结构。翻车现场五测试段末尾的预测值突然上翘或下坠误差集中在最后几个点。原因有两层EEMD端点效应在序列末端最严重包络外推得到的分量末端本身就有畸变加上递归预测到最后一个点时没有真实值回填只能依赖最后一次窗口轻微误差也被放大。解决评估时跳过最后3到5个点更稳妥的是在训练段末尾预留20个点的缓冲窗口用这部分数据“预热”测试段开头减少端点效应传导。很多项目源码里都没有这个细节自己加上能明显改善末端表现。6. 一个容易忽略的精度技巧用PACF为每个分量选lookback6.1 为什么高频和低频分量不能共用同一个lookback大多数EEMD-LSTM实现里所有分量共用同一个lookback图省事。但高频IMF波动快12步窗口可能已经包含十几个周期输入里混着大量重复波动低频IMF变化缓慢12步窗口只能覆盖很小一段趋势LSTM没有足够上下文辨识方向。对高频分量用短窗口、低频分量用长窗口理论上更合理。快速初选可以用偏自相关函数PACF它衡量去掉中间滞后影响后当前值和过去某个滞后值的直接相关性。PACF在某个滞后阶数后截尾说明记忆长度大约到此为止这个位置就可以作为该分量lookback的候选。6.2 PACF初选后网格微调的具体代码与取舍from statsmodels.tsa.stattools import pacf import numpy as np # 对某个分量的训练段做PACF分析 partial pacf(imf_train, nlags30) n len(imf_train) threshold 1.96 / np.sqrt(n) # 找出显著超过置信阈值的滞后阶数 candidates [lag for lag, val in enumerate(partial) if abs(val) threshold] print(显著滞后阶数:, candidates[:10])选出候选阶数后不用太精确在候选值附近的常用档位[6, 12, 24, 48]里做网格搜索。训练成本有限时不用所有分量都单独搜索只对前2个高频IMF和残差项各跑一轮其余分量统一用12或24大部分效果提升都来自高频部分的短窗口和残差的长窗口。PACF只能给一个起点最终还是要看验证集RMSE。step一次只调一个分量的lookback其他分量保持不动避免多个变量同时变化导致无法定位是谁的贡献。这一步调完整体预测曲线的滞后现象会明显减轻。早期做量化交易策略代码时我偷懒全用默认12在分钟级行情上怎么调units都压不住滞后后来按PACF把高频分量压到6、把残差抬到48问题当场缓解。现在每逢新的EEMD-LSTM项目都会先花十分钟看各分量的PACF截尾点再决定整批lookback档位算是花小钱办大事。希望帮到你。本文还有配套的精品资源点击获取