SHAP (SHapley Additive exPlanation) 值利用博弈论来解释任何机器学习模型的输出。它使我们能够详细探讨外部特征如何影响最终预测,无论是在单个预测步骤还是在整个预测范围内。

使用外部特征进行预测时,您可以访问每个预测步骤中所有序列的 SHAP 值,并使用流行的 shap Python 包来绘制不同的图表并解释特征的影响。

本教程假定您了解如何使用外部特征进行预测,因此请务必阅读我们的外部变量教程。此外,shap 包必须单独安装,因为它不是 nixtla 的依赖项。

可以从 PyPIconda-forge 安装 shap

pip install shap

or

conda install -c conda-forge shap

有关 SHAP 的官方文档,请访问:https://shap.readthedocs.cn/en/latest/

1. 导入包

首先,我们导入所需的包并初始化 Nixtla 客户端。

import pandas as pd
from nixtla import NixtlaClient
nixtla_client = NixtlaClient(
    # defaults to os.environ.get("NIXTLA_API_KEY")
    api_key = 'my_api_key_provided_by_nixtla'
)

👍 使用 Azure AI 端点

要使用 Azure AI 端点,请记住同时设置 base_url 参数

nixtla_client = NixtlaClient(base_url="your azure ai endpoint", api_key="your api_key")

2. 加载数据

在此 SHAP 值示例中,我们将使用外部变量(也称为协变量)来提高电力市场预测的准确性。我们将使用一个名为 EPF 的著名数据集,该数据集可在此处公开获取:此处

该数据集包含来自五个不同电力市场的数据,每个市场都有独特的定价动态,例如负价格、零价格和价格飙升的频率和发生情况各不相同。由于电价受到外部因素的影响,每个数据集还包含两个额外的时间序列:针对每个市场的两个重要外部因素的日内预测。

为简单起见,我们将重点关注比利时电力市场 (BE)。该数据集包含每小时价格 (y)、负荷的日内预测 (Exogenous1) 和发电量的日内预测 (Exogenous2)。它还包含独热编码,用于指示特定日期是否为一周中的特定日期。例如:周一 (day_0 = 1)、周二 (day_1 = 1) 等等。

如果您的数据依赖于价格、折扣、特殊假日、天气等外部因素或协变量,您可以遵循类似的结构。

market = "BE"
df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/electricity-short-with-ex-vars.csv')
df.head()
unique_iddsyExogenous1Exogenous2day_0day_1day_2day_3day_4day_5day_6
0BE2016-10-22 00:00:0070.0057253.049593.00.00.00.00.00.01.00.0
1BE2016-10-22 01:00:0037.1051887.046073.00.00.00.00.00.01.00.0
2BE2016-10-22 02:00:0037.1051896.044927.00.00.00.00.00.01.00.0
3BE2016-10-22 03:00:0044.7548428.044483.00.00.00.00.00.01.00.0
4BE2016-10-22 04:00:0037.1046721.044338.00.00.00.00.00.01.00.0

3. 使用外部变量预测电价

为了生成预测,我们还需要添加外部变量的未来值。

如果您的预测依赖于其他变量,务必确保这些变量在预测时可用。在此示例中,我们知道电价取决于需求 (Exogenous1) 和发电量 (Exogenous2)。因此,我们需要在预测时提供这些未来值。如果这些值不可用,我们始终可以使用 TimeGPT 进行预测

在这里,我们读取一个包含我们特征未来值的数据集。在本例中,我们希望预测未来 24 个步骤,因此每个 unique_id 将有 24 个观测值。

重要提示

如果您想使用 TimeGPT 进行预测时使用外部变量,您也需要拥有这些外部变量的未来值。

future_ex_vars_df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/electricity-short-future-ex-vars.csv')
future_ex_vars_df.head()
unique_iddsExogenous1Exogenous2day_0day_1day_2day_3day_4day_5day_6
0BE2016-12-31 00:00:0070318.064108.00.00.00.00.00.01.00.0
1BE2016-12-31 01:00:0067898.062492.00.00.00.00.00.01.00.0
2BE2016-12-31 02:00:0068379.061571.00.00.00.00.00.01.00.0
3BE2016-12-31 03:00:0064972.060381.00.00.00.00.00.01.00.0
4BE2016-12-31 04:00:0062900.060298.00.00.00.00.00.01.00.0

让我们调用 forecast 方法,添加这些信息。要访问 SHAP 值,我们还需要在 forecast 方法中指定 feature_contributions=True

timegpt_fcst_ex_vars_df = nixtla_client.forecast(df=df, 
                                                 X_df=future_ex_vars_df, 
                                                 h=24, 
                                                 level=[80, 90],
                                                 feature_contributions=True)
timegpt_fcst_ex_vars_df.head()
INFO:nixtla.nixtla_client:Validating inputs...
INFO:nixtla.nixtla_client:Inferred freq: h
INFO:nixtla.nixtla_client:Querying model metadata...
INFO:nixtla.nixtla_client:Preprocessing dataframes...
INFO:nixtla.nixtla_client:Using future exogenous features: ['Exogenous1', 'Exogenous2', 'day_0', 'day_1', 'day_2', 'day_3', 'day_4', 'day_5', 'day_6']
INFO:nixtla.nixtla_client:Calling Forecast Endpoint...
unique_iddsTimeGPTTimeGPT-hi-80TimeGPT-hi-90TimeGPT-lo-80TimeGPT-lo-90
0BE2016-12-31 00:00:0051.63283061.59882066.08829541.66684337.177372
1BE2016-12-31 01:00:0045.75087754.61198860.17644536.88976731.325312
2BE2016-12-31 02:00:0039.65054346.25621052.84280833.04487626.458277
3BE2016-12-31 03:00:0034.00007244.01531047.42900023.98483520.571144
4BE2016-12-31 04:00:0033.78537043.14050348.58124024.43023918.989498

4. 提取 SHAP 值

现在我们已经使用外部特征进行了预测,接下来就可以提取 SHAP 值,并使用客户端的 feature_contributions 属性来理解它们的关联性。这将返回一个 DataFrame,其中包含每个序列在预测范围内的每个步骤的 SHAP 值和基础值。

shap_df = nixtla_client.feature_contributions
shap_df = shap_df.query("unique_id == @market")
shap_df.head()
unique_iddsTimeGPTExogenous1Exogenous2day_0day_1day_2day_3day_4day_5day_6base_value
0BE2016-12-31 00:00:0051.63283027.929638-16.3636070.081917-1.8835550.346484-0.2286110.424167-3.4116621.11391043.624146
1BE2016-12-31 01:00:0045.75087717.678530-12.240089-0.758545-0.077536-0.160390-0.3095670.871469-3.9272681.21871443.455560
2BE2016-12-31 02:00:0039.65054321.632694-21.400244-0.926842-0.470276-0.022417-0.2253890.220258-3.9272681.14573643.624290
3BE2016-12-31 03:00:0034.00007213.879354-20.681124-0.114050-0.4881410.048164-0.1266270.200692-3.4004851.14495943.537330
4BE2016-12-31 04:00:0033.78537013.465129-20.619830-0.036112-0.4704960.048375-0.1266270.200692-3.4004851.14495943.579760

在上面的 Dataframe 中,我们可以看到每个预测步骤都有 SHAP 值,以及 TimeGPT 的预测结果和基础值。请注意,基础值是模型在外部特征未知时的预测结果。

因此,TimeGPT 的预测结果等于给定行中基础值与每个外部特征的 SHAP 值之和。

5. 使用 shap 绘制图表

现在我们可以访问 SHAP 值了,我们可以使用 shap 包绘制任何我们想要的图表。

5.1 条形图

在这里,我们为每个序列及其特征绘制条形图,以便了解哪些特征对预测的影响最大。

import shap
import matplotlib.pyplot as plt

shap_columns = shap_df.columns.difference(['unique_id', 'ds', 'TimeGPT', 'base_value'])
shap_values = shap_df[shap_columns].values  # SHAP values matrix
base_values = shap_df['base_value'].values  # Extract base values
features = shap_columns  # Feature names

# Create a SHAP values object
shap_obj = shap.Explanation(values=shap_values, base_values=base_values, feature_names=features)

# Plot the bar plot for SHAP values
shap.plots.bar(shap_obj, max_display=len(features), show=False)
plt.title(f'SHAP values for {market}')
plt.show()

上图显示了每个特征在整个预测范围内的平均 SHAP 值。

在这里,我们看到 Exogenous1 是最重要的特征,因为它具有最大的平均贡献。请记住,它表示预期的能源需求,因此我们可以看到这个变量对最终预测有很大影响。另一方面,day_2 是最不重要的特征,因为它具有最低的值。

5.2 瀑布图

现在,让我们看看如何绘制瀑布图来探索特征在单个预测步骤中的影响。下面的代码选择一个特定日期。当然,这可以修改为任何序列或日期。

selected_ds = shap_df['ds'].min()

filtered_df = shap_df[shap_df['ds'] == selected_ds]

shap_values = filtered_df[shap_columns].values.flatten()
base_value = filtered_df['base_value'].values[0]
features = shap_columns

shap_obj = shap.Explanation(values=shap_values, base_values=base_value, feature_names=features)

shap.plots.waterfall(shap_obj, show=False)
plt.title(f'Waterfall Plot: {market}, date: {selected_ds}')
plt.show()

在上面的瀑布图中,我们可以更详细地探索单个预测。在这里,我们研究了 2016 年 12 月 31 日开始时的最终预测。

x 轴表示我们序列的值。在底部,我们看到 E[f(X)],它表示基准值(如果外部特征未知时的预测值)。

然后,我们看到每个特征如何影响最终预测。day_3day_1day_5Exogenous2 等特征都将预测推向左侧(较小的值)。另一方面,day_0day_2day_4day_6Exogenous1 将其推向右侧(较大的值)。

让我们思考一下。在引言中,我们提到 Exogenous1 代表电力负荷,而 Exogenous2 代表发电量。* Exogenous1,即电力负荷,对总体预测产生积极影响。这似乎很合理:如果我们预期需求更高,我们可能会预期价格上涨。* 另一方面,Exogenous2 对总体预测产生消极影响。这似乎也很合理:如果发电量更高,我们预期价格会更低。因此,Exogenous2 对预测的贡献为负。

在右上角,我们看到 f(x),它是考虑外部特征影响后模型的最终输出。请注意,此值对应于 TimeGPT 的最终预测结果。

5.3 热力图

我们还可以绘制热力图,以查看每个特征如何影响最终预测。在这里,我们只需要选择一个特定的序列。

shap_columns = shap_df.columns.difference(['unique_id', 'ds', 'TimeGPT', 'base_value'])
shap_values = shap_df[shap_columns].values  
feature_names = shap_columns.tolist()

shap_obj = shap.Explanation(values=shap_values, feature_names=feature_names)

shap.plots.heatmap(shap_obj, show=False)
plt.title(f'SHAP Heatmap (Unique ID: NP)')
plt.show()

通过热力图,我们可以基本看到每个特征在每个时间步如何影响最终预测的细分情况。

在 x 轴上,我们有实例数,这对应于预测步骤数(本例中为 24,因为我们的预测范围设置为 24 小时)。在 y 轴上,我们有外部特征的名称。

首先,请注意顺序与条形图中相同,其中 Exogenous1 最重要,而 day_6 最不重要。

然后,热力图的颜色指示特征在每个预测步骤中是倾向于增加还是减少最终预测。例如,Exogenous1 在整个预测范围的 24 小时内总是增加预测。

我们还看到,除 day_5 外的所有日期在任何预测步骤中都没有非常大的影响,这表明它们对最终预测几乎没有影响。

最终,feature_contributions 属性为您提供了所有必要的信息,以便使用 shap 包解释外部特征的影响。