EEMD-LSTM时间序列预测:从分解原理到完整源码与参数调优
2026/9/12 22:02:00 网站建设 项目流程

简介:一套完整的Python实现EEMD-LSTM时间序列预测源码与数据资源包,面向计算机、电子信息工程、数学等专业的学生,适用于课程设计、期末大作业和毕业设计等场景。资源基于Anaconda + PyCharm + Python + TensorFlow环境编写,将经验模态分解(EEMD)与长短期记忆网络(LSTM)相结合,可用于处理非平稳、非线性时间序列数据,并给出完整预测流程。资源包共3个文件,包括1个Python源码文件和2个CSV数据集文件,压缩包仅47KB,文件体量轻量、结构简洁。源码采用参数化编程,关键参数方便调整,代码思路清晰,并配有保姆级注释,几乎一行一注释,尤其适合刚接触深度学习的入门者对照学习。目前已有383人学习下载。获取这份资源即可得到可直接运行的预测脚本和配套数据,能帮助理解EEMD分解、LSTM建模及二者结合的完整流程,同时也可作为毕业设计或课程项目的基础框架进行二次扩展。

1. EEMD-LSTM 为什么是时间序列预测的常见组合

真实场景里的时序数据几乎没有一条严格平稳,趋势、多个周期和噪声叠在一起。直接把原始序列丢给 LSTM,模型要同时拟合三种尺度,梯度被高频抖动干扰,表现就是预测滞后、峰值被削平。EEMD-LSTM 的思路是先拆再算:用集成经验模态分解把序列拆成若干本征模态函数和一条残余趋势,LSTM 逐个分量拟合,最后叠加还原。

标题里的「完整源码和数据」落地就是一条流水线:EEMD 分解、滑动窗口构造、LSTM 训练、多分量预测整合。下文按这条主线推进,把集成次数、噪声幅值、lookback、训练轮数这些参数和常见坑讲清楚,拿到的不只是能跑的脚本,而是能换数据换参数的框架。

适合用 Python 做时间序列预测的读者,也适合想对比 EEMD-LSTM 与单一 LSTM、Transformer 方案差异的人。先理解分解,才能定参数,所以第 2 章从 EEMD 讲起。

2. 用 PyEMD 做 EEMD 分解:参数怎么设、分量怎么用

EEMD 的全称是 Ensemble Empirical Mode Decomposition,中文常叫集成经验模态分解。它和傅里叶变换、小波分解最大的区别是没有预设基函数,完全由数据自己驱动:通过局部极值点拟合包络线,把序列按频率从高到低逐层剥离。对非平稳、非线性的时间序列来说,这种自适应分解比固定基变换更贴近真实成分,尤其适合负荷、气象、金融这类趋势和周期混叠的数据。

EMD 的算法核心可以概括为:找出当前信号的局部极大值和极小值,用三次样条分别拟合上下包络,取包络均值后从原信号中减去,再判断剩余部分是否满足 IMF 条件,不满足就继续迭代。一条合格的 IMF 要同时满足两个条件:过零点数与极值点数相差不超过 1;上下包络的均值为 0。分解结果按频率从高到低排列,最后一个分量通常是残余趋势,形态上整体单调、过零率极低。

单独的 EMD 有一个突出问题:数据里存在间歇性成分时,比如一段平稳后突然出现尖峰,不同频率的成分会混进同一个 IMF,也就是模态混叠。EEMD 的改进思路很朴素:把白噪声多次加入原始序列,每次重新做一次完整 EMD,最后按位置取平均。每次加入的噪声为间歇成分提供了统一的参照尺度,多次平均后随机噪声互相抵消,模态混叠被明显抑制。代价是计算量乘以 trials 倍,这也是第 4 章要控制 IMF 数量的原因。

2.1 PyEMD 的安装与最小分解代码

Python 里做 EEMD 最常用的库是 PyEMD,pip 安装名是 EMD-signal,导入路径却是from PyEMD import EEMD,这个不一致会卡住不少人。

pip install EMD-signal

PyEMD 依赖 numpy 和 scipy,scipy 版本太旧时三次样条插值接口可能不兼容,建议在 python 3.9 以上的环境里安装,顺手把 numpy 升级到 1.23 左右。装好后先用一段仿真数据验证分解逻辑,信号由低频正弦、高频正弦、线性趋势和高斯噪声四部分叠加,分解后理论上能被拆开:

import numpy as np from PyEMD import EEMD np.random.seed(0) t = np.linspace(0, 1, 500) data = (np.sin(2 * np.pi * 5 * t) + 0.5 * np.sin(2 * np.pi * 20 * t) + 0.2 * t + np.random.normal(0, 0.05, len(t))) eemd = EEMD() eemd.trials = 50 eemd.noise_width = 0.05 eemd.noise_seed(42) imfs = eemd.eemd(data) print('分量数:', imfs.shape[0]) print('重构误差:', np.max(np.abs(data - imfs.sum(axis=0)))) for i, imf in enumerate(imfs): print(f'IMF{i}: 均值={imf.mean():.4f} ' f'过零次数={np.sum(np.diff(np.sign(imf)) != 0)}')

逻辑说明:trials是集成次数,每加入一组随机白噪声就做一次完整 EMD,最后逐点平均;noise_width是白噪声标准差相对信号标准差的比值;noise_seed(42)固定随机噪声序列,保证结果可复现。imfs是形状为 (分量数, 样本数) 的二维数组,每行一个 IMF,最后一行通常是趋势残余。

参数说明:imfs.sum(axis=0)把全部分量相加,和原始数据的最大误差在 1e-8 量级,说明分解是完整的。分量数不是手动指定的,EEMD 按信号复杂度自动决定,这组仿真数据一般分出 5 到 6 个分量。需要注意eemd.eemd()不接受 NaN,数据里有缺失必须先插值补全,否则整条分解结果都会错位。

2.2 最值得调的三个 EEMD 参数

EEMD 的可调参数不多,真正影响分解结果的就是三个,其余保持默认即可。

参数常用范围作用与调参方向
trials30–100集成次数。误差波动大、模态混叠重时加大;计算成本线性上升
noise_width0.01–0.2噪声幅值占比。间歇尖峰明显的序列取 0.1 以上,较平稳的取 0.02–0.05
noise_seed固定整数不固定则结果不可复现,对比实验必须固定

默认的 trials=100、noise_width=0.05 对大多数负荷、流量、传感器数据都能直接工作。我一般先用 trials=30 快速跑一次,确认趋势分量形态合理后,再正式用 80 以上的 trials 做训练。

噪声幅值的判断经验:从信号标准差的 5% 起步。如果相邻 IMF 的频段明显重叠,或者最高频分量的方差异常大,再把 noise_width 上调到 0.1 以上。噪声过大时重构误差虽然还是很小,但每个 IMF 都被噪声污染,单个分量的信噪比下降,反而拖累后面的 LSTM。

2.3 IMF 筛选、合并与端点效应处理

分解完第一件事不是直接喂给 LSTM,而是筛分量。判断维度有三个:均值是否接近 0、过零率高低、方差贡献率。用np.var(imf) / np.var(data)计算每个分量的方差贡献,累计贡献超过 99% 之后剩下的极高频分量基本是噪声,直接舍弃或合并。常见做法是当 IMF 数量超过 7 个时,把前两三个高频分量相加合并成一项,只对合并项训练一个 LSTM,模型数量能减少三分之一以上。

端点效应需要单独强调。EEMD 的包络拟合依赖端点附近的极值,序列两端的分量经常被甩出合理范围,这就是端点飞翼。预测的目标值恰好落在序列尾部,如果分解区间刚好截止在训练集末尾,最后几个点的高频 IMF 会明显畸变,预测自然不准。缓解方法有两个:预测时把训练集和待预测段拼接成完整序列一起分解,让算法在更长的数据上确定边界;或者在训练集后再补一段同分布历史数据再分解,预测结束后只截取需要的部分。

提示:固定 noise_seed 后单次结果可复现,但 trials 不同时分解结果仍有细微差别。评估模型时同一组参数跑三次取平均,不要拿单次结果下结论。

3. LSTM 输入构造:滑动窗口、归一化与训练集划分

很多人换一个数据集就复现失败,问题大多不在模型,而在输入构造。LSTM 的输入是三阶张量 (样本数, lookback, 特征数),窗口怎么切、归一化怎么算、训练集怎么分,这三件事定了,结果就定了七八成。

3.1 归一化先做:scaler 只在训练集上 fit

LSTM 默认用 tanh 激活,输入不缩放到 [0,1] 区间,梯度更新会很不稳定。尤其是 EEMD 分解后的高频 IMF,幅值可能只有 0.01 量级,而趋势分量幅值是它的几百倍,共用一个未归一化的输入,模型大概率不收敛。

from sklearn.preprocessing import MinMaxScaler def scale_series(train, val=None): scaler = MinMaxScaler(feature_range=(0, 1)) train_scaled = scaler.fit_transform(train.reshape(-1, 1)).flatten() if val is not None: val_scaled = scaler.transform(val.reshape(-1, 1)).flatten() return train_scaled, val_scaled, scaler return train_scaled, scaler

逻辑说明:fit_transform只作用在训练序列上,transform只做缩放、不更新 scaler 内部的 min/max。如果对验证集也调用 fit,验证集的最大最小值会污染缩放器,造成信息泄露,离线指标会比线上表现虚高一截。每条 IMF 都应保有自己的 scaler,原因在第 4 章多分量叠加时展开。

参数说明:reshape(-1, 1)把一维序列变成单列,是 sklearn 接口的固定要求。预测结束后用同一个 scaler 的inverse_transform还原到原尺度,所有叠加操作都应在还原之后进行。

3.2 滑动窗口与 lookback 的选择依据

单步预测的样本构造很直接:用连续的 lookback 个历史点预测后一个点。窗口函数最通用的写法如下:

def make_windows(series, lookback, step=1): X, y = [], [] for i in range(0, len(series) - lookback, step): X.append(series[i:i + lookback]) y.append(series[i + lookback]) return np.array(X), np.array(y)

逻辑说明:循环从 0 开始取连续 lookback 个点作为 X,后紧跟的一个点作为 y。step 控制窗口滑动步长,step=1 表示重叠滑动,样本量最大但相邻样本高度相关;step=lookback 表示完全不重叠,样本独立性更好。喂给 LSTM 之前需要把 X reshape 成 (样本数, lookback, 1),最后一个维度是特征数,单特征就是 1。

lookback 不要拍脑袋,优先按主周期定:

数据场景主周期推荐 lookback
小时级电力负荷24 小时24 或 48
日度商品销量7 天7 或 14
分钟级设备振动一个工作循环一个循环的采样点数

原则是最少覆盖一个完整主周期。覆盖两个周期以上时收益很小,反而减少样本量、拉长训练时间,模型学到的主要是平滑效果而不是周期规律。

3.3 为什么时间序列不能随机划分

直接用 sklearn 的train_test_split()切时间序列是新手最常见的错误。随机划分会把未来的点混进训练集,LSTM 学到的是“和哪些位置相似”而不是真实的时序演变规律,验证集指标会虚高得很明显。

n = len(scaled) train_end = int(n * 0.7) val_end = int(n * 0.85) train_seq = scaled[:train_end] val_seq = scaled[train_end:val_end] test_seq = scaled[val_end:] # 先切完整序列,再各自建窗 train_X, train_y = make_windows(train_seq, lookback) val_X, val_y = make_windows(val_seq, lookback)

逻辑说明:先对完整序列按时间顺序切片,再对每一段各自调用make_windows()。这样验证集样本仍然能看到训练集尾部的历史信息,和线上“用历史预测未来”的推理方式一致。如果先建窗再切分,验证集样本可能会吞进训练集尾部的窗口,评估结果虽然也能跑,但反映的是窗口泄露后的效果。

切分比例上 70/15/15 是常见起点。数据总量低于 500 个点时,验证集占比可以降到 10%,但测试集不要低于 10%,否则误差指标波动太大,看不出不同模型之间的差异。

4. EEMD-LSTM 完整预测流程:多步预测与参数调优

前两章是准备,这一章把整条流水线串起来。EEMD-LSTM 的核心结构是:对每条 IMF 各训练一个 LSTM,分别预测后再按时间点求和。结构本身不难,但细节决定成败。

4.1 为每条 IMF 构建独立 LSTM 模型

每条 IMF 的频率尺度不同,高频分量变化剧烈,低频分量平滑,共用模型可以,但共用一次训练不推荐。独立建模的优点是每个模型只学一个尺度的模式,参数可以单独调,缺点是模型数量和训练时间翻倍。IMF 数量在 5 个以内时,独立建模明显占优。

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from tensorflow.keras.callbacks import EarlyStopping def build_lstm(lookback, units=64): model = Sequential([ LSTM(units, activation='tanh', input_shape=(lookback, 1)), Dense(1) ]) model.compile(optimizer='adam', loss='mse') return model def train_imf_model(imf, lookback, units=64, epochs=100, val_ratio=0.15): X, y = make_windows(imf, lookback) X = X.reshape((X.shape[0], X.shape[1], 1)) split = int(len(X) * (1 - val_ratio)) early_stop = EarlyStopping( monitor='val_loss', patience=10, restore_best_weights=True ) model = build_lstm(lookback, units) model.fit( X[:split], y[:split], validation_data=(X[split:], y[split:]), epochs=epochs, batch_size=32, callbacks=[early_stop], verbose=0 ) return model

逻辑说明:input_shape=(lookback, 1)里的 1 是特征数,EEMD 之后每个分量是单特征,所以是 1。EarlyStopping在验证损失连续 10 轮不下降时停止训练,并回滚到最佳权重,避免 LSTM 过拟合。patience 太小会在训练不充分时提前退出,一般取 10 到 20。

参数说明:units 取 32 到 128 之间。趋势类分量非常平滑,用小 units 拟合反而更可靠;高频分量可以适当加大 units 去拟合细节。batch_size 对短序列用 32,序列超过 5000 个点再考虑 64 或 128。新版 TensorFlow 里 keras 的导入路径可能变成from keras ...,两种写法目前都可用,但要保持一致。

4.2 多步预测的递归法与直接法

单步模型只能预测下一个点,要预测未来 H 个点有两种做法。递归法把上一步的预测值当作输入再预测下一步,实现最简单,但误差逐点累积,预测步数越长偏差越大。直接法让输出层一次性输出 H 个点,适合中等长度的预测范围:

def build_multi_lstm(lookback, horizon, units=64): model = Sequential([ LSTM(units, activation='tanh', input_shape=(lookback, 1)), Dense(horizon) ]) model.compile(optimizer='adam', loss='mse') return model def make_multi_windows(series, lookback, horizon): X, y = [], [] for i in range(len(series) - lookback - horizon + 1): X.append(series[i:i + lookback]) y.append(series[i + lookback:i + lookback + horizon]) return np.array(X), np.array(y)

逻辑说明:make_multi_windows()的 y 从窗口后的第一个点一直取到第 horizon 个点,输出层Dense(horizon)对应这 H 个输出,每个神经元负责一个预测步长。直接法的优点是误差不累积,缺点是各步输出互相独立,长序列预测时曲线可能出现轻微跳动。

选择经验:预测步数不超过 5 步用递归法,代码量小且曲线自然;预测 5 到 30 步用直接法;超过 30 步就要考虑滚动重新预测,任何模型一次性外推太长都会漂移。

4.3 分解-预测-叠加的完整函数

把分解、训练、递归预测、叠加整合成一个函数。这里的关键细节是:分解时用训练集加测试集的完整序列,避免测试段端点飞翼;但训练 LSTM 时只用训练段。

def eemd_lstm_forecast(train, test, lookback, horizon, trials=50, noise_width=0.05): full = np.concatenate([train, test]) eemd = EEMD() eemd.trials = trials eemd.noise_width = noise_width eemd.noise_seed(42) imfs = eemd.eemd(full) forecasts = np.zeros((imfs.shape[0], horizon)) for i in range(imfs.shape[0]): train_part = imfs[i][:len(train)] train_scaled, scaler = scale_series(train_part) X, y = make_windows(train_scaled, lookback) X = X.reshape((X.shape[0], X.shape[1], 1)) model = build_lstm(lookback) model.fit(X, y, epochs=100, batch_size=32, verbose=0) seed = train_scaled[-lookback:].reshape(1, lookback, 1) preds = [] for _ in range(horizon): p = model.predict(seed, verbose=0)[0, 0] preds.append(p) seed = np.append(seed[:, 1:, :], [[p]], axis=1) forecasts[i] = scaler.inverse_transform( np.array(preds).reshape(-1, 1) ).flatten() return forecasts.sum(axis=0)

逻辑说明:外层循环对每个 IMF 完整做“缩放-建窗-训练-递归预测”。train_part = imfs[i][:len(train)]保证模型只看训练段;seed取训练段最后 lookback 个点作为递归起点。每次预测出 p 后,用np.append(seed[:, 1:, :], [[p]], axis=1)把窗口整体左移一位,新预测值补到末尾,实现滚动预测。

参数说明:forecasts[i]是第 i 个分量的 horizon 步预测,遍历完所有 IMF 后按行求和,就是最终的 EEMD-LSTM 预测序列。函数总耗时约等于 IMF 数量乘以单个 LSTM 训练时间,IMF 超过 7 个时先按第 2 章的方法合并高频分量。

4.4 训练时最常见的四个坑

第一,每条 IMF 的幅值差异极大,必须在训练前各自归一化,预测后用各自的 scaler 单独还原。上面代码里scale_series()对每条 IMF 单独调用,就是因为高频分量和趋势分量不在同一个量纲上。

第二,趋势分量(最后一个 IMF)单调递增或递减,LSTM 学这种近似直线的序列很吃力,外推容易飘。常见做法是把趋势分量单独拎出来,先做一阶差分变成平稳序列再送 LSTM,或者干脆用线性回归外推。EEMD 相关的文献里趋势项的处理方式五花八门,实操中用线性外推最省事也最可靠。

第三,验证集损失持续下降但预测曲线是一条水平线,通常出在信噪比过低的高频 IMF 上,模型学到的是输出均值。处理方法是直接丢弃该分量,而不是加复杂网络结构去硬拟合噪声。

第四,数据量太小时 LSTM 的优势发挥不出来。训练点数少于 300 时,每条 IMF 用指数平滑或线性回归就够了,强行上 LSTM 只会过拟合。

参数起点值调整方向异常时的表现
lookback一个主周期周期长则增大过小丢失周期,过大样本骤减
units64数据复杂用 128过拟合、训练变慢
batch_size32长序列用 64收敛慢但显存占用小
epochs100 配早停数据量大用 200无早停时严重过拟合
trials50指标波动大用 100分解不稳定,结果不可复现

5. 分解质量检查与预测误差的验证技巧

5.1 一次算齐 RMSE、MAE、MAPE

三个指标足够覆盖大多数预测任务,代码一次算完:

from sklearn.metrics import mean_squared_error, mean_absolute_error def evaluate(y_true, y_pred): rmse = np.sqrt(mean_squared_error(y_true, y_pred)) mae = mean_absolute_error(y_true, y_pred) mape = np.mean(np.abs((y_true - y_pred) / (y_true + 1e-8))) * 100 return rmse, mae, mape

逻辑说明:RMSE 对大偏差敏感,适合惩罚峰值预测失败;MAE 反映平均偏差水平;MAPE 是相对误差,但真实值接近 0 时会被分母放大,分母加 1e-8 只是兜底。序列含零值时改用 sMAPE。对比时要同时跑一个不加 EEMD 的纯 LSTM,判断 EEMD 有没有用要看两个 RMSE 的差值,而不是看绝对数值。

5.2 分解质量的快速体检

分解质量不用画图也能快速判断。第一,重构误差:用np.max(np.abs(data - imfs.sum(axis=0)))检查,超过 1e-4 时说明 trials 不够或数据里有缺失。第二,过零率顺序:高频 IMF 的过零次数应该明显高于低频分量,顺序反了说明模态混叠没有压住。第三,趋势占比:趋势分量的方差贡献超过 50% 时,数据本身是趋势主导,EEMD 对周期成分的剥离意义有限,应该先做差分再分解。

5.3 walk-forward 回测与随机性验证

最后一个技巧最贴近上线。一次性切出测试集只能证明离线效果,真实场景是数据不断进来。walk-forward 回测的做法是每预测 H 步就把真实值并入历史,重新分解、重新训练,再预测下一段,最终把各段预测拼接起来。它与一次性测试集结果的差距,直接反映模型在数据分布缓慢变化时的退化速度。

另一个验证是 EEMD 随机性的影响。固定 noise_seed 后单次结果可复现,但换一个 seed 指标可能明显浮动。同一份数据换 3 个 seed 各跑一次,如果 RMSE 的标准差超过均值的 10%,说明分解随机性没压住,先加大 trials 到 100 以上,再回头看 LSTM 的超参数。这个顺序不要反,多数“模型不稳定”的结论其实出在分解这一步,而不是网络结构。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询