sktime 内嵌 EnbPI 预测区间算法源码解析:从 aws-fortuna 提取的分位数共形预测实现
【免费下载链接】sktimeA unified framework for machine learning with time series项目地址: https://gitcode.com/GitHub_Trending/sk/sktime
导读
EnbPI(Ensemble Batch Prediction Intervals,集成批预测区间)是一种基于 bootstrap 与分位数共形预测的时间序列回归区间估计算法。由于上游库 aws-fortuna 存在依赖版本冲突、难以直接安装的问题,sktime 将其核心EnbPI类提取并轻微重写,存放于sktime/libs/_aws_fortuna_enbpi/,并在此基础上实现了集成式概率预测器EnbPIForecaster。本文以该 README 为线索,结合仓库内的完整实现源码,讲解 EnbPI 的算法原理、EnbPI类的完整 API 与逐段逻辑、在 sktime 预测框架中的集成方式,以及实际调用示例与使用限制。读完本文,你将能够在自己的时序预测任务中直接复用该共形区间计算管线,或深入理解EnbPIForecaster的区间生成机制。
背景:为什么 sktime 要内嵌一个 EnbPI 实现
Fortuna(aws-fortuna)是亚马逊开源的概率预测与不确定性量化库,其中提供了 EnbPI 算法的实现。然而,正如关联文档 README.md 所述:
Extracted and slightly rewritten enbpi class from the aws-fortuna library due to installation issues
由于原库的依赖(如 JAX 生态相关包)存在版本匹配问题,导致直接安装 aws-fortuna 不可行(仓库 libs/README.md 中的记录为 "The installation of the original package is not working due to dependency mismatches")。sktime 因此将其中EnbPI类所在的代码提取出来,做轻微改写后随仓库一同分发,从而在不引入 aws-fortuna 及其重依赖的前提下,为 sktime 的预测模块提供共形区间计算能力。
这一做法在 sktime 中并不罕见:sktime/libs/README.md 将libs目录划分为三类:
- 随 sktime 分发并维护的库:如
fracdiff、pykalman、vmdpy,可独立使用; - 私有 vendor fork(完整或部分拷贝):如
granite_ttm、lag_llama、timesfm等,供 sktime 内部估计器使用; - 来自其他库的私有片段:即文件夹名以下划线
_开头的目录,_aws_fortuna_enbpi就属于这一类——它们"不应被 sktime 用户直接访问",而是作为内部基础设施供sktime估计器调用。
随包分发的代码同时附带 LICENSE.txt(Apache License 2.0)与 NOTICE.txt(Copyright Amazon.com, Inc. or its affiliates),保留了上游版权归属。
EnbPI 算法原理
EnbPI(Ensemble Batch Prediction Intervals)是 Chen Xu 与 Yao Xie 于 2021 年提出的针对动态时间序列的共形预测区间算法。其核心思想可概括为三步:
- Bootstrap 重采样:对训练时序数据做有放回抽样,生成 B 组 bootstrap 样本;
- 集成预测:对每一组样本训练一个模型,并对训练点与测试点分别给出预测;
- 分位数共形校正:利用"未被该 bootstrap 样本包含的训练点"(即 out-of-bag 样本)计算残差分布,再取
1 - error分位数构造区间,从而对区间宽度进行自适应校准,满足近似边际覆盖率保证。
该算法的两个显著特性(来自 enbpi.py 的类文档):
- 通过 bootstrap 与集成,能为每个测试点计算出满足近似边际保证(approximate marginal guarantee)的共形区间;
- 支持在线反馈:当新的批次数据陆续到来时,可以增量更新区间,而无需重新训练模型。
在 sktime 内部,这一算法被用于EnbPIForecaster的predict_interval,为点预测补充置信区间(见 sktime/forecasting/enbpi.py)。
EnbPI 类 API 详解
EnbPI类定义在 sktime/libs/_aws_fortuna_enbpi/enbpi.py,构造函数只暴露一个参数。
构造参数aggregation_fun
| 取值 | 含义 | 默认 |
|---|---|---|
"mean" | 对多个 bootstrap 模型的预测取均值聚合 | ✔ |
"median" | 对多个 bootstrap 模型的预测取中位数聚合 | — |
源码中的映射逻辑(enbpi.py#L26-L30):
def __init__(self, aggregation_fun="mean"): if aggregation_fun == "mean": self.aggregation_fun = lambda x: np.mean(x, 0) elif aggregation_fun == "median": self.aggregation_fun = lambda x: np.median(x, 0)注意:若传入"mean"、"median"之外的字符串,aggregation_fun属性将不会被赋值,后续调用会抛出AttributeError。相比之下,sktime 更高层的EnbPIForecaster会对非法取值显式抛出ValueError(见下文"集成"一节),这是一个实现上的差异点。
方法conformal_interval(...)
该方法是 EnbPI 的全部核心,用于为每个测试输入计算给定覆盖率下的区间。
参数一览:
| 参数 | 形状 | 说明 |
|---|---|---|
bootstrap_indices | (B, T) | 每个 bootstrap 样本从训练集中有放回抽样的原始索引;第一维是 B 个 bootstrap 样本,第二维是训练点数 T。可用numpy.random.choice(T, size=(B, T))生成。用户需自行保证各模型正是在这些索引对应的数据上训练的 |
bootstrap_train_preds | (B, T)或(B, T, d) | 各 bootstrap 模型在训练输入点上的预测;第三维若存在必须是 1(仅支持一维目标) |
bootstrap_test_preds | (B, T_test)或(B, T_test, d) | 各 bootstrap 模型在测试输入点上的预测 |
train_targets | (T,)或(T, 1) | 训练点对应的真实目标值 |
error | float | 期望的覆盖率误差,取值区间 [0, 1](含端点);最终区间覆盖率为1 - error |
return_residuals | bool | 若为True,额外返回训练集上计算的残差(供在线更新等场景复用),默认False |
返回值:
return_residuals=False:返回conformal_intervals,形状为(T_test, 2),第二维的两个分量分别对应区间左边界与右边界;return_residuals=True:返回元组(conformal_intervals, train_residuals),后者是形状为(T, 1)的训练残差。
该方法仅支持一维目标变量,且每次只能处理一条时间序列(见 enbpi.py#L44-L48)。
源码级解读:区间是怎么算出来的
conformal_interval的实现(enbpi.py#L103-L136)可拆解为以下几个阶段:
1. 构造 out-of-bag 掩码。把每个 bootstrap 样本抽中的索引转化为布尔掩码矩阵:
n_bootstraps, n_train_times = bootstrap_indices.shape in_bootstrap_indices = np.zeros((n_bootstraps, n_train_times), dtype=bool) np.put_along_axis(in_bootstrap_indices, bootstrap_indices, values=1, axis=1)2. 逐训练点计算"留一集成"预测与残差。对每个训练时间点t,找出所有没有在 bootstrap 中抽到t的模型(which_bootstraps),用聚合函数对它们的训练预测取均值/中位数,得到该点的聚合预测;残差即真实值与聚合预测的绝对差:
for t in range(n_train_times): which_bootstraps = np.where(~(in_bootstrap_indices[:, t]))[0] if len(which_bootstraps) > 0: aggr_bootstrap_train_pred = self.aggregation_fun(bootstrap_train_preds[which_bootstraps, t]) train_residuals[t] = np.abs(train_targets[t] - aggr_bootstrap_train_pred) aggr_bootstrap_test_preds[t] = self.aggregation_fun(bootstrap_test_preds[which_bootstraps]) else: train_residuals[t] = np.abs(train_targets[t])这里蕴含一个细节:所有测试点共享同一组 "out-of-bag" 模型——对每个训练点
t,选取的是不包含t的那批模型,再把这些模型的测试预测聚合,作为"去偏"后的测试预测。这保证了测试预测与残差估计在模型集合上的一致性,是共形保证成立的关键。
3. 分位数合成区间。分别对测试聚合预测和训练残差取1 - error分位数,然后用"预测分位数 ± 残差分位数"构造区间:
test_quantiles = np.quantile(aggr_bootstrap_test_preds, q=1 - error, axis=0) residuals_quantile = np.quantile(train_residuals, q=1 - error, axis=0) left = test_quantiles - residuals_quantile right = test_quantiles + residuals_quantile conformal_intervals = np.array(list(zip(left, right)))可以看到,区间宽度完全由训练残差分布的分位数自适应决定,无需对误差分布做高斯等参数假设,这正是共形预测的"分布无关"特性。若error取 0.05,则得到约 95% 覆盖率的区间。
4. 残差复用(可选)。当return_residuals=True时,train_residuals一并返回,可用于在后续批次数据到达时增量调整区间宽度(即文档所述的在线反馈),而不必重训模型。
与 sktime 预测框架的集成:EnbPIForecaster
EnbPI 在 sktime 中的直接消费者是EnbPIForecaster(sktime/forecasting/enbpi.py),它把sktime 基础预测器 + tsbootstrap 引导器 + EnbPI 算法三者组合成一个完整的概率预测器,声明其能力标签为"capability:pred_int": True(即能够输出预测区间)。
训练阶段的三步流程
- 用 bootstrap 变换器对目标序列生成 bootstrap 样本,并同时返回原始序列的索引(要求变换器具备
capability:bootstrap_index标签且return_indices=True); - 对每个 bootstrap 样本的前
n - max(fh)个值分别拟合一个基础预测器; - 用每个已拟合的预测器预测各样本最后
max(fh)个值,保存为训练期预测。
概率预测阶段
_predict_interval(sktime/forecasting/enbpi.py#L214-L237)将 bootstrap 索引、训练期预测、测试期预测、训练目标与error = 1 - cov一并传入EnbPI(...).conformal_interval(...),为每个覆盖水平cov生成(T_test, 2)的区间数组,最后按 sktime 的predict_interval列规范({y}{cov}{lower/upper}形式)组装成pd.DataFrame返回。
关键参数
| 参数 | 默认值 | 说明 |
|---|---|---|
forecaster | None(退化为NaiveForecaster()) | 每个 bootstrap 样本上拟合的基础预测器 |
bootstrap_transformer | None(退化为MovingBlockBootstrapTransformer(return_indices=True)) | 生成 bootstrap 样本的变换器,必须支持返回索引 |
random_state | None | 随机种子,用于可复现性 |
aggregation_function | "mean" | 集成预测的聚合方式,仅支持"mean"或"median",否则抛ValueError |
其可用的内置 bootstrap 变换器包括 sktime/transformations/bootstrap/_mbb.py 中的MovingBlockBootstrapTransformer与 sktime/transformations/bootstrap/_tsbootstrap.py 中的TSBootstrapAdapter(用于适配tsbootstrap库)。注意EnbPIForecaster依赖软依赖tsbootstrap>=0.1.0,且当前在测试中标记为tests:skip_all(见 sktime/forecasting/enbpi.py#L104-L121 的_tags,跳过原因为 issue #10083)。
实战示例:跑通 EnbPI 预测区间
以下是 sktime/forecasting/enbpi.py 类文档中提供的官方使用示例,读者可直接在已安装tsbootstrap的环境中运行:
import numpy as np from tsbootstrap import MovingBlockBootstrap from sktime.forecasting.enbpi import EnbPIForecaster from sktime.forecasting.naive import NaiveForecaster from sktime.datasets import load_airline from sktime.transformations.difference import Differencer from sktime.transformations.detrend import Deseasonalizer from sktime.forecasting.base import ForecastingHorizon y = load_airline() forecaster = Differencer(lags=[1]) * Deseasonalizer(sp=12) * EnbPIForecaster( forecaster=NaiveForecaster(sp=12), bootstrap_transformer=MovingBlockBootstrap(n_bootstraps=10), ) fh = ForecastingHorizon(np.arange(1, 13)) forecaster.fit(y, fh=fh) res = forecaster.predict() res_int = forecaster.predict_interval(coverage=[0.5])示例要点:
- 通过
Differencer(lags=[1]) * Deseasonalizer(sp=12) * EnbPIForecaster(...)以管道方式组合差分、去季节化与 EnbPI 预测器; MovingBlockBootstrap(n_bootstraps=10)生成 10 组 bootstrap 样本;fh = ForecastingHorizon(np.arange(1, 13))指定未来 12 步的预测范围;predict_interval(coverage=[0.5])调用 EnbPI 计算 50% 覆盖率的共形区间。
若想单独复用底层EnbPI类(不经过EnbPIForecaster),可仿照其内部调用方式直接构造:
import numpy as np from sktime.libs._aws_fortuna_enbpi.enbpi import EnbPI B, T = 10, 100 # bootstrap 样本数、训练点数 bootstrap_indices = np.random.choice(T, size=(B, T)) # 有放回采样索引 bootstrap_train_preds = np.random.rand(B, T) # (B, T) bootstrap_test_preds = np.random.rand(B, 12) # (B, T_test) train_targets = np.random.rand(T, 1) # (T, 1) intervals = EnbPI("mean").conformal_interval( bootstrap_indices=bootstrap_indices, bootstrap_train_preds=bootstrap_train_preds, bootstrap_test_preds=bootstrap_test_preds, train_targets=train_targets, error=0.05, # 期望覆盖率 95% ) print(intervals.shape) # (12, 2),每行依次为 [下界, 上界]使用限制与注意事项
- 适用范围:
EnbPI.conformal_interval仅支持一维目标变量、单条时间序列的处理(enbpi.py#L44-L48),多元目标或面板数据需自行封装循环; - 调用契约:调用方必须保证
bootstrap_train_preds、bootstrap_test_preds与bootstrap_indices之间的一一对应——即第b组模型确实是在bootstrap_indices[b]对应的数据上训练的,否则共形保证将失效; error取值范围:为 [0, 1] 闭区间内的标量,对应覆盖率1 - error;- 内部模块定位:
sktime/libs/_aws_fortuna_enbpi属于"来自其他库的私有片段"(sktime/libs/README.md),sktime 官方建议通过上层估计器(如EnbPIForecaster)间接使用,而非直接 import 该内部模块; - 依赖与测试状态:
EnbPIForecaster需要软依赖tsbootstrap>=0.1.0,且当前测试被临时跳过(issue #10083),使用前需评估其维护状态; - 许可:该片段源自 aws-fortuna,随包保留 Apache License 2.0(LICENSE.txt)与 Amazon 版权声明(NOTICE.txt),二次分发或修改时需遵守对应条款。
总结
sktime/libs/_aws_fortuna_enbpi是 sktime 为解决 aws-fortuna 安装问题而内嵌的 EnbPI 共形预测算法实现,目录虽小,却承担了EnbPIForecaster概率区间生成的全部底层逻辑。通过本文的拆解可以看到:其conformal_interval方法用"out-of-bag 模型聚合 + 分位数残差校准"的方式,在无需重新训练的前提下为时序回归输出带近似边际保证的区间;而EnbPIForecaster则把该算法与 bootstrap 变换器和 sktime 基础预测器组合,形成了开箱即用的集成式概率预测器。理解这一实现,不仅能让你掌握一个可复用的共形区间工具,也有助于读懂 sktime 处理概率预测与不确定性量化的整体设计思路。
【免费下载链接】sktimeA unified framework for machine learning with time series项目地址: https://gitcode.com/GitHub_Trending/sk/sktime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考