多步预测

在前面的部分中,我们介绍了预测的一些基础知识以及用于时间序列预测的不同类型的建模技术。然而,一个完整的预测系统不仅仅是模型。时间序列预测的一些机制会产生很大的不同。这些主题不能被称为“基础”,因为它们需要对预测范 paradigm 有细致入微的理解,这就是为什么我们没有一开始就介绍它们。

既然你已经使用了一些预测模型并熟悉了时间序列,现在是时候让我们的方法更加细腻了。我们在本书中完成的大多数预测练习都集中在预测下一个时间步。在本章中,我们将探讨生成多步预测的策略——换句话说,如何预测接下来的 H 个时间步。在大多数实际的预测应用中,我们需要预测多个时间步的未来,而能够处理这种情况是一项基本技能。

在本章中,我们将涵盖以下主要内容:

为什么需要多步预测?

多步预测任务包括预测时间序列 y1, …, yt 的下一个 H 个时间步 yt+1, …, yt+H,其中 H > 1。大多数时间序列预测的实际应用都要求多步预测,无论是家庭能源消耗还是产品销量。这是因为预测从来不是为了知道未来会发生什么,而是为了让我们利用获得的可见性采取行动。

为了有效采取行动,我们希望提前一点知道预测结果。例如,本书中使用的数据集是关于家庭能源消耗的,每半小时记录一次。如果能源供应商希望规划其能源生产以满足客户需求,那么接下来的半小时根本没有帮助。同样,如果我们考虑零售场景,希望预测产品的销量,我们会希望预测未来几天,以便及时购买必要商品、运送到商店等,以应对需求。

尽管多步预测是一种更普遍的用例,但它并未得到应有的关注。原因之一是存在经典统计模型或计量经济学模型,如 ARIMA指数平滑 方法,它们内置了我们所谓的多步策略;因此,这些模型可以轻松生成多个时间步(尽管在本章中我们将看到,它们依赖于一种特定的多步策略来生成预测)。由于这些模型是最常用的模型,实践者无需担心多步预测策略。然而,机器学习ML)和 深度学习DL)方法在时间序列预测中的应用再次开启了对多步预测策略进行更集中研究的需求。

多步预测不太受欢迎的另一个原因是它比单步预测更难。这是因为我们向未来推断的步数越多,由于不同未来步骤之间的复杂相互作用,预测中的不确定性就越大。根据我们选择的策略,我们将不得不管理对先前预测的依赖、误差的传播和放大等。

有许多策略可用于生成多步预测,下图清晰地总结了这些策略:

图17.1 – 多步预测策略

图18.1:多步预测策略

图18.1中图的每个节点是一种策略,具有共同元素的不同策略通过图中的边连接在一起。在本章的其余部分,我们将介绍这些节点(策略)中的每一个并详细解释它们。

标准符号

让我们建立一些基本符号来帮助理解这些策略。我们有一个时间序列 YT,包含 T 个时间步,y1, …, yTYT 表示同一序列但结束于时间步 t。我们还考虑一个函数 W,它从时间序列中生成一个大小为 k > 0 的窗口。

该函数是我们如何为本书中介绍的不同模型准备输入的代理。因此,如果我们看到 W(Yt),意味着该函数将从 YT 中提取一个结束于时间步 t 的窗口。我们还将考虑 H 为预测范围,其中 H > 1。我们还将使用 ; 作为连接运算符。

现在,让我们看看不同的策略(参考文献 1 是一篇很好的关于不同策略的综述论文)。关于每种策略的优缺点以及适用场景的讨论将在接下来的章节中一起介绍。

递归策略

递归策略是最古老、最直观、也是最流行的生成多步预测的技术。要理解一种策略,我们需要理解两个主要方面:

让我们借助一个图表来理解递归策略:

图17.2 – 多步预测的递归策略

图18.2:多步预测的递归策略

我们来详细讨论这些机制。

训练阶段

递归策略涉及训练一个单一的模型来执行单步预测。我们可以在图18.2中看到,我们使用窗口函数W(Yt)从Yt中抽取一个窗口,并训练模型预测Yt+1

在训练期间,会使用一个损失函数(衡量模型输出$\hat{y}_{t+1}$与真实值Yt+1之间的差异)来优化模型参数。

预测阶段

我们已经训练了一个模型来做单步预测。现在,我们以递归方式使用该模型生成H个时间步的预测。第一步,我们使用W(Yt),即使用训练数据中最新时间戳的窗口,生成单步预测$\hat{y}_{T+1}$。然后,将这个生成的预测添加到历史数据中,并从该历史数据中绘制一个新窗口$W(Y_T; \hat{y}_{T+1})$。将该窗口输入到同一个单步模型,生成下一个时间步的预测$\hat{y}_{T+2}$。重复此过程,直到获得所有H个时间步的预测。

这是那些经得起时间考验的经典模型(如ARIMA指数平滑)在生成多步预测时内部使用的策略。在机器学习上下文中,这意味着我们将训练一个模型来预测单步(正如我们在本书中所做的那样),然后执行递归操作:预测一步,使用新预测重新计算所有特征(如滞后、滚动窗口等),并预测下一步。该方法的伪代码如下:

# Function to create features (e.g., lags, rolling windows, external features like holidays or item category)
def create_features(df, **kwargs):
    ## Feature Pipeline goes here ##
    # Return features DataFrame
    return features
# Function to train the model
def train_model(train_df, **kwargs):
    # Create features from the training data
    features = create_features(train_df, **kwargs)
    ## Training code goes here ##
    # Return the trained model
    return model
def recursive_forecast(model, train_df, forecast_steps, **kwargs):
    """
    Perform recursive forecasting using the trained one-step model.
    - model: trained one-step-ahead model
    - train_df: DataFrame with time series data
    - forecast_steps: number of steps ahead to forecast
    - kwargs: other parameters necessary like lag size, rolling size etc.
    """
    forecasts = []
    for step in range(forecast_steps):
        input_features = create_features(train_df, **kwargs)
        ## Replace with actual model.predict() code ##
        next_forecast = model.predict(input_features)
        forecasts.append(next_forecast)
        train_df = train_df.append({'target': next_forecast, "other_features": other_features}, ignore_index=True)
    return forecasts

在深度学习模型的上下文中,我们可以将其视为将预测添加到上下文窗口,并使用训练好的模型生成下一步。该方法的伪代码如下:

def recursive_dl_forecast(dl_model, train_df, forecast_steps, **kwargs):
    """
    - dl_model: trained DL model (e.g., LSTM, Transformer)
    - train_df: DataFrame with time series data (context window)
    - forecast_steps: number of steps ahead to forecast
    - kwargs: other parameters like window size, etc.
    """
    forecasts = []
    # Extract initial context window from the end of the training data
    context_window = train_df['target'].values[-kwargs['window_size']:]
    for step in range(forecast_steps):
        ## Replace with actual dl_model.predict() code ##
        next_forecast = dl_model.predict(context_window)
        forecasts.append(next_forecast)
        # Update the context window by removing the oldest value and adding the new forecast
        context_window = np.append(context_window[1:], next_forecast)
    return forecasts

请注意,这个伪代码并非可以直接运行的代码,而更像是一个骨架,你可以根据自己的用例进行调整。现在,我们来看另一种多步预测的策略。

直接策略

直接策略,也称为独立策略,是预测中一种使用机器学习的流行策略。它涉及独立地预测每个预测步长。首先来看一个示意图:

图17.3 – 多步预测的直接策略

图18.3:多步预测的直接策略

接下来,我们详细讨论这些阶段。

训练阶段

在直接策略(图18.3)下,我们训练H个不同的模型,它们使用相同的窗口函数,但分别被训练来预测预测期内的不同时间步。因此,我们为预测期内的每个时间步学习一组独立的参数,使得所有模型组合起来学习从窗口W(Yt)到预测期H的直接且独立的映射。

这种策略随着基于机器学习的时间序列预测的普及而得到推广。从机器学习的角度来看,我们可以通过两种方式实际实现它:

上述两种方法在特征仅为滞后项时效果很好。例如,要消除特征,我们可以直接删除有问题的滞后项并训练模型。但在使用滚动特征和其他更复杂特征的情况下,简单地删除并不奏效,因为滞后1已被用于计算滚动特征,这会导致数据泄露。在这种情况下,我们可以创建一个动态函数来计算这些特征,该函数接受一个参数来指定我们为哪个预测期创建这些特征。我们在第6章《时间序列预测的特征工程》中使用的所有辅助方法(add_rolling_featuresadd_seasonal_rolling_featuresadd_ewma)都有一个名为n_shift的参数,用于处理这种情况。如果我们为H=2训练模型,我们需要传递n_shift=2,然后该方法会处理剩下的工作。现在,在训练模型时,我们使用这个动态方法为每个预测期重新计算这些特征。

预测阶段

预测范式也相当直接。我们有训练好的 H 模型,每个对应预测时域中的一个时间步,并使用 W(Yt) 独立地预测每个模型。

对于机器学习模型,这要求我们为每个时间步训练独立的模型,但 MultiOutputRegressorscikit-learn 使得这一过程更易管理。我们来看一些伪代码:

# Function to create shifted targets for direct strategy
def create_shifted_targets(df, horizon, **kwargs):
    ## Add one step ahead, 2 step ahead etc targets to the feature dataframe ##
    return dataframe, target_cols
def train_direct_ml_model(train_df, horizon, **kwargs):
    # Create shifted target columns for the horizon
    train_df, target_cols = create_shifted_targets(train_df, horizon, **kwargs)
    # Prepare features (X) and shifted targets (y) for training
    X = train_df.loc[:, [c for c in train_df.columns if c not in target_cols]]
    y = train_df.loc[:, target_cols]
    # Initialize a base model (e.g., Linear Regression) and MultiOutputRegressor
    base_model = LinearRegression()  # Example: can use any other model
    multioutput_model = MultiOutputRegressor(base_model)
    # Train the MultiOutputRegressor on the features and shifted targets
    multioutput_model.fit(X, y)
    return multioutput_model
def direct_ml_forecast(multioutput_model, test_df, horizon, **kwargs):
    # Adjust based on how test_df is structured
    X_test = test_df.loc[:, features]
    # (array with H steps)
    forecasts = multioutput_model.predict(X_test)
    return forecasts

现在,是时候考虑另一种策略了。

联合策略

前两种策略考虑模型具有单一输出。这是大多数机器学习模型的情况;我们构建模型以在接收输入数组后预测单一标量值:多输入单输出 (MISO)。但有些模型,如深度学习模型,可以配置为产生多个输出。因此,联合策略,也称为 多输入多输出 (MIMO),旨在学习一个能输出整个预测时域的单一模型:

图17.4 – 多步预测的联合策略

图18.4:多步预测的联合策略

我们来看看这些范式如何工作。

训练阶段

联合策略涉及训练一个单一的多输出模型,以一次性预测时域中的所有时间步。我们在 图18.4 中看到,我们使用窗口函数 W(Yt),从 Yt 中抽取一个窗口,并训练模型预测 yt+1,…, yt+H。在训练过程中,使用一个衡量模型所有输出 $\hat{y}_{t+1}, \ldots, \hat{y}_{t+H}$ 与真实值 yt+1,…, yt+H 之间差异的损失函数来优化模型参数。

预测阶段

预测机制也非常简单。我们有一个能够预测整个预测范围内所有时间步的已训练模型,并使用 W(Yt) 一次性完成预测。

这种策略通常用于深度学习模型,我们将最后一层配置为输出 H 个标量,而不是1个。

我们在本书的多个地方已经看到过这种策略的实际应用:

混合策略

我们已经介绍的三种策略是多步预测的基本策略,各有优缺点。多年来,研究人员尝试将它们组合成混合策略,以捕捉每种策略的优点。这里我们简要介绍几种。这并不是一个完整的列表,因为不存在这样的列表。任何有足够创造力的人都可以提出其他策略,但我们只介绍一些受到预测社区关注和深入研究的策略。

DirRec策略

顾名思义,DirRec 策略是 直接递归 策略的组合,用于多步预测。直接方法的缺点之一是它独立预测每个时间步,因此在预测较远的未来时会丢失一些上下文信息。为了弥补这一不足,我们将直接方法和递归方法结合起来,将 n 步 ahead 模型生成的预测作为 n+1 步 ahead 模型的特征。

让我们看下面的示意图来加深理解:

图17.5 – 用于多步预测的DirRec策略

图18.5:用于多步预测的DirRec策略

现在,让我们看看这些模式如何用于DirRec策略。

训练阶段

与直接策略类似,DirRec策略(图18.5)也有H个模型用于预测步长H,但略有不同。我们首先使用W(Yt)训练一个模型来预测下一步。在递归策略中,我们将这个预测的时间步用于同一模型以预测下一个时间步。但在DirRec中,我们为H = 2训练一个单独的模型,使用在H = 1生成的预测。推广到时间步h < H,除了W(Yt)之外,我们还包含由不同模型在时间步1到h生成的所有预测。

预测阶段

预测模式与训练模式类似,但不是训练模型,而是使用H个已训练的模型递归地生成预测。

让我们看一些高级伪代码来巩固理解:

def train_dirrec_models(train_data, horizon, **kwargs):
    models = []  # To store the trained models for each timestep
    # Train the first model to predict the first step ahead (t+1)
    model_t1 = train_model(train_data)  # Train model for t+1
    models.append(model_t1)
    for step in range(2, horizon + 1):
        previous_forecasts = []
        for prev_model in models:
            # Recursive prediction
            previous_forecasts.append(prev_model.predict(train_data))
        # Use the forecasts as features for the next model
        augmented_train_data = add_forecasts_as_features(train_data, previous_forecasts)
        # Train the next model (e.g., for t+2, t+3, ...)
        model = train_model(augmented_train_data)
        models.append(model)
    return models
def dirrec_forecast(models, input_data, horizon, **kwargs):
    forecasts = []
    # Generate the first forecast (t+1)
    forecast_t1 = models[0].predict(input_data)
    forecasts.append(forecast_t1)
    # Generate subsequent forecasts recursively
    for step in range(1, horizon):
        augmented_input_data = add_forecasts_as_features(input_data, forecasts)
        next_forecast = models[step].predict(augmented_input_data)
        forecasts.append(next_forecast)
    return forecasts

现在,让我们了解另一种创新的多步预测方法。

迭代分块直接策略

迭代分块直接IBD)策略也称为迭代多SVR策略,以此向提出该研究论文(参考文献2)致敬。直接策略需要训练H个不同的模型,这使得它难以扩展到长步长预测。

IBD策略试图通过使用分块迭代式预测来解决这个缺点:

图17.6 – 用于多步预测的IBD策略

图18.6:用于多步预测的IBD策略

我们来理解该策略的训练和预测机制。

训练阶段

在IBD策略中,我们将预测范围H分割成R个长度为L的块,使得H = L × R。我们不训练H个直接模型,而是训练L个直接模型。

预测阶段

在预测时(图18.6),我们使用训练的L个模型来生成H中前L个时间步(T+1到T+L)的预测,使用窗口W(YT)。我们称这个L步预测为YT+L。现在,我们将使用YT+L以及YT,通过窗口函数绘制一个新窗口W(YT;YT+L)。这个新窗口用于生成接下来L个时间步(T+LT+2L)的预测。重复这个过程多次以完成整个范围的预测。

让我们也看看这个过程的高级伪代码:

def train_ibd_models(train_data, horizon, block_size, **kwargs):
    # Calculate the number of models (L)
    n_models = horizon // block_size
    models = []
    # Train a model for each block
    for n in range(n_models):
        block_model = train_direct_model(train_data, n)
        models.append(block_model)
    return models
def ibd_forecast(models, input_data, horizon, block_size, **kwargs):
    forecasts = []
    window = input_data  # Initial window from the time series data
    num_blocks = horizon // block_size
    # Generate forecasts block by block
    for _ in range(num_blocks):
        # Predict the next block of size L using direct models
        block_forecast = []
        for model in models:
            block_forecast.append(model.predict(window))
        # Append the block forecast to the overall forecast
        forecasts.extend(block_forecast)
        # Update the window by including the new block of predictions
        window = update_window(window, block_forecast)
    return forecasts

现在,我们来看另一种混合不同策略的创造性方法。

纠正策略

纠正策略是另一种结合直接和递归策略的方法。它通过形成两阶段训练和推理方法,在两者之间取得中间立场。我们可以将其视为一种模型堆叠方法(第9章,集成与堆叠),但应用于不同的多步预测策略之间。在第一阶段,我们训练一个一步预测模型,并使用该模型生成递归预测。

然后,在第二阶段,我们使用原始窗口和特征以及递归预测来训练针对预测范围的直接模型。

图17.7 – 多步预测的校正策略

图18.7: 多步预测的校正策略

让我们详细了解这一策略的工作原理。

训练阶段

训练分为两步。首先对预测范围应用递归策略,生成所有 H 个时间步的预测。我们将此称为 $\hat{y}_{t+H}$。然后,针对每个预测范围,使用原始历史数据 Yt 和递归预测 $\hat{y}_{t+H}$ 作为输入,训练直接模型。

预测阶段

预测机制与训练类似,首先生成递归预测,然后将其与原始历史数据一起用于生成最终预测。

下面给出一些高级伪代码:

# Stage 1: Train recursive models
recursive_model, recursive_forecasts = train_one_step_ahead_model(train_data, horizon=horizon)
# Stage 2: Train direct models
direct_models = train_direct_models(train_data, recursive_forecasts, horizon=horizon)
def rectify_forecast(recursive_model, direct_models, input_data, horizon, **kwargs):
    # Generate recursive forecasts using the recursive model
    recursive_forecasts = generate_recursive_forecasts(recursive_model, input_data, horizon)
    # Generate final direct forecasts using original data and recursive forecasts
    direct_forecasts = generate_direct_forecasts(direct_models, input_data, recursive_forecasts, horizon)
    return direct_forecasts
forecast = rectify_forecast(recursive_model, direct_models, train_data, horizon)

接下来,我们将介绍最后一种策略。

RecJoint

顾名思义,RecJoint 是递归策略和联合策略的结合,但适用于多输出模型。它旨在通过利用递归预测来平衡两者的优势,同时考虑预测范围内多个时间步之间的依赖关系。

图17.8 – 多步预测的RecJoint策略

图18.8:用于多步预测的RecJoint策略

以下各节详细介绍了该策略的工作原理。

训练阶段

RecJoint策略中的训练阶段(图18.8)与递归策略非常相似,它训练单个模型并递归地使用t + 1时刻的预测作为输入来训练t + 2时刻,以此类推。但递归策略仅在下一个时间步上训练模型,而RecJoint生成整个预测范围的预测,并在训练时联合优化整个范围的预测。这迫使模型关注接下来的H个时间步并联合优化整个范围,而不是短视的一步预测目标。我们在使用RNN编码器和解码器训练Seq2Seq模型时(第13章《时间序列常见建模模式》)看到了这一策略的应用。

预测阶段

RecJoint的预测阶段与递归策略完全相同。

现在我们已经了解了几种策略,让我们讨论它们的优缺点。

如何选择多步预测策略

让我们将所有已学习的策略汇总到一张表中:

图17.9 – 多步预测策略 – 总结

图18.9:多步预测策略——总结

其中,以下内容适用:

该表帮助我们理解并从多个角度决定哪种策略更优:

它还有助于我们决定每种策略可使用的模型类型。例如,联合策略只能通过支持多输出的模型实现,例如DL模型。然而,我们尚未讨论这些策略如何影响精度。

尽管在机器学习中,最终评判依据是经验证据,但我们仍可通过分析方法来理解不同方法,从而获得一些指导原则。Taieb等人从理论和模拟数据两方面分析了这些多步预测策略的偏差与方差。

结合这一分析以及多年来其他实证发现,我们得以理解这些策略的优势与不足,并从中总结出一些指导原则。

参考文献核实

Taieb等人的研究论文被引用为参考文献[3]

Taieb等人基于误差分析的偏差与方差分量,指出了递归策略相对于直接策略的若干缺点。他们进一步通过实证研究证实了这些观察。

阐明性能差异的关键要点如下:

混合策略,如DirRec、IBD等,试图平衡基本策略(如直接、递归和联合)的优缺点。通过这些优缺点,我们可以建立一个有依据的实验框架,以得出针对当前问题的最佳策略。

小结

在本章中,我们讨论了预测中一个与现实世界用例高度相关但很少被提及和研究的具体方面。我们了解了为什么需要多步预测,然后回顾了一些常用的策略。我们探讨了流行且基本的策略,如直接、递归和联合,然后研究了一些混合策略,如DirRec、rectify等。最后,我们审视了这些策略的优缺点,并讨论了一些选择适合你问题的策略的指导原则。

在下一章中,我们将探讨预测的另一个重要方面——评估。

参考文献

以下是本章中使用的参考文献列表:

  1. Taieb, S.B., Bontempi, G., Atiya, A.F., and Sorjamaa, A. (2012). A review and comparison of strategies for multi-step ahead time series forecasting based on the NN5 forecasting competition. Expert Syst. Appl., 39, 7067–7083: https://arxiv.org/pdf/1108.3259.pdf
  2. Li Zhang, Wei-Da Zhou, Pei-Chann Chang, Ji-Wen Yang, and Fan-Zhang Li. (2013). Iterated time series prediction with multiple support vector regression models. Neurocomputing, Volume 99, 2013: https://www.sciencedirect.com/science/article/pii/S0925231212005863
  3. Taieb, S.B. and Atiya, A.F. (2016). A Bias and Variance Analysis for Multistep-Ahead Time Series Forecasting. in IEEE Transactions on Neural Networks and Learning Systems, vol. 27, no. 1, pp. 62–76, Jan. 2016: https://ieeexplore.ieee.org/document/7064712