简介这是一套基于Python实现的EEMD-LSTM时间序列预测完整源码与配套数据面向计算机、电子信息工程、数学等专业学生可满足课程设计、期末大作业及毕业设计需求。项目在Anaconda、PyCharm环境下以Python与TensorFlow构建采用集合经验模态分解EEMD对原始序列进行分解再结合长短期记忆网络LSTM完成预测适合希望学习信号分解与深度学习组合模型的入门者。压缩包共3个文件包含2个csv数据文件与1个py主程序整体仅47KB结构紧凑数据与代码分离便于直接运行与替换。代码采用参数化编程几乎一行一注释思路清晰既方便理解每个步骤也便于调整参数开展对比实验。资源发布以来已有385人学习浏览作者为资深算法工程师长期从事神经网络预测与信号处理仿真注释详尽、友好度高适合作为毕业设计、竞赛项目或进阶学习的复现与扩展基础。1. 用EEMD-LSTM做预测先把非平稳信号拆开再让LSTM逐项学习做电力负荷预测时我拿到过一段低频波动里叠着高频毛刺的信号。直接用LSTM建模训练集上loss降得很漂亮测试集上一到尖峰就翻车——模型的误差集中在趋势项上而趋势项恰恰被噪声模态带偏了。EEMD-LSTM的思路是先用集合经验模态分解把信号拆成不同尺度的本征模态函数IMF再让LSTM对每个模态单独预测最后相加。这个标题讲的就是把这套流程用Python完整落地从分解参数设置到LSTM训练再到结果重构。适合做负荷、风速、股价、振动这类非平稳时序预测的工程师和学生新手能照着搭出一套可运行的工程熟手可以对照参数边界和踩坑记录调整自己的方案。2. EEMD分解为什么先拆信号以及怎样用PyEMD跑通第一步2.1 为什么是EEMD而不是EMD或VMD模态混叠问题的解法直接对非平稳信号做LSTM预测最大的麻烦是趋势项、周期项和噪声叠在一起网络要同时拟合不同尺度的规律。EMD可以把信号自适应地拆成若干IMF但它有个老毛病——模态混叠相近频率的成分会在不同IMF之间串来串去一个IMF里既有这段频率又有那段频率LSTM学起来就会很混乱。EEMD解决模态混叠的办法很直接给原始信号加多次白噪声每次加完噪声做一次EMD再把所有分解结果的同阶IMF取平均。白噪声在不同尺度上均匀分布的特性会把极值点分布的密度拉均匀模态混叠就被平均掉了。实际使用中EEMD比EMD稳定得多尤其是信号里含有间歇性高频成分时。也有人用VMD做同样的事但VMD需要预先指定模态个数K。K这个值是靠经验和实验试出来的有一定的玄学成分EEMD的自适应分解则不需要预设模态数量给一套噪声参数就能跑落地更省事。所以在这个项目里我一般选EEMD。2.2 用PyEMD完成EEMD分解最小可运行代码PyEMD是Python里最常用的EMD/EEMD实现库安装方式很简单。下面的代码构造一段仿真信号完成EEMD分解并把结果保存成CSV后续所有建模都从这个CSV读取数据import numpy as np import pandas as pd from PyEMD import EEMD # 构造一段待分解序列真实项目中这里换成你的时序数据 t np.linspace(0, 1, 1000) signal (0.5 * np.sin(2 * np.pi * 5 * t) 0.2 * np.sin(2 * np.pi * 30 * t) 0.1 * np.random.randn(len(t))) # EEMD分解集成100次白噪声幅值设为信号标准差的0.2倍 eemd EEMD(trials100, noise_width0.2) imfs eemd.eemd(signal) # 保存分解结果第0列是原始信号后面依次是IMF1..IMFn和残差项 df pd.DataFrame(imfs.T) df.insert(0, original, signal) df.to_csv(eemd_result.csv, indexFalse, headerFalse)eemd.eemd()返回的imfs是一个二维数组第0行是最高频的IMF最后一行是残差项。因为EEMD集成了多次EMD结果它天然地比EMD慢1000个点的序列跑一百次集成大约需要十几秒。这里有两个关键参数trials是集成次数noise_width是白噪声幅值占信号标准差的比例。运行完建议画一下每个IMF的波形确认高频模态在前、低频趋势在后的排列再进入下一步。2.3 噪声幅值与集成次数怎么设两个参数的落地经验噪声幅值和集成次数是EEMD最重要的两个参数。noise_width取值太小白噪声起不到均匀化极值分布的作用模态混叠压制不住取值太大会把真实信号淹没在噪声里分解出的高频IMF基本就是噪声本身。常见范围是0.1到0.4我一般从0.2起步然后观察第一个IMF是否还有明显周期性。如果IMF1长得像纯随机序列说明噪声幅值偏大调小到0.1再试。trials决定了集成平均的稳定性。50次以下分解结果抖动明显同一个信号跑两次IMF形态会有差异100次以上结果基本稳定但耗时线性增加。我一般固定100次。另外PyEMD允许设置最大IMF数量如果分解出的模态过多出现相邻两个IMF波形几乎相同的“孪生模态”那就是过分解了可以把max_imfs调小或者换一个更小的noise_width重新分解。提示EEMD的分解结果受随机种子影响工程化落地时一定要固定随机状态否则每次训练前分解结果都不同模型复现会变成一场灾难。后面第5章我会细说这个问题。3. 把分解结果变成LSTM的输入数据预处理与滑窗生成3.1 归一化为什么一定要放在分解之后EEMD分解完成之后每个IMF都有自己的幅值尺度高频IMF幅值可能只有0.01而残差趋势项幅值是几百甚至几千。如果对这些IMF统一做归一化高频成分会被直接压平LSTM学不到有用信息。正确的顺序是先分解再对每个IMF单独归一化。这里有一个常被忽略的细节——不要先归一化再分解。原因有两点其一EEMD的noise_width参数是相对信号标准差来设置的先归一化会把信号幅值压缩到[0,1]同样倍数的白噪声在相对意义上完全不同其二分解后的每个IMF都保留自己的物理幅值尺度后续做误差分析时你能清楚看到预测误差主要来自哪个频段。常见的做法是对每个IMF分别做MinMaxScaler或StandardScaler。MinMaxScaler适合幅值范围明确的模态StandardScaler适合接近正态分布的残差项。我在实际项目中会把这两者混用周期分量用MinMax残差和高频噪声用StandardScaler。3.2 用滑动窗口构造监督学习样本LSTM不能直接吃一维时序数据需要把序列改成“过去lookback步预测未来horizon步”的监督学习格式。下面的函数是把单个IMF转换成LSTM训练样本的核心工具import numpy as np def create_sequences(data, lookback24, horizon1): X, y [], [] for i in range(len(data) - lookback - horizon 1): X.append(data[i:i lookback]) y.append(data[i lookback:i lookback horizon]) return np.array(X), np.array(y)lookback是回看窗口长度horizon是预测步数。回看窗口的长度与时序数据的周期有关日粒度数据有周周期就至少取7的倍数我一般取周期的2倍左右如果你做的是设备振动预测采样率高回看窗口可以按频谱分析的结果来定。horizon如果是多步预测这个函数会直接输出多步的目标序列LSTM的输出层维度也要相应调成horizon。滑窗构造的样本量大约为总长度 - lookback - horizon 1如果你的原始数据只有几百个点滑窗后样本量会很小LSTM很容易过拟合这时要把lookback调短一些。3.3 训练集/验证集/测试集切分时间序列不能用随机切分很多初学者在构造完滑窗样本后直接调用train_test_split随机切分这是时间序列预测里最典型的数据泄露错误。随机切分会让测试集里混入训练集前后时间段的信息LSTM学到的是“跳跃式”组合规律而不是连续演化的规律测试集loss会虚低一上真实环境就露馅。时间序列必须按时间顺序切分。切分顺序要和滑窗顺序严格对应前70%做训练中间15%做验证最后15%做测试。代码逻辑如下n_train int(len(X) * 0.7) n_val int(len(X) * 0.15) X_train, X_val, X_test X[:n_train], X[n_train:n_trainn_val], X[n_trainn_val:] y_train, y_val, y_test y[:n_train], y[n_train:n_trainn_val], y[n_trainn_val:]还有一个和切分配套的细节每个IMF的归一化器必须在训练集上fit再用同样的参数对验证集和测试集做transform。如果对整个序列先求min/max再切分相当于把未来区间的极值信息提前泄露给了训练过程这就是“用未来信息训练过去”的典型坑。等五个模态全部处理完你会得到一套干净的、逐模态的监督学习样本下一步就可以进入LSTM建模阶段。现在也有一些基于Agent的自动化时间序列预测框架在尝试把这类流程封装成编排任务但底层模型核心仍然是LSTM这类循环神经网络数据预处理这一步绕不开。4. 构造并训练EEMD-LSTM完整模型代码与参数说明4.1 对每个IMF分别建模还是统一建模两种结构的取舍EEMD分解出的IMF频率尺度差异很大这里存在两种建模路线。第一种是“分解-预测-重构”路线简称additive模式对每个IMF单独训练一个LSTM子模型各自预测后把结果相加。第二种是把所有IMF作为多变量输入统一送给一个LSTM模型让网络自己学模态之间的交互。我一般选additive模式理由很直接高频IMF和低频趋势项的尺度差太远统一建模时网络会把高频分量当作噪声忽略掉导致预测结果只拟合了趋势丢失了细节。对每个模态单独建模每个子模型只需要学一个频率范围内的时间演化规律难度大大降低。它的代价是要训练N个模型训练时间线性增加——如果你分解出8个IMF就要训练8个LSTM。不过additive模式也有例外情况当某个IMF的方差贡献极小时它本质上就是噪声没必要为它单独建模直接丢弃或者合并进相邻模态这部分策略我会在第5章展开讲。4.2 Keras实现LSTM预测的最小模型下面是一个标准的LSTM回归模型输入形状是(样本数, lookback, 1)输出是预测值。这个结构足以应对大多数单步预测场景from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping def build_lstm(lookback, units64): model Sequential([ LSTM(units, return_sequencesTrue, input_shape(lookback, 1)), Dropout(0.1), LSTM(units // 2), Dense(1) ]) model.compile(optimizeradam, lossmse) return model第一个LSTM层设置return_sequencesTrue是为了把完整的时间步输出给第二层LSTM让网络能捕捉更长时间跨度的依赖第二层不设return_sequences只输出最后一个时间步的隐状态。units的取法和lookback相关经验值是lookback的一半到等量之间我习惯从64起步。Dropout(0.1)是给第一层到第二层之间加一点正则化防止在样本量不大时过拟合。全部IMF的LSTM结构可以共用同一个build_lstm函数但要注意每个IMF的归一化范围不同所以每个模态要单独配一个scaler对象。训练时的EarlyStopping回调按验证集loss触发超过10个epoch没有改善就停止并恢复最佳权重。4.3 预测结果重构反归一化与各模态相加训练和预测循环的完整逻辑如下。核心是把归一化的预测结果还原成原始幅值再把所有IMF的预测叠加from sklearn.preprocessing import MinMaxScaler predictions [] lookback, horizon 24, 1 for i in range(imfs.shape[0]): imf imfs[i] # 每个IMF单独归一化fit时用训练区间 scaler MinMaxScaler(feature_range(-1, 1)) imf_train imf[:n_train].reshape(-1, 1) scaler.fit(imf_train) # 构造滑窗样本注意顺序先fit归一化器再transform imf_scaled scaler.transform(imf.reshape(-1, 1)).flatten() X, y create_sequences(imf_scaled, lookback, horizon) # 按时间顺序切分 X_train, y_train X[:n_train], y[:n_train] X_val, y_val X[n_train:n_trainn_val], y[n_train:n_trainn_val] X_test, y_test X[n_trainn_val:], y[n_trainn_val:] # 调整形状LSTM输入是 (样本数, lookback, 特征数) X_train X_train.reshape((X_train.shape[0], lookback, 1)) X_val X_val.reshape((X_val.shape[0], lookback, 1)) X_test X_test.reshape((X_test.shape[0], lookback, 1)) model build_lstm(lookback) model.fit(X_train, y_train, epochs80, batch_size32, validation_data(X_val, y_val), callbacks[EarlyStopping(patience10, restore_best_weightsTrue)], verbose0) pred model.predict(X_test) pred_inv scaler.inverse_transform(pred.reshape(-1, 1)).flatten() predictions.append(pred_inv) # 所有模态预测相加得到最终预测 final_pred np.sum(predictions, axis0)这段代码有两个值得注意的点。第一scaler.fit用的是训练区间的数据transform才作用于全序列这保证了min/max不包含未来信息。第二每个IMF的预测结果在相加之前必须对齐索引因为滑窗会让预测起点整体后移X_test对应的真实值应从n_train n_val lookback处开始取。final_pred和y_test的对应关系是第k个预测值对应原始序列第n_train n_val lookback k个点画对比图时一定要按这个索引对齐否则你会看到预测曲线整体“超前/滞后”这是建模结果重构时最容易翻车的地方。提示如果IMF数量多建议把每个模态的模型保存到磁盘例如model_imf0.h5下次直接加载预测不需要重新训练。工程上这是必须的生产环境不可能每次都训练一遍。5. EEMD-LSTM落地避坑5个高频翻车点与排查思路5.1 分解结果在序列两端发飘边界效应的压制方法现象EEMD分解后每个IMF在序列的开始和结束几十个点处出现明显摆动幅度比中间段大得多训练出的模型在预测末尾段时误差陡增。原因EMD系列的筛分过程依赖极值点进行包络插值序列两端没有极值点算法只能用边界附近的极值外推外推本身就不稳定叠加多次集成后两端误差被进一步放大。短序列受这个影响尤其严重。解决在分解前对序列做镜像延拓左右各延长20到50个点分解完成后把延拓部分裁剪掉。镜像延拓的代码很轻量用np.pad配合modereflect即可。如果做的是在线预测每次只预测未来一步EEMD分解窗口也要包含足够的“预测前缓冲”让端点效应落在你不需要的那一段。5.2 归一化范围不一致导致重构后的预测值偏移现象每个IMF的反归一化预测结果单独看形态正常相加之后整体幅值偏小尤其是峰谷处被明显削平。原因如果归一化用MinMaxScaler(feature_range(0,1))LSTM输出层默认是线性激活但训练时目标值被压缩到[0,1]区间网络输出被“惯性”拉向区间中部预测值很难触达0和1附近的极值。反归一化后峰谷值就被系统性低估了。解决把feature_range改为(-1, 1)让目标值分布在对称区间网络对极值的预测压力更小。如果削顶现象仍然存在检查最后一个Dense层的激活函数是否是linearKeras默认就是。残差项如果偏态明显就不要用MinMax改用StandardScaler它的输出没有固定上下界对极值更宽容。重构后做一次残差散点图看误差是否集中在峰谷能快速定位是不是归一化引起的。5.3 训练时用到了未来信息最隐蔽的数据泄露现象验证集loss稳步下降测试集loss也好看但部署到新数据上效果急转直下预测曲线比真实曲线滞后一拍。原因除了第3章讲的先归一化再切分问题还有一个更隐蔽的泄露点——EEMD分解本身是全局操作。EEMD对整段序列做分解每个时间点的IMF值都受到整段序列极值分布的影响相当于分解过程已经把测试区间的统计特性“泄漏”给了训练样本。这不是PyEMD的bug而是信号分解类方法固有的全局性。解决在工程上做滚动分解每次只用截止到预测点之前的一段窗口做EEMD得到最后一个IMF值作为当前时刻的分解结果。训练阶段可以全序列分解但验证和测试阶段要模拟滚动预测每预测一步都重新分解一次。这样做的代价是训练慢不少但结果可信。这条是我踩过最深的一个坑也是EEMD-LSTM在学术paper里效果好、生产落地难的最常见原因。5.4 IMF数量过分解高频噪声模态该丢就丢现象分解出的前两三个IMF几乎没有周期特征波形完全像白噪声模型在它们上训练出的预测结果基本是随机数最终叠加后把主信号的预测精度拖垮。原因EEMD在信号长度不足、噪声幅值设置偏大时会把白噪声单独拆成高频IMF。这些IMF在方差占比上可能很小但对LSTM来说它们是一段无规律可学的序列模型只能硬拟合结果就是预测时输出噪声。解决添加一个模态筛选步骤用方差贡献率决定哪些IMF值得建模var_ratio np.array([np.var(imf) for imf in imfs]) / np.var(signal) keep_idx np.where(var_ratio 0.01)[0]方差贡献低于1%的模态即使保留也建议把它的预测结果设为0即用均值代替不让噪声进入最终叠加结果。筛选之后可以进一步把几个相邻的高频噪声模态求和成一个高频噪声项再决定是丢弃还是加入。这个“先看方差再决定建不建模”的习惯能帮你省下大量无效调参时间。5.5 PyEMD版本差异导致结果不可复现现象同一个脚本、同一份数据隔一天跑出来的分解结果不同LSTM训练精度也有细微差异。原因PyEMD内部有随机数生成逻辑部分版本EEMD的噪声生成没有固定随机种子每次调用eemd()都会产生不同的分解结果。另外不同版本PyEMD的参数名不完全一致有些版本用noise_width有的版本用width混用时会直接报TypeError。解决固定随机种子并且把分解结果落盘。在创建EEMD对象时如果当前版本支持seed或random_state参数就显式传入不支持的话在最顶部调用np.random.seed(42)同时对每个import版本做好记录。最稳妥的做法是像我第2章代码里那样把分解结果保存成CSV训练时直接读CSV整个过程不再重新分解。这样既保证复现又省去了每次训练都要重新跑一次EEMD的时间成本。6. 把预测误差再压低一档模态合并策略、验证指标与我的检查习惯最后这一层说的是在基础流程跑通之后怎么精进。我常用的一个手段是模态合并把方差贡献极小、频率相近的相邻IMF合并成一个模态降低LSTM子模型的数量。比如风速数据分解出9个IMF前3个高频模态方差贡献合计只有2%单独建模纯属给网络送噪声把这三个模态相加成一个“高频噪声项”直接丢弃或用简单指数平滑处理保留剩下6个模态建模误差反而下降。# 模态合并示例把方差贡献小于1%的前几个高频IMF相加 merge_idx [i for i in range(3) if var_ratio[i] 0.01] if merge_idx: noise_imf np.sum([imfs[i] for i in merge_idx], axis0) # 用零均值替代该噪声项或对噪声做简单阈值截断 noise_imf[np.abs(noise_imf) np.std(noise_imf)] 0验证阶段不要只看训练集的loss要同时看三样东西。第一是RMSE和MAERMSE对大误差敏感能暴露预测是否在某些尖峰处严重偏移MAE反映平均偏移水平两个指标一起看。第二是预测曲线和真实曲线的叠图重点看峰谷处的相位对齐如果预测曲线总是慢半拍问题多半出在滑窗长度不足或数据未平稳化处理。第三是预测残差的白噪声检验用Ljung-Box检验看残差是否还有显著自相关如果残差呈现出明显的周期性说明某个IMF没有被充分建模需要回头调整对应模态的网络结构。我做风速预测时曾经为了多保留一个高频IMF把模型调了三天换来换去各种超参数最后发现丢掉它误差反而小了。踩过这个坑之后我再也不追求把每一个IMF都送进LSTM而是先看方差贡献再决定哪些模态值得建模。这个思路可以在你的项目里直接复用分解跑通后先打印一张各模态方差贡献表开会讨论模型结构时这张表比任何参数曲线都更有说服力。希望帮到你。本文还有配套的精品资源点击获取