
简介面向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, encodinggbk) print(df.columns) # 先看一眼列名下面按实际列名取 series df.iloc[:, 1].values.astype(float) # 取第2列按需调整 # 2. 初始化CEEMDAN ceemdan CEEMDAN(trials100, epsilon0.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), axis1) ! 0, axis1) / 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(axis0)判定时别把相关系数当成绝对标准。有的 IMF 相关系数不高但过零率也低说明它是低频缓变信号仍值得参与预测有的 IMF 相关系数中等却过零率极高多半是噪声。我习惯把过零率超过所有 IMF 均值三倍以上的判为噪声然后结合相关系数做二次确认。预处理结束后denoised 就是下一步喂给 WOA-LSTM 的输入序列。顺手保存一份pd.DataFrame({denoised: denoised}).to_csv(denoised.csv, indexFalse)这样不用每次重跑分解。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(optimizerAdam(learning_ratelr), lossmse) model.fit(X_train, y_train, epochs30, batch_size32, verbose0) 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 在 lr0.01 量级反复震荡每个组合训练出的模型都没收敛适应度自然被拉平。其次是鲸鱼种群太小30 只个体在三维空间里探索能力有限很快聚到同一个局部最优。解决把 lr 的搜索边界从 [0.0001, 0.01] 缩到 [0.0005, 0.005]并做对数尺度采样lr 10 ** np.random.uniform(-4, -2)同时把种群 pop 从 30 提到 50max_iter 不小于 30。如果适应度是精确不变的常数比如一直等于 0.3182先别怀疑算法单独把 obj_func 用一组手写参数跑一遍多半是 int(params[0]) 传成了整个数组参数类型转换写错了。4.4 问题四预测结果反归一化后数值整体偏小现象模型输出看着正常反归一化后预测曲线比真实曲线低一大截甚至整体偏移。原因MinMaxScaler 在切分之前就用 fit_transform 把全量数据拟合了训练集混入未来信息属于数据泄漏。虽然训练集上指标好看验证集却失真反归一化时幅值对不上。另一个隐蔽原因是 CEEMDAN 分解出的 IMF 有正有负有人为了归一化提前取绝对值导致重构后相位被破坏。解决先切分再归一化scaler 只从训练集学习 min 和 maxscaler 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], labeltrain) plt.plot(history.history[val_loss], labelval) plt.legend() plt.subplot(1, 2, 2) autocorrelation_plot(residual) plt.show()5.2 轻量对比实验纯 LSTM 与 CEEMDAN-WOA-LSTM 的同一验证集测试验证这套组合拳是否值得最直接的办法是跑一组对比。纯 LSTM 固定 look_back12、lr0.001、units50不分解、不寻优另一边走完整流程两者用同一段验证集记录 RMSE 和 MAPE模型RMSEMAPE纯 LSTM记录值A记录值BCEEMDAN-WOA-LSTM记录值C记录值D不必预设分解一定赢。数据本身干净、周期单一的情况下分解反而可能丢掉残差信息。这套组合真正擅长的是带噪声、多周期叠加的序列先分解再寻优才能体现出“单尺度映射”的优势。从那以后我每次拿到新数据都会强制按“分解 → 筛 IMF → 归一化 → 预测 → 反归一化 → 残差自相关”的顺序走一遍确认没踩滞后陷阱才敢把结果交出去。这套流程里最费时间的往往不是训练而是中间每一步都能向自己解释清楚“为什么这么做”。希望帮到你。本文还有配套的精品资源点击获取