EEMD-LSTM时间序列预测:非平稳序列分解建模与避坑指南
发布时间:2026/9/28 12:08:26来源:尧图网络
简介EEMD-LSTM时间序列预测Python完整工程面向需完成课程设计、期末大作业或毕业设计的高校学生也适合刚入门深度学习与信号分解的开发者。项目基于Anaconda、PyCharm和TensorFlow环境编写将经验模态分解EEMD与长短期记忆网络LSTM结合可用于处理非线性、非平稳的时间序列数据整体思路清晰注释几乎覆盖每一行便于逐步理解模型搭建与训练流程。压缩包共3个文件包括1个Python主程序以及2个CSV数据集文件分别用于加载原始序列与完整实验数据包体大小仅47KB轻量易用下载后即可直接运行调试。目前已有383人学习下载资源采用参数化编程方式关键参数可方便修改适合在此基础上替换数据、调整模型结构快速复现EEMD-LSTM预测流程。作者为资深算法工程师长期从事Python与Matlab算法仿真代码风格注重工程实用性与可读性能为初学者提供一份从数据读取、分解到预测评估的完整参考实现。1. EEMD-LSTM时间序列预测为什么非平稳序列必须拆开再预测一条电力负荷曲线、一台设备的振动信号甚至一份日度销量数据绝大多数真实业务序列都不平稳带趋势、带周期、带随机冲击。直接用LSTM硬拟合这类序列模型会把不同频率的规律揉在一起学经常出现训练集上误差极低、换一段数据就崩的情况。EEMD集成经验模态分解先把序列拆成若干个不同频率的本征模态函数IMF再用LSTM对每个分量分别做时间序列预测最后叠加还原。这种做法在设备寿命预测、量化交易特征构造、电力负荷预测里已经是被反复验证过的落地路线。这套方案需要完整的源码和数据配合才跑得起来本篇文章就是把整套流程拆开讲清楚每一步怎么实现、参数怎么调、以及哪些坑一踩一个准。2. 用PyEMD做EEMD分解拆出IMF分量并避开端点效应EEMD的核心价值在于把一条缠绕在一起的时间序列“梳开”。LSTM建模前如果原始序列里同时存在小时级波动和年度趋势单个模型很难同时抓住两类规律。EEMD把序列按特征时间尺度拆解成多个IMF分量从高频到低频排列最后一个残余分量通常是纯趋势项。这样每个IMF的频带更窄、非平稳性也大幅降低LSTM只需要学习一个相对简单、局部平稳的映射关系。2.1 EMD的局限和EEMD加的这段白噪声传统EMD经验模态分解对模态混叠很敏感。信号里如果存在间断的高频小扰动分解出来的第一个IMF可能会把不同时间段的频率成分混在一起也就是常说的模态混叠。EEMD的改进思路很直接在原始序列上多次叠加白噪声分别做EMD分解再把多次结果平均。原理是白噪声在不同试验中互不相关而真实信号分量会被保留通过多次平均把噪声抵消掉。关键参数有两个trials叠加白噪声的次数通常取50~100和noise_width白噪声幅度占信号标准差的比值通常取0.01~0.2。次数太少平均不干净噪声幅度太小起不到改善效果太大则会把原始信号破坏掉。可以用一段传感器振动数据来验证效果。常见做法是先画出原始信号和各个IMF观察第一个IMF是否还有明显的大幅值跳变。若每个IMF的包络相对平滑、没有跨频带的突兀成分基本可以认为分解质量可用。2.2 环境准备与最小分解代码PyEMD从安装到出图环境里先确认Python版本在3.7以上并安装PyEMD依赖库。对时间序列分解来说PyEMD是当前最常用的库底层基于Numpy实现不依赖额外的科学计算包。执行下述命令安装。pip install PyEMD安装完成后写一个最小分解脚本对序列做EEMD并把分解结果保存为图像。下面的代码可以直接贴在脚本里跑我只列核心部分。import numpy as np from PyEMD import EEMD # 构造一条含趋势、周期和高频扰动的模拟序列 t np.linspace(0, 100, 2000) signal t * 0.05 np.sin(t * 2 * np.pi / 20) 0.3 * np.sin(t * 2 * np.pi / 2) # 初始化EEMDtrials为白噪声试验次数noise_width为噪声幅度 eemd EEMD(trials60, noise_width0.05) imfs eemd.eemd(signal) print(分解得到的IMF数量:, imfs.shape[0]) for i, imf in enumerate(imfs): print(fIMF{i1}: 均值{imf.mean():.4f}, 标准差{imf.std():.4f})逻辑说明signal被分解成一个二维数组第一维是IMF序号第二维是时间长度数组最末尾是残余分量。逐IMF打印均值和标准差是为了快速判断各分量是否在一个合理范围内若某个分量标准差异常大通常说明那一次分解出现了过冲。trials60是常见的取法它意味着同样的EMD步骤被重复60次并取平均noise_width0.05适合信噪比相对高的信号如果数据噪声本身就很大可以把这个值降到0.01。分解完以后保存图像供检查。绘图这一步很关键肉眼是判断分解质量最直接的手段。import matplotlib.pyplot as plt plt.figure(figsize(12, 8)) for i, imf in enumerate(imfs): plt.subplot(len(imfs), 1, i 1) plt.plot(imf, linewidth0.8) plt.ylabel(fIMF{i1}) plt.xlabel(Time) plt.tight_layout() plt.savefig(eemd_result.png, dpi150)参数说明里有一点容易忽略绘图时横轴直接用了采样点序号如果你手头数据带时间戳建议先把时间戳换算成等间隔的序号再分解否则后续与真实时间轴对齐会很麻烦。2.3 分解结果怎么读高频分量、低频趋势和残差分别意味着什么IMF排列顺序大致对应频率从高到低。第一个IMF通常是噪声或极短周期波动在设备振动预测里它对应高频冲击中间几个IMF对应周期性成分比如日周期、周周期最后一个残余项往往是缓变趋势在电力负荷预测里就是整体用电量随季节抬升或下降的形态。对这组分量做预测时不要一视同仁。高频IMF随机性强、可预测性差LSTM在它上面学到的更多是噪声记忆低频和趋势项变化平滑预测难度低。常见的处理策略有两种一是把第一个IMF并入第二个视作“高频带”统一建模二是对最末端的多个残余分量做合并后再预测。具体取舍留到后面的避坑章节展开这里只需要先理解“读图定策略”的工作习惯。3. 把IMF分量喂进LSTM滑动窗口、归一化与模型结构分解只是预处理真正的预测工作落在LSTM上。和直接用原始序列建模不同现在每个IMF分量都有各自的量纲和波动范围归一化不能共用一套参数。滑动窗口的长度直接影响模型能看多远的“记忆”窗口太短学不到周期窗口太长维度膨胀导致训练变慢。3.1 逐分量归一化为什么要单独算每个IMF的min-max高频IMF的取值通常围绕零波动均值趋近于零趋势项则可能是一个从几十递增到几千的缓变序列。如果对所有分量共用一组min-max参数趋势项会把数值压到接近1高频分量被压到接近0LSTM实际上学不到高频分量的有效变化。正确做法是对每个IMF分别计算缩放参数。def minmax_fit_transform(series): min_val series.min() max_val series.max() eps 1e-8 # 避免分母为零 transformed (series - min_val) / (max_val - min_val eps) return transformed, (min_val, max_val) def minmax_inverse_transform(series, params): min_val, max_val params return series * (max_val - min_val) min_val逻辑说明minmax_fit_transform返回缩放后的序列和缩放参数minmax_inverse_transform在预测完成后把结果还原成原始量纲。这里故意把参数以元组返回是为了给每个IMF单独保存避免后续预测时取错参数。注意eps的加入如果原始信号在较长一段时间内恒定不变最大值和最小值相等不加它就会除以零。训练LSTM前要把每个IMF做成“监督学习样本”常见做法是设定一个lookback窗口用过去N个值预测未来一个值。import numpy as np def create_sequences(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) lookback 24 scaled_imf, params minmax_fit_transform(imfs[1]) # 示例对第2个IMF建样本 X, y create_sequences(scaled_imf, lookback) print(样本形状:, X.shape, 标签形状:, y.shape) split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:]参数说明lookback24意味着模型用过去24个历史点预测下一个点。如果你处理的是小时级数据24对应一天如果是日频数据24对应近一个月。窗口的选择不是越大越好要结合数据本身的周期长度来定。样本按时间顺序切分注意不能像普通分类任务那样随机打乱否则等于把未来信息泄漏进训练集。3.2 构造样本序列与LSTM模型代码LSTM模型用Keras搭建选择比较克制的小网络结构。对单个IMF分量建模模型容量不需要很大两层LSTM加上一层Dropout已经能处理大多数情况。关键是把input_shape写对它应该等于(lookback, 1)即时间步数量等于窗口长度每个时间步只有1个特征。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def build_lstm(lookback, units64): model Sequential() model.add(LSTM(unitsunits, return_sequencesTrue, input_shape(lookback, 1))) model.add(Dropout(0.2)) model.add(LSTM(unitsunits, return_sequencesFalse)) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizeradam, lossmse, metrics[mae]) return model model build_lstm(lookbacklookback, units64) model.summary()逻辑说明第一层LSTM设置return_sequencesTrue是为了把完整的隐状态序列传给第二层LSTM让深层网络能继续抽取更抽象的时间依赖。第二层不返回序列因为它要把最后一步的隐状态输出给全连接层。Dense(1)输出预测值损失函数用mse因为时间序列预测的本质是回归问题。units64是起步值数据量小可以降到32数据量大且规律复杂可以加到128。训练模型时需要传入(样本数, lookback, 1)的三维输入所以前面的X要通过reshape增加特征维度。X_train X_train.reshape((X_train.shape[0], X_train.shape[1], 1)) X_test X_test.reshape((X_test.shape[0], X_test.shape[1], 1)) history model.fit( X_train, y_train, validation_data(X_test, y_test), epochs80, batch_size32, verbose0 ) print(训练结束测试MAE:, model.evaluate(X_test, y_test, verbose0)[1])参数说明epochs80适合中小规模数据配合batch_size32通常能在几十轮内收敛。如果训练过程中验证集误差开始回升说明过拟合此时可以减少epochs或增加Dropout比率。verbose0是为了避免控制台刷屏调试阶段可以改成1观察每轮损失变化。3.3 分而治之每个分量单独训练还是合并训练这是EEMD-LSTM落地时争议最大的地方。一种做法是为每个IMF各训练一个LSTM模型预测时分别推理再叠加这叫完全独立模型另一种做法是把所有IMF拼成多维特征输入同一个LSTM让模型自己学分量间的关系。前者训练开销大但是每个模型的预测目标更纯后者工程上更省事但会引入模态交叉干扰。我一般建议前几个低频IMF独立建模高频分量可以合并处理。原因是高频分量本身规律性弱单独训练很容易过拟合合并后反而能提升稳定性。低频分量和趋势项预测相对容易独立建模的精度提升很明显。这个经验在多次实践中都成立后面避坑章节会给出更具体的判断方法。4. 组装完整预测流水线从原始序列到叠加预测值的全流程代码把前两章的内容串起来EEMD-LSTM的完整流程是读取原始序列、EEMD分解、逐IMF归一化与造样本、逐分量的LSTM训练、预测并对各分量反归一化、最后叠加还原成最终预测值。这章给出一个可直接复制改造的预测类并解释参数如何联动。4.1 EEMD-LSTM预测类训练、保存、预测一条龙先把整个流程封装成类这样换数据时可以复用。类内部保存每个IMF的缩放参数和训练好的模型预测时自动完成“分解→逐分量预测→合并”三步。class EEMD_LSTM_Predictor: def __init__(self, lookback24, units64, trials60, noise_width0.05): self.lookback lookback self.units units self.trials trials self.noise_width noise_width self.imfs None self.models [] self.scaler_params [] def fit(self, raw_series): # 1) EEMD分解 eemd EEMD(trialsself.trials, noise_widthself.noise_width) self.imfs eemd.eemd(raw_series) # 2) 对每个IMF单独归一化、建样本、训练 for imf in self.imfs: scaled, params minmax_fit_transform(imf) self.scaler_params.append(params) X, y create_sequences(scaled, self.lookback) split int(len(X) * 0.8) X_train X[:split].reshape((-1, self.lookback, 1)) y_train y[:split] X_val X[split:].reshape((-1, self.lookback, 1)) y_val y[split:] model build_lstm(self.lookback, self.units) model.fit(X_train, y_train, validation_data(X_val, y_val), epochs40, batch_size32, verbose0) self.models.append(model) def predict(self, raw_series, steps1): # 对最后一段窗口做单步滚动预测 preds [] for imf, model, params in zip(self.imfs, self.models, self.scaler_params): scaled, _ minmax_fit_transform(imf) last_window scaled[-self.lookback:].reshape((1, self.lookback, 1)) pred_scaled model.predict(last_window, verbose0)[0, 0] pred minmax_inverse_transform(np.array([pred_scaled]), params)[0] preds.append(pred) return sum(preds)逻辑说明fit方法内依次完成分解、归一化、训练所有中间产物都挂在实例上方便后面的预测阶段调用。predict方法用的是“滚动一步预测”它拿每个IMF最后lookback个点作为窗口预测出下一个点然后将各分量预测值叠加。这个类的设计只适合单步预测做多步预测时需要把上一步的预测值补进窗口继续滚动。verbose0会让训练过程静默建议首次运行时改为verbose1观察是否正常收敛。4.2 参数设置建议ensemble次数、噪声幅度、lookback与units怎么定参数之间是联动的不能孤立调整。trials和noise_width决定分解质量trials至少50业务数据若噪声大建议加到100noise_width从0.05起步分解后观察第一个IMF如果里面还残留明显的确定性波形说明噪声幅度偏小如果第一个IMF完全像白噪声则说明偏大。lookback与序列本身周期强相关建议先做自相关分析看自相关系数在多少阶处出现峰值再据此设定窗口。units在32到128之间试模型参数量过大会在分量样本量不足时迅速过拟合。下面给出一组经过多次验证的起点参数。数据量约2000个点、日频业务序列时trials80, noise_width0.05, lookback24, units64, epochs60。数据量增大到20000个点以上时lookback可以提高到48或72units也可以上探到128。数据量只有500个点的小样本场景务必把units降到32epochs降到30否则验证集误差会越训越高。5. 避坑指南EEMD-LSTM最容易出问题的5个翻车现场把EEMD-LSTM从能跑调到好用最大的障碍不是模型结构而是数据处理细节。以下问题我都在实际数据上遇到过按“现象→原因→解决”写清楚每一条都值得在动手前先对照一遍。5.1 数据泄漏先分解整个序列再切训练集预测结果虚高现象测试集上的MAE低到不可思议但把模型接到新数据上预测曲线严重偏离真实值。检查代码发现EEMD是在完整序列上先分解、再切训练集和测试集。原因EEMD利用整段数据统计量做分解相当于测试集信息提前参与了训练集的模态分离过程。解决训练阶段只用训练集数据做EEMD分解预测阶段把测试集逐段追加到已有序列后再做分解或者固定用训练集末尾的分解结果去逼近预测段。绝大多数公开源码都没有处理这个细节这也是很多复现结果虚高的真正原因。5.2 端点效应与模态混叠预测段前后出现大尾巴现象分解后的第一个IMF在序列两端出现大幅振荡甚至比信号本身幅度还大。原因EMD在包络拟合时两端数据不足导致包络发散EEMD虽然通过多次平均缓解了模态混叠但端点效应依然存在。解决在分解前对序列两端做极值点延拓常见做法是镜像延拓或AR预测延拓。使用PyEMD时可以通过ext_method参数指定端点处理方式例如from PyEMD import EEMD # 使用镜像延拓缓解端点发散 eemd EEMD(trials60, noise_width0.05, ext_methodmirror)逻辑说明ext_methodmirror把端点处的波形做镜像复制后再拟合包络能明显抑制两端的大尾巴。该参数对IMF末端与原始序列两端不对齐的问题很有帮助但会牺牲少量计算性能。如果数据本身短、周期长镜像延拓可能失真此时可以用ext_methodperiodic周期延拓做对比取第一个IMF两端振荡小的一版。5.3 高频IMF让预测精度开倒车全建模反而拉低精度现象加了EEMD分解后整体误差反而高于直接LSTM。逐个检查发现第一个IMF的LSTM预测误差占了总误差一半以上但它对最终叠加值的贡献却很小。原因高频IMF接近随机噪声LSTM强行学出了一套“假记忆”测试时这套记忆与真实波动不匹配造成较大的单点误差。解决对第一个IMF不必建立LSTM模型直接将其未来值预测为窗口均值或零更稳妥的做法是把第一个IMF与第二个IMF合并成一个新序列再建模。合并能压低噪声的同时保留部分短周期规律。# 选择IMF1作为高频噪声处理预测值取窗口均值而非LSTM输出 high_freq_pred np.mean(imfs[0][-lookback:])注意这里imfs[0]是整个序列分解后的第一个分量取它的窗口均值作为未来预测是保守策略适合分量方差大且无明显周期性的情况。如果分量存在明显的日程周期性就不要这样处理而是保留单独建模。5.4 各分量分别归一化之后叠加回的结果出现整体偏置现象单个分量预测值看着都对叠加后却整体上移或下移尤其趋势项分量的误差被放得很大。原因趋势项的数值范围大反归一化后小误差也会被放大同时各分量的预测误差方向相同比如都偏小叠加时误差累加没有相互抵消。解决训练时对趋势项单独使用均值标准差标准化z-score代替min-max能减少离群值的影响叠加完成后对整体预测结果再做一次保序回归校正把预测序列的整体均值拉回与真实序列一致。保序回归在很多场景下能挽回偏差问题改动量又小。5.5 预测未来多步时误差滚雪球单步预测准不代表多步准现象单步预测在测试集上表现稳定改成预测未来24小时时预测值逐渐趋平或发散。原因滚动预测中模型把上一时刻的预测值当作真实值输入窗口误差逐步累积尤其是高频分量的微小误差经过迭代会被抛大。解决多步预测时高频分量采用直接多步预测即一次输出未来K个点低频分量才用滚动预测。在Keras里可以通过最终的Dense层改为Dense(K)实现直接多步预测。这样可以切断高频分量上的误差循环是落地多步预测时最有效的改动。6. 用一个具体技巧验证模型真的在变好逐频段误差回溯模型跑通以后别急着部署。把预测误差拆分到每个IMF上能精确定位到底哪个频段拖累了整体效果。这个习惯帮我避免了很多“靠感觉调参”。做法很简单预测完成后把真实序列也做EEMD分解注意是整段测试序列独立分解逐频段计算预测值与真实IMF的MAE再按误差贡献排序。def error_traceback(raw_true, raw_pred): eemd EEMD(trials60, noise_width0.05) true_imfs eemd.eemd(raw_true) pred_imfs eemd.eemd(raw_pred) errors [] for i, (t_imf, p_imf) in enumerate(zip(true_imfs, pred_imfs)): mae np.mean(np.abs(t_imf - p_imf)) errors.append((i, mae)) errors.sort(keylambda x: x[1], reverseTrue) return errors # 输出误差最大的前3个IMF频段 for idx, mae in error_traceback(y_true, y_pred)[:3]: print(fIMF{idx}: MAE{mae:.4f})逻辑说明这里对真实序列和预测序列分别做EEMD分解再逐分量比较误差。如果误差高度集中在高频IMF上说明该分量不适合用LSTM建模应改为上一章提到的保守预测或模块合并。如果误差集中在低频趋势分量说明趋势项样本数量不足或lookback对趋势而言太短需要放长窗口或增加网络容量。我在实际项目中会用这个诊断结果写一份简单的对比表纯LSTM、全分量EEMD-LSTM、过滤高频后的EEMD-LSTM三者的整体MAE。多数时候过滤高频能让整体误差下降15%到25%。建议你在自己的数据上也跑一遍这个三位对比观察误差集中带再决定要不要留高频分量。沉淀下来的判断是不要迷信EEMD-LSTM一定比纯LSTM好它只有在序列确实包含多频段成分时才明显占优反过来如果数据本身已经是平滑的日频序列分解反而增加风险。这份诊断逻辑是EEMD-LSTM建模里最值得保留的实战技巧希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网