☰
ARIMA-CNN-LSTM组合模型:Python实现时间序列预测实战
2026/10/3 3:33:14 网站建设 项目流程

在开始前先说明:这是一篇关于时间序列预测建模的实操性总结,围绕 ARIMA、CNN、LSTM 三种模型的组合应用展开,基于 Python 实现,适合对时序预测有一定基础、想尝试传统统计模型与深度学习融合方案的读者。如果你正卡在"ARIMA 只能做线性预测、LSTM 又不容易收敛"的两难境地,这篇内容应该能给你一个相对完整的解决路径。

1. 整体设计思路:为什么非要把 ARIMA、CNN、LSTM 拼在一起

1.1 单一模型的局限:线性与非线性能力很难两全

先说一个很多人在实际预测项目里都会撞上的问题:单用 ARIMA,它对趋势和季节性这种线性规律拟合得确实漂亮,解释性也强,可一旦数据里混入波动大、非线性强的片段,残差就会变得很难看。反过来,LSTM 这类深度学习模型处理非线性模式的能力很强,却经常忽略时序数据里天然存在的线性结构,导致训练时间长、收敛慢,还可能学出一堆没意义的噪声规律。

我早期做过一个销量预测的项目,用 ARIMA 跑出来的均方根误差看着还行,但一到促销节点前后,预测曲线明显跟不上实际曲线的突变。换成 LSTM 单独跑,整体误差虽然降了一点,但模型对周期性规律的记忆明显弱于 ARIMA,尤其在长周期数据上表现得不够稳定。后来才意识到,问题不在某个模型不好,而是我没有把模型的优势组合起来。

1.2 ARIMA-CNN-LSTM 组合架构的分工逻辑

这套组合的核心思路是这样的:ARIMA 负责捕捉序列中的线性成分和趋势项,把它的预测结果作为一个基准特征;CNN 这块其实不是用来做图像识别的,它工作在时间维度上,通过卷积核提取序列的局部特征,相当于给数据做了一次局部模式扫描,把突变点、短期形态这些信息先提炼一层;LSTM 再接棒处理 CNN 输出中仍然存在的长期依赖关系。

一个容易踩的坑是,有人会把三个模型做成串联结构,也就是 ARIMA 预测完,把结果塞给 CNN,再塞给 LSTM,最后直接出最终值。实际上这样做误差会叠加,而且 ARIMA 的残差信息会被下一层模型当成输入特征,反而引入了更多噪声。我建议采用并联加融合的方式,准确来说是让 ARIMA 处理原始序列的线性部分,同时让 CNN-LSTM 这块处理原始序列的残差部分,最后把两部分的预测结果叠加在一起,得到最终的预测值。这种结构里,每个模型都只处理自己擅长的那部分信息,不会互相干扰。

2. 数据准备与特征工程:整套模型的起点其实在这里

2.1 数据清洗:别让脏数据把三个模型一起带偏

任何预测模型的第一步都是数据质量检查,这一步做不好,后面调参调得再勤快也是白费。对于时间序列数据,我一般会按几个固定动作来检查:

  • 缺失值处理:连续缺失超过 5% 的序列段,直接标记为异常区间,结合业务场景判断是否需要插补。对一般的销量、流量类数据,线性插值就够了,但对波动率很大的数据,用前向填充加后向填充的组合方式会更稳妥。
  • 异常值检测:这里推荐用残差法,也就是先跑一个简单模型,把残差异常大的点位揪出来,再人工判断是真实波动还是数据采集错误。直接按 3 倍标准差删点很容易误伤真实规律。
  • 时间戳对齐:多个数据源拼接时,时间戳不一致是常见的坑,必须统一成同一个时区、同一个采样频率,否则 CNN 的卷积核扫描窗口会把不同频率的信息混在一起。

2.2 三种模型的输入格式差异与统一策略

这里要特别提醒一点,ARIMA、CNN、LSTM 三个模型的输入格式是不一样,不能直接拿同一份数据丢给三个模型,需要做一次格式适配。

ARIMA 的输入是一维序列,直接传入完整的时间序列值,它在建模时会自己处理滞后项和差分。

CNN 的输入要求是二维矩阵,形状是(样本数,时间步长,特征数)。对时间序列来说,这里的"特征数"不是指多变量特征,而是指你把多少个历史值堆叠成一个样本。比如用过去 10 天预测第 11 天,那每个样本就是一个 10 维向量。

LSTM 的输入是三维张量,形状是(样本数,时间步长,每个时间步的特征维度)。注意这里的时间步长和 CNN 里的时间步长含义不太一样,LSTM 是把序列按时间展开,每个时间步是一个观测点,特征维度指的是每个观测点上有几个变量。

统一策略上,我会先把原始序列做一阶差分,得到一个平稳序列,ARIMA 在这个差分序列上建模;同时把差分序列按滑动窗口切成固定长度的样本,用于 CNN 和 LSTM 的训练。这样三个模型实际上是共享了同一份"差分后"的信息,避免了水平差异造成的模型偏差。

2.3 滑动窗口长度怎么定:一个实操经验值

滑动窗口长度是最影响模型效果的超参数之一。太短,模型看不到足够的历史规律;太长,CNN 的卷积核覆盖范围会被稀释,LSTM 的梯度传播也会变得困难。

我的经验是先从数据本身的周期长度入手。如果数据有以 7 天为单位的周期性,窗口长度至少覆盖两个周期也就是 14 天;如果有月度效应,窗口长度要覆盖至少 30 天。窗口长度的确定本质上是一个通过实验验证的过程,建议在 10、14、21、30 这几个值上用验证集分别跑一轮,看验证集误差最小的是哪个。不要一上来就凭感觉选个 20,那样后面调参时很难判断是模型问题还是窗口问题。

3. Python 环境与核心库配置:跑通模型前的最后一道关

3.1 版本兼容性:深度学习框架的老大难问题

我在配置环境时踩过很多次坑,最典型的就是 tensorflow、numpy、pandas 三者版本不兼容,装完库之后 import 直接报错。为了防止读者在环境配置上浪费大量时间,我直接给出一套经过验证的版本组合:

库名推荐版本说明
Python3.9 或 3.103.11 下部分深度学习库的兼容性还有问题
numpy1.24.3不要装高于 2.x,很多库还没适配但后续会好一些
pandas2.0.3与 numpy 1.24.x 配套
statsmodels0.14.0ARIMA 模型实现库
tensorflow2.13.0CPU 环境就够用,GPU 可选
scikit-learn1.3.0用于数据标准化和误差计算
matplotlib3.7.2可视化预测结果

3.2 安装命令与快速验证:装完立刻能确认环境可用

建议用虚拟环境隔离项目依赖,避免多个项目的包之间互相干扰。安装命令整理如下:

conda create -n ts_forecast python=3.9 -y conda activate ts_forecast pip install numpy==1.24.3 pandas==2.0.3 statsmodels==0.14.0 pip install tensorflow==2.13.0 scikit-learn==1.3.0 matplotlib==3.7.2

装完之后,跑一段快速验证代码确认环境没问题:

import tensorflow as tf import statsmodels.api as sm import numpy as np print("TensorFlow version:", tf.__version__) print("NumPy version:", np.__version__) print("Statsmodels version:", sm.__version__) # 快速验证一个最小 LSTM 模型能否构建 from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense model = Sequential([ LSTM(16, input_shape=(10, 1)), Dense(1) ]) model.compile(optimizer='adam', loss='mse') print("LSTM model built successfully")

如果这段代码能顺利跑通,说明基础环境没问题,后面专心调模型逻辑就行。

3.3 CPU 与 GPU 的取舍:中小数据集根本不需要 GPU

很多人一提到深度学习就觉得必须上 GPU,其实对于时间序列预测这种任务,绝大多数业务场景下的数据集规模都很有限。我自己测过,几万条数据用 CPU 跑 LSTM 训练,一轮 epoch 也就是几秒到十几秒的事,完全在可接受范围内。如果你只是在学习或者做原型验证,不需要配 GPU 环境,反而省去了驱动配置的一大堆麻烦。只有当数据量达到百万级、特征维度很高时,GPU 的加速效果才值得考虑。

4. 模型构建与代码实现:分层拆解三个模型的落地细节

4.1 ARIMA 部分:从定阶到拟合的完整代码

ARIMA 的建模核心是 p、d、q 三个参数的确定。d 是差分阶数,一般通过 ADF 检验来判断序列平稳性;p 和 q 可以通过自相关函数 ACF 和偏自相关函数 PACF 的截尾特征来初判,也可以用 AIC/BIC 信息准则来自动搜索。我通常的做法是先画 ACF 和 PACF 图确认范围,再用 AIC 做网格搜索。

import statsmodels.api as sm from statsmodels.tsa.stattools import adfuller from statsmodels.graphics.tsaplots import plot_acf, plot_pacf import itertools import warnings warnings.filterwarnings('ignore') # 1. ADF 检验判断平稳性 def check_stationarity(series): result = adfuller(series) print(f'ADF Statistic: {result[0]:.4f}') print(f'p-value: {result[1]:.4f}') return result[1] < 0.05 # 2. 网格搜索最优 (p, d, q) def find_best_arima(train_series, max_p=5, max_q=5, d_range=range(0, 3)): best_aic = float('inf') best_order = None for d in d_range: for p in range(max_p + 1): for q in range(max_q + 1): try: model = sm.tsa.ARIMA(train_series, order=(p, d, q)) result = model.fit() if result.aic < best_aic: best_aic = result.aic best_order = (p, d, q) except: continue print(f'Best ARIMA order: {best_order}, AIC: {best_aic:.4f}') return best_order # 3. 拟合与预测 best_order = find_best_arima(train_series) arima_model = sm.tsa.ARIMA(train_series, order=best_order) arima_result = arima_model.fit() # 预测未来 N 步 forecast_steps = len(test_series) arima_forecast = arima_result.forecast(steps=forecast_steps)

这里的网格搜索会有一点耗时,但数据量不大的情况下完全可控。一个很重要的细节是,ARIMA 拟合的序列必须是差分后的平稳序列,如果原始序列本身就不平稳,直接传入建模结果会非常不稳定,这是统计学模型和神经网络模型一个很大的差异。

4.2 CNN 部分:时间序列上的卷积如何理解

CNN 在时间序列预测里,通常用一维卷积 Conv1D,它的工作方式和图像卷积不太一样。图像卷积是在二维空间上滑动,时间序列的一维卷积则是在时间轴上滑动一个窗口,把窗口内的几个连续时间点的值做加权求和,从而提取局部模式。

假设输入序列长度为 10,每个样本是一个 10 维向量,那么 Conv1D 的卷积核大小设置为 3 时,它每次扫描连续 3 个时间点,输出一个特征值,然后把窗口往后挪一步,继续扫描。这样扫完整个序列,就得到了一个新的特征序列。多个卷积核就相当于用多组不同的权重模板去提取不同的局部模式。

在代码层面,以下是一个可跑的 CNN 部分实现:

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense def build_cnn_model(input_shape): model = Sequential([ Conv1D(filters=64, kernel_size=3, activation='relu', input_shape=input_shape), MaxPooling1D(pool_size=2), Conv1D(filters=32, kernel_size=3, activation='relu'), MaxPooling1D(pool_size=2), Flatten(), Dense(units=50, activation='relu'), Dense(units=1) ]) model.compile(optimizer='adam', loss='mse', metrics=['mae']) return model

这里有两个参数需要解释。filters 是卷积核数量,相当于提取多少种局部模式,过多容易过拟合,过少则特征提取不足,一般从 32 到 64 开始尝试。kernel_size 是卷积核大小,对时间序列来说通常取 3 到 7 之间的小值,因为局部模式一般不会跨越很长的区间。池化层的作用是降维,但池化步长不能太大,否则会丢掉重要的时序位置信息。

4.3 LSTM 部分:核心原理与关键结构调整

LSTM 是这套组合里的主力记忆单元。它和普通 RNN 的区别在于引入了细胞状态和三个门结构:遗忘门、输入门、输出门。用大白话说,遗忘门决定哪些历史信息要丢弃,输入门决定哪些新信息要写入记忆,输出门决定当前时刻输出什么。正是这个机制,让 LSTM 能在长序列上保持梯度稳定,不至于因为序列太长而丢失早期的重要规律。

在实际编码中,有几个容易出问题的地方。第一个是 return_sequences 参数的设置,它是控制 LSTM 层连接方式的关键。如果这一层后面还有 LSTM 层,那么 return_sequences 必须设为 True,因为要输出每个时间步的结果给下一层;如果后面直接接全连接层,就设为 False,只输出最后一个时间步的结果。

第二个是 dropout 的设置。LSTM 非常容易过拟合,尤其是在数据量不是特别大的情况下。建议在 LSTM 层之间加入 dropout 层,丢弃比例从 0.2 开始,根据验证集情况逐步增加。

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def build_lstm_model(input_shape): model = Sequential([ LSTM(units=50, return_sequences=True, input_shape=input_shape), Dropout(0.2), LSTM(units=50, return_sequences=False), Dropout(0.2), Dense(units=25, activation='relu'), Dense(units=1) ]) model.compile(optimizer='adam', loss='mse', metrics=['mae']) model.summary() return model

input_shape 这里是一个二元组,比如(10,1),表示时间步长为 10,每个时间步的特征维度为 1。如果数据是多元的,特征维度就对应变量的数量。units 是 LSTM 隐藏单元的数量,它决定了记忆容量,50 是一个比较稳妥的起点。

4.4 融合层:将 ARIMA 与 CNN-LSTM 的预测合并

融合方式上,我采用加法融合而非拼接融合,原因是两者的预测目标不同。ARIMA 的预测结果是差分序列上的值,CNN-LSTM 的输出也是差分序列上的值,但两者捕捉的信息侧重点不同,一个偏线性趋势,一个偏非线性残差。将两者直接相加,就得到了最终的差分预测值,再还原差分得到最终结果。

# 假设 arima_pred 是 ARIMA 模型的预测结果(差分序列) # cnn_lstm_pred 是 CNN-LSTM 模型的预测结果(差分序列) # 二者长度一致 final_pred_diff = arima_pred + cnn_lstm_pred # 还原差分:差分逆变换 def inverse_diff(original_series, diff_pred, steps): last_value = original_series.iloc[-1] restored = [] current = last_value for v in diff_pred: current = current + v restored.append(current) return np.array(restored) final_predictions = inverse_diff(train_series, final_pred_diff, len(test_series))

这段代码是融合的骨架,实际使用时需要注意 ARIMA 的预测步长、CNN-LSTM 的预测窗口长度必须一致,否则没法直接相加。另外,差分还原时所用的"最后一个原始值"应该是训练集的最后一位,而不是测试集的第一个值,这一步容易搞混。

5. 训练流程与模型评估:光会搭结构还远远不够

5.1 数据划分与归一化:验证集是用来调参的,测试集是用来交成绩的

一般情况下,我会把数据集按 6:2:2 划分,训练集用于拟合模型参数,验证集用于选择超参数,测试集只用来最终评估。测试集在模型调优阶段是不能碰的,否则你评估出的指标就失去了意义,这相当于考试前先看了答案。

CNN-LSTM 这部分在设计上对数据的绝对值范围敏感,激活函数在输入过大或过小的数值下容易饱和,所以必须做归一化。常用的两种方式:

  • MinMaxScaler:把数据压缩到 0 到 1 之间,适合数据没有极端异常值、且分布相对稳定的情况。
  • StandardScaler:把数据变成均值 0、方差 1 的标准正态分布,适合数据存在长尾或波动较大的情况。

我自己的习惯是,如果后续要还原预测值做业务解释,用 MinMaxScaler 更直观,还原很简单;如果模型训练中出现梯度异常,换成 StandardScaler 往往就能解决。

归一化的关键点是:一定要用训练集的统计量去转换验证集和测试集,不能把训练集和测试集混在一起计算均值、方差,否则会造成数据泄漏,模型在测试集上的表现会虚高。

5.2 训练策略:batch_size、epochs 与早停机制

训练神经网络模型最典型的毛病就是欠拟合和过拟合来回横跳。我的做法是设一个较大的 epochs 上限,比如 100,然后配合 EarlyStopping 回调,监控验证集损失,当连续 10 个 epoch 验证损失没有改善时,就自动停止训练,并恢复到验证损失最低时的权重。这一步非常关键,能省掉很多手动调节的功夫。

from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stop = EarlyStopping( monitor='val_loss', patience=10, restore_best_weights=True ) reduce_lr = ReduceLROnPlateau( monitor='val_loss', factor=0.5, patience=5, min_lr=1e-6 ) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=100, batch_size=32, callbacks=[early_stop, reduce_lr], verbose=1 )

ReduceLROnPlateau 是另一个实用工具,当验证损失陷入平台期时,它会把学习率自动减半,帮助模型跳出局部最优。batch_size 的选择需要兼顾内存和稳定性,32 是一个通用的起点,如果数据波动大,可以降到 16,训练会慢一些但会更稳定。

5.3 评估指标的组合使用:别只盯着一个数值看

单一指标很容易被数据分布欺骗。均方根误差 RMSE 对大误差很敏感,如果预测在某些时间点上偏得离谱,RMSE 会一下变得很大;MAE 则对大误差的惩罚相对温和,更能反映整体水平的偏差;MAPE 是相对误差,适合业务方理解预测精度,但遇到接近零的真实值时会爆炸。

指标公式适用场景注意事项
RMSEsqrt(mean((y_true - y_pred)^2))关注大误差场景对异常点敏感
MAEmean(abs(y_true - y_pred))整体偏差评估忽略误差分布差异
MAPEmean(abs((y_true - y_pred)/y_true)) * 100%业务汇报常用真实值接近0时失效

我建议三个指标一起打印,既能看整体水平,又能观察是否存在个别异常大的预测偏差。用下面的代码可以一次性计算清楚:

from sklearn.metrics import mean_squared_error, mean_absolute_error def evaluate_model(y_true, y_pred): 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 r2 = 1 - np.sum((y_true - y_pred)**2) / np.sum((y_true - np.mean(y_true))**2) return { 'RMSE': rmse, 'MAE': mae, 'MAPE (%)': mape, 'R2': r2 } metrics = evaluate_model(test_series.values, final_predictions) for name, value in metrics.items(): print(f'{name}: {value:.4f}')

6. 踩坑实录与问题排查:实操中反复遇见的九个典型问题

6.1 ARIMA 部分的两个高频问题

问题一:模型报错提示"non-stationary data"。这说明你输入的序列没有做平稳化处理,或者差分阶数 d 选择不对。解决办法是重新做 ADF 检验,必要时增加差分阶数。如果差分后仍然不平稳,检查是否数据里存在结构性突变,比如某一天突然换了统计口径。

问题二:ARIMA 预测结果是一条直线或者近似直线。这通常发生在差分阶数 d 设置过高时,序列原有的变化信息被过度差分化了。另一个可能是预测步长太大,超出了模型的有效记忆范围。我的经验是,ARIMA 的预测步长最好不要超过训练集长度的三分之一,超出这个范围后预测值会逐渐趋于序列均值,这是 ARIMA 模型本身的数学性质决定的。

6.2 CNN-LSTM 部分的高频问题

问题一:LSTM 训练损失不下降,一直卡在某个值上。先检查数据是否做了归一化,再看学习率是否设置过大或过小。还有一个经常被忽视的原因,即输入张量的形状不对,例如 LSTM 拿到的 input_shape 三维信息有误,模型的结构虽然能建立,但学到的规律完全是乱的。

问题二:训练集损失很低,验证集损失很高。这就是典型的过拟合。优先增加 dropout 比例,降低 LSTM 单元的个数,再看看训练数据量是否太少。对于小样本数据,可以考虑在滑动窗口生成样本时加入步长,扩大样本量,但要注意别引入太多重叠样本造成数据的有效多样性不足。

问题三:预测结果整体比真实值滞后一个周期。这本质上是因为模型学到的只是一个"复制前一期"的模式,对冲数据的突变规律捕捉不到。建议在特征工程中加入时间戳特征,比如星期几、是否节假日、月份等,让模型有更多依据去理解周期性突变。

问题四:验证集和测试集指标差异特别大。先检查是否在数据划分时混入了未来信息,比如归一化时用了全局统计量,或者滑动窗口生成样本时把测试集的数据也卷进了训练集。这类数据泄漏问题在时序模型里特别容易发生,因为样本之间存在时间关联,必须严格按时间顺序切分。

6.3 融合阶段容易忽略的坑

ARIMA 和 CNN-LSTM 的预测结果在融合后误差反而变大了。这种情况一般出在最简单的加法融合上,说明两者的预测误差不是互补关系而是同向关系,叠加后误差被放大了。解决办法是给两者的预测结果分别加上权重,用验证集跑一遍搜索最优权重,让误差小的模型多贡献一点:

from scipy.optimize import minimize_scalar def weighted_combination(w): combined = w * arima_pred + (1 - w) * cnn_lstm_pred return mean_squared_error(y_val, combined) optimal_w = minimize_scalar(weighted_combination, bounds=(0, 1), method='bounded') print(f'Optimal weight for ARIMA: {optimal_w.x:.4f}') final_pred = optimal_w.x * arima_pred + (1 - optimal_w.x) * cnn_lstm_pred

权重搜索的思路很简单,核心目的就是让融合系数的选择依据实际表现说话,而不是拍脑袋定一个 0.5 的固定数。

7. 模型扩展方向:这套组合还能怎么接着玩

7.1 引入 XGBoost 作为集成学习的补充

如果你已经跑通了 ARIMA-CNN-LSTM 这套结构,一个非常自然的扩展是把 XGBoost 加入进来,作为第三个基础模型参与融合。XGBoost 在表格型数据上的拟合能力很强,尤其擅长捕捉特征的交叉作用。可以让 XGBoost 直接吃原始序列的统计特征(均值、方差、最大值、最小值、滞后值等),然后和前面三个模型的输出一起做加权融合,或者训练一个简单的线性回归层来实现堆叠。思路本质上还是"让不同模型各自贡献信息",只不过最终融合的手段从人工固定权重升级到了用模型学出来的权重。

7.2 注意力机制加持 LSTM:让模型学会自己挑重点

LSTM 在处理长序列时,虽然比普通 RNN 好很多,但信息承载能力仍然有限。在 LSTM 层之上加一层注意力机制,可以让模型在解码阶段自动关注输入序列中更重要的时间点,而不是把所有信息均匀对待。这一改动往往能带来比较明显的精度提升,尤其当序列中存在明显的重点事件(比如促销、节假日效应)时,注意力模块能捕捉到这些关键位置的模式。代码实现上,Keras 里可以直接用 Attention 层,也可以手动实现一个简单的注意力函数。

7.3 多步预测策略的切换

如果你需要预测未来 14 天而不是只需要预测下一天,预测策略会完全不同。直接多步预测(一次性输出 14 个未来值)训练容易,但误差会累积;递归预测(把预测值当作输入继续预测)思路简单,但长期效果不稳定;序列到序列的模型也就是 Seq2Seq 架构则更适合中长期的预测任务,编码器负责压缩历史信息,解码器逐步生成未来值。组合框架下,建议根据业务需求评估是多步直出还是递归递推,不要盲目套用一个结构。

根据我个人在这些项目里的体会,ARIMA-CNN-LSTM 这套组合的真正价值不在于某一个模型有多强,而在于把统计模型的可解释性、卷积的局部特征提取能力和循环网络的长程记忆能力放在了一个框架里,各取所长。对于刚入门时间序列预测的读者,建议先不要一口气把三个模型都跑通,而是按照 ARIMA 单独跑通、CNN-LSTM 跑通、最后融合的顺序分步实现。每一步的中间结果都用图例画出来,亲眼看到每个模型的效果差异,你对这套组合的理解会比看任何教程都深入得多。

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

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

立即咨询