用 Python 轻松实现时间序列预测:Darts 模型集成 Ensembling Models

文中内容仅限技术学习与代码实践参考,市场存在不确定性,技术分析需谨慎验证,不构成任何投资建议。

Darts

Darts 是一个 Python 库,用于对时间序列进行用户友好型预测和异常检测。它包含多种模型,从 ARIMA 等经典模型到深度神经网络。所有预测模型都能以类似 scikit-learn 的方式使用 fit()predict() 函数。该库还可以轻松地对模型进行回溯测试,将多个模型的预测结果结合起来,并将外部数据考虑在内。Darts 支持单变量和多变量时间序列和模型。基于 ML 的模型可以在包含多个时间序列的潜在大型数据集上进行训练,其中一些模型还为概率预测提供了丰富的支持。

模型集成

Ensembling Models

以下是对 Darts 中集成模型的简要演示。从 Quickstart Notebook 提供的示例出发,将详细说明一些高级功能与细节。

本笔记本涵盖以下主题:

  • 基础与参考
  • 朴素集成(Naive Ensembling)
    • 确定性
    • 协变量与多元序列
    • 概率性
  • 可学习集成(Learned Ensembling)
    • 确定性
    • 概率性
  • Bootstrap
  • 预训练集成
# 若本地运行,先修正 Python 路径
from utils import fix_pythonpath_if_working_locally

fix_pythonpath_if_working_locally()
%load_ext autoreload
%autoreload 2
%matplotlib inline
import warnings

import matplotlib.pyplot as plt

from darts.dataprocessing.transformers import Scaler
from darts.datasets import AirPassengersDataset
from darts.metrics import mape
from darts.models import (
    ExponentialSmoothing,
    KalmanForecaster,
    LinearRegressionModel,
    NaiveDrift,
    NaiveEnsembleModel,
    NaiveSeasonal,
    RandomForestModel,
    RegressionEnsembleModel,
    TCNModel,
)
from darts.utils.timeseries_generation import (
    datetime_attribute_timeseries as dt_attr,
)

warnings.filterwarnings("ignore")

import logging

logging.disable(logging.CRITICAL)

基础与参考

集成(Ensembling)将多个“弱”模型的预测结果组合起来,以获得更稳健、更准确的模型。

Darts 的所有集成模型均基于 stacking 技术参考文献)。它们与其他预测模型提供相同的功能。根据所集成模型的不同,它们可以:

  • 利用协变量
  • 在多个序列上进行训练
  • 预测多元目标
  • 生成概率预测
  • 以及更多…
# 使用 darts 内置的 AirPassenger 数据集
ts_air = AirPassengersDataset().load()
ts_air.plot()
<Axes: xlabel='Month'>

img

朴素集成

朴素集成仅对集成的预测模型生成的预测结果取平均(均值)。Darts 的 NaiveEnsembleModel 可同时接受局部模型与全局模型(以及两者的组合,但存在额外限制)。

naive_ensemble = NaiveEnsembleModel(
    forecasting_models=[NaiveSeasonal(K=12), NaiveDrift()]
)

backtest = naive_ensemble.historical_forecasts(ts_air, start=0.6, forecast_horizon=3)

ts_air.plot(label="series")
backtest.plot(label="prediction")
print("NaiveEnsemble (naive) MAPE:", round(mape(backtest, ts_air), 5))
NaiveEnsemble (naive) MAPE: 11.87818

img

注意:查看每个模型的 MAPE 后,会发现 NaiveSeasonal 单独使用时的效果实际上优于与 NaiveDrift 集成。在定义集成之前,检查单个模型的性能通常是良好实践。

在创建新的 NaiveEnsemble 前,我们将筛选模型,找出哪些模型组合表现更好。候选模型包括:

  • LinearRegressionModel:经典简单模型
  • ExponentialSmoothing:滑动窗口模型
  • KalmanForecaster:基于滤波的模型
  • RandomForestModel:决策树模型
candidates_models = {
    "LinearRegression": (LinearRegressionModel, {"lags": 12}),
    "ExponentialSmoothing": (ExponentialSmoothing, {}),
    "KalmanForecaster": (KalmanForecaster, {"dim_x": 12}),
    "RandomForestModel": (RandomForestModel, {"lags": 12, "random_state": 0}),
}

backtest_models = []

for model_name, (model_cls, model_kwargs) in candidates_models.items():
    model = model_cls(**model_kwargs)
    backtest_models.append(
        model.historical_forecasts(ts_air, start=0.6, forecast_horizon=3)
    )
    print(f"{model_name} MAPE: {round(mape(backtest_models[-1], ts_air), 5)}")
LinearRegression MAPE: 4.64008
ExponentialSmoothing MAPE: 4.44774
KalmanForecaster MAPE: 4.5539
RandomForestModel MAPE: 8.02264
fix, axes = plt.subplots(2, 2, figsize=(9, 6))
for ax, backtest, model_name in zip(
    axes.flatten(),
    backtest_models,
    list(candidates_models.keys()),
):
    ts_air[-len(backtest) :].plot(ax=ax, label="ground truth")
    backtest.plot(ax=ax, label=model_name)

    ax.set_title(model_name)
    ax.set_ylim([250, 650])
plt.tight_layout()

img

LinearRegressionModelKalmanForecaster 的历史预测结果十分相似,而 ExponentialSmoothing 倾向于低估真实值,RandomForestModel 则无法捕捉峰值。为了从集成中获益,我们将选择多样性更高的 LinearRegressionModelExponentialSmoothing

ensemble = NaiveEnsembleModel(
    forecasting_models=[LinearRegressionModel(lags=12), ExponentialSmoothing()]
)

backtest = ensemble.historical_forecasts(ts_air, start=0.6, forecast_horizon=3)

ts_air[-len(backtest) :].plot(label="series")
backtest.plot(label="prediction")
plt.ylim([250, 650])
print("NaiveEnsemble (v2) MAPE:", round(mape(backtest, ts_air), 5))
NaiveEnsemble (v2) MAPE: 4.04297

img

与单个模型的 MAPE 分数相比,LinearRegressionModel 为 4.64008,ExponentialSmoothing 为 4.44874,集成将准确率提高至 4.04297!

使用协变量与预测多元序列

根据所用预测模型的不同,EnsembleModel 当然也可以利用协变量或预测多元序列!协变量将仅传递给支持它们的预测模型。

在下面的示例中,ExponentialSmoothing 模型不支持任何协变量,而 LinearRegressionModel 支持 future_covariates

ensemble = NaiveEnsembleModel([
    LinearRegressionModel(lags=12, lags_future_covariates=[0]),
    ExponentialSmoothing(),
])

# 将月份编码为整数并归一化
future_cov = dt_attr(ts_air.time_index, "month", add_length=12) / 12
backtest = ensemble.historical_forecasts(
    ts_air, future_covariates=future_cov, start=0.6, forecast_horizon=3
)

ts_air[-len(backtest) :].plot(label="series")
backtest.plot(label="prediction")
plt.ylim([250, 650])
print("NaiveEnsemble (w/ future covariates) MAPE:", round(mape(backtest, ts_air), 5))
NaiveEnsemble (w/ future covariates) MAPE: 4.07502

img

概率朴素集成

组合支持概率预测的模型将得到一个概率性 NaiveEnsembleModel!我们可以轻松将上述模型调整为概率型,并在预测中获得置信区间:

ensemble_probabilistic = NaiveEnsembleModel(
    forecasting_models=[
        LinearRegressionModel(
            lags=12,
            likelihood="quantile",
            quantiles=[0.05, 0.5, 0.95],
        ),
        ExponentialSmoothing(),
    ]
)

# 必须传入 num_samples > 1 以获得概率预测
backtest = ensemble_probabilistic.historical_forecasts(
    ts_air, start=0.6, forecast_horizon=3, num_samples=100
)

ts_air[-len(backtest) :].plot(label="ground truth")
backtest.plot(label="prediction")
<Axes: xlabel='time'>

img

可学习集成

集成也可视为监督回归问题:给定一组预测(特征),找到能够将它们组合以最小化目标误差的模型。这正是 RegressionEnsembleModel 所做的工作。主要包含三个参数:

  • forecasting_models:待集成的预测模型列表。
  • regression_train_n_points:用于拟合“集成回归”模型(即组合预测的内部模型)的时间步数。
  • regression_model:可选,用于集成回归的 sklearn 兼容回归模型或 Darts 的 SKLearnModel。若未指定,则使用 Darts 的 LinearRegressionModel。使用 sklearn 模型开箱即用,但使用 Darts 回归模型可利用单个预测的任意滞后作为回归模型的输入。

一旦这些元素就绪,RegressionEnsembleModel 即可像常规预测模型一样使用:

ensemble_model = RegressionEnsembleModel(
    forecasting_models=[NaiveSeasonal(K=12), NaiveDrift()],
    regression_train_n_points=12,
)

backtest = ensemble_model.historical_forecasts(ts_air, start=0.6, forecast_horizon=3)

ts_air.plot()
backtest.plot()

print("RegressionEnsemble (naive) MAPE:", round(mape(backtest, ts_air), 5))
RegressionEnsemble (naive) MAPE: 4.85142

img

与朴素集成部分最初得到的 MAPE 11.87818 相比,在两个朴素模型之上添加 LinearRegressionModel 确实提高了预测质量。

现在,让我们看看当 RegressionEnsemble 的预测模型不是朴素模型时,是否也能观察到类似提升:

ensemble = RegressionEnsembleModel(
    forecasting_models=[LinearRegressionModel(lags=12), ExponentialSmoothing()],
    regression_train_n_points=12,
)

backtest = ensemble.historical_forecasts(ts_air, start=0.6, forecast_horizon=3)

ts_air.plot(label="series")
backtest.plot(label="prediction")

print("RegressionEnsemble (v2) MAPE:", round(mape(backtest, ts_air), 5))
RegressionEnsemble (v2) MAPE: 4.63334

img

有趣的是,尽管与依赖朴素模型的 RegressionEnsemble(MAPE: 4.85142)相比 MAPE 有所提升,但它并未超越使用相似预测模型的 NaiveEnsemble(MAPE: 4.04297)。

这种性能差距部分是由于留出用于训练集成 LinearRegression 的点造成的;两个预测模型(LinearRegressionExponentialSmoothing)无法访问序列的最新值,而这些值包含明显的上升趋势。

出于好奇,我们可以使用 sklearn 库中的 Ridge 回归模型来集成预测:

from sklearn.linear_model import Ridge

ensemble = RegressionEnsembleModel(
    forecasting_models=[LinearRegressionModel(lags=12), ExponentialSmoothing()],
    regression_train_n_points=12,
    regression_model=Ridge(),
)

backtest = ensemble.historical_forecasts(ts_air, start=0.6, forecast_horizon=3)

print("RegressionEnsemble (Ridge) MAPE:", round(mape(backtest, ts_air), 5))
RegressionEnsemble (Ridge) MAPE: 6.46803

在此特定场景下,使用带正则项的回归模型反而降低了预测质量,但在其他情况下可能会得到改善。

使用历史预测进行训练

当预测值的数量大于其 output_chunk_length 时,GlobalForecastingModels 依靠自回归(使用自身输出作为输入)来预测更远未来的值。然而,随着预测时间点远离观测结束,预测质量可能显著下降。在 RegressionEnsemble 的回归模型训练期间,预测模型在已知真实值的时间点上生成预测,因此可以使用 historical_forecasts 而非 predict()

可通过设置 train_using_historical_forecasts=True 激活。

在底层,集成模型将以 forecast_horizon=model.output_chunk_lengthstride=model.output_chunk_lengthlast_points_only=False 以及 overlap_end=False 触发每个模型的历史预测,以预测目标序列的最后 regression_train_n_points 个点。

# 将 ExponentialSmoothing(局部模型)替换为 RandomForestModel(全局模型)
ensemble = RegressionEnsembleModel(
    forecasting_models=[
        LinearRegressionModel(lags=12),
        RandomForestModel(lags=12, random_state=0),
    ],
    regression_train_n_points=12,
    train_using_historical_forecasts=False,
)
backtest = ensemble.historical_forecasts(ts_air, start=0.6, forecast_horizon=3)

ensemble_hist_fct = RegressionEnsembleModel(
    forecasting_models=[
        LinearRegressionModel(lags=12),
        RandomForestModel(lags=12, random_state=0),
    ],
    regression_train_n_points=12,
    train_using_historical_forecasts=True,
)
backtest_hist_fct = ensemble_hist_fct.historical_forecasts(
    ts_air, start=0.6, forecast_horizon=3
)

print("RegressionEnsemble (no hist_fct) MAPE:", round(mape(backtest, ts_air), 5))
print("RegressionEnsemble (hist_fct) MAPE:", round(mape(backtest_hist_fct, ts_air), 5))
RegressionEnsemble (no hist_fct) MAPE: 5.7016
RegressionEnsemble (hist_fct) MAPE: 5.12017

如预期,使用历史预测训练回归模型可产生更优的预测。

概率回归集成

为了使 RegressionEnsembleModel 具有概率性,必须拥有一个概率性集成回归模型(参见 README 中的表格):

ensemble = RegressionEnsembleModel(
    forecasting_models=[LinearRegressionModel(lags=12), ExponentialSmoothing()],
    regression_train_n_points=12,
    regression_model=LinearRegressionModel(
        lags_future_covariates=[0], likelihood="quantile", quantiles=[0.05, 0.5, 0.95]
    ),
)

backtest = ensemble.historical_forecasts(
    ts_air, start=0.6, forecast_horizon=3, num_samples=100
)

ts_air[-len(backtest) :].plot(label="ground truth")
backtest.plot(label="prediction")

print("RegressionEnsemble (probabilistic) MAPE:", round(mape(backtest, ts_air), 5))
RegressionEnsemble (probabilistic) MAPE: 5.15071

img

Bootstrap 回归集成

RegressionEnsembleModel 的预测模型具有概率性时,其预测的样本维度将被压缩并用作集成回归的协变量。由于集成回归模型是确定性的,生成的预测也是确定性的。

ensemble = RegressionEnsembleModel(
    forecasting_models=[
        LinearRegressionModel(
            lags=12, likelihood="quantile", quantiles=[0.05, 0.5, 0.95]
        ),
        ExponentialSmoothing(),
    ],
    regression_train_n_points=12,
    regression_train_num_samples=100,
    regression_train_samples_reduction="median",
)

backtest = ensemble.historical_forecasts(ts_air, start=0.6, forecast_horizon=3)

ts_air[-len(backtest) :].plot(label="ground truth")
backtest.plot(label="prediction")

print("RegressionEnsemble (bootstrap) MAPE:", round(mape(backtest, ts_air), 5))
RegressionEnsemble (bootstrap) MAPE: 5.10138

img

预训练集成

由于 NaiveEnsembleModelRegressionEnsembleModel 均接受 GlobalForecastingModel 作为预测模型,因此它们可用于集成已预训练的深度学习与回归模型。请注意,仅当所有集成的预测模型均为 GlobalForecastingModel 类的实例,且在创建集成时已训练完成时,才支持此功能。

免责声明:请注意,不要使用验证期间使用的数据预训练模型,否则会引入显著偏差。

注意TCNModel 的参数大量参考了 TCNModel 示例笔记本。

# 留出用于验证的值
train, val = ts_air.split_after(0.8)

# 对目标进行缩放
scaler = Scaler()
train = scaler.fit_transform(train)
val = scaler.transform(val)

# 使用月份作为协变量
month_series = dt_attr(ts_air.time_index, attribute="month", one_hot=True)
scaler_month = Scaler()
month_series = scaler_month.fit_transform(month_series)

# 训练一个不带任何协变量的常规线性回归
linreg_model = LinearRegressionModel(lags=24)
linreg_model.fit(train)

# 为 AirPassenger 数据集优化的参数实例化 TCN 模型
tcn_model = TCNModel(
    input_chunk_length=24,
    output_chunk_length=12,
    n_epochs=500,
    dilation_base=2,
    weight_norm=True,
    kernel_size=5,
    num_filters=3,
    random_state=0,
)
tcn_model.fit(train, past_covariates=month_series)
TCNModel(kernel_size=5, num_filters=3, num_layers=None, dilation_base=2, weight_norm=True, dropout=0.2, input_chunk_length=24, output_chunk_length=12, n_epochs=500, random_state=0)

作为完整性检查,我们将查看各模型的单独预测:

# 单个模型预测
pred_linreg = linreg_model.predict(24)
pred_tcn = tcn_model.predict(24, verbose=False)

# 反缩放预测
pred_linreg_rescaled = scaler.inverse_transform(pred_linreg)
pred_tcn_rescaled = scaler.inverse_transform(pred_tcn)

# 绘图
ts_air[-24:].plot(label="ground truth")
pred_linreg_rescaled.plot(label="LinearRegressionModel")
pred_tcn_rescaled.plot(label="TCNModel")
plt.show()

img

现在我们已了解每个模型的单独性能,可以将它们集成。必须确保设置 train_forecasting_models=False,否则集成需要先拟合才能调用 predict()

建议:使用 save() 方法导出模型并保留权重副本。

naive_ensemble = NaiveEnsembleModel(
    forecasting_models=[tcn_model, linreg_model], train_forecasting_models=False
)
# 使用预训练模型初始化的 NaiveEnsemble 可直接调用 predict(),
# 但仍需提供 `series` 参数
pred_naive = naive_ensemble.predict(len(val), train)

pretrain_ensemble = RegressionEnsembleModel(
    forecasting_models=[tcn_model, linreg_model],
    regression_train_n_points=24,
    train_forecasting_models=False,
    train_using_historical_forecasts=False,
)
# RegressionEnsemble 即使预测模型已训练,也必须训练集成模型
pretrain_ensemble.fit(train)
pred_ens = pretrain_ensemble.predict(len(val))

# 反缩放预测
pred_naive_rescaled = scaler.inverse_transform(pred_naive)
pred_ens_rescaled = scaler.inverse_transform(pred_ens)

# 绘图
plt.figure(figsize=(8, 5))
scaler.inverse_transform(val).plot(label="ground truth")
pred_naive_rescaled.plot(label="pre-trained NaiveEnsemble")
pred_ens_rescaled.plot(label="pre-trained RegressionEnsemble")
plt.ylim([250, 650])

# MAPE
print("LinearRegression MAPE:", round(mape(pred_linreg_rescaled, ts_air), 5))
print("TCNModel MAPE:", round(mape(pred_tcn_rescaled, ts_air), 5))
print("NaiveEnsemble (pre-trained) MAPE:", round(mape(pred_naive_rescaled, ts_air), 5))
print(
    "RegressionEnsemble (pre-trained) MAPE:", round(mape(pred_ens_rescaled, ts_air), 5)
)
LinearRegression MAPE: 3.91311
TCNModel MAPE: 4.70491
NaiveEnsemble (pre-trained) MAPE: 3.82837
RegressionEnsemble (pre-trained) MAPE: 3.61749

img

结论

对预训练的 LinearRegressionTCNModel 模型进行集成,使我们超越了单个模型的性能,并在这些模型的预测之上训练线性回归进一步改善了 MAPE 分数。

尽管在此小数据集上提升有限,集成是一种强大的技术,可带来令人印象深刻的结果,并被第 4 届 Makridakis 竞赛的获胜者所使用(网站github 仓库)。

风险提示与免责声明
本文内容基于公开信息研究整理,不构成任何形式的投资建议。历史表现不应作为未来收益保证,市场存在不可预见的波动风险。投资者需结合自身财务状况及风险承受能力独立决策,并自行承担交易结果。作者及发布方不对任何依据本文操作导致的损失承担法律责任。市场有风险,投资须谨慎。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

船长Q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值