☰
EMD-LSTM时间序列预测:分解非平稳序列提升LSTM预测精度
2026/10/1 10:33:13 网站建设 项目流程

简介:一份基于EMD与LSTM结合的时间序列预测Python完整方案,面向计算机、电子信息、数学等专业的大学生,可服务课程设计、期末大作业和毕业设计,也适合刚接触深度学习时序预测的初学者。代码采用参数化编程,关键参数方便修改,几乎每一行都有注释,能帮助读者理解经验模态分解(EMD)与长短期记忆网络(LSTM)如何协作完成预测任务。压缩包共3个文件,包含1个Python脚本和2个csv数据集,整体仅47KB,轻量便携;csv数据以焦作等真实记录为例,可直接运行复现。当前已有475人学习下载。读者拿到后,可使用PyCharm与Anaconda环境直接打开,对照保姆级注释掌握数据读取、EMD分解、LSTM训练以及结果评估的完整链路;同时数据文件可按需替换,便于迁移到自己的数据集上做进一步实验,对撰写课程报告或毕业设计论文有较强参考价值。

1. 为什么EMD-LSTM能预测非平稳序列:一条数据拆出趋势再学拐点

做电力负荷预测时我把原始负荷序列直接丢给LSTM,验证集损失怎么也降不到预期水平,画出来的预测曲线几乎贴着均值走,完全没抓住尖峰和拐点。后来我意识到问题不在模型,而在数据——原始序列里趋势、周期、突发扰动全混在一起,LSTM同时学这些东西,梯度互相拉扯,结果学成了均值回归器。EMD-LSTM时间序列预测的思路是先做经验模态分解,把原始序列拆成若干个从高频到低频的本征模函数(IMF)加一个残差趋势项,让每个LSTM只学一个分量的规律,最后把各分量的预测叠加回去。这个方案对电力负荷、风速、水位、流量、价格这类带明显趋势和周期成分的非平稳数据特别合适,网上能找到不少开源的完整源码和数据来对照复现,下文我会把每一步做法和参数边界摊开讲。

2. EMD分解的参数与边界:把信号拆成可预测的IMF序列

2.1 EMD到底在做什么:把混合信号按频率“剥洋葱”

经验模态分解(EMD)不像小波变换那样需要预设基函数,它完全由数据自身驱动。分解过程一句话概括:找到信号的所有局部极大值点和极小值点,分别用插值拟合成上包络线和下包络线,取上下包络的均值作为一条低频中心线,用原始信号减去这条中心线,就得到第一轮筛分结果。这个减法要反复做,直到剩下的分量满足两个条件:极值点数量与过零点数量相等或最多差一个,上下包络的均值趋近于零。满足条件后,这个分量就是第一阶IMF,通常对应原始信号里频率最高的成分。把这一阶IMF从剩余信号里去掉,对剩余部分继续做同样的筛分,就能依次得到第二阶、第三阶IMF,直到剩余部分单调或幅度极小。

为了看得直观,可以用PyEMD库在本地数据上做一次分解,观察每个IMF的形态和振幅分布:

import numpy as np import pandas as pd from PyEMD import EMD df = pd.read_csv("series.csv") data = df["value"].to_numpy(dtype=float) emd = EMD() emd.max_imfs = 8 # 限制分解层数,防止过度细化 emd.sift_iters = 30 # 每层筛分的迭代上限 imfs = emd(data) # 返回 (n_imfs, n_points) 的二维数组 print("IMF shape:", imfs.shape) for i, imf in enumerate(imfs): print(f"IMF {i}: mean={imf.mean():.4f}, std={imf.std():.4f}")

这里imfs.shape的n_imfs表示实际分解出几层,n_points等于原始序列长度。打印的均值接近零说明筛分收敛,标准差逐层变小说明IMF确实在按频率从高到低剥离。实际业务里,高频IMF的标准差往往很小但波动剧烈,低频IMF平滑但振幅大,LSTM对这两类的学习难度完全不同,这就是后面要逐IMF单独建模仿的原因。

2.2 三个必调参数:最大IMF数、筛选迭代次数、端点处理

用PyEMD跑分解时,真正需要关注的参数就三个,下面这个表是按我自己的使用经验整理的,不是官方标准答案。

参数作用我常用的设置什么时候调整
max_imfs限制最多分解出几阶IMF8 ~ 10数据有明确周期成分时适当调大;序列短就调小避免过拟合
sift_iters每层筛分的最大迭代次数20 ~ 50数据特别平滑时调小;波动复杂时调大
Sift停止条件判断包络均值是否足够接近零默认即可遇到分解出纯正弦形状的分量时收紧条件

max_imfs是最值得手调的参数。分解层数如果太多,高频IMF会被拆成零碎的近似噪声,LSTM在它上面学到的全是随机波动,对最终的预测贡献很小,反而拖慢训练;分解层数太少,趋势和低频周期没分开,LSTM又要面对非平稳输入。我一般先用默认参数跑一次,看打印出来的IMF数量,再通过max_imfs把它限制在8层以内。

关键在于端点处理。EMD用三次样条插值拟合包络时,序列两端往往没有足够的极值点,包络线会在端点处发散,导致两端部分的IMF变形。这个现象在做预测时特别致命,因为测试集正好就在数据末端。常见做法是在序列两端各延长一段数据参与分解,预测完成后再把两端切掉;或者用镜像延拓,把端点附近的波形对称复制一段出去。我一般倾向于在数据末尾多留20到30个点作为缓冲区,跑完分解和预测后只取需要的那一段。

2.3 当分解结果不稳定:模态混叠与边界

EMD的分解结果依赖极值点分布,数据发生微小变化时,极值点会增减,某一个频率成分就可能在不同IMF之间来回跳动,这就是模态混叠。典型的例子是:一个稳定的日周期信号,在某些时段被随机噪声打断,原本该落在同一阶IMF里的周期成分,被拆成了两阶,每阶都带着一部分周期形态。

模态混叠对LSTM训练的影响是直接的——同一个业务周期被拆散后,两个IMF里都有它的碎片,LSTM分别去学两个碎片,学习的是一半规律,叠回来看整体误差仍然偏高。处理办法是用EEMD(集合经验模态分解)替代EMD,通过多次加噪声分解取平均来稳定结果,这部分放在第6章展开。除此之外还要意识到,EMD不适合在线场景:每次新增一个数据点,整个数据段重新分解后历史IMF轨迹也会改变,之前训练好的LSTM拿到的输入分布就变了。所以EMD-LSTM更适合离线批量预测,业务侧有新增数据时定期重训一次模型。

3. 把EMD和LSTM接起来:逐IMF建模与信号重构的完整流程

3.1 两种接法:逐IMF单独建模与多IMF拼接

EMD分解完成之后,接LSTM有两条常见路线。第一条是逐IMF建模:把得到的每个IMF分别作为一条单变量序列,各自归一化,各自训练一个LSTM,预测出每个IMF的下一段值,最后把所有IMF的预测结果逐点相加,再加上最后一个趋势项的预测值。第二条路线是把所有IMF按列拼成一个多维输入矩阵,喂给同一个LSTM,让模型自己学IMF之间的关系。

我一般用逐IMF建模。原因有三个:每个IMF的尺度和波动模式差异很大,高频IMF振幅小、噪声重,低频IMF平滑、趋势性强,分开建模可以给不同IMF配不同的归一化范围和训练参数;拼接输入的LSTM容易被振幅大的低频IMF主导,高频IMF的信息几乎没有梯度;逐IMF建模还能单独检查每个分量的预测质量,业务上要写预测依据时也方便说明。代价是训练时间随IMF数量成倍增加,这个后面聊到坑时再展开。

3.2 从分解到预测的完整源码:直接抄

下面这段代码是逐IMF建模的完整流程,读进来一份只有时间顺序的CSV,输出每个IMF的测试集预测结果,并在最后叠加成最终预测。

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 df = pd.read_csv("series.csv") data = df["value"].to_numpy(dtype=float) # 1. EMD分解 emd = EMD() emd.max_imfs = 8 imfs = emd(data) n_imfs = imfs.shape[0] # 2. 构造监督学习样本:用前steps个点预测后1个点 def make_dataset(seq, steps=12, horizon=1): X, y = [], [] for i in range(len(seq) - steps - horizon + 1): X.append(seq[i:i + steps]) y.append(seq[i + steps + horizon - 1]) return np.array(X), np.array(y) window_len = 12 pred_list = [] for i in range(n_imfs): # 每个IMF单独归一化,避免不同尺度互相压制 scaler = MinMaxScaler(feature_range=(0, 1)) s = imfs[i].reshape(-1, 1) s_scaled = scaler.fit_transform(s).flatten() X, y = make_dataset(s_scaled, steps=window_len, horizon=1) split = int(len(X) * 0.8) X_train, X_test = X[:split], X[split:] y_train, y_test = y[:split], y[split:] X_train = X_train.reshape((X_train.shape[0], window_len, 1)) X_test = X_test.reshape((X_test.shape[0], window_len, 1)) # 3. 每个IMF一个LSTM,结构保持一致 model = Sequential() model.add(LSTM(32, activation="tanh", input_shape=(window_len, 1))) model.add(Dense(1)) model.compile(optimizer="adam", loss="mse") model.fit(X_train, y_train, epochs=30, batch_size=32, verbose=0) # 4. 预测并反归一化 y_pred = model.predict(X_test, verbose=0).flatten() pred = scaler.inverse_transform(y_pred.reshape(-1, 1)).flatten() pred_list.append(pred) # 5. 所有IMF预测逐点相加,得到最终序列 final_pred = np.sum(pred_list, axis=0)

几个关键参数说明:window_len=12表示用前12个历史点预测下一点,如果数据是小时级,12就代表过去12小时;如果是日级,12就是过去12天。horizon=1是单步预测,想预测未来多步可以把horizon调大,但样本量会明显减少,需要配合滚动预测使用。split=0.8保证前80%的数据进入训练,后20%完全没参与拟合,用来观察模型在未知段上的表现。每个IMF都用独立的MinMaxScaler,这一步非常关键——高频IMF的振幅可能只有0.01,低频IMF的振幅可能是几十,全局缩放会把高频分量压到几乎没有梯度。

要注意的是,final_pred的长度等于y_test的长度,也就是测试集能构成的样本数,不是原始序列末端的长度。拿到final_pred后,要跟y_test对齐比较,不能直接把它的下标映射回原始数据。

3.3 信号重构后的验证指标:不能只看RMSE

预测结果叠加回来之后,很多人只看RMSE,但RMSE对滞后非常不敏感。一个只把上一时刻真实值复制过来充当预测结果的模型,RMSE往往很低,因为它和真实曲线只差了一个时间步,这个误差在数值上并不大。要同时看三个指标:RMSE、MAE,以及预测序列与真实序列的相关系数。

from sklearn.metrics import mean_squared_error, mean_absolute_error # 注意:y_test 来自make_dataset,长度和final_pred一致 rmse = np.sqrt(mean_squared_error(y_test, final_pred)) mae = mean_absolute_error(y_test, final_pred) corr = np.corrcoef(y_test, final_pred)[0, 1] print(f"RMSE={rmse:.4f}, MAE={mae:.4f}, Corr={corr:.4f}")

如果corr接近0.99,但RMSE和MAE都偏大,说明形状很吻合,只是幅度有系统性偏差;如果RMSE不大但corr只有0.8左右,说明预测整体滞后或者相位偏了,这时候要把预测结果往前平移一步再算一次相关,以判断滞后是否是主要误差源。滞后问题在时序预测里非常常见,第5章会专门讲它怎么来的。

4. 数据预处理与训练配置:归一化、窗口切分与损失函数选型

4.1 归一化:每个IMF单独处理,别用全局min/max

归一化范围的选择直接影响LSTM能否学到有效特征。对LSTM这种用tanh或sigmoid做激活的网络来说,输入落到0到1之间通常最稳定。EMD场景下的特殊之处在于,每个IMF的物理幅度差异很大,而且高频IMF往往只是低频IMF振幅的百分之一,如果对整条原始序列做一次全局MinMaxScaler,高频IMF里的信息会被压缩到小数点后三位,LSTM连梯度都传不动。

正确做法就是第3章代码里写的:每个IMF用一个独立的MinMaxScaler,在训练段上fit_transform,在测试段上只调用transform。这一点下文马上展开,因为它是数据泄露的高发点。另外,对最后一个趋势项(余项)也要单独归一化,它可能是单调递增或递减的,数值范围往往比所有IMF都大。

一个常见误用是直接把所有IMF堆叠成一个矩阵,然后用一个scaler对整个矩阵做归一化。这样做会导致每一列的分布被其他列牵扯,反变换的时候还要记住每个列对应的拟合参数,出错率很高。我建议宁可多写几行代码,每个分量独立处理。

4.2 训练集划分与时间窗:怎么选窗口长度

数据划分只有一个铁律:先划分,后归一化。训练段负责拟合scaler的min和max,测试段只能使用训练段算出来的结果做变换,不能自己再fit一次。如果测试段里的最大值被写进了scaler,模型的输入分布里就带了未来信息,测试集的RMSE会虚低,上线以后立刻现原形。

窗口长度window_len的选择有一个经验底线:至少要覆盖数据中一个完整的主要周期。日周期数据用24,周周期数据用168,月周期数据用720。如果业务上不允许窗口太长,可以用LSTM的序列到序列结构来压缩历史长度,但这也意味着要同时调整模型结构,不能用简单的单层LSTM糊弄。窗口太短,比如只有4到6个点,LSTM很难捕捉到尺度稍大的波动;窗口太长,比如超过一个月的日数据,训练样本数量会大幅减少,模型容易过拟合。

另一个容易被忽略的点是,训练集和测试集的切分要按时间顺序切,不能随机打乱。随机打乱会让未来的样本混进训练集,这在时间序列预测里是致命的。如果想把更多数据用于训练,可以改成滚动时间窗验证:把数据集分成多段,每次用前n段训练、第n+1段验证,逐步向后滚动。

4.3 损失函数与训练策略:回归别只用MSE

LSTM回归预测默认用MSE,但逐IMF建模时不是每个IMF都适合MSE。高频IMF包含大量尖峰和离群点,MSE会把梯度集中在少数离群点上,模型会花大量容量去拟合尖峰,导致其余部分的预测变得平滑。低频IMF则相反,曲线平滑,MSE表现稳定,不需要换损失函数。

IMF特征推荐损失学习率参考说明
低频平滑趋势MSE1e-3收敛快,拟合稳定
中频周期波动Huber1e-3对偶发尖峰鲁棒
高频噪声主导Huber或MAE1e-4学习率调低避免过拟合噪声

Keras里切换到Huber损失很容易,model.compile(optimizer="adam", loss="huber")即可。学习率方面,我习惯为高频IMF单独配置更低的学习率,因为高频IMF的样本方差大,默认学习率容易让loss反复震荡。实操上可以为每个IMF直接设置一个基础学习率,再在高频分量上除以10。

训练轮数也值得单独讲。高频IMF噪声大,训练集loss和验证集loss经常在某个epoch之后开始背离,这是过拟合的开始。不要对所有IMF统一用同样的epoch数,低频IMF可能30个epoch就收敛,高频IMF往往在20轮左右就开始学噪声了。最可靠的做法是给每个模型加一个EarlyStopping回调,监听验证集loss,连续几个epoch不下降就停止。

提示:Keras的model.fit默认为每个epoch结束输出进度条,逐IMF训练时建议把verbose=0关掉,统一在训练结束后打印该IMF的验证集loss,否则跑十几个IMF时日志会被刷得没法看。

5. EMD-LSTM常见翻车现场:5个容易忽略的坑

5.1 预测曲线整体滞后一拍:把真实值当成了输入的一部分

现象:测试集上RMSE很低,但把预测曲线和真实曲线叠在一起看,预测曲线明显整体向右平移了一个时间步,峰和谷全部错位。如果不画图只看数字,这个问题会被很好地掩盖。

原因:训练样本构造的是用t时刻及以前的数据预测t+1时刻,LSTM在训练中会发现最简单的路径是拿最近的一个值直接当作下一时刻的预测,因为真实值本身就存在强自相关,这个“捷径”能让loss很快下降。

解决:第一步是引入滞后评估,把预测结果和真实值错位对齐后再算RMSE,如果对齐后RMSE大幅下降,说明模型学会了复制而不是预测。第二步是改用多步预测,比如预测未来第2、第3个点,让模型无法依赖最近值;或者给输入窗口加时间序列特征,比如把窗口内数据的差分、趋势斜率作为额外输入,弱化直接复制最近值的倾向。我一般会在评估时先做错位对齐,判断模型是真预测还是假预测,再决定要不要调整训练目标。

5.2 端点发散:最后一段的预测误差莫名爆炸

现象:整体表现不错,唯独测试集末端一段误差明显偏大,而且误差大小和IMF频率相关,高频IMF的末端发散最严重。

原因:EMD分解时序列末端极值点少,三次样条包络在两端会向外发散,末端的数据段被分解成畸变的IMF。LSTM学的是畸变后的规律,在末端预测自然失真。

解决:在分解之前,先在原始数据末尾追加一段镜像数据或延伸数据,把序列加长,分解完成后预测末端再截断。另一个办法是在归一化和建模时,把原始数据的后一段单独切出来作为验证集,训练时完全不碰它,这样能提前暴露末端发散问题,但无法根治。对于要求高的业务,直接换EEMD更省心。

5.3 loss很低但预测是一条直线:模型学到了输出均值

现象:训练集和验证集loss都低到看起来不错,但是预测曲线几乎是一条水平线,只在很小的范围内波动,完全没跟上真实曲线。

原因:回归任务中MSE的最优解是条件均值。当输入窗口内的信息不足以支持预测时,LSTM会退化成输出训练集目标均值的常数预测器。特别是在高频IMF上,如果窗口太短,噪声又大,模型找不到输入和输出之间的稳定关系,就会选择保守策略。

解决:先检查预测值的标准差,如果预测值标准差明显小于真实值的标准差,基本可以确定模型在输出均值附近。然后按顺序排查:窗口长度是否过短,数据是否被过度归一化压平,IMF选择是否包含了有意义的信息。高频IMF如果本身就是噪声,可以考虑直接丢弃,不给它配模型。

5.4 分解结果不稳定:每次运行IMF数量都不一样

现象:同一份数据在同一台机器上跑两次,EMD分解出的IMF数量不一致,或者某几阶IMF的形态差异明显,导致训练出来的模型指标忽高忽低。

原因:EMD筛分过程中极值点分布对数据微小差异敏感,不同的sifting迭代次数或包络插值细节会导致某一步多筛出一层或少筛出一层。这种不稳定性会让整个EMD-LSTM流程缺乏可复现性。

解决:固定随机种子是基本操作,对numpy、random、tensorflow都要设seed,保证模型初始化一致。然后固定max_imfs和sift_iters,不要用默认值跑一次就结束。更彻底的办法是切换到EEMD,多次加噪分解取均值,IMF数量稳定性会好很多。

5.5 模型太多训练慢:生产上跑不动

现象:分解出10个IMF,每个IMF训练一个LSTM,数据量大时要等很长时间才能出结果;把这个流程部署成定时任务后,运行时长超出了业务可接受范围。

原因:逐IMF建模的复杂度是线性的,IMF数量越多,模型数量越多,再加上每个模型内部还有epoch和batch的乘法关系,训练时长很容易失控。

解决:第一优先是分层丢弃,把标准差极小、自相关几乎为零的高频IMF直接去掉,只对前四到五阶低频IMF建模型;第二是合并弱分量,把多个高频IMF相加成一个分量再建模,因为单个高频IMF可预测性低,合起来反而平滑;第三是给所有模型统一用EarlyStopping,训练到收敛就停,避免无谓的固定epoch开销。生产环境里我通常会把IMF数量压到5个以内。

6. 进阶验证:用EEMD抑制模态混叠并量化预测可靠性

6.1 把EMD换成EEMD:加噪声取平均的代价

如果你的预测结果存在第5章里提到的分解不稳定问题,可以直接把PyEMD的实现从EMD换成EEMD。EEMD的做法是多次向原始数据加入有限幅度的白噪声,对每条加噪后的序列分别做EMD分解,最后把所有分解结果按IMF阶数取平均。噪声在多次平均中互相抵消,极值点分布被扰动后,模态混叠问题得到明显抑制。

from PyEMD import EEMD data = df["value"].to_numpy(dtype=float) eemd = EEMD() eemd.trials = 50 # 加噪分解次数,越多越稳定,但越慢 imfs = eemd(data)

trials=50是我常用的值,数据越长,可以适当降到20到30次以节省时间。要付出的代价很直白:分解耗时变成原来的几十倍,而且EEMD分解出的IMF数量同样需要用max_imfs限制。如果离线批量预测对时间敏感,可以先在样本数据上对比一次EMD和EEMD的结果,如果两者的验证集RMSE差距不大,保留EMD省时间;如果EEMD在测试集上的稳定性明显更好,我建议为了业务可信度接受这个计算成本。

6.2 滚动多步预测与稳定性检验

业务预测通常不是只预测下一步,而是要预测未来一段窗口。滚动预测的做法是把前一步的预测值当作输入回填到窗口里,继续预测后面的点。对逐IMF模型,每个IMF都要做同样的滚动,但代码逻辑完全一致:

def roll_forecast(model, last_window, steps, scaler): window = last_window.copy() preds = [] for _ in range(steps): p = model.predict(window.reshape(1, window_len, 1), verbose=0)[0, 0] preds.append(p) window = np.append(window[1:], p) return scaler.inverse_transform(np.array(preds).reshape(-1, 1)).flatten()

注意,window里的数据必须是经过该IMF对应scaler变换后的值,不能直接拿原始值喂给模型,这是滚动预测里最容易做错的地方。滚动预测把误差一步步向后传导,预测步数越多偏差越大,这是正常现象,所以要记录每个步数上的误差分布,看误差是线性累积还是指数爆炸。

验证稳定性时,不要跑一次就下结论。我自己的习惯是固定随机种子跑三到五次,记录每次的RMSE和相关系数,计算均值和标准差。标准差小说明流程稳定可复现,标准差大说明分解或训练环节有不确定性,优先排查EMD分解稳定性。这个习惯救过我很多次,因为单次结果的好坏很可能只是运气。做完这些再判断一个EMD-LSTM方案能不能投产,基本就不会被单次运气骗到了,希望帮到你。

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

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

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

立即咨询