进阶篇 第 6 篇:时间序列遇见机器学习与深度学习
(图片来源: Tara Winstead on Pexels)
在上一篇中,我们探讨了如何通过精心的特征工程,将时间序列预测问题转化为机器学习可以处理的监督学习任务。我们学习了如何创建滞后特征、滚动统计特征和日期时间特征,为利用强大的机器学习算法奠定了基础。
现在,是时候将这些特征付诸实践了!本篇我们将深入探讨两个关键领域:
- 机器学习模型的应用与验证: 如何选择合适的机器学习模型(特别是基于树的模型),以及如何在时间序列场景下进行可靠的模型评估,避免常见陷阱。
- 深度学习入门: 初步了解循环神经网络 (RNN),特别是 LSTM 和 GRU,为何它们特别适合处理序列数据,以及如何开始使用它们进行时间序列预测。
我们将看到,机器学习和深度学习为时间序列分析带来了新的视角和强大的能力。
Part 1: 机器学习模型在时间序列上的实战与可靠验证
当我们完成了上一篇介绍的特征工程后,我们就拥有了一个类似于传统监督学习的表格数据集:每一行代表一个时间点 t
,包含多个特征 X(t)
(滞后项、滚动统计、日期特征等)和一个目标值 y(t)
。
模型选择:超越线性
虽然线性模型(如岭回归、Lasso)可以作为简单的基线,但时间序列数据中的关系往往是非线性的,并且特征之间可能存在复杂的交互。因此,基于树的模型 (Tree-based Models) 在实践中通常表现更佳:
- 随机森林 (Random Forest): 通过构建多个决策树并取平均结果,降低过拟合风险,对噪声相对鲁棒。
- 梯度提升机 (Gradient Boosting Machines - GBM):
- XGBoost, LightGBM, CatBoost: 这些是 GBM 的高效、优化实现,通常性能优越。它们能够处理大规模数据,内置正则化,并且通常能更好地捕捉复杂模式。
- 优点: 通常无需对特征进行严格的缩放,能自动学习特征交互。
选择哪个模型取决于具体问题、数据量和计算资源,但 LightGBM 或 XGBoost 通常是性能和效率上的优秀起点。
关键挑战:时间序列交叉验证
这是将机器学习应用于时间序列时最容易出错也最关键的一环!
为什么标准的 K-Fold 交叉验证会失效?
标准的 K-Fold CV 会随机打乱数据并划分成 K 个折叠。在时间序列中这样做是灾难性的,因为它:
- 破坏了时间顺序: 模型可能在训练时“看到”它本不应知道的未来数据点。
- 导致数据泄漏 (Data Leakage): 验证集中的数据点可能与其训练集中的“未来”数据点相关联(例如,通过滚动特征计算),导致评估结果过于乐观,无法反映模型在真实预测场景下的表现。
正确的做法:保持时间顺序!
我们需要使用尊重时间顺序的交叉验证策略:
-
前向滚动划分 (Walk-Forward Validation / Rolling Forecast Origin):
- 逻辑: 模拟真实的预测过程。
- 用初始窗口的数据训练模型 (e.g., 前 100 个点)。
- 预测下一个点 (第 101 个点)。
- 将真实值 (第 101 个点) 加入训练集。
- 用更新后的训练集 (前 101 个点) 重新训练(或更新)模型。
- 预测下一个点 (第 102 个点)。
- …依此类推,直到遍历完测试数据。
- 优点: 最贴近真实预测场景。
- 缺点: 计算成本高,因为模型需要多次训练。可以采用固定训练窗口大小(滑动窗口)或扩展训练窗口大小(每次增加数据)两种变体。
- 逻辑: 模拟真实的预测过程。
-
时间序列 K 折交叉验证 (
TimeSeriesSplit
in Scikit-learn):- 逻辑:
sklearn.model_selection.TimeSeriesSplit
提供了一种简化的前向划分。它将数据分成 K 个折叠,但确保每个折叠的训练集总是早于其对应的验证集。- Fold 1: train=[0], test=[1]
- Fold 2: train=[0, 1], test=[2]
- Fold 3: train=[0, 1, 2], test=[3]
- …
- Fold k: train=[0, …, k-1], test=[k] (这是最简单的形式,实际
TimeSeriesSplit
可以控制训练集和测试集大小)
- 优点: 实现简单,计算效率比完整的 Walk-Forward 更高。
- 缺点: 验证集之间可能不独立(它们共享部分历史数据)。
- 逻辑:
from sklearn.model_selection import TimeSeriesSplit
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
import numpy as np
# 假设 X 是特征 DataFrame, y 是目标 Series (索引已排序)
# X, y = df.drop('y', axis=1), df['y'] # 来自上一篇特征工程后的数据
# 示例: 使用 TimeSeriesSplit
n_splits = 5 # 例如,分成 5 折
tscv = TimeSeriesSplit(n_splits=n_splits)
rmse_scores = []
print(f"--- Running Time Series Cross-Validation (n_splits={n_splits}) ---")
fold = 0
for train_index, val_index in tscv.split(X):
fold += 1
X_train, X_val = X.iloc[train_index], X.iloc[val_index]
y_train, y_val = y.iloc[train_index], y.iloc[val_index]
print(f"Fold {fold}:")
print(f" Train indices: {train_index.min()} - {train_index.max()} (size: {len(train_index)})")
print(f" Validation indices: {val_index.min()} - {val_index.max()} (size: {len(val_index)})")
# 选择并训练模型 (以 RandomForest 为例)
model = RandomForestRegressor(n_estimators=100, random_state=42, n_jobs=-1)
model.fit(X_train, y_train)
# 预测并评估
y_pred = model.predict(X_val)
rmse = np.sqrt(mean_squared_error(y_val, y_pred))
rmse_scores.append(rmse)
print(f" Validation RMSE: {rmse:.4f}")
print("-" * 20)
print(f"\nAverage RMSE across all folds: {np.mean(rmse_scores):.4f}")
实践流程总结:
- 特征工程 (如上一篇所述)。
- 选择时间序列交叉验证策略 (
TimeSeriesSplit
或 Walk-Forward)。 - 选择机器学习模型 (如 LightGBM, XGBoost)。
- 在每个 CV 折叠中训练和评估模型。
- (可选) 超参数调优: 使用
RandomizedSearchCV
或GridSearchCV
,但需要将cv
参数设置为TimeSeriesSplit
对象。 - 最终模型训练: 使用所有历史数据重新训练最佳模型(或最佳参数组合)。
- 生成未来预测: 需要预测未来时间点的特征(这可能需要对滞后项和滚动项进行特殊处理或假设)。
Part 2: 初探深度学习在时间序列的应用
虽然基于特征工程的机器学习方法非常强大,但特征工程本身可能非常耗时且需要领域知识。深度学习 (Deep Learning),特别是循环神经网络 (Recurrent Neural Networks, RNNs),提供了一种不同的思路:让模型自动从原始序列数据中学习相关的模式和特征。
为何选择 RNN (LSTM/GRU)?
- 处理序列信息: RNN 被设计用来处理序列数据(如文本、语音、时间序列)。它们内部具有“记忆”机制,可以捕捉时间上的依赖关系。
- 克服梯度问题 (LSTM/GRU): 基础的 RNN 存在梯度消失/爆炸问题,难以学习长期依赖。长短期记忆网络 (Long Short-Term Memory, LSTM) 和门控循环单元 (Gated Recurrent Unit, GRU) 通过引入“门控机制”,能够选择性地记忆或遗忘信息,从而更有效地捕捉长期模式。
核心思想:从序列到序列 (或序列到值)
与机器学习方法为每个时间点创建一行特征不同,DL 方法通常将时间序列视为一个序列。
- 数据准备是关键: 我们需要将原始时间序列转换成适合 RNN 输入的格式。常见做法是创建滑动窗口样本:
- 使用过去
n
个时间步的数据(序列X_window = [y(t-n), ..., y(t-1)]
)作为输入。 - 预测未来
h
个时间步的数据(序列y_target = [y(t), ..., y(t+h-1)]
)作为输出(Sequence-to-Sequence),或者只预测下一个点y(t)
(Sequence-to-Vector)。
- 使用过去
# 概念性数据准备示例
def create_sequences(data, seq_length, forecast_horizon=1):
xs, ys = [], []
for i in range(len(data) - seq_length - forecast_horizon + 1):
x = data[i:(i + seq_length)]
y = data[(i + seq_length):(i + seq_length + forecast_horizon)]
xs.append(x)
ys.append(y)
return np.array(xs), np.array(ys)
# 假设 'scaled_data' 是归一化后的时间序列 numpy 数组
sequence_length = 60 # 例如,用过去 60 个点
horizon = 1 # 预测未来 1 个点
X_dl, y_dl = create_sequences(scaled_data, sequence_length, horizon)
# X_dl 的形状会是 (n_samples, sequence_length, n_features)
# y_dl 的形状会是 (n_samples, forecast_horizon) 或 (n_samples,) 如果 horizon=1
# 注意:通常需要先对数据进行归一化 (e.g., MinMaxScaler)
基本模型结构 (概念性 Keras 示例)
# from tensorflow import keras
# from keras.models import Sequential
# from keras.layers import LSTM, Dense, Dropout
# model = Sequential()
# model.add(LSTM(units=50, # LSTM 单元数量
# return_sequences=True, # 如果后面还有 LSTM 层,则为 True
# input_shape=(sequence_length, n_features))) # 输入形状
# model.add(Dropout(0.2)) # 防止过拟合
# model.add(LSTM(units=50, return_sequences=False)) # 最后一层 LSTM 通常 False
# model.add(Dropout(0.2))
# model.add(Dense(units=forecast_horizon)) # 输出层,单元数等于预测步长
# model.compile(optimizer='adam', loss='mean_squared_error')
# print(model.summary())
# 训练模型 (需要划分训练/验证集)
# history = model.fit(X_train_dl, y_train_dl,
# epochs=50,
# batch_size=32,
# validation_data=(X_val_dl, y_val_dl),
# verbose=1)
深度学习的优势与挑战
优势:
- 自动特征学习: 可能减少手动特征工程的需求。
- 捕捉复杂非线性与长依赖: LSTM/GRU 在这方面通常优于传统模型。
- 端到端学习: 可以直接从原始(或少量预处理)数据学习到预测。
挑战:
- 数据需求大: 通常需要比传统模型更多的数据才能表现良好。
- 计算成本高: 训练时间长,需要较好的硬件(GPU)。
- 模型复杂性与调参: 网络结构、层数、单元数、优化器、学习率等超参数选择复杂。
- 可解释性较差: 理解模型为何做出特定预测更困难(“黑盒”问题)。
- 对数据预处理敏感: 归一化等步骤非常重要。
总结:选择你的武器
本篇我们跨越了从应用机器学习模型到初探深度学习的广阔领域:
- 强调了在使用机器学习模型(如 Random Forest, XGBoost)时,进行可靠的时间序列交叉验证(如
TimeSeriesSplit
, Walk-Forward)的极端重要性。 - 介绍了深度学习(特别是 LSTM/GRU)作为一种自动从序列中学习特征和模式的强大替代方案,并了解了其基本原理、数据准备方式和优缺点。
现在,你的时间序列工具箱更加丰富了。你是选择精雕细琢特征工程驱动的机器学习,还是拥抱端到端的深度学习,亦或是根据问题特点将它们结合?这取决于你的数据、目标、资源和对模型复杂性的偏好。
下一篇预告 (系列终章):
我们已经探索了从经典统计模型到现代机器学习和深度学习的各种方法。在最后一篇中,我们将尝试融会贯通,讨论如何根据问题特点选择合适的模型策略,了解组合预测的思想,并展望时间序列分析领域未来的发展方向和持续学习的资源。
准备好整合你的知识,为你的时间序列探索之旅画上句号(也是新的开始)了吗?敬请期待!
(你觉得在你的项目中,是基于特征工程的机器学习更容易上手,还是直接尝试深度学习更有吸引力?在使用 TimeSeriesSplit 时遇到了哪些挑战?欢迎分享你的经验!)