news 2026/10/1 17:37:16

CEEMDAN-WOA-LSTM时间序列预测:分解、寻优与实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CEEMDAN-WOA-LSTM时间序列预测:分解、寻优与实战指南

简介:面向Python时间序列预测学习者的完整实现方案,提供CEEMDAN信号分解、WOA鲸鱼优化与LSTM预测模型一体化源码,针对非平稳、非线性数据预测难题,可直接应用于课程设计、期末大作业或毕业设计。代码采用参数化编程,关键参数可灵活修改,算法流程与TensorFlow训练细节配有接近逐行的保姆级注释,极大降低小白入门门槛。资源包共3个文件,包含1个主程序py和2个csv数据文件,分别存放原始序列与测试样本,压缩包整体仅48KB,轻量易用。代码从数据读取、CEEMDAN分解、WOA寻优到LSTM训练与预测均有清晰模块划分,使用者可替换自有数据、调整超参数,快速迁移到电力负荷、交通流量、气象等场景。目前已有285人学习下载,适合希望借助完整源码理解分解-优化-预测链路,并在此基础上完成实验扩展与二次开发的研究者。

1. 用 CEEMDAN-WOA-LSTM 做时间序列预测:先把数据拆明白,再让鲸鱼替 LSTM 找参数

“焦作.csv”到手,典型的气象或水文时间序列。想预测未来一段时间的变化,第一反应是把序列直接丢给 LSTM。LSTM 确实擅长捕捉长短期依赖,但这类原始序列往往噪声和趋势混叠,直接训练要么收敛慢,要么预测结果比真实曲线滞后一拍——这是纯 LSTM 做单变量预测最常见的翻车点。

压缩包里的方案是:先用 CEEMDAN 把序列自适应分解成多个本征模态函数 IMF 和残差,再用 WOA 鲸鱼算法自动寻优 LSTM 超参数,最后逐项预测并叠加重构。适合正在做课程设计、毕业设计和论文复现的 Python 用户,环境是 Anaconda 加 TensorFlow,源码近乎一行一注释,数据是规范 CSV,下载后改几行路径就能跑通。

2. CEEMDAN 分解:把混叠序列拆成规则分量,PyEMD 实操与 IMF 筛选

2.1 为什么先用 CEEMDAN 而不是直接上 LSTM

大多数时间序列预测教程会让新手直接构造监督样本喂给 LSTM,模型确实能学到一些模式,但遇到带尖峰、趋势突变和多周期叠加的数据时,单一网络很难同时拟合低频趋势和高频扰动。EMD 类方法的价值在于把原始序列按频率自适应拆开,每个 IMF 在某个尺度上更平稳,LSTM 只需要针对单个 IMF 做相对单一的映射,最后叠加,单步预测难度会明显下降。

EEMD 是对 EMD 的改进,通过加入白噪声来避免模态混叠,但它分解出的 IMF 数量和噪声水平不稳定,同一段数据跑两次可能得到不同结果。CEEMDAN 在 EEMD 的基础上引入自适应噪声,每一轮都根据残差重新计算添加的噪声量,IMF 分量更干净,重构误差也更小,所以工程上更常用。另一个常被拿来对比的方法是 VMD,但 VMD 需要预设模态数 K 和惩罚因子 alpha,序列特征未知时这两个参数本身就是优化问题;CEEMDAN 不需要预设 K,分解完自然得到一组 IMF 加一个残差。对于“尽可能少人工干预”的自动寻优流程,CEEMDAN 更匹配。

2.2 PyEMD 安装与分解代码

先装依赖。PyEMD 在 PyPI 上的包名很容易踩坑,安装命令和导入命令不一样:

pip install EMD-signal

这里的包名是 EMD-signal,导入时却用 PyEMD。如果你用的是 Anaconda,也可以在 conda-forge 里搜 emd-signal,效果一样。装完以后在终端验证一下导入是否成功:

from PyEMD import CEEMDAN print(CEEMDAN)

能打印出类对象,说明环境正常。接着写分解代码:

import pandas as pd import numpy as np from PyEMD import CEEMDAN, Visualisation # 1. 读取CSV,中文表头优先用gbk df = pd.read_csv("焦作.csv", encoding="gbk") print(df.columns) # 先看一眼列名,下面按实际列名取 series = df.iloc[:, 1].values.astype(float) # 取第2列,按需调整 # 2. 初始化CEEMDAN ceemdan = CEEMDAN(trials=100, epsilon=0.005) imfs = ceemdan(series) # shape: (n_imfs, len(series)) # 3. 画IMF图,确认每个分量形态 vis = Visualisation() vis.plot_imfs(imfs, series)

几个参数按数据长度和噪声程度调整。trials 是集合平均次数,控制每次加噪重复分解的次数,数据短可以减到 50,长序列建议提到 200;调小省时间,但 IMF 可能带残留噪声。epsilon 是噪声幅值系数,0.001 到 0.01 之间取值,调高会让低频分量更平滑,但也可能把一些细节抹掉;这套数据用 0.005 表现正常。注意 astype(float) 这步不能省,CSV 里哪怕有一个缺失值变成 NaN,后面 CEEMDAN 会直接报警或产出全 NaN 的 IMF。

提示:CSV 里一旦出现缺失值,CEEMDAN 会把 NaN 传染给所有 IMF,先 fillna 再分解。

2.3 IMF 筛选:哪些留下预测,哪些当噪声剔除

不是所有 IMF 都值得喂给 LSTM。高频 IMF 通常对应噪声或瞬时扰动,强行预测这部分会让叠加结果抖动明显,还会拖慢整体训练。我一般用两个指标做筛选:

  • 过零率:单位长度内符号正负交替的次数,高频噪声明显高于趋势分量。
  • 相关系数:每个 IMF 与原始序列的 Pearson 相关系数,低于阈值的视为弱相关。
# 过零率计算:相邻符号发生变化即为一次过零 zero_cross = np.sum(np.diff(np.sign(imfs), axis=1) != 0, axis=1) / imfs.shape[1] # 每个IMF与原始序列的相关系数 corr = [np.corrcoef(imfs[i], series)[0, 1] for i in range(len(imfs))] print("过零率:", zero_cross) print("相关系数:", corr) # 假设前两个高频IMF被判定为噪声,其余叠加 denoised = imfs[2:].sum(axis=0)

判定时别把相关系数当成绝对标准。有的 IMF 相关系数不高,但过零率也低,说明它是低频缓变信号,仍值得参与预测;有的 IMF 相关系数中等却过零率极高,多半是噪声。我习惯把过零率超过所有 IMF 均值三倍以上的判为噪声,然后结合相关系数做二次确认。预处理结束后,denoised 就是下一步喂给 WOA-LSTM 的输入序列。顺手保存一份:

pd.DataFrame({"denoised": denoised}).to_csv("denoised.csv", index=False)

这样不用每次重跑分解。

3. WOA 寻优 LSTM 超参数:units、学习率与时间步长的三参数优化

3.1 三个参数凭什么值得优化

LSTM 调参空间很大,但单变量时间序列预测这个任务里,真正影响结果的是三个参数:隐藏层神经元数 units、优化器学习率 lr、监督样本构造的时间步长 look_back。

  • units 决定网络容量。太小拟合不了复杂映射,太大不仅慢,还容易把噪声也背下来。
  • lr 控制梯度下降步长。Adam 默认 0.001 是通用值,但不同数据和网络深度下,最优值经常在 0.0001 到 0.01 之间漂移。
  • look_back 决定用过去多少个点预测下一个点。它直接决定输入矩阵形状,选小了模型看不到足够上下文,选大了引入大量冗余历史。

三个参数互相耦合。手动调参每次改一个参数就要重训练一遍,网格搜索更是几十种组合起跳。WOA 的价值在于把寻优建模成鲸鱼找猎物,用较少的评估次数在三维空间收敛到一组可用参数。实践中通常两三百次评估内就能看到结果,相比网格搜索动辄上千次训练,省下的时间很明显。

有个常见误区是顺手把 LSTM 的层数、Dropout、batch_size 也丢进 WOA 一起优化。不是不行,而是在硬件有限的情况下,搜索维度越高收敛越慢,维度灾难在这里同样成立。先用三个主要参数拿到基线,再手动调一两个次要参数,性价比最高,这比我以前瞎试参数那种玄学路子要靠谱得多。

3.2 WOA 核心机制与位置更新实现

完整概念这里不展开,只说代码里必须理解的三件事:

  • 包围猎物:鲸鱼向当前最优位置收缩,对应代码里的 A 系数。
  • 气泡网攻击:以螺旋轨迹接近猎物,兼顾局部精细搜索。
  • 随机搜索:A 的绝对值大于等于 1 时,鲸鱼向随机个体移动,避免过早收敛到局部最优。
import numpy as np def woa_lstm(pop, dim, lb, ub, max_iter, obj_func): # 初始化鲸鱼种群 positions = np.random.uniform(lb, ub, (pop, dim)) fitness = np.array([obj_func(p) for p in positions]) best_idx = np.argmin(fitness) best_pos, best_fit = positions[best_idx].copy(), fitness[best_idx] for t in range(max_iter): a = 2 - 2 * t / max_iter # a从2线性降到0 for i in range(pop): r1, r2 = np.random.rand(), np.random.rand() A = 2 * a * r1 - a C = 2 * r2 p = np.random.rand() if p < 0.5: if abs(A) < 1: D = np.abs(C * best_pos - positions[i]) positions[i] = best_pos - A * D # 包围猎物 else: rand_idx = np.random.randint(pop) rand_pos = positions[rand_idx] D = np.abs(C * rand_pos - positions[i]) positions[i] = rand_pos - A * D # 随机搜索 else: D = np.abs(best_pos - positions[i]) l = np.random.uniform(-1, 1) positions[i] = D * np.exp(2 * l) * np.cos(2 * np.pi * l) + best_pos # 螺旋更新 positions[i] = np.clip(positions[i], lb, ub) # 边界约束 fitness = np.array([obj_func(p) for p in positions]) if fitness.min() < best_fit: best_fit = fitness.min() best_pos = positions[np.argmin(fitness)].copy() return best_pos, best_fit

代码逻辑不复杂:a 从 2 线性下降到 0,前期 A 绝对值大,偏向全局探索,后期偏局部开发。p 是 0 到 1 的随机数,决定走螺旋还是包围,两种策略随机切换。np.clip 把越界鲸鱼拉回搜索空间,这一步必须做,否则可能出现负学习率导致模型编译失败。obj_func 接收一个三维参数向量,返回验证集 MSE,适应度越小代表参数组合越好。

这类基础 WOA 代码网上有很多版本,区别主要在细节:有的用 Levy 飞行增强探索,有的加入惯性权重。项目源码里用的是标准 WOA 结构,对新手更好读,也更容易改成你自己的优化器。

3.3 适应度函数与 LSTM 训练封装:归一化顺序别搞错

适应度函数是整个 WOA 和 LSTM 的桥梁。常见做法是构造完监督样本后,训练一个短 epoch 的 LSTM,用验证集 MSE 作为适应度。这里归一化的顺序很讲究,如果先 fit_transform 全部数据再切分,验证集信息提前泄漏进训练集,选出来的参数在真实场景会水土不服。

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from tensorflow.keras.optimizers import Adam from sklearn.preprocessing import MinMaxScaler from sklearn.metrics import mean_squared_error def obj_func(params): units, lr, look_back = int(params[0]), float(params[1]), int(params[2]) X, y = create_samples(denoised, look_back) # 构造监督样本 split = int(len(X) * 0.8) X_train, X_val = X[:split], X[split:] y_train, y_val = y[:split], y[split:] model = Sequential() model.add(LSTM(units, input_shape=(look_back, 1))) model.add(Dense(1)) model.compile(optimizer=Adam(learning_rate=lr), loss="mse") model.fit(X_train, y_train, epochs=30, batch_size=32, verbose=0) pred = model.predict(X_val).flatten() # 反归一化到原始尺度再算MSE,避免量纲误导 pred_real = scaler.inverse_transform(pred.reshape(-1, 1)).flatten() y_val_real = scaler.inverse_transform(y_val.reshape(-1, 1)).flatten() return mean_squared_error(y_val_real, pred_real)

这里有个细节容易被忽略:int(params[0]) 和 int(params[2]) 必须做,否则 LSTM 层收到浮点神经元数会直接抛异常。学习率用 float(params[1]) 接收,后续 Adam 才能正常解析。create_samples 函数负责把一维序列转成监督矩阵,look_back 为几,就用前几个时间点预测下一个点。epochs 在寻优阶段控制在 30 以内,数据量大时甚至 20 就够,先让 WOA 收敛;拿到最优参数后,再用 100 epoch 精训练一轮做最终预测。

选 MSE 还是 MAPE 当适应度取决于业务口径。MSE 对大幅值误差敏感,MAPE 对量纲变化不敏感但遇到接近零的真实值会爆炸。我建议默认 MSE,输出结果时再算 MAPE 给业务看,别让两个指标混着当寻优目标。

4. 避坑排错:PyEMD 安装失败、预测滞后与 WOA 不收敛的排查记录

4.1 问题一:pip 装完 EMD-signal 后 import 仍然失败

现象:pip install EMD-signal 显示 Successfully installed,进 PyCharm 写 from PyEMD import CEEMDAN 却报 ModuleNotFoundError: No module named 'PyEMD'。

原因:pip 包名和导入模块名不一致是最大的坑,PyPI 上搜 PyEMD 会指向别的包,正确包名是 EMD-signal。另一个常见原因是 PyCharm 的解释器与终端解释器不是同一个环境,pip 装进了 base 环境,项目里用的却是另一个虚拟环境。这条坑我自己踩过最多次,几乎每个新手都中招。

解决:先用终端确认当前解释器路径,再对照 PyCharm 设置:

python -c "import sys; print(sys.executable)" pip show EMD-signal

如果路径不一致,在 PyCharm 的 Project Interpreter 里切换到同一个解释器,或者直接在项目终端重装。装完跑一句验证:

from PyEMD import CEEMDAN print(CEEMDAN)

打印出类对象才算通。Python 3.10 以上的机器还可能遇到 pyhht 编译失败,报一堆 gcc error,这时优先用 conda 从 conda-forge 安装,或者退回 Python 3.8 环境,这是目前最省力的两条路。

4.2 问题二:预测曲线比真实曲线滞后一拍

现象:训练损失很低,预测曲线与真实曲线形状几乎重合,但明显向右平移了一两个点,像一条“延迟复制版”。

原因:时间序列预测最容易误判成成功的失败模式。两种来源最常见:一是监督样本构造时 look_back 和预测步长不一致,模型学会把上一时刻值搬运到下一时刻;二是 CEEMDAN 分解后没做 IMF 筛选,模型在低频趋势部分发现“复制上一个点”误差已经很小,就没有再学复杂映射。

解决:先用一个简单诊断确认是不是滞后:

# 最佳滞后诊断,结果接近0正常 lag = np.argmax(np.correlate(pred_real, y_val_real, "full")) - len(pred_real) + 1 print("最佳滞后:", lag)

lag 明显大于 0 时,优先剔除高频噪声 IMF 再做重构,其次考虑把预测目标从一步改成多步滚动输出,让模型必须依赖长程上下文而不是相邻复制。还有一个验证技巧:打印预测值与真实值前十项的差分,如果差分基本是原序列的滞后差分,基本可以断定模型在“背答案”。

4.3 问题三:WOA 迭代几十轮,适应度一动不动

现象:best_fit 打印了两三百轮几乎没变化,甚至比前面迭代还差一点。

原因:最常见的是学习率搜索范围太宽,Adam 在 lr=0.01 量级反复震荡,每个组合训练出的模型都没收敛,适应度自然被拉平。其次是鲸鱼种群太小,30 只个体在三维空间里探索能力有限,很快聚到同一个局部最优。

解决:把 lr 的搜索边界从 [0.0001, 0.01] 缩到 [0.0005, 0.005],并做对数尺度采样:

lr = 10 ** np.random.uniform(-4, -2)

同时把种群 pop 从 30 提到 50,max_iter 不小于 30。如果适应度是精确不变的常数,比如一直等于 0.3182,先别怀疑算法,单独把 obj_func 用一组手写参数跑一遍,多半是 int(params[0]) 传成了整个数组,参数类型转换写错了。

4.4 问题四:预测结果反归一化后数值整体偏小

现象:模型输出看着正常,反归一化后预测曲线比真实曲线低一大截,甚至整体偏移。

原因:MinMaxScaler 在切分之前就用 fit_transform 把全量数据拟合了,训练集混入未来信息,属于数据泄漏。虽然训练集上指标好看,验证集却失真,反归一化时幅值对不上。另一个隐蔽原因是 CEEMDAN 分解出的 IMF 有正有负,有人为了归一化提前取绝对值,导致重构后相位被破坏。

解决:先切分再归一化,scaler 只从训练集学习 min 和 max:

scaler = MinMaxScaler(feature_range=(0, 1)) train_scaled = scaler.fit_transform(train_real.reshape(-1, 1)) val_scaled = scaler.transform(val_real.reshape(-1, 1))

预测输出用同一个 scaler.inverse_transform 还原。IMF 的负值是正常物理含义,不要取绝对值,否则后面叠加结果会漂。

5. 落地验证:损失曲线、残差自相关与纯 LSTM 对比,一个都不能少

5.1 两个图判断模型是否真的学到了规律

训练结束不要只看预测曲线。先看损失曲线:训练和验证损失同步下降并趋缓,是正常;验证损失降到低位后回升,是过拟合,应减少 epochs 或加 Dropout。再看残差的自相关:把预测值减真实值得到残差,画 autocorrelation_plot,如果残差自相关在滞后几步后掉进置信区间,说明模型已经把主要规律学走,残余部分基本是白噪声;如果残差还带着明显的周期性,说明有 IMF 没被模型利用好,或 look_back 太短。

import matplotlib.pyplot as plt from pandas.plotting import autocorrelation_plot plt.figure(figsize=(10, 3)) plt.subplot(1, 2, 1) plt.plot(history.history["loss"], label="train") plt.plot(history.history["val_loss"], label="val") plt.legend() plt.subplot(1, 2, 2) autocorrelation_plot(residual) plt.show()

5.2 轻量对比实验:纯 LSTM 与 CEEMDAN-WOA-LSTM 的同一验证集测试

验证这套组合拳是否值得,最直接的办法是跑一组对比。纯 LSTM 固定 look_back=12、lr=0.001、units=50,不分解、不寻优;另一边走完整流程,两者用同一段验证集记录 RMSE 和 MAPE:

模型RMSEMAPE
纯 LSTM记录值A记录值B
CEEMDAN-WOA-LSTM记录值C记录值D

不必预设分解一定赢。数据本身干净、周期单一的情况下,分解反而可能丢掉残差信息。这套组合真正擅长的是带噪声、多周期叠加的序列,先分解再寻优,才能体现出“单尺度映射”的优势。

从那以后我每次拿到新数据,都会强制按“分解 → 筛 IMF → 归一化 → 预测 → 反归一化 → 残差自相关”的顺序走一遍,确认没踩滞后陷阱才敢把结果交出去。这套流程里最费时间的往往不是训练,而是中间每一步都能向自己解释清楚“为什么这么做”。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 17:35:25

云资源规划备考指南:容量、成本与高可用全解析

1. 为什么说云资源规划是新版教材里最不该跳过的一章 新版《系统规划与管理师教程&#xff08;第二版&#xff09;》发下来的时候&#xff0c;很多人习惯性地翻目录找变化&#xff0c;结果发现原来“信息系统综合知识”拆成了好几个专门章节&#xff0c;其中“云资源规划”是实…

作者头像 李华
网站建设 2026/10/1 17:34:12

C#调用FFmpeg实现录像水印与分辨率控制全攻略

1. 整体思路&#xff1a;C#为什么该用命令行调FFmpeg干上位机的兄弟&#xff0c;十有八九迟早会遇到这么一个问题&#xff1a;C#写得好好的界面和逻辑&#xff0c;突然要加视频录像、要往画面上打水印、还得让用户自己选分辨率。我当时第一反应是找SDK&#xff0c;看了几天授权…

作者头像 李华
网站建设 2026/10/1 17:33:29

电商API网关请求处理实战:路由、鉴权、限流与熔断

1. 先把问题说清楚&#xff1a;电商API网关到底在扛什么 做电商后端的朋友应该都有同感&#xff0c;API网关这个层级的代码看起来不复杂&#xff0c;真正上线之后才发现它是最容易出幺蛾子的地方。我参与过的几个电商项目&#xff0c;从日订单几万到几十万单的规模都经历过&…

作者头像 李华
网站建设 2026/10/1 17:32:36

免费降AI率全攻略:从AI检测原理到五步改写技巧

1. 降AI率这事儿&#xff0c;先搞清楚AI检测到底在查什么说句实在话&#xff0c;我接触过的很多朋友一听说"免费降AI率"&#xff0c;第一反应就是去找各种号称一键降AI的网页工具&#xff0c;结果不是要付费解锁全文&#xff0c;就是改完以后句子读起来跟机器翻译似的…

作者头像 李华
网站建设 2026/10/1 17:29:43

Unity A*寻路算法实战:从原理到动态避障与性能优化

1. 先从需求说起&#xff1a;A*寻路真的过时了吗&#xff1f; 做 Unity 游戏也快十年了&#xff0c;每年都有新框架新方案冒出来&#xff0c;但寻路这块&#xff0c;只要项目里需要“在地图上有逻辑地移动”&#xff0c;A* 寻路算法依然是绕不开的基石。很多朋友一听到 A* 就觉…

作者头像 李华
网站建设 2026/10/1 17:28:59

前端Leader转型AI Agent实战:从DOM到智能体的架构迁移与并发工程

1. 一个前端Leader的AI Agent转型路线图&#xff1a;从DOM到智能体的认知跃迁做了八年多前端&#xff0c;带过十几人的团队&#xff0c;去年年底开始认真琢磨转型这件事。原因不复杂——前端的天花板越来越明显&#xff0c;业务复杂度上去了&#xff0c;但技术纵深就那么些东西…

作者头像 李华