统计、机器学习和神经预测方法 在本教程中,我们将探讨如何利用最合适的模型为 M5 数据集中的每个时间序列进行预测。我们将通过一个重要的技术来实现这一点,即交叉验证。这种方法有助于我们评估模型的预测性能,并选择对每个时间序列产生最佳性能的模型。

M5 数据集包含来自 Walmart 的为期五年的分层销售数据。目标是预测未来 28 天的每日销售额。数据集细分为美国的 50 个州,每个州有 10 家门店。

在时间序列预测和分析领域,更复杂的任务之一是确定最适合特定序列组的模型。通常,这种选择过程很大程度上依赖于直觉,这可能不一定与数据集的实证现实相符。

在本教程中,我们旨在为 M5 基准数据集中的不同序列组提供一种更结构化、数据驱动的模型选择方法。这个数据集在预测领域广为人知,使我们能够展示我们方法的通用性和强大性。

我们将训练来自各种预测范式的模型集合

StatsForecast

  • 基准模型:这些模型简单但通常非常有效,为预测问题提供初步视角。我们将使用 SeasonalNaiveHistoricAverage 模型作为此类别的模型。
  • 间歇性模型:对于需求零星、非连续的序列,我们将使用 CrostonOptimizedIMAPAADIDA 等模型。这些模型特别适用于处理零膨胀序列。
  • 状态空间模型:这些是使用系统的数学描述进行预测的统计模型。statsforecast 库中的 AutoETS 模型属于此类。

MLForecast

机器学习:利用 LightGBMXGBoostLinearRegression 等机器学习模型可能非常有利,因为它们能够发现数据中的复杂模式。我们将为此使用 MLForecast 库。

NeuralForecast

深度学习:深度学习模型,如 Transformers (AutoTFT) 和神经网络 (AutoNHITS),使我们能够处理时间序列数据中复杂的非线性依赖关系。我们将为此使用 NeuralForecast 库。

使用 Nixtla 库套件,我们将能够用数据驱动我们的模型选择过程,确保我们为数据集中的特定序列组使用最合适的模型。

概要

  • 读取数据:在初始步骤中,我们将数据集加载到内存中,以便进行后续分析和预测。在此阶段了解数据集的结构和细微之处非常重要。

  • 使用统计和深度学习方法进行预测:我们应用从基本统计技术到高级深度学习模型的各种预测方法。目标是根据我们的数据集生成未来 28 天的预测。

  • 在不同窗口上评估模型性能:我们在不同的窗口上评估模型的性能。

  • 为一组序列选择最佳模型:利用性能评估,我们为每个序列组确定最佳模型。此步骤确保所选模型针对每个组的独特特征进行定制。

  • 筛选最佳可能预测:最后,我们筛选所选模型生成的预测,以获得最有希望的预测。这是我们的最终输出,代表了根据我们的模型对每个序列的最佳可能预测。

警告

本教程最初使用 c5d.24xlarge EC2 实例执行。

安装库

!pip install statsforecast mlforecast neuralforecast datasetforecast s3fs pyarrow

下载并准备数据

该示例使用 M5 数据集。它包含 30,490 条底层时间序列。

import pandas as pd
# Load the training target dataset from the provided URL
Y_df = pd.read_parquet('https://m5-benchmarks.s3.amazonaws.com/data/train/target.parquet')

# Rename columns to match the Nixtlaverse's expectations
# The 'item_id' becomes 'unique_id' representing the unique identifier of the time series
# The 'timestamp' becomes 'ds' representing the time stamp of the data points
# The 'demand' becomes 'y' representing the target variable we want to forecast
Y_df = Y_df.rename(columns={
    'item_id': 'unique_id', 
    'timestamp': 'ds', 
    'demand': 'y'
})

# Convert the 'ds' column to datetime format to ensure proper handling of date-related operations in subsequent steps
Y_df['ds'] = pd.to_datetime(Y_df['ds'])

为了简单起见,我们只保留一个类别

Y_df = Y_df.query('unique_id.str.startswith("FOODS_3")').reset_index(drop=True)

Y_df['unique_id'] = Y_df['unique_id'].astype(str)

基本绘图

使用 StatsForecast 类中的 plot 方法绘制一些序列。此方法会打印数据集中的 8 条随机序列,对于基本 EDA 很有用。

from statsforecast import StatsForecast
/home/ubuntu/statsforecast/statsforecast/core.py:23: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from tqdm.autonotebook import tqdm
# Feature: plot random series for EDA
StatsForecast.plot(Y_df)
# Feature: plot groups of series for EDA
StatsForecast.plot(Y_df, unique_ids=["FOODS_3_432_TX_2"])
# Feature: plot groups of series for EDA
StatsForecast.plot(Y_df, unique_ids=["FOODS_3_432_TX_2"], engine ='matplotlib')

使用 Stats、Ml 和 Neural 方法创建预测。

StatsForecast

StatsForecast 是一个全面的库,提供了一套流行的单变量时间序列预测模型,所有模型都注重高性能和可扩展性。

以下是使 StatsForecast 成为时间序列预测强大工具的原因

  • 本地模型的集合:StatsForecast 提供了一个多样化的本地模型集合,这些模型可以单独应用于每个时间序列,使我们能够捕获每个序列中的独特模式。

  • 简单易用:使用 StatsForecast,训练、预测和回测多个模型变得非常简单,只需几行代码即可完成。这种简单性使其成为初学者和经验丰富的从业者的便捷工具。

  • 速度优化:StatsForecast 中的模型实现针对速度进行了优化,确保高效执行大规模计算,从而减少模型训练和预测的总时间。

  • 水平可扩展性:StatsForecast 的一个显著特点是其水平扩展能力。它与 Spark、Dask 和 Ray 等分布式计算框架兼容。此功能使其能够通过将计算分布到集群中的多个节点来处理大型数据集,使其成为大规模时间序列预测任务的首选解决方案。

StatsForecast 接收一个模型列表,用于拟合每个时间序列。由于我们处理的是每日数据,使用 7 作为季节性周期将非常有益。

# Import necessary models from the statsforecast library
from statsforecast.models import (
    # SeasonalNaive: A model that uses the previous season's data as the forecast
    SeasonalNaive,
    # Naive: A simple model that uses the last observed value as the forecast
    Naive,
    # HistoricAverage: This model uses the average of all historical data as the forecast
    HistoricAverage,
    # CrostonOptimized: A model specifically designed for intermittent demand forecasting
    CrostonOptimized,
    # ADIDA: Adaptive combination of Intermittent Demand Approaches, a model designed for intermittent demand
    ADIDA,
    # IMAPA: Intermittent Multiplicative AutoRegressive Average, a model for intermittent series that incorporates autocorrelation
    IMAPA,
    # AutoETS: Automated Exponential Smoothing model that automatically selects the best Exponential Smoothing model based on AIC
    AutoETS
)

我们通过使用以下参数实例化新的 StatsForecast 对象来拟合模型

  • models:模型列表。从模型中选择您想要的模型并导入它们。
  • freq:指示数据频率的字符串。(参见 panda 可用的频率。)
  • n_jobs:int,并行处理中使用的作业数,使用 -1 表示所有核心。
  • fallback_model:如果某个模型失败,则使用的备用模型。任何设置都将传递给构造函数。然后您调用其 fit 方法并传入历史数据框。
horizon = 28
models = [
    SeasonalNaive(season_length=7),
    Naive(),
    HistoricAverage(),
    CrostonOptimized(),
    ADIDA(),
    IMAPA(),
    AutoETS(season_length=7)
]
# Instantiate the StatsForecast class
sf = StatsForecast(
    models=models,  # A list of models to be used for forecasting
    freq='D',  # The frequency of the time series data (in this case, 'D' stands for daily frequency)
    n_jobs=-1,  # The number of CPU cores to use for parallel execution (-1 means use all available cores)
)

forecast 方法接受两个参数:预测未来 h(预测期)和 level。

  • h (int):表示未来 h 步的预测。在这种情况下,提前 12 个月。
  • level (list of floats):此可选参数用于概率预测。设置预测区间的 level(或置信百分位数)。例如,level=[90] 意味着模型期望真实值有 90% 的时间落在该区间内。

这里的 forecast 对象是一个新的数据框,其中包含一列模型名称和 y hat 值,以及不确定性区间的列。

此代码块计时 StatsForecast 类的预测函数运行所需的时间,该函数预测未来 28 天(h=28)。level 设置为 [90],意味着它将计算 90% 的预测区间。时间以分钟为单位计算并在最后打印出来。

from time import time

# Get the current time before forecasting starts, this will be used to measure the execution time
init = time()

# Call the forecast method of the StatsForecast instance to predict the next 28 days (h=28) 
# Level is set to [90], which means that it will compute the 90% prediction interval
fcst_df = sf.forecast(df=Y_df, h=28, level=[90])

# Get the current time after the forecasting ends
end = time()

# Calculate and print the total time taken for the forecasting in minutes
print(f'Forecast Minutes: {(end - init) / 60}')
Forecast Minutes: 2.270755163828532
fcst_df.head()
dsSeasonalNaiveSeasonalNaive-lo-90SeasonalNaive-hi-90NaiveNaive-lo-90Naive-hi-90HistoricAverageHistoricAverage-lo-90HistoricAverage-hi-90CrostonOptimizedADIDAIMAPAAutoETSAutoETS-lo-90AutoETS-hi-90
unique_id
FOODS_3_001_CA_12016-05-231.0-2.8471744.8471742.00.0983633.9016370.448738-1.0095791.9070550.3451920.3454770.3472490.381414-1.0281221.790950
FOODS_3_001_CA_12016-05-240.0-3.8471743.8471742.0-0.6893214.6893210.448738-1.0095791.9070550.3451920.3454770.3472490.286933-1.1241361.698003
FOODS_3_001_CA_12016-05-250.0-3.8471743.8471742.0-1.2937325.2937320.448738-1.0095791.9070550.3451920.3454770.3472490.334987-1.0776141.747588
FOODS_3_001_CA_12016-05-261.0-2.8471744.8471742.0-1.8032745.8032740.448738-1.0095791.9070550.3451920.3454770.3472490.186851-1.2272801.600982
FOODS_3_001_CA_12016-05-270.0-3.8471743.8471742.0-2.2521906.2521900.448738-1.0095791.9070550.3451920.3454770.3472490.308112-1.1075481.723771

MLForecast

MLForecast 是一个强大的库,为时间序列预测提供自动化特征创建,促进全局机器学习模型的使用。它旨在提供高性能和可扩展性。

MLForecast 的主要特点包括

  • 支持 sklearn 模型:MLForecast 与遵循 scikit-learn API 的模型兼容。这使其具有高度灵活性,并允许其与各种机器学习算法无缝集成。

  • 简单易用:使用 MLForecast,训练、预测和回测模型的任务只需几行代码即可完成。这种精简的简单性使其对所有专业水平的从业者都非常友好。

  • 速度优化:MLForecast 旨在快速执行任务,这在处理大型数据集和复杂模型时至关重要。

  • 水平可扩展性:MLForecast 能够使用 Spark、Dask 和 Ray 等分布式计算框架进行水平扩展。此功能使其能够通过将计算分布到集群中的多个节点来高效处理海量数据集,非常适合大规模时间序列预测任务。

from mlforecast import MLForecast
from mlforecast.target_transforms import Differences
from mlforecast.utils import PredictionIntervals
from window_ops.expanding import expanding_mean
!pip install lightgbm xgboost
# Import the necessary models from various libraries

# LGBMRegressor: A gradient boosting framework that uses tree-based learning algorithms from the LightGBM library
from lightgbm import LGBMRegressor

# XGBRegressor: A gradient boosting regressor model from the XGBoost library
from xgboost import XGBRegressor

# LinearRegression: A simple linear regression model from the scikit-learn library
from sklearn.linear_model import LinearRegression

要使用 MLForecast 进行时间序列预测,我们需要实例化一个新的 MLForecast 对象,并为其提供各种参数,以便根据我们的特定需求定制建模过程

  • models:此参数接受您希望用于预测的机器学习模型列表。您可以从 scikit-learn、lightgbm 和 xgboost 中导入您偏爱的模型。

  • freq:这是一个指示您的数据频率(小时、日、周等)的字符串。此字符串的特定格式应与 pandas 识别的频率字符串对齐。

  • target_transforms:这些是在模型训练之前和模型预测之后应用于目标变量的转换。这在处理可能受益于转换的数据时很有用,例如对高度偏斜的数据进行对数转换。

  • lags:此参数接受要用作回归量的特定滞后值。滞后表示在为模型创建特征时,您希望回溯多少时间步。例如,如果您想使用前一天的数据作为预测今天值的特征,您将指定滞后为 1。

  • lags_transforms:这些是针对每个滞后的特定转换。这使您可以对滞后特征应用转换。

  • date_features:此参数指定要用作回归量的日期相关特征。例如,您可能希望在模型中包含星期几或月份作为特征。

  • num_threads:此参数控制用于并行化特征创建的线程数,有助于在处理大型数据集时加快此过程。

所有这些设置都传递给 MLForecast 构造函数。一旦 MLForecast 对象使用这些设置初始化,我们调用其 fit 方法并将历史数据框作为参数传递。fit 方法在提供的历史数据上训练模型,为将来的预测任务做好准备。

# Instantiate the MLForecast object
mlf = MLForecast(
    models=[LGBMRegressor(), XGBRegressor(), LinearRegression()],  # List of models for forecasting: LightGBM, XGBoost and Linear Regression
    freq='D',  # Frequency of the data - 'D' for daily frequency
    lags=list(range(1, 7)),  # Specific lags to use as regressors: 1 to 6 days
    lag_transforms = {
        1:  [expanding_mean],  # Apply expanding mean transformation to the lag of 1 day
    },
    date_features=['year', 'month', 'day', 'dayofweek', 'quarter', 'week'],  # Date features to use as regressors
)

只需调用 fit 方法训练选定的模型。在这种情况下,我们正在生成共形预测区间。

# Start the timer to calculate the time taken for fitting the models
init = time()

# Fit the MLForecast models to the data, with prediction intervals set using a window size of 28 days
mlf.fit(Y_df, prediction_intervals=PredictionIntervals(window_size=28))

# Calculate the end time after fitting the models
end = time()

# Print the time taken to fit the MLForecast models, in minutes
print(f'MLForecast Minutes: {(end - init) / 60}')
MLForecast Minutes: 2.2809854547182717

之后,只需调用 predict 生成预测。

fcst_mlf_df = mlf.predict(28, level=[90])
fcst_mlf_df.head()
unique_iddsLGBMRegressorXGBRegressorLinearRegressionLGBMRegressor-lo-90LGBMRegressor-hi-90XGBRegressor-lo-90XGBRegressor-hi-90LinearRegression-lo-90LinearRegression-hi-90
0FOODS_3_001_CA_12016-05-230.5495200.5984310.359638-0.2139151.312955-0.0200501.2169120.0300000.689277
1FOODS_3_001_CA_12016-05-240.5531960.3372680.100361-0.2513831.357775-0.2014490.875985-0.2161950.416917
2FOODS_3_001_CA_12016-05-250.5996680.3496040.175840-0.2039741.403309-0.2844160.983624-0.1505930.502273
3FOODS_3_001_CA_12016-05-260.6380970.3221440.1564600.1186881.157506-0.0858720.730160-0.2738510.586771
4FOODS_3_001_CA_12016-05-270.7633050.3003620.328194-0.3130911.839701-0.2966360.897360-0.6570891.313476

NeuralForecast

NeuralForecast 是一个强大的神经预测模型集合,注重可用性和性能。它包含各种模型架构,从经典网络如多层感知机 (MLP) 和循环神经网络 (RNN),到 N-BEATS、N-HITS、时态融合 Transformer (TFT) 等新颖贡献,应有尽有。

NeuralForecast 的主要特点包括

  • 广泛的全局模型集合。开箱即用地实现了 MLP、LSTM、RNN、TCN、DilatedRNN、NBEATS、NHITS、ESRNN、TFT、Informer、PatchTST 和 HINT。
  • 一个简单直观的界面,只需几行代码即可进行各种模型的训练、预测和回测。
  • 支持 GPU 加速以提高计算速度。

这台机器没有 GPU,但 Google Colabs 提供一些免费的。

使用 Colab 的 GPU 训练 NeuralForecast

# Read the results from Colab
fcst_nf_df = pd.read_parquet('https://m5-benchmarks.s3.amazonaws.com/data/forecast-nf.parquet')
fcst_nf_df.head()
unique_iddsAutoNHITSAutoNHITS-lo-90AutoNHITS-hi-90AutoTFTAutoTFT-lo-90AutoTFT-hi-90
0FOODS_3_001_CA_12016-05-230.00.02.00.00.02.0
1FOODS_3_001_CA_12016-05-240.00.02.00.00.02.0
2FOODS_3_001_CA_12016-05-250.00.02.00.00.01.0
3FOODS_3_001_CA_12016-05-260.00.02.00.00.02.0
4FOODS_3_001_CA_12016-05-270.00.02.00.00.02.0
# Merge the forecasts from StatsForecast and NeuralForecast
fcst_df = fcst_df.merge(fcst_nf_df, how='left', on=['unique_id', 'ds'])

# Merge the forecasts from MLForecast into the combined forecast dataframe
fcst_df = fcst_df.merge(fcst_mlf_df, how='left', on=['unique_id', 'ds'])
fcst_df.head()
unique_iddsSeasonalNaiveSeasonalNaive-lo-90SeasonalNaive-hi-90NaiveNaive-lo-90Naive-hi-90HistoricAverageHistoricAverage-lo-90AutoTFT-hi-90LGBMRegressorXGBRegressorLinearRegressionLGBMRegressor-lo-90LGBMRegressor-hi-90XGBRegressor-lo-90XGBRegressor-hi-90LinearRegression-lo-90LinearRegression-hi-90
0FOODS_3_001_CA_12016-05-231.0-2.8471744.8471742.00.0983633.9016370.448738-1.0095792.00.5495200.5984310.359638-0.2139151.312955-0.0200501.2169120.0300000.689277
1FOODS_3_001_CA_12016-05-240.0-3.8471743.8471742.0-0.6893214.6893210.448738-1.0095792.00.5531960.3372680.100361-0.2513831.357775-0.2014490.875985-0.2161950.416917
2FOODS_3_001_CA_12016-05-250.0-3.8471743.8471742.0-1.2937325.2937320.448738-1.0095791.00.5996680.3496040.175840-0.2039741.403309-0.2844160.983624-0.1505930.502273
3FOODS_3_001_CA_12016-05-261.0-2.8471744.8471742.0-1.8032745.8032740.448738-1.0095792.00.6380970.3221440.1564600.1186881.157506-0.0858720.730160-0.2738510.586771
4FOODS_3_001_CA_12016-05-270.0-3.8471743.8471742.0-2.2521906.2521900.448738-1.0095792.00.7633050.3003620.328194-0.3130911.839701-0.2966360.897360-0.6570891.313476

预测图

sf.plot(Y_df, fcst_df, max_insample_length=28 * 3)

使用 plot 函数探索模型和 ID

sf.plot(Y_df, fcst_df, max_insample_length=28 * 3, 
        models=['CrostonOptimized', 'AutoNHITS', 'SeasonalNaive', 'LGBMRegressor'])

验证模型性能

这三个库——StatsForecastMLForecastNeuralForecast——提供了专门为时间序列设计的开箱即用的交叉验证功能。这使我们能够使用历史数据评估模型的性能,以获得对每个模型在新数据上表现如何的无偏评估。

StatsForecast 中的交叉验证

StatsForecast 类中的 cross_validation 方法接受以下参数

  • df:表示训练数据的 DataFrame。
  • h (int):预测期,表示我们希望预测的未来步数。例如,如果我们预测小时数据,h=24 将表示 24 小时预测。
  • step_size (int):每个交叉验证窗口之间的步长。此参数确定我们希望多久运行一次预测过程。
  • n_windows (int):用于交叉验证的窗口数量。此参数定义了我们希望评估过去多少个预测过程。

这些参数允许我们控制交叉验证过程的范围和粒度。通过调整这些设置,我们可以平衡计算成本和交叉验证的彻底性。

init = time()
cv_df = sf.cross_validation(df=Y_df, h=horizon, n_windows=3, step_size=horizon, level=[90])
end = time()
print(f'CV Minutes: {(end - init) / 60}')
/home/ubuntu/statsforecast/statsforecast/ets.py:1041: RuntimeWarning:

divide by zero encountered in double_scalars
CV Minutes: 5.206169327100118

crossvaldation_df 对象是一个新的数据框,包含以下列

  • unique_id 索引:(如果您不喜欢使用索引,只需运行 forecasts_cv_df.resetindex())
  • ds:日期戳或时间索引
  • cutoff:n_windows 的最后一个日期戳或时间索引。如果 n_windows=1,则只有一个唯一的 cutoff 值;如果 n_windows=2,则有两个唯一的 cutoff 值。
  • y:真实值
  • "model":包含模型名称和拟合值的列。
cv_df.head()
dscutoffySeasonalNaiveSeasonalNaive-lo-90SeasonalNaive-hi-90NaiveNaive-lo-90Naive-hi-90HistoricAverageHistoricAverage-lo-90HistoricAverage-hi-90CrostonOptimizedADIDAIMAPAAutoETSAutoETS-lo-90AutoETS-hi-90
unique_id
FOODS_3_001_CA_12016-02-292016-02-280.02.0-1.8788855.8788850.0-1.9170111.9170110.449111-1.0218131.9200360.6184720.6183750.6179980.655286-0.7657312.076302
FOODS_3_001_CA_12016-03-012016-02-281.00.0-3.8788853.8788850.0-2.7110642.7110640.449111-1.0218131.9200360.6184720.6183750.6179980.568595-0.8539661.991155
FOODS_3_001_CA_12016-03-022016-02-281.00.0-3.8788853.8788850.0-3.3203613.3203610.449111-1.0218131.9200360.6184720.6183750.6179980.618805-0.8052982.042908
FOODS_3_001_CA_12016-03-032016-02-280.01.0-2.8788854.8788850.0-3.8340233.8340230.449111-1.0218131.9200360.6184720.6183750.6179980.455891-0.9697531.881534
FOODS_3_001_CA_12016-03-042016-02-280.01.0-2.8788854.8788850.0-4.2865684.2865680.449111-1.0218131.9200360.6184720.6183750.6179980.591197-0.8359872.018380

MLForecast

MLForecast 类中的 cross_validation 方法接受以下参数。

  • data:训练数据框
  • window_size (int):表示正在预测的未来 h 步。在这种情况下,提前 24 小时。
  • step_size (int):每个窗口之间的步长。换句话说:您希望多久运行一次预测过程。
  • n_windows (int):用于交叉验证的窗口数量。换句话说:您希望评估过去多少个预测过程。
  • prediction_intervals:用于计算共形区间的类。
init = time()
cv_mlf_df = mlf.cross_validation(
    data=Y_df, 
    window_size=horizon, 
    n_windows=3, 
    step_size=horizon, 
    level=[90],
)
end = time()
print(f'CV Minutes: {(end - init) / 60}')
/home/ubuntu/miniconda/envs/statsforecast/lib/python3.10/site-packages/mlforecast/forecast.py:576: UserWarning:

Excuting `cross_validation` after `fit` can produce unexpected errors

/home/ubuntu/miniconda/envs/statsforecast/lib/python3.10/site-packages/mlforecast/forecast.py:468: UserWarning:

Please rerun the `fit` method passing a proper value to prediction intervals to compute them.

/home/ubuntu/miniconda/envs/statsforecast/lib/python3.10/site-packages/mlforecast/forecast.py:468: UserWarning:

Please rerun the `fit` method passing a proper value to prediction intervals to compute them.

/home/ubuntu/miniconda/envs/statsforecast/lib/python3.10/site-packages/mlforecast/forecast.py:468: UserWarning:

Please rerun the `fit` method passing a proper value to prediction intervals to compute them.
CV Minutes: 2.961174162228902

crossvaldation_df 对象是一个新的数据框,包含以下列

  • unique_id 索引:(如果您不喜欢使用索引,只需运行 forecasts_cv_df.resetindex())
  • ds:日期戳或时间索引
  • cutoff:n_windows 的最后一个日期戳或时间索引。如果 n_windows=1,则只有一个唯一的 cutoff 值;如果 n_windows=2,则有两个唯一的 cutoff 值。
  • y:真实值
  • "model":包含模型名称和拟合值的列。
cv_mlf_df.head()
unique_iddscutoffyLGBMRegressorXGBRegressorLinearRegression
0FOODS_3_001_CA_12016-02-292016-02-280.00.4356740.556261-0.312492
1FOODS_3_001_CA_12016-03-012016-02-281.00.6396760.625806-0.041924
2FOODS_3_001_CA_12016-03-022016-02-281.00.7929890.6596500.263699
3FOODS_3_001_CA_12016-03-032016-02-280.00.8068680.5351210.482491
4FOODS_3_001_CA_12016-03-042016-02-280.00.8291060.3133530.677326

NeuralForecast

这台机器没有 GPU,但 Google Colabs 提供一些免费的。

使用 Colab 的 GPU 训练 NeuralForecast

cv_nf_df = pd.read_parquet('https://m5-benchmarks.s3.amazonaws.com/data/cross-validation-nf.parquet')
cv_nf_df.head()
unique_iddscutoffAutoNHITSAutoNHITS-lo-90AutoNHITS-hi-90AutoTFTAutoTFT-lo-90AutoTFT-hi-90y
0FOODS_3_001_CA_12016-02-292016-02-280.00.02.01.00.02.00.0
1FOODS_3_001_CA_12016-03-012016-02-280.00.02.01.00.02.01.0
2FOODS_3_001_CA_12016-03-022016-02-280.00.02.01.00.02.01.0
3FOODS_3_001_CA_12016-03-032016-02-280.00.02.01.00.02.00.0
4FOODS_3_001_CA_12016-03-042016-02-280.00.02.01.00.02.00.0

合并交叉验证预测

cv_df = cv_df.merge(cv_nf_df.drop(columns=['y']), how='left', on=['unique_id', 'ds', 'cutoff'])
cv_df = cv_df.merge(cv_mlf_df.drop(columns=['y']), how='left', on=['unique_id', 'ds', 'cutoff'])

绘图 CV

cutoffs = cv_df['cutoff'].unique()
for cutoff in cutoffs:
    img = sf.plot(
        Y_df, 
        cv_df.query('cutoff == @cutoff').drop(columns=['y', 'cutoff']), 
        max_insample_length=28 * 5, 
        unique_ids=['FOODS_3_001_CA_1'],
    )
    img.show()

总需求

agg_cv_df = cv_df.loc[:,~cv_df.columns.str.contains('hi|lo')].groupby(['ds', 'cutoff']).sum(numeric_only=True).reset_index()
agg_cv_df.insert(0, 'unique_id', 'agg_demand')
agg_Y_df = Y_df.groupby(['ds']).sum(numeric_only=True).reset_index()
agg_Y_df.insert(0, 'unique_id', 'agg_demand')
for cutoff in cutoffs:
    img = sf.plot(
        agg_Y_df, 
        agg_cv_df.query('cutoff == @cutoff').drop(columns=['y', 'cutoff']),
        max_insample_length=28 * 5,
    )
    img.show()

按序列和 CV 窗口进行评估

在本节中,我们将评估每个模型在每个时间序列和每个交叉验证窗口上的性能。由于组合很多,我们将使用 dask 进行并行评估。并行化将使用 fugue 完成。

from typing import List, Callable

from distributed import Client
from fugue import transform
from fugue_dask import DaskExecutionEngine
from datasetsforecast.losses import mse, mae, smape

evaluate 函数接收时间序列和窗口的唯一组合,并计算 df 中每个模型的不同 metrics

def evaluate(df: pd.DataFrame, metrics: List[Callable]) -> pd.DataFrame:
    eval_ = {}
    models = df.loc[:, ~df.columns.str.contains('unique_id|y|ds|cutoff|lo|hi')].columns
    for model in models:
        eval_[model] = {}
        for metric in metrics:
            eval_[model][metric.__name__] = metric(df['y'], df[model])
    eval_df = pd.DataFrame(eval_).rename_axis('metric').reset_index()
    eval_df.insert(0, 'cutoff', df['cutoff'].iloc[0])
    eval_df.insert(0, 'unique_id', df['unique_id'].iloc[0])
    return eval_df
str_models = cv_df.loc[:, ~cv_df.columns.str.contains('unique_id|y|ds|cutoff|lo|hi')].columns
str_models = ','.join([f"{model}:float" for model in str_models])
cv_df['cutoff'] = cv_df['cutoff'].astype(str)
cv_df['unique_id'] = cv_df['unique_id'].astype(str)

让我们创建一个 dask 客户端。

client = Client() # without this, dask is not in distributed mode
# fugue.dask.dataframe.default.partitions determines the default partitions for a new DaskDataFrame
engine = DaskExecutionEngine({"fugue.dask.dataframe.default.partitions": 96})

transform 函数接收 evaluate 函数,并使用我们之前创建的 dask 客户端将其应用于时间序列 (unique_id) 和交叉验证窗口 (cutoff) 的每个组合。

evaluation_df = transform(
    cv_df.loc[:, ~cv_df.columns.str.contains('lo|hi')], 
    evaluate, 
    engine="dask",
    params={'metrics': [mse, mae, smape]}, 
    schema=f"unique_id:str,cutoff:str,metric:str, {str_models}", 
    as_local=True,
    partition={'by': ['unique_id', 'cutoff']}
)
/home/ubuntu/miniconda/envs/statsforecast/lib/python3.10/site-packages/distributed/client.py:3109: UserWarning:

Sending large graph of size 49.63 MiB.
This may cause some slowdown.
Consider scattering data ahead of time and using futures.
evaluation_df.head()
unique_idcutoffmetricSeasonalNaiveNaiveHistoricAverageCrostonOptimizedADIDAIMAPAAutoETSAutoNHITSAutoTFTLGBMRegressorXGBRegressorLinearRegression
0FOODS_3_003_WI_32016-02-28mse1.1428571.1428570.8166460.8164711.1428571.1428571.1428571.1428571.1428570.8320101.0203610.887121
1FOODS_3_003_WI_32016-02-28mae0.5714290.5714290.7295920.7312610.5714290.5714290.5714290.5714290.5714290.7727880.6199490.685413
2FOODS_3_003_WI_32016-02-28smape71.42857471.428574158.813507158.516235200.000000200.000000200.00000071.42857471.428574145.901947188.159164178.883743
3FOODS_3_013_CA_32016-04-24mse4.0000006.2142862.4067643.5612022.2678532.2676002.2686772.7500002.1250002.1605082.3702282.289606
4FOODS_3_013_CA_32016-04-24mae1.5000002.1428571.2142861.3404461.2142861.2142861.2142861.1071431.1428571.1400841.1575481.148813
# Calculate the mean metric for each cross validation window
evaluation_df.groupby(['cutoff', 'metric']).mean(numeric_only=True)
SeasonalNaiveNaiveHistoricAverageCrostonOptimizedADIDAIMAPAAutoETSAutoNHITSAutoTFTLGBMRegressorXGBRegressorLinearRegression
cutoffmetric
2016-02-28mae1.7442892.0404961.7307041.6330171.5279651.5287721.4975531.4349381.4854191.6884031.5141021.576320
mse14.51071019.08058512.85899411.78503211.11449711.10090910.34784710.01098210.96466410.43620610.96878810.792831
smape85.20204287.719086125.418488124.749908127.591858127.704102127.79067279.13261480.983368118.489983140.420578127.043137
2016-03-27mae1.7959732.1064491.7540291.6620871.5707011.5727411.5353011.4324121.5023931.7124931.6001931.601612
mse14.81025926.04447212.80410412.02062012.08386112.12003311.3150139.44586710.76287710.72358912.92431210.943772
smape87.40747189.453247123.587196123.460030123.428459123.538521123.61299179.92678182.013168116.089699138.885941127.304871
2016-04-24mae1.7859831.9907741.7625061.6092681.5276271.5297211.5018201.4474011.5051271.6929461.5418451.590985
mse13.47635016.23491713.15131110.64704810.07222510.0623959.3934399.36389110.43621410.34707310.77420210.608137
smape89.23881590.685867121.124947119.721245120.325401120.345284120.64958281.40274883.614029113.334198136.755234124.618622

先前实验中显示的结果。

modelMSE
MQCNN10.09
DeepAR-student_t10.11
DeepAR-lognormal30.20
DeepAR9.13
NPTS11.53

前 3 个模型:DeepAR、AutoNHITS、AutoETS。

误差分布

!pip install seaborn
import matplotlib.pyplot as plt
import seaborn as sns
evaluation_df_melted = pd.melt(evaluation_df, id_vars=['unique_id', 'cutoff', 'metric'], var_name='model', value_name='error')

SMAPE

sns.violinplot(evaluation_df_melted.query('metric=="smape"'), x='error', y='model')

为序列组选择模型

特征

  • 包含所有不同模型预测的统一数据框
  • 简易集成
  • 例如,平均预测
  • 或 MinMax(选择即集成)
# Choose the best model for each time series, metric, and cross validation window
evaluation_df['best_model'] = evaluation_df.idxmin(axis=1, numeric_only=True)
# count how many times a model wins per metric and cross validation window
count_best_model = evaluation_df.groupby(['cutoff', 'metric', 'best_model']).size().rename('n').to_frame().reset_index()
# plot results
sns.barplot(count_best_model, x='n', y='best_model', hue='metric')

合众为一(Et pluribus unum):一个包容性的预测饼图。

# For the mse, calculate how many times a model wins
eval_series_df = evaluation_df.query('metric == "mse"').groupby(['unique_id']).mean(numeric_only=True)
eval_series_df['best_model'] = eval_series_df.idxmin(axis=1)
counts_series = eval_series_df.value_counts('best_model')
plt.pie(counts_series, labels=counts_series.index, autopct='%.0f%%')
plt.show()

sf.plot(Y_df, cv_df.drop(columns=['cutoff', 'y']), 
        max_insample_length=28 * 6, 
        models=['AutoNHITS'],
        unique_ids=eval_series_df.query('best_model == "AutoNHITS"').index[:8])

为不同序列组选择预测方法

# Merge the best model per time series dataframe
# and filter the forecasts based on that dataframe
# for each time series
fcst_df = pd.melt(fcst_df.set_index('unique_id'), id_vars=['ds'], var_name='model', value_name='forecast', ignore_index=False)
fcst_df = fcst_df.join(eval_series_df[['best_model']])
fcst_df[['model', 'pred-interval']] = fcst_df['model'].str.split('-', expand=True, n=1)
fcst_df = fcst_df.query('model == best_model')
fcst_df['name'] = [f'forecast-{x}' if x is not None else 'forecast' for x in fcst_df['pred-interval']]
fcst_df = pd.pivot_table(fcst_df, index=['unique_id', 'ds'], values=['forecast'], columns=['name']).droplevel(0, axis=1).reset_index()
sf.plot(Y_df, fcst_df, max_insample_length=28 * 3)

技术债

  • 在完整数据集上训练统计模型。
  • 增加神经自动模型中 num_samples 的数量。
  • 包含其他模型,例如 ThetaARIMARNNLSTM 等。

更多资料