TimeGPT和TimeGEN的SHAP值

!pip install -Uqq nixtla
!pip install shap
from nixtla.utils import in_colab
IN_COLAB = in_colab()
if not IN_COLAB:
    from nixtla.utils import colab_badge
    from dotenv import load_dotenv

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

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

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

shap可以从PyPIconda-forge安装:

pip install shap

或

conda install -c conda-forge shap

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

if not IN_COLAB:
    load_dotenv()
    colab_badge('docs/tutorials/21_shap_values')

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="您的 Azure AI 端点", api_key="您的 api_key")

if not IN_COLAB:
    nixtla_client = NixtlaClient()

2. 加载数据

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

此数据集包含来自五个不同电力市场的数据,每个市场都有独特的价格动态,例如频率和负价格、零价格及价格尖峰的出现。因此,电力价格受到外生因素的影响,每个数据集还包含两个额外的时间序列:特定于每个市场的两项重要外生因素的日先预测。

为了简化,我们将重点关注Nord Pool电力市场(NP),该市场对应于北欧国家的交易。此数据集包括每小时价格(y)、负载的日先预测(Exogenous1)和风电生成(Exogenous2)。它还包含独热编码,以指示特定日期是否为特定星期中的某一天。例如:星期一(day_0 = 1),星期二(day_1 = 1),等等。

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

df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/electricity-short-with-ex-vars.csv')
df = df.query('unique_id == "NP"')
df.head()
unique_id ds y Exogenous1 Exogenous2 day_0 day_1 day_2 day_3 day_4 day_5 day_6
5040 NP 2018-10-15 00:00:00 2.17 34078.0 1791.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
5041 NP 2018-10-15 01:00:00 4.03 33469.0 1489.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
5042 NP 2018-10-15 02:00:00 4.88 33313.0 1233.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
5043 NP 2018-10-15 03:00:00 10.47 33535.0 1035.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
5044 NP 2018-10-15 04:00:00 17.51 34267.0 854.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0

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

为了产生预测,我们还必须添加外生变量的未来值。

如果您的预测依赖于其他变量,确保在预测时能够获取这些变量是很重要的。在这个例子中,我们知道电价依赖于需求(Exogenous1)和生产量(Exogenous2)。因此,我们需要在预测时获取这些未来值。如果这些值不可用,我们可以随时使用 TimeGPT 进行预测

在这里,我们读取一个包含我们特征未来值的数据集。在这种情况下,我们希望预测 24 个时间步,因此每个 unique_id 将有 24 个观察值。

Important

如果您想在使用 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 = future_ex_vars_df.query('unique_id == "NP"')
future_ex_vars_df.head()
unique_id ds Exogenous1 Exogenous2 day_0 day_1 day_2 day_3 day_4 day_5 day_6
72 NP 2018-12-24 00:00:00 49119.0 461.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
73 NP 2018-12-24 01:00:00 48115.0 484.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
74 NP 2018-12-24 02:00:00 47727.0 497.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
75 NP 2018-12-24 03:00:00 47673.0 509.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0
76 NP 2018-12-24 04:00:00 47848.0 510.0 1.0 0.0 0.0 0.0 0.0 0.0 0.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 the following 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_id ds TimeGPT TimeGPT-hi-80 TimeGPT-hi-90 TimeGPT-lo-80 TimeGPT-lo-90
0 NP 2018-12-24 00:00:00 50.981989 52.728327 53.216042 49.235651 48.747936
1 NP 2018-12-24 01:00:00 51.055580 52.811099 53.512205 49.300060 48.598954
2 NP 2018-12-24 02:00:00 50.088422 51.780883 52.976762 48.395962 47.200082
3 NP 2018-12-24 03:00:00 49.411024 51.441344 52.756716 47.380704 46.065332
4 NP 2018-12-24 04:00:00 49.270679 51.309494 51.811496 47.231863 46.729862

4. 提取SHAP值

现在我们已经使用外生特征进行了预测,我们可以提取SHAP值以理解它们的相关性,使用客户端的 feature_contributions 属性。这将返回一个DataFrame,其中包含每个系列在每个阶段的SHAP值和基准值。

shap_df = nixtla_client.feature_contributions
shap_df.head()
unique_id ds TimeGPT Exogenous1 Exogenous2 day_0 day_1 day_2 day_3 day_4 day_5 day_6 base_value
0 NP 2018-12-24 00:00:00 50.981989 -0.263536 2.837252 -0.650399 -0.031493 -0.009930 -0.052363 0.065249 0.024908 0.003811 49.058489
1 NP 2018-12-24 01:00:00 51.055580 -0.728780 2.979878 -0.394336 -0.024114 -0.002384 -0.074530 0.060947 0.011700 0.005597 49.221602
2 NP 2018-12-24 02:00:00 50.088422 -1.650111 2.759695 -0.353382 -0.018440 0.026398 -0.080132 0.045982 0.011485 0.003172 49.343755
3 NP 2018-12-24 03:00:00 49.411024 -1.555851 2.465833 -0.470166 -0.018440 0.026398 -0.080132 0.045982 0.012374 0.003172 48.981852
4 NP 2018-12-24 04:00:00 49.270679 -1.555851 2.465833 -0.470166 -0.018440 0.026398 -0.080132 0.045982 0.012374 0.003172 48.841507

在上面的数据框中,我们可以看到每个预测步骤的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值矩阵
base_values = shap_df['base_value'].values  # 提取基础值
features = shap_columns  # 特征名称

# 创建一个SHAP值对象
shap_obj = shap.Explanation(values=shap_values, base_values=base_values, feature_names=features)

# 绘制SHAP值的条形图
shap.plots.bar(shap_obj, max_display=len(features), show=False)
plt.title(f'SHAP values for NP')
plt.show()

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

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

请注意,所有值都是正数,这意味着平均而言,每个特征都倾向于增加预测值。

5.2 瀑布图

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

selected_ds = '2018-12-24 00:00:00'

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: NP, date: {selected_ds}')
plt.show()

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

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

接着,我们看到每个特征对最终预测的影响。像day_6day_1day_3Exogenous1day_0等特征都使预测值向左移动(较小的值)。另一方面,day_5day_4Exogenous2则使预测值向右移动(较大的值)。

右上角我们看到f(x),这是模型在考虑外生特征影响后的最终输出。注意,这个值对应于TimeGPT的最终预测。因此,我们看到Exogenous2具有较大的正值,最终导致预测值大于基线值。这是合理的,因为该变量是预期的能源产出。如果预期产生更多的能源,实际产生的能源更大也是合情合理的。

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()

通过热图,我们基本上可以看到每个特征对每个时间步的最终预测的影响程度。

在横轴上,我们有实例的数量,这对应于预测步骤的数量(在这里是24,因为我们的预见期设置为24小时)。在纵轴上,我们有外生特征的名称。

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

然后,热图的颜色指示特征在每个预测步骤是否倾向于增加或减少最终预测。例如,Exogenous1倾向于增加前10小时的预测,但其影响在之后是相反的。

我们还看到day_6day_5day_2day_4day_1在任何预测步骤中都没有很大的影响,这表明它们对最终预测几乎没有影响。

最终,feature_contributions 属性使您可以访问所有必要的信息,以利用 shap 包解释外生特征的影响。

Give us a ⭐ on Github