ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CEEMDAN-CNN-LSTM时间序列预测实战:从信号分解到深度学习

CEEMDAN-CNN-LSTM时间序列预测实战:从信号分解到深度学习 简介本资源是一套面向本科生课程设计、毕业设计及科研入门者的Python时间序列预测完整实现方案聚焦CEEMDAN信号分解与CNN-LSTM混合建模技术解决非平稳时序数据高精度预测难题。适用于计算机、电子信息、应用数学等专业学生尤其适合缺乏项目经验但需快速上手智能算法仿真的初学者。压缩包共3个文件2个CSV实测数据集1个主程序PY文件总大小仅47KB轻量易部署其中Python脚本采用参数化编程范式关键步骤逐行注释涵盖CEEMDAN自适应分解、多尺度特征提取、CNN-LSTM联合训练及滚动预测全流程结构清晰、变量命名规范、超参配置集中可调。已有497人学习下载配套焦作地区实测气象/负荷类时序数据开箱即用无需额外调试即可复现结果是理解混合模型设计逻辑与工程落地细节的优质教学级案例。 不知道你有没有这种感受拿到一批时序数据不管是电力负荷、股价、交通流量还是设备振动第一反应都是“上LSTM”。但跑完才发现原始信号里全是噪声、趋势和周期混在一起LSTM再强也很难直接吃下去。后来我在一个负荷预测项目里被逼着换了思路把信号先做分解、再分头建模、最后重构预测效果一下就不一样了。今天分享的这套CEEMDAN-CNN-LSTM方案就是沿着这个思路搭起来的完整源码和数据我都放在项目里你可以直接拿去跑。简单说它把非平稳、非线性很强的序列先用CEEMDAN拆成若干个相对平稳的本征模态函数IMF再按IMF分别用CNN-LSTM提取特征并预测最后叠加结果。这套组合特别适合风速、电价、负荷这类“看着就头疼”的序列如果你手上刚好是这类数据又想找一个能落地、能改、能跑的完整工程这篇文应该能让你少走不少弯路。1. 项目整体设计与思路拆解1.1 为什么用CEEMDAN做信号分解最早我用的是EMD经验模态分解不用设置基函数就能拆序列确实方便但有个老毛病模态混叠严重。同一个IMF里既有高频分量又有低频趋势模型根本分不清该学哪一段。后来换EEMD通过加白噪声来缓解混叠可噪声一旦加进去分解结果就不稳定了每次跑出来不完全一样复现实验都是个问题。CEEMDAN完全自适应噪声集合经验模态分解在这两点上都做了改良。它不是在原始信号上直接加噪声而是在每一轮分解时对残余分量加自适应白噪声并利用全局平均来计算每个IMF。这样做的好处有两个一是模态混叠明显减少二是分解结果具有可复现性白噪声的影响会被控制在极低水平。对于工程应用来说可复现这点太重要了同一个模型上午跑和下午跑结果不一样你是调模型还是调运气需要说明的是CEEMDAN不是万灵药。如果原序列本身就是干净的周期信号分解反而多余。但面对电力负荷、交通流量、股票价格这类受多种因素影响的序列先分解再预测几乎是必须的。我个人的判断标准很简单先画出序列的频谱图如果多个频段都有明显能量或者趋势项和周期项混合在一起那就值得做分解。1.2 为什么选CNN-LSTM做时序建模拆出IMF之后另一个问题来了每个IMF用什么模型去预测只用一个LSTM当然可以但LSTM是序列模型擅长捕捉时间依赖却不擅长在同一时刻上提取多个局部特征。而CNN一维卷积恰好善于提取局部模式计算还比LSTM轻很多。所以CNN-LSTM的组合逻辑是先用一维卷积层对每个IMF做局部特征提取相当于先帮你找出“最近几个时刻里最有价值的模式”再把压缩后的特征序列交给LSTM去学习长期依赖关系。这个设计的实际效果是模型收敛更快而且对局部异常点没那么敏感。在实际项目里如果你发现单LSTM在训练集上迟迟不收敛或者收敛后预测曲线明显滞后那么多半是局部特征没有被有效提取这时候加一层CNN往往比堆LSTM层更管用。卷积核大小我一般从3开始试如果序列周期较长可以试5甚至7。你也可以把CNN部分理解成一个自动的特征工程模块这一步做得好LSTM后续的负担就小很多。1.3 与单一模型和普通EMD方案的对比为了让你更直观地理解这套组合的定位我用了三个替代方案做对比方案A不分解直接用LSTM预测原始序列。方案BEMD分解后对每个IMF用LSTM预测。方案C不分解直接用CNN-LSTM预测原始序列。在实际测试中方案A的问题在于序列里的趋势项和随机项混在一起LSTM经常学到的是“惯性上涨”遇到转折点就迟钝。方案B比方案A好一些但EMD分解出来的IMF不够干净预测结果也不稳定。方案C比A强但没解决信号非平稳的根子问题。最后的CEEMDAN-CNN-LSTM方案在RMSE和MAPE两项指标上都明显领先尤其是在序列突变区间预测偏差能缩小20%以上。需要提醒的是模型结构变复杂以后训练时间和代码调试成本也会上升。如果你的数据量很少比如只有几百个点那我不建议你上这套组合拆出十几个IMF后每个样本都稀疏得要命模型根本学不出东西。数据量至少要有几千个点这套方法才有发挥空间。方案是否分解信号平稳性处理局部特征提取长期依赖建模综合表现单一LSTM否无弱强一般EMDLSTM是中等弱强中等CNN-LSTM否无强强较好CEEMDAN-CNN-LSTM是强强强最好2. 环境准备与数据说明2.1 Python环境依赖与安装这个项目我是在Python 3.9上跑通的3.8到3.11应该都没问题。核心依赖如下pip install numpy pandas matplotlib scikit-learn pip install tensorflow pip install EMD-signal其中EMD-signal这个库是重点CEEMDAN分解直接用它。早期我踩过坑用了一个名字很像的包结果里面根本没有ceemdan接口。你安装完以后在Python里执行下面这行能正常返回版本号就行from PyEMD import CEEMDAN, EEMD, EMD print(PyEMD import OK)EMD-signal内部会用到numpy和scipy所以这两个基础库最好先装好。TensorFlow我用的是2.10版本CPU版也能跑只是训练慢一些。如果机器有NVIDIA显卡建议装对应版本的tensorflow-gpu后面训练CNN-LSTM会快很多。另外强烈建议用虚拟环境来装依赖。我见过太多次因为全局环境里包版本冲突导致import tensorflow直接崩溃的情况。你执行python -m venv ceemdan_env source ceemdan_env/bin/activate # Windows下是 ceemdan_env\Scripts\activate然后再执行上面的pip安装命令能隔离掉大部分莫名其妙的环境问题。2.2 数据来源与预处理项目里附带了一份示例数据结构很简单两列第一列是时间戳第二列是待预测的数值。我建议你用自己的数据时也保持这个格式代码不用大改。示例数据来自公开的电力负荷记录整体波动比较大早晚高峰明显很适合用来展示CEEMDAN的分解效果。拿到原始数据后第一步是清洗。我习惯的处理流程是检查缺失值。时间序列里的缺失点我用前后均值填充窗口长度取5。检查异常值。超过3倍标准差或者超过上下四分位数1.5倍IQR的点标记出来人工确认后处理。标准化。对每个IMF单独做z-score标准化而不是对原始序列整体做。原因很简单不同IMF的量纲虽然一致但数值范围差异很大放到同一个尺度下训练更稳定。关于标准化还有一个细节标准化参数只用训练集的均值和标准差验证集和测试集要用同样的参数转换不能重新计算。否则相当于把未来信息泄露给了模型评估出来的指标会偏乐观。2.3 项目目录结构我的源码目录很简单方便维护ceemdan_cnn_lstm/ ├── data/ │ └── load_data.csv ├── src/ │ ├── ceemdan_decompose.py │ ├── make_dataset.py │ ├── model.py │ ├── train.py │ └── predict.py ├── results/ │ ├── imf_plots/ │ ├── prediction_plots/ │ └── metrics.csv └── config.pyconfig.py里放着所有可调参数比如序列长度、LSTM隐藏单元数、卷积核大小、训练轮数、学习率等。我不知道你是怎么管理实验参数的但我强烈建议把所有参数集中到一个文件里而不是散落在各个脚本中。否则你在调参的时候真的会忘记自己“上一版到底改了啥”。3. 核心代码实现与解析3.1 CEEMDAN分解模块实现CEEMDAN分解是整个流程的第一环也是拉开效果差距的关键环节。我封了一个单独的模块ceemdan_decompose.py核心逻辑如下import numpy as np from PyEMD import CEEMDAN def ceemdan_decompose(series, max_imf10): ceemdan CEEMDAN(trials100, epsilon0.005) imfs ceemdan(series, max_imfmax_imf) return imfs代码就这几行但参数不是随便设的。trials表示噪声试验次数官方建议50到100之间。次数太少了分解不稳定太多了训练变慢。epsilon是噪声强度比例我试过0.001到0.010.005在这个数据上效果比较平衡。如果你做的是其他领域的数据建议跑一次多组参数对比选IMF数量稳定、残余项趋势清晰的那组。这里要特别提醒一个容易踩的坑CEEMDAN分解前不要先做标准化。原因在于分解算法对原始幅值的相对关系是敏感的如果先标准化再分解IMF的物理含义会被改变。正确的做法是先分解再分别对每个IMF做标准化。分解完成后把每个IMF画出来看看。理想情况下IMF应该是从高频到低频依次排列最后一两个是低频率的趋势项。如果你看到某个IMF明显锯齿状、振幅忽大忽小说明分解效果不理想可以适当增大trials或者调整epsilon重新跑。3.2 样本构造与数据规范化分解得到多个IMF后怎么构造训练样本我是按每个IMF单独构造样本集然后训练多个CNN-LSTM模型每个IMF一个模型。虽然训练时间会线性增加但建模更灵活因为不同IMF的波动规律差异很大共用一个模型反而可能会互相干扰。样本构造的核心是滑窗。我常用的参数有两个lookback输入序列长度和horizon预测步长。在负荷预测场景中我一般取lookback48一天96点的话就是半天horizon1预测下一点。当然你也可以设置horizon24直接预测未来一天但多步预测误差会累积建议先做单步验证流程没问题后再扩展。构造样本的代码如下def create_samples(data, lookback48, horizon1): X, y [], [] for i in range(len(data) - lookback - horizon 1): X.append(data[i:ilookback]) y.append(data[ilookback:ilookbackhorizon]) return np.array(X), np.array(y)注意一个关键点验证集和测试集的样本构造必须严格按时间顺序切分不能随机打乱。时序预测如果随机打乱数据等于把未来信息泄露出去了。但训练时每个batch内部的样本顺序可以打乱这样有助于模型稳定收敛。这个“全局顺序保留、batch顺序打乱”的操作很多新手容易搞混。数据规范化我用的是sklearn.preprocessing.StandardScaler对每个IMF分别拟合scaler。预测完成后要把结果逆标准化回去再计算误差指标。千万别忘了这一步否则你看到的误差数字会大得离谱还会以为是自己模型出了问题。3.3 CNN-LSTM模型搭建模型本身用Keras搭非常简洁。结构是“一维卷积池化 LSTM Dense”。我给大家提供一个可运行的版本from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense, Dropout def build_cnn_lstm(input_shape, cnn_filters64, kernel_size3, lstm_units32): model Sequential([ Conv1D(filterscnn_filters, kernel_sizekernel_size, activationrelu, input_shapeinput_shape), MaxPooling1D(pool_size2), LSTM(lstm_units, return_sequencesFalse), Dropout(0.2), Dense(1) ]) model.compile(optimizeradam, lossmse, metrics[mae]) return model这里有一个细节值得说return_sequences为什么设为False因为我们的目标是单步预测只需要LSTM最后一个时间步的输出。如果你要做多步预测可以改成True再接一个TimeDistributed(Dense(...))或者再叠一层LSTM。关于卷积核个数和LSTM单元数我一般从64和32开始然后根据数据量调整。数据量大就加大容量数据量小就减小。还有一个很实用的技巧把lookback设为48时kernel_size不要超过8否则卷积核太长会导致边界信息丢失严重。CNN的作用是提取局部模式太长的卷积核反而容易“看花眼”。3.4 训练与评估模块训练代码比较常规但我加入了一个重要的回调EarlyStopping和ReduceLROnPlateau。前者是防止过拟合后者是训练后期自动降低学习率帮助模型在小梯度区域继续收敛。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) reduce_lr ReduceLROnPlateau(monitorval_loss, factor0.5, patience3, min_lr1e-5) history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs100, batch_size64, callbacks[early_stop, reduce_lr], verbose1 )评估指标我在代码里实现了三个RMSE、MAE和MAPE。RMSE对大误差敏感MAE更直观MAPE适合和业务方沟通。其中MAPE有个容易被骂的坑当真实值接近0时MAPE会变成天文数字。所以如果数据里有接近0的时段最好对MAPE做一点保护比如真实值小于某个阈值时跳过该点。def calc_mape(y_true, y_pred, eps1e-6): y_true np.array(y_true) y_pred np.array(y_pred) mask np.abs(y_true) eps return np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100训练完成后每个IMF都会得到一组预测值。最后一步是把所有IMF的预测结果直接加和得到最终预测。这个过程要注意的是每个IMF的标准化是独立的所以要把各IMF的预测先逆标准化再加和。如果顺序反了加和结果完全不对。4. 实验过程与结果分析4.1 实验设置与超参数这次实验用的数据是电力负荷采样间隔15分钟一共记录了约30天共2880个点。前70%做训练中间15%做验证最后15%做测试。这样切分既保证了训练数据量足够也让测试集覆盖了完整的业务周期。超参数设置如下参数取值CEEMDAN trials100CEEMDAN epsilon0.005lookback48horizon1CNN filters64kernel_size3LSTM units32Dropout0.2batch_size64epochs上限100优化器Adam学习率0.001自适应下降这个组合不是我拍脑袋定的。一开始我用过128个LSTM单元结果过拟合很明显后来降到32配合dropout和早停验证集表现反而更好。调参这件事很多时候不是模型越复杂越好而是模型复杂度要和数据量匹配。4.2 结果对比直接说结论。测试集上CEEMDAN-CNN-LSTM的RMSE大约是直接LSTM的63%MAE大约是61%MAPE从原来的约8.5%下降了近3个百分点。这组提升主要来自两个方面一是CEEMDAN把复杂信号拆成了更易学习的IMF二是CNN局部特征提取减少了LSTM的负担。再细看不同时间段的预测效果。在负荷平稳段三种方案差异不大差的只有几个百分点。但在早晚高峰的陡变段直接LSTM会明显“反应慢半拍”预测曲线比真实曲线滞后一到两个采样点。而CEEMDAN-CNN-LSTM因为先拆出了趋势和周期突变信息在对应的IMF里被单独建模预测曲线跟随性明显更好。我印象最深的是在某个工作日的早高峰时段直接LSTM预测偏差接近12%而CEEMDAN-CNN-LSTM把偏差压到了5%以内。这个场景对工程应用非常关键因为业务方最关心的就是尖峰时段准不准。4.3 可视化结果代码里会自动生成两张图。第一张是CEEMDAN分解图能把每个IMF以及残余项画出来方便你做模态分析。第二张是预测对比图用测试集真实值和预测值画在同一张图上。如果你自己复现建议重点观察第二张图的尾部是否在峰值处有明显偏移。另外我还习惯画一个误差分布直方图。如果误差集中在0附近呈对称分布说明模型没有系统性偏差。如果误差明显偏向某个方向例如负荷高峰时总是低估那就说明模型没学到高峰模式可能需要增大lookback或者增加卷积层数。这里分享一个小技巧画图时把真实值和预测值的曲线透明度调低一点再放大关键区域比直接看全局图更容易发现问题。我经常是用20分钟的时间窗口切片去看高峰期一到模型几条曲线之间的差异一目了然。5. 常见问题与排查技巧实录5.1 数据泄露时序分割里的隐形杀手这是时序预测里最隐蔽的问题没有之一。我在第一次运行时验证集指标非常好心里还挺美结果后来发现写代码时标准化用了全量数据的均值方差导致验证集的信息混进了训练过程。测试集表现一下就垮了。判断是否数据泄露最简单的办法是检查训练和验证指标之间的差距训练集指标好、验证集指标差这是正常的过拟合但如果验证集好得惊人、测试集却很普通就要怀疑是不是泄露了。解决方案就是我在前面强调的切分之后再做标准化而且scaler只在训练集上fit。另外滑窗构造样本时也要确保测试集样本没有“跨到”训练集的未来时间点。尤其当lookback比较大时容易在切分边界处犯这个错。可以打印每个样本的起止索引检查一下多花两分钟能省掉后面一个晚上的排查时间。5.2 分解出的IMF过多或过少怎么办有些序列非平稳性很强CEEMDAN可能跑出十几个IMF。如果每个IMF都独立建模训练时间会成倍增加而且部分IMF尤其是高频噪声IMF预测难度大会拉低整体精度。我的经验是如果IMF数量超过8个可以把最后几个低频分量合并成趋势项或者设定一个阈值把振幅极小的IMF直接当作噪声丢弃。反过来如果IMF数量太少比如只有2-3个大概率是CEEMDAN参数没调好或者数据本身太简单。可以适当增大trials或者检查数据是否经过差分处理。对于强趋势序列先做一阶差分再分解通常能得到更丰富的IMF。5.3 模型不收敛或loss震荡如果训练过程中loss不降反升很可能不是模型结构问题而是数据规范化没做好。检查每个IMF标准化后的均值是否接近0、方差是否接近1。另外确认batch_size没有过大或过小过大收敛慢过小loss震荡剧烈。我遇到过一种比较棘手的情况某个IMF几乎全是高频噪声CNN-LSTM怎么训练loss都下不去。后来我发现这类IMF的预测贡献本来就不高果断把它单独提出来直接用简单的历史均值替代预测整体误差反而更小了。这听上去有点违背常理但在工程上很实用因为你不需要对每个成分都用重武器。5.4 预测曲线滞后怎么解决这是时间序列预测里被问得最多的现象预测曲线整体比真实曲线晚了一拍。滞后出现时先别急着加模型复杂度。最常见的三个原因是lookback太短信息不足LSTM单元数太多模型过于依赖最近时刻序列本身有强自相关模型发现复制上一个值能拿到低loss就不想学真正的趋势了。针对第三个原因可以尝试减小LSTM单元数、增加正则化、或者对序列做一阶差分后再建模。差分会把自相关削弱模型必须学更本质的映射关系。改完之后你再测一下滞后现象通常会有明显改善。5.5 常见问题速查表问题可能原因排查方向指标在验证/测试间差距大标准化泄露检查scaler是否只在训练集上fit分解结果不稳定CEEMDAN参数不合适增大trials调整epsilon训练loss不降数据未标准化检查IMF的均值方差预测曲线滞后lookback太短或过拟合调整窗口长度加正则化测试集MAPE异常大真实值接近0对MAPE加阈值保护程序内存不足滑窗样本太多用生成器方式读取数据6. 实操心得与扩展方向6.1 实操心得这套CEEMDAN-CNN-LSTM组合我在负荷预测和风速预测项目里都用过整体稳定性不错但有几个心得必须说一下。首先CEEMDAN分解不是必须对每个IMF都用复杂模型。高频IMF对应随机噪声部分CNN-LSTM对它的预测能力有限强行建模反而增加整体误差。低频趋势项很容易拟合甚至可以不用机器学习模型直接用简单的线性外推或者一阶滞后模型。只有那些既有规律又有波动的中频IMF才是CNN-LSTM发挥最大的地方。其次训练时间要心里有数。假设一个序列拆出8个IMF每个IMF训练一个CNN-LSTM单模型训练3分钟那就是24分钟。每次调参都要这么久所以建议先用一个IMF子集跑通流程再全量跑。我通常是先拿第3或第4个IMF来试参数因为它最代表“中等复杂度”的特征调好后再套到其他IMF上微调。另外这套流程尽量保持模块化。分解、构造样本、训练、预测分开写脚本方便你单独替换任何一个环节。比如后面你不想用CNN-LSTM想换Transformer只需要改model.py即可其他部分不用动。这种松耦合的代码结构在实际项目中维护成本低很多。6.2 可以继续扩展的方向如果你的数据量和算力允许有几个扩展方向值得尝试注意力机制在LSTM后面加一个注意力层让模型在解码时关注更重要的历史时刻。对长序列预测注意力带来的提升通常比单纯堆层数更明显。多步预测把horizon从1改成24或更多配合return_sequencesTrue和TimeDistributed层实现真正的多步预测。这时要特别注意误差累积问题可以试试用迭代预测或直接多输出两种方案做对比。多变量输入如果除了目标序列你还有温度、湿度、节假日标记等外生变量可以把它们作为额外特征拼接到CNN-LSTM的输入中。注意对齐时间窗口。自动调参用Optuna或网格搜索对kernel_size、lstm_units、batch_size做自动搜索。虽然耗时但能找到更优组合。我个人在实际使用中发现这个项目扩展成“多变量CEEMDAN-CNN-LSTM”之后在负荷预测上的效果还能再提升一截代价是代码复杂度变大。如果你刚开始建议先把单变量的整套流程吃透跑通以后再做扩展。最后再说一个小技巧训练完之后把每个IMF的测试集误差打印出来按误差占比排序。你会发现大部分总误差集中在某几个IMF上下一步优化只需要盯着这些IMF来调不用眉毛胡子一把抓。我在几次项目里都是这样做省下的时间都不止是几顿饭的事。希望这份完整源码和数据能帮你把CEEMDAN-CNN-LSTM真正落到自己的场景里。本文还有配套的精品资源点击获取
返回列表