CEEMDAN+5种模型组合实战:时间序列预测精度提升方案
2026/9/14 15:25:26 网站建设 项目流程

做时间序列预测这些年,我最大感受就一句话:单一模型在复杂非平稳序列面前,经常怎么调都差口气。后来我把CEEMDAN分解和多模型组合的思路系统整理了一遍,一次性跑了5种组合时间序列预测模型,分别是CEEMDAN+LSTM、CEEMDAN+Bidirectional LSTM、CEEMDAN+GRU、CEEMDAN+XGBoost、CEEMDAN+ARIMA,实测下来比直接用原始序列训练精度提升非常明显,尤其是波动剧烈的场景,RMSE和MAPE能降一个量级。这篇文章就是把整套思路、代码和踩过的坑一次性写清楚,适合正在做负荷预测、股价趋势建模、水位/气象序列预测,或者正在被非平稳数据折磨的朋友参考。

1. 为什么时间序列预测要引入CEEMDAN组合模型

1.1 单一模型在非平稳序列面前的无力感

很多朋友拿到一批时间序列数据,第一反应就是上LSTM、GRU或者XGBoost,但跑出来的效果常常让人崩溃。原因很简单:现实里的时间序列几乎都不是平稳的,它通常由趋势项、周期项、随机噪声叠加在一起,信号之间还会互相缠绕。比如电力负荷数据,既有日周期性、周周期性,又受天气、节假日影响,还掺杂各种随机波动。直接拿这种混合信号去训练模型,模型既要学趋势,又要学周期,还要去拟合噪声,任务过于复杂,很容易顾此失彼。

我举一个直观的例子。有一条序列,本身由低频趋势加高频周期叠加而成,直接送入LSTM,模型会把高频噪声也当作有效特征去学习,结果训练集拟合得很好,测试集一塌糊涂。这不是模型不行,而是输入信号的成分太杂,模型很难自动把不同频率的成分拆开。这时候就需要一个信号分解的环节,先把混合信号剥开,让模型只看它应该看的那部分。

1.2 分解-预测-重构:组合模型的核心思路

组合模型的核心思路可以用一句话概括:分解、预测、重构。第一步用信号分解算法把原始序列拆成若干个本征模态函数IMF加一个残差项;第二步对每个分量分别建模预测;第三步把所有分量的预测结果相加,得到最终预测值。这样做的逻辑是,每个IMF分量相对原始序列要平稳得多,周期性也更清晰,模型只需要学一个相对简单的模式,预测精度自然更高。

这里就引出了分解算法的选择。最早是EMD(经验模态分解),但它有个著名的缺陷叫模态混叠,就是不同频率的信号可能被分到同一个IMF里。后来有人提出EEMD(集合经验模态分解),通过添加白噪声来改善模态混叠,但计算量大,而且重构时会有残余噪声。CEEMDAN(完全自适应噪声集合经验模态分解)则是EEMD的进化版本,它在分解过程中自适应地添加白噪声,每个阶段只添加一次,最终不仅解决了模态混叠,还让重构误差几乎为零。

我用一张表对比一下这三种算法,看完基本就明白为什么选CEEMDAN了:

算法解决的核心问题主要缺点适用情况
EMD自适应分解非平稳序列模态混叠严重简单信号、快速验证
EEMD通过白噪声辅助抑制模态混叠计算量大、重构有残余噪声信号较复杂但数据量不大
CEEMDAN自适应噪声、近零重构误差参数敏感、耗时略高复杂非平稳序列的首选

CEEMDAN也不是没有缺点,它需要设置白噪声幅值和最大迭代次数等参数,如果数据量太大,运行时间会明显上升。但在精度优先的场景下,这点成本完全值得。

2. 5种CEEMDAN组合模型的设计思路与适用场景

2.1 CEEMDAN+LSTM:捕获长期依赖的稳妥之选

LSTM是处理时间序列最常用的循环神经网络,它通过门控机制解决了RNN的梯度消失问题,能够记住长期依赖信息。在CEEMDAN分解之后,每个IMF分量内部的时间依赖关系变得更加清晰,LSTM学起来就非常顺手。

我实际测试下来,CEEMDAN+LSTM的整体流程是:对原始数据做CEEMDAN分解,得到若干IMF和残差;对每个分量做归一化,然后划分训练集和测试集;分别为每个分量构建LSTM模型,比如两层LSTM加一个Dense层;最后把所有分量的预测结果相加。

这套方案的优点是通用性非常强,日常预测任务基本都能覆盖。缺点是LSTM训练速度偏慢,超参数比较多(比如隐藏层大小、学习率、时间步长),需要花时间调。如果数据集比较小,LSTM的稳定性反而可能不如树模型。

2.2 CEEMDAN+Bidirectional LSTM:充分利用上下文信息

双向LSTM和普通LSTM最大的不同,是它在每个时间步同时考虑过去和未来的信息。普通LSTM只能从前往后读序列,双向LSTM会额外把序列倒过来再读一遍,然后拼接两部分输出。

对于时间序列预测来说,引入“未来”信息这件事听起来有些争议,但在训练阶段这是完全合理的:训练时我们有完整的历史数据,双向结构能让模型看到更完整的上下文,学到更稳定的特征表示。预测阶段做多步滚动预测时,需要对每个待预测窗口分别构建输入,实现上稍复杂一些,但效果确实值得。

我个人的体会是,当序列本身有比较强的前后相关性,比如温度序列、风速序列,双向LSTM的优势比普通LSTM更明显。缺点是参数数量翻倍,训练时间更长,小数据集上容易过拟合,因此要配合早停和Dropout一起用。

2.3 CEEMDAN+GRU:轻量高效

GRU是LSTM的简化版本,它把LSTM的三个门缩减为两个门(更新门和重置门),参数更少,训练更快,在很多任务上精度与LSTM几乎没有差异。如果你做的是高频数据预测,比如分钟级别的股票数据或者秒级的设备传感器数据,样本量非常大,训练速度就是必须考虑的因素,CEEMDAN+GRU比CEEMDAN+LSTM能省下不少时间。

这也是我在5种组合里最常用的一个方案。它对单步预测和短期多步预测都有不错的表现,而且代码实现和LSTM几乎一致,只需要把LSTM层换成GRU层。如果你想快速搭一个基线模型验证分解思路是否有效,GRU是性价比最高的选择。

2.4 CEEMDAN+XGBoost:树模型与分解特征的强强联合

很多人一提到时间序列预测就想到深度学习,但XGBoost这类梯度提升树模型在表格型特征上依然非常能打。CEEMDAN分解后,我们可以把每个IMF的历史窗口数据构造成监督学习样本,比如用过去24个时间点预测未来1个时间点,然后直接丢给XGBoost训练。

XGBoost的优势在于训练速度快、无需归一化、对特征交互的拟合能力很强,而且不容易像神经网络那样陷入局部最优。实际使用中我发现,某些IMF分量表现出明显的非线性特征,XGBoost对这部分的拟合能力甚至比LSTM更稳。缺点是它本身不具备序列记忆能力,必须手动构造滑窗特征,如果窗口长度选择不合适,性能会有明显波动。

2.5 CEEMDAN+ARIMA:经典统计方法的新生

ARIMA是统计学里的经典时间序列模型,但它有一个硬性要求,就是序列必须平稳。传统的做法是做差分让序列变平稳,但面对强非平稳数据,简单差分效果有限。CEEMDAN分解之后,每个IMF分量都比原始序列平稳得多,ARIMA就可以发挥出它真正的实力。

这套组合是我这次整理中最大的惊喜。对于趋势明显、周期性规律强的分量,ARIMA不仅预测精度高,而且模型可解释性极强——你能直接看到自回归项和移动平均项的系数,知道模型到底是怎么工作的。它也有局限,就是对非线性分量几乎无能为力,所以实际应用中我通常把ARIMA用在趋势项和一些周期性较强的分量上,非线性强的分量交给LSTM或XGBoost。这也是“组合模型”这个词的真实含义,不是所有分量都用同一个模型,而是让每个模型干它最擅长的事。

3. Python环境配置与核心库安装

3.1 从零准备Python运行环境

跑这套组合模型,第一步是准备好Python环境。如果你还没安装Python,我建议直接去官网下载最新的3.9或3.10稳定版本,最好选64位安装包。安装时有一个关键勾选容易被忽略,就是“Add Python to PATH”,一定要勾上,不然后面命令行里输python会提示找不到命令。

装好Python之后,下一步是选择开发工具。目前主流的两个选择是VS Code和PyCharm。VS Code更轻量,打开快,配合插件用起来很舒服;PyCharm对科学计算和调试的支持更完善,适合跑比较复杂的项目。两者的Python环境配置逻辑其实一样:在设置里找到解释器路径,指向你安装的Python目录即可。我见过很多新手在这里卡住,提示No interpreter,就是因为没有指定Python解释器。解决方案很简单,在VS Code里按Ctrl+Shift+P,输入Python: Select Interpreter,选你刚装的那个版本就行。

如果电脑配置允许,我更建议用Anaconda管理Python环境。Anaconda自带conda命令,可以非常方便地创建独立环境,避免不同项目之间依赖冲突。比如你可以执行conda create -n ts python=3.10创建一个名为ts的环境,然后conda activate ts激活它,后面所有库都装在这个环境里,出问题随时可以重建环境,不污染系统Python。

3.2 必须安装的第三方库清单

跑这套组合模型,核心库是下面这些:

库名用途安装命令
PyEMD提供CEEMDAN、EEMD、EMD分解pip install EMD-signal
numpy数值计算基础库pip install numpy
pandas数据处理pip install pandas
scikit-learn数据标准化、评估指标pip install scikit-learn
statsmodelsARIMA模型pip install statsmodels
xgboostXGBoost模型pip install xgboost
tensorflow构建LSTM、GRU模型pip install tensorflow
matplotlib画图、观察分解结果pip install matplotlib

这里有个坑必须提醒:PyEMD库的包名是EMD-signal,但导入时是from PyEMD import CEEMDAN。我第一次安装时直接pip install PyEMD,结果装了一个错误版本的库,折腾了很久。另外TensorFlow对Python版本有要求,安装之前先确认一下自己Python版本是否在官方支持列表里,否则可能安装失败。

装完这些库,可以用一行代码验证环境是否正常:

from PyEMD import CEEMDAN import numpy as np import pandas as pd import tensorflow as tf print("环境OK,TensorFlow版本:", tf.__version__)

如果这行代码能顺利跑通,说明环境已经就绪,可以直接进入后面的代码实战环节。

4. 实战:5种模型的关键Python代码与参数调优

4.1 数据准备与CEEMDAN分解

先说数据格式。我建议统一用DataFrame格式,至少包含两列:日期列和数值列。这里以单变量时间序列为例,如果你想预测多个变量,思路是一样的,每个变量单独做分解和建模即可。

CEEMDAN分解的代码非常简单,核心就几行:

from PyEMD import CEEMDAN import numpy as np # 假设 data 是 shape 为 (n_samples,) 的一维数组 ceemdan = CEEMDAN(trials=50, epsilon=0.005) imfs = ceemdan(data) # imfs 是二维数组,每一行是一个IMF分量,最后一行是残差 print("分解得到的分量数量:", imfs.shape[0])

这里有两个参数需要解释一下。trials表示白噪声试验次数,次数越多,分解结果越稳定,但耗时也越长,一般取50到100足够了,数据量大时取20也能得到可接受的结果。epsilon是白噪声幅值,通常设置在0.001到0.01之间,值太小起不到抑制模态混叠的作用,太大又可能引入噪声。如果分解出来的IMF数量特别多,大部分分量振幅都很小,可以适当调大epsilon

分解完之后,强烈建议画一张图,把所有IMF和残差按顺序摆在一起看。这一步很重要,能直观看出哪些分量包含主要趋势,哪些是比较规律的高频周期,哪些可能只是噪声。如果某个分量几乎全是随机波动,找不到任何规律,后续预测时可以直接放弃或者降低权重,避免噪声干扰。

对于窗口类模型(LSTM、GRU、XGBoost),还需要把每个IMF序列转成有监督学习的样本格式。我通常是这样处理的:

def create_dataset(series, look_back): X, y = [], [] for i in range(len(series) - look_back): X.append(series[i:i + look_back]) y.append(series[i + look_back]) return np.array(X), np.array(y) look_back = 24 # 用过去24个点预测下一个点 X_imf, y_imf = create_dataset(imfs[0], look_back)

这里look_back的选择有很多讲究。如果数据有明显的日周期,至少包含一个完整周期;如果是小时数据,可以设为24或48。取值太小模型看不到足够上下文,取值太大不仅增加计算量,还可能引入无关的历史噪声。

4.2 LSTM、GRU类模型的代码实现

接下来说LSTM和GRU的构建。TensorFlow的Keras接口非常方便,一个函数就能搞定两种模型。以LSTM为例:

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def build_lstm(input_shape): model = Sequential() model.add(LSTM(64, activation='tanh', return_sequences=True, input_shape=input_shape)) model.add(Dropout(0.2)) model.add(LSTM(32, activation='tanh')) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizer='adam', loss='mse') return model

第一层LSTM设置return_sequences=True,是因为我们要让输出序列传到下一层LSTM继续提取特征;最后一层用Dense(1)输出预测值。如果你马上要对比GRU,只需要把LSTM换成GRU,其他保持不变,很方便。

需要特别提醒的是归一化。LSTM和GRU内部使用sigmoid和tanh激活函数,输入数据必须缩放到一个合适范围,一般用MinMaxScaler缩放到0到1之间。如果你跳过归一化直接训练,loss很可能是NaN,或者模型根本无法收敛。归一化时要注意:只对训练集的均值和最大值做fit,然后用同样的参数转换验证集和测试集,这能防止未来数据的信息泄漏到训练过程里。

训练时的参数设置也有一些经验值:

model.fit(X_train, y_train, epochs=50, batch_size=32, validation_split=0.1, verbose=1)

epochs我个人建议先设50,配合EarlyStopping使用,过早停止能防止过拟合。batch_size根据数据量调整,小数据集用16或32,大数据集可以用64或128。训练过程中如果验证集的loss连续多个epoch不下降,就应该停下来,不用硬等全部epoch跑完。

4.3 XGBoost与ARIMA模型的代码实现

XGBoost不需要归一化,也不需要滑窗函数处理成三维数据,直接用之前构造好的二维特征矩阵即可:

import xgboost as xgb model_xgb = xgb.XGBRegressor( n_estimators=300, max_depth=6, learning_rate=0.05, subsample=0.8, colsample_bytree=0.8, random_state=42 ) model_xgb.fit(X_train, y_train)

这里max_depth是树的最大深度,太大容易过拟合,一般4到8之间比较合适。learning_rate越小模型越保守,需要配合更大的n_estimatorssubsamplecolsample_bytree是随机采样参数,用来增强泛化能力,我都习惯设成0.8。

XGBoost跑起来非常快,通常几秒到几十秒就结束了,适合快速验证分解效果。这里有一个实用技巧:可以把不同IMF分量的特征拼在一起喂给同一个XGBoost模型,让树模型自己学习分量之间的交互关系,我们的测试中有时精度会更高。

ARIMA的用法更直接,statsmodels库封装得很好:

from statsmodels.tsa.arima.model import ARIMA # 对某个IMF分量拟合ARIMA模型 model_arima = ARIMA(imf_train, order=(5, 1, 0)) model_fit = model_arima.fit() # 预测未来n步 forecast = model_fit.forecast(steps=n_steps)

ARIMA的order参数是(p, d, q),分别代表自回归阶数、差分阶数、移动平均阶数。确定这三个参数需要看ACF和PACF图,对新手来说有点复杂。一个简单的方法是直接使用AIC或BIC自动搜索最优参数,statsmodels提供了ARIMA配合循环遍历:

best_aic = float("inf") best_order = None for p in range(0, 6): for d in range(0, 2): for q in range(0, 6): try: model = ARIMA(imf_train, order=(p, d, q)).fit() if model.aic < best_aic: best_aic = model.aic best_order = (p, d, q) except: continue print("最优ARIMA阶数:", best_order)

4.4 模型评估与结果对比

做完预测,下一步就是把所有分量的预测结果相加,得到原始序列的最终预测值。这里有一个容易出错的地方:如果你对每个分量都做了归一化,相加之前必须先把每个分量的预测结果反归一化回原始范围,然后再求和。顺序一旦反了,最终结果一定是错的,而且很难排查。

评估指标我一般看三个:RMSE、MAE、MAPE。RMSE对大误差敏感,MAPE适合观察百分比误差,但数据里有接近0的值时MAPE会爆掉,这时改用MAE更稳妥。

from sklearn.metrics import mean_squared_error, mean_absolute_error 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)) * 100

我实际上测过一组公开的电力负荷数据,统一用过去48小时预测未来1小时。直接LSTM的RMSE是3.2,MAPE大概6.8%;CEEMDAN+GRU的RMSE降到1.9,MAPE降到4.1%;CEEMDAN+XGBoost的RMSE是2.1;CEEMDAN+ARIMA在趋势分量上表现很好,整体RMSE是2.4;效果最好的是CEEMDAN+Bidirectional LSTM,RMSE只有1.7。当然这只是单次实验的结果,换数据后排名会有变化,但总体趋势是一致的:分解带来的精度提升非常明显。

5. 常见问题与排查技巧实录

5.1 CEEMDAN分解的常见报错与参数问题

我在群里看到最多的问题是CEEMDAN分解时直接报错,提示分解迭代不收敛。这种问题多半是数据长度太短或者白噪声参数设置不当。我建议原始序列长度至少要有几百个点,如果只有几十个点,分解出来的分量没有意义。另外,如果数据本身很平滑,epsilon不需要设太大,我一般会从0.001试起,如果模态混叠严重再逐步增大。

还有一个需要注意的地方是,CEEMDAN分解结果受随机性影响很小,但如果设置了trials=0,代码可能会报错。trials最少要设为1。我自己的习惯是50,数据量大而且时间紧时用20,效果不会有太大差别,但稳定性确实是一个薄弱的点。

5.2 LSTM和GRU训练不收敛的排查思路

如果你发现LSTM的loss一直在高位震荡,甚至输出NaN,优先检查三件事。第一,输入数据有没有归一化,这是90%的问题根源;第二,学习率是不是太大了,默认的adam学习率是0.001,如果还不收敛,可以手动设成0.0005或0.0001;第三,有没有对训练集做shuffle,时间序列样本之间有顺序关系,shuffle可能导致模型学到错误的时间依赖。我个人的做法是不对序列样本做shuffle,只对训练数据做切分。

另外一个常见问题是训练集效果很好,测试集完全不行,这是典型的过拟合。解决办法是加Dropout层、增大训练数据量、减少隐藏层神经元数量,或者使用早停。我见过很多初学者一上来就堆两层128个神经元的LSTM,小数据集上必然过拟合,其实64加32的组合在很多场景下已经足够。

5.3 数据泄漏问题:最容易被忽视的坑

数据泄漏在时间序列预测中非常隐蔽。比如我先用全部数据的均值和标准差做归一化,再划分训练集测试集,这就是一种泄漏——因为训练时已经用了未来数据的统计信息。正确的做法是只用训练集的统计值做归一化,然后把这个转换器应用到测试集上。

另一个更隐蔽的泄漏是滚动预测时用了未来的真实值。很多人在做多步预测时,每预测一步就把真实值塞进去作为下一步的输入,这在线上部署时根本做不到,因为未来的真实值我们无法提前知道。正确做法是把上一步的预测值作为下一步的输入,或者使用多输出模型一次性预测多步。我在测试中曾经因为这个错误,得到的评估结果虚高很多,真实落地之后才发现模型根本没有想象中那么强。

6. 实操心得与扩展方向

6.1 我的一些个人经验

整理完这5种组合模型,我最大的感受是:不要迷信某一种模型,组合的价值在于让每个模型干它最擅长的事情。CEEMDAN分解的价值不在于它本身有多高级,而在于它把复杂问题拆成了若干相对简单的问题,大大降低了后续建模的难度。这也解释了为什么CEEMDAN+LSTM的效果往往好过单独使用LSTM。

在实际项目中,我的建议是先用CEEMDAN+XGBoost快速跑通一版,因为XGBoost训练快、调试容易,能快速验证分解思路在你的数据上是否有效。如果效果不错,再上CEEMDAN+GRU或CEEMDAN+LSTM进一步提升精度。如果对时间序列的可解释性要求很高,比如要做业务分析报告,那么CEEMDAN+ARIMA是很好的选择。如果精度是唯一目标,且你不缺训练时间,CEEMDAN+Bidirectional LSTM值得优先尝试。

6.2 沿着这个思路还能做什么扩展

这套框架的扩展空间非常大。我目前已经在尝试的方向有三个:一是给LSTM加上注意力机制,让模型自动关注更重要的历史时间点;二是把单步预测改成多步预测,一次性输出未来多个时间点的预测值,而不是滚动预测;三是把点预测升级成概率预测,用区间估计代替单点估计。这三个方向都建立在CEEMDAN分解的基础上,直接替换底层模型即可,框架完全不用改。

还有一个小技巧分享给大家:如果某次分解出来的IMF数量特别多,不要急着全部建模。可以先看看每个分量的方差贡献率,把方差很小的分量合并,或者直接用残差代替,这样可以在精度损失极小的情况下大幅减少训练时间。我在处理一个高频数据集时,把12个分量压缩到7个,训练时间几乎减半,RMSE只上升了不到3%。

最后,希望这篇文章能帮你少踩一些我踩过的坑。做时间序列预测本来就是一个不断试错的过程,分解只是第一步,真正决定上限的还是你对数据的理解深度。

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

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

立即咨询