前提条件

本教程假设您对 StatsForecast 有基本了解。有关最小示例,请访问快速入门

引言

当我们生成预测时,通常会产生一个称为点预测的单一值。然而,这个值并没有告诉我们与预测相关的任何不确定性。为了衡量这种不确定性,我们需要预测区间

预测区间是预测在给定概率下可能取值的一个范围。因此,95% 的预测区间应该包含一个值范围,该范围以 95% 的概率包含实际的未来值。概率预测旨在生成完整的预测分布。另一方面,点预测通常返回上述分布的均值或中位数。然而,在实际场景中,最好不仅预测最可能的未来结果,还要预测许多替代结果。

问题在于,有些时间序列模型提供了预测分布,但另一些只提供点预测。那么,我们如何估计预测的不确定性呢?

预测区间

对于已经提供预测分布的模型,请查看预测区间

保形预测

要观看视频介绍,请参阅PyData 西雅图演讲

多分位数损失和统计模型可以提供预测区间,但问题是这些区间未校准,这意味着实际观测值落在区间内的频率与相关的置信水平不符。例如,一个经过校准的 95% 预测区间在重复抽样中应该包含真实值 95% 的时间。另一方面,一个未校准的 95% 预测区间可能只在 80% 的时间或可能 99% 的时间包含真实值。在第一种情况下,区间过窄并低估了不确定性;而在第二种情况下,区间过宽并高估了不确定性。

统计方法也假设正态性。这里,我们将讨论另一种称为保形预测的方法,它不需要任何分布假设。有关该方法的更多信息,请参阅Valery Manokhin 拥有的这个仓库

保形预测区间使用交叉验证在一个点预测器模型上生成区间。这意味着不需要先验概率,并且输出是经过良好校准的。不需要额外的训练,模型被视为一个黑箱。这种方法与任何模型都兼容。

Statsforecast 现在支持在所有可用模型上进行保形预测。

安装库

我们假设您已经安装了 StatsForecast。如果还没有,请查阅此指南了解如何安装 StatsForecast

使用 pip install statsforecast 安装必要的软件包

pip install statsforecast -U

加载并探索数据

在本例中,我们将使用来自M4 竞赛的每小时数据集。我们首先需要从 URL 下载数据,然后将其加载为 pandas 数据框。注意,我们将分别加载训练数据和测试数据。我们还将测试数据的 y 列重命名为 y_test

import pandas as pd
train = pd.read_csv('https://auto-arima-results.s3.amazonaws.com/M4-Hourly.csv')
test = pd.read_csv('https://auto-arima-results.s3.amazonaws.com/M4-Hourly-test.csv').rename(columns={'y': 'y_test'})
train.head()
unique_iddsy
0H11605.0
1H12586.0
2H13586.0
3H14559.0
4H15511.0

由于本 notebook 的目标是生成预测区间,我们将仅使用数据集的前 8 个序列,以减少总计算时间。

n_series = 8 
uids = train['unique_id'].unique()[:n_series] # select first n_series of the dataset
train = train.query('unique_id in @uids')
test = test.query('unique_id in @uids')

我们可以使用 utilsforecast 库中的 plot_series 函数绘制这些序列。此函数/方法有多个参数,本 notebook 中生成图表所需参数说明如下。

  • df:一个包含列 [unique_id, ds, y] 的 pandas 数据框。
  • forecasts_df:一个包含列 [unique_id, ds] 和模型名称的 pandas 数据框。
  • plot_random:布尔值 = True。随机绘制时间序列。
  • models:List[str]。包含我们希望绘制的模型列表。
  • level:List[float]。包含我们希望绘制的预测区间列表。
  • engine:str = matplotlib。也可以是 plotlyplotly 生成交互式图表,而 matplotlib 生成静态图表。
from utilsforecast.plotting import plot_series
plot_series(train, test, plot_random=False)

训练模型

StatsForecast 可以高效地在不同时间序列上训练多个模型。这些模型大多可以生成概率预测,这意味着它们既可以生成点预测,也可以生成预测区间。

在本例中,我们将使用SimpleExponentialSmoothingADIDA,它们本身不提供预测区间。因此,使用保形预测来生成预测区间是有意义的。

我们还将展示如何将其与ARIMA 一起使用,以提供不假设正态性的预测区间。

要使用这些模型,我们首先需要从 statsforecast.models 导入它们,然后进行实例化。

from statsforecast.models import SeasonalExponentialSmoothing, ADIDA, ARIMA
from statsforecast.utils import ConformalIntervals

# Create a list of models and instantiation parameters 
intervals = ConformalIntervals(h=24, n_windows=2)
# P.S. n_windows*h should be less than the count of data elements in your time series sequence.
# P.S. Also value of n_windows should be atleast 2 or more.

models = [
    SeasonalExponentialSmoothing(season_length=24, alpha=0.1, prediction_intervals=intervals),
    ADIDA(prediction_intervals=intervals),
    ARIMA(order=(24,0,12), prediction_intervals=intervals),
]

要实例化一个新的 StatsForecast 对象,需要以下参数:

  • df:包含训练数据的数据框。
  • models:上一步中定义的模型列表。
  • freq:指示数据频率的字符串。请参阅pandas 的可用频率
  • n_jobs:一个整数,指示并行处理中使用的作业数。使用 -1 选择所有核心。
sf = StatsForecast(models=models, freq=1, n_jobs=-1)

现在我们准备生成预测和预测区间。为此,我们将使用 forecast 方法,它接受两个参数:

  • h:一个整数,表示预测范围。在本例中,我们将预测未来 24 小时。
  • level:浮点数列表,表示预测区间的置信水平。例如,level=[95] 意味着值范围应以 95% 的概率包含实际的未来值。
levels = [80, 90] # confidence levels of the prediction intervals 

forecasts = sf.forecast(df=train, h=24, level=levels)
forecasts.head()
unique_idds季节性ES季节性ES-低-90季节性ES-低-80季节性ES-高-80季节性ES-高-90ADIDAADIDA-低-90ADIDA-低-80ADIDA-高-80ADIDA-高-90ARIMAARIMA-低-90ARIMA-低-80ARIMA-高-80ARIMA-高-90
0H1701624.132703553.097423556.359139691.906266695.167983747.292568599.519220600.030467894.554670895.065916618.078274609.440076610.583304625.573243626.716472
1H1702555.698193496.653559506.833156604.563231614.742827747.292568491.669220498.330467996.2546701002.915916549.789291510.464070515.232352584.346231589.114513
2H1703514.403029462.673117464.939840563.866218566.132941747.292568475.105038475.7937911018.7913461019.480099508.099925496.574844496.990264519.209587519.625007
3H1704482.057899433.030711436.161413527.954385531.085087747.292568440.069220440.1304671054.4546701054.515916486.376622471.141813471.516997501.236246501.611431
4H1705460.222522414.270186416.959492503.485552506.174858747.292568415.805038416.1937911078.3913461078.780099470.159478445.162316446.808608493.510348495.156640

绘制预测区间

这里我们将绘制一个时间序列的不同区间。

下方显示了使用季节性指数平滑的预测区间。即使模型只生成点预测,我们也能够获得预测区间。80% 的预测区间没有越过 90% 的预测区间,这表明区间是经过校准的。

plot_series(train, forecasts, level=levels, ids=['H105'], models=['SeasonalES'])

对于拟合能力较弱的模型,保形预测区间可能更大。更好的模型对应着更窄的区间。

plot_series(train, forecasts, level=levels, ids=['H105'], models=['ADIDA'])

ARIMA 是一个提供预测分布的模型的例子,但我们仍然可以使用保形预测来生成预测区间。如前所述,此方法的优点在于不假设正态性。

plot_series(train, forecasts, level=levels, ids=['H105'], models=['ARIMA'])

StatsForecast 对象

或者,可以在 StatsForecast 对象上定义预测区间。这将应用于所有未定义 prediction_intervals 的模型。

from statsforecast.models import SimpleExponentialSmoothing, ADIDA
from statsforecast.utils import ConformalIntervals
from statsforecast import StatsForecast

models = [
    SimpleExponentialSmoothing(alpha=0.1),
    ADIDA()
]

res = StatsForecast(
    models=models, 
    freq=1,
).forecast(df=train, h=24, prediction_intervals=ConformalIntervals(h=24, n_windows=2), level=[80]) 
res.head()
unique_iddsSESSES-低-80SES-高-80ADIDAADIDA-低-80ADIDA-高-80
0H1701742.669064649.221405836.116722747.292568600.030467894.554670
1H1702742.669064550.551324934.786804747.292568498.330467996.254670
2H1703742.669064523.621405961.716722747.292568475.7937911018.791346
3H1704742.669064488.121405997.216722747.292568440.1304671054.454670
4H1705742.669064464.0214051021.316722747.292568416.1937911078.391346

未来工作

保形预测已成为一种强大的不确定性量化框架,可在不作任何分布假设的情况下提供经过良好校准的预测区间。在过去几年中,其应用在学术界和工业界都大幅增长。我们将继续致力于此,未来教程可能包括:

  • 探索更大的数据集
  • 纳入行业特定示例
  • 研究与保形预测密切相关的专门方法,例如 jackknife+(有关 jackknife+ 的详细信息,请参阅此处)。

如果您对其中任何一项或任何其他相关主题感兴趣,请在 GitHub 上提出问题告知我们。

致谢

我们要感谢 Kevin Kho 编写本教程,并感谢 Valeriy Manokhin 在保形预测方面的专业知识以及推广此工作。

参考文献

Manokhin, Valery. (2022). Machine Learning for Probabilistic Prediction. 10.5281/zenodo.6727505.