> 技术文档 > 用 Python 轻松实现时间序列预测:Darts 静态协变量 Static Covariates

用 Python 轻松实现时间序列预测:Darts 静态协变量 Static Covariates


文中内容仅限技术学习与代码实践参考,市场存在不确定性,技术分析需谨慎验证,不构成任何投资建议。

Darts

Darts 是一个 Python 库,用于对时间序列进行用户友好型预测和异常检测。它包含多种模型,从 ARIMA 等经典模型到深度神经网络。所有预测模型都能以类似 scikit-learn 的方式使用 fit()predict() 函数。该库还可以轻松地对模型进行回溯测试,将多个模型的预测结果结合起来,并将外部数据考虑在内。Darts 支持单变量和多变量时间序列和模型。基于 ML 的模型可以在包含多个时间序列的潜在大型数据集上进行训练,其中一些模型还为概率预测提供了丰富的支持。

静态协变量

# 如果在本地工作,请修复 Python 路径from utils import fix_pythonpath_if_working_locallyfix_pythonpath_if_working_locally()
%load_ext autoreload%autoreload 2%matplotlib inline
import warningsimport matplotlib.pyplot as pltimport numpy as npimport pandas as pdfrom darts import TimeSerieswarnings.filterwarnings(\"ignore\")import logginglogging.disable(logging.CRITICAL)

静态协变量是时间序列的特征/常量,不会随时间变化。当处理多个时间序列时,静态协变量可以帮助特定模型改进预测。Darts 的模型只会考虑嵌入在目标序列(我们想要预测未来值的序列)中的静态协变量,而不会考虑过去和/或未来的协变量(外部数据)。

在本教程中,我们将探讨:

  1. 如何定义静态协变量(数值型和/或分类型)
  2. 如何将静态协变量添加到现有的目标序列
  3. 如何在创建 TimeSeries 时添加静态协变量
  4. 如何使用 TimeSeries.from_group_dataframe() 自动提取带有嵌入静态协变量的 TimeSeries
  5. 如何对嵌入在序列中的静态协变量进行缩放/转换/编码
  6. 如何在 Darts 的模型中使用静态协变量

我们首先生成一个包含三个分量的多元时间序列 [\"comp1\", \"comp2\", \"comp3\"]

np.random.seed(0)series = TimeSeries.from_times_and_values( times=pd.date_range(start=\"2020-01-01\", periods=10, freq=\"h\"), values=np.random.random((10, 3)), columns=[\"comp1\", \"comp2\", \"comp3\"],)series.plot()

img

1. 定义静态协变量

将静态协变量定义为 pd.DataFrame,其中列代表静态变量,行代表要添加到的单变量/多变量 TimeSeries 的分量。

  • 行数必须为 1 或与 series 的分量数相等。
  • 在多变量(多分量)series 中使用单行静态协变量 DataFrame 时,静态协变量将全局映射到所有分量。
# 任意的连续和分类静态协变量(单行)static_covs_single = pd.DataFrame(data={\"cont\": [0], \"cat\": [\"a\"]})print(static_covs_single)# 多变量静态协变量(多个分量)。# 注意行数与 `series` 的分量数匹配static_covs_multi = pd.DataFrame(data={\"cont\": [0, 2, 1], \"cat\": [\"a\", \"c\", \"b\"]})print(static_covs_multi)
 cont cat0 0 a cont cat0 0 a1 2 c2 1 b

2. 向现有 TimeSeries 添加静态协变量

使用 with_static_covariates() 方法(参见文档)从现有 TimeSeries 创建带有添加静态协变量的新序列。

  • 单行静态协变量与多变量 series 创建“global_components”,映射到所有分量
  • 多行静态协变量与多变量 series 将映射到 series 的分量名称(参见静态协变量索引/行名称)
assert series.static_covariates is Noneseries_single = series.with_static_covariates(static_covs_single)print(\"Single row static covarites with multivariate `series`\")print(series_single.static_covariates)series_multi = series.with_static_covariates(static_covs_multi)print(\"\\nMulti row static covarites with multivariate `series`\")print(series_multi.static_covariates)
Single row static covarites with multivariate `series`static_covariates cont catglobal_components 0.0 aMulti row static covarites with multivariate `series`static_covariates cont catcomponentcomp1  0.0 acomp2  2.0 ccomp3  1.0 b

3. 在 TimeSeries 构建时添加静态协变量

静态协变量也可以直接在使用大多数 TimeSeries.from_*() 方法创建时间序列时通过参数 static_covariates 添加。

# 添加任意的连续和分类静态协变量series = TimeSeries.from_values( values=np.random.random((10, 3)), columns=[\"comp1\", \"comp2\", \"comp3\"], static_covariates=static_covs_multi,)print(series.static_covariates)
static_covariates cont catcomponentcomp1  0.0 acomp2  2.0 ccomp3  1.0 b

在多个 TimeSeries 中使用静态协变量

静态协变量只有在跨多个 TimeSeries 使用时才真正有用。按照惯例,所有序列的静态协变量布局(pd.DataFrame 列、索引)必须相同。

first_series = series.with_static_covariates( pd.DataFrame(data={\"ID\": [\"SERIES1\"], \"var1\": [0.5]}))second_series = series.with_static_covariates( pd.DataFrame(data={\"ID\": [\"SERIES2\"], \"var1\": [0.75]}))print(\"Valid static covariates for multiple series\")print(first_series.static_covariates)print(second_series.static_covariates)series_multi = [first_series, second_series]
Valid static covariates for multiple seriesstatic_covariates ID var1global_components SERIES1 0.5static_covariates ID var1global_components SERIES2 0.75

4. 使用 from_group_dataframe()DataFrame 中按组提取时间序列列表

如果您的 DataFrame 包含多个垂直堆叠的时间序列,您可以使用 TimeSeries.from_group_dataframe()(请参阅此处的文档)将它们提取为时间序列实例列表。这需要对 DataFrame 进行分组的一列或一列列表(参数 group_cols )。可以使用参数 static_cols 将其他列作为静态协变量。

在下面的示例中,我们生成了包含两个不同时间序列(日期重叠/重复)\"SERIES1 \"和 \"SERIES2 \"数据的 DataFrame,并使用 from_group_dataframe() 提取时间序列。

# 生成 DataFrame 示例df = pd.DataFrame( data={ \"dates\": [ \"2020-01-01\", \"2020-01-02\", \"2020-01-03\", \"2020-01-01\", \"2020-01-02\", \"2020-01-03\", ], \"comp1\": np.random.random((6,)), \"comp2\": np.random.random((6,)), \"comp3\": np.random.random((6,)), \"ID\": [\"SERIES1\", \"SERIES1\", \"SERIES1\", \"SERIES2\", \"SERIES2\", \"SERIES2\"], \"var1\": [0.5, 0.5, 0.5, 0.75, 0.75, 0.75], })print(\"Input DataFrame\")print(df)series_multi = TimeSeries.from_group_dataframe( df, time_col=\"dates\", group_cols=\"ID\", # 通过将 `df` 按 `group_cols` 分组,提取单个时间序列 static_cols=[ \"var1\" ], # 也提取这些附加列作为静态协变量(不分组) value_cols=[ \"comp1\", \"comp2\", \"comp3\", ], # 可选择指定时间变化列)print(f\"\\n{len(series_multi)} series were extracted from the input DataFrame\")for i, ts in enumerate(series_multi): print(f\"Static covariates of series {i}\") print(ts.static_covariates) ts[\"comp1\"].plot(label=f\"comp1_series_{i}\")
Input DataFrame dates comp1 comp2 comp3 ID var10 2020-01-01 0.158970 0.820993 0.976761 SERIES1 0.501 2020-01-02 0.110375 0.097101 0.604846 SERIES1 0.502 2020-01-03 0.656330 0.837945 0.739264 SERIES1 0.503 2020-01-01 0.138183 0.096098 0.039188 SERIES2 0.754 2020-01-02 0.196582 0.976459 0.282807 SERIES2 0.755 2020-01-03 0.368725 0.468651 0.120197 SERIES2 0.752 series were extracted from the input DataFrameStatic covariates of series 0static_covariates ID var1global_components SERIES1 0.5Static covariates of series 1static_covariates ID var1global_components SERIES2 0.75

img

5. 缩放/编码/转换静态协变量数据

可能需要缩放数值静态协变量或编码分类静态协变量,因为并非所有模型都能处理非数值静态协变量。

使用 StaticCovariatesTransformer(参见文档)来缩放/转换静态协变量。默认情况下,它使用 MinMaxScaler 缩放数值数据,使用 OrdinalEncoder 编码分类数据。数值和分类转换器都将全局拟合在传递给 StaticCovariatesTransformer.fit() 的所有时间序列的静态协变量数据上。

from darts.dataprocessing.transformers import StaticCovariatesTransformertransformer = StaticCovariatesTransformer()series_transformed = transformer.fit_transform(series_multi)for i, (ts, ts_scaled) in enumerate(zip(series_multi, series_transformed)): print(f\"Original series {i}\") print(ts.static_covariates) print(f\"Transformed series {i}\") print(ts_scaled.static_covariates) print(\"\")
Original series 0static_covariates ID var1global_components SERIES1 0.5Transformed series 0static_covariates ID var1global_components 0.0 0.0Original series 1static_covariates ID var1global_components SERIES2 0.75Transformed series 1static_covariates ID var1global_components 1.0 1.0

6. 使用 TFTModel 和静态协变量进行预测示例

现在让我们看看在预测问题中添加静态协变量是否可以提高预测准确性。我们将使用支持数值和分类静态协变量的 TFTModel

import numpy as npimport pandas as pdfrom pytorch_lightning.callbacks import TQDMProgressBarfrom darts import TimeSeriesfrom darts.dataprocessing.transformers import StaticCovariatesTransformerfrom darts.metrics import rmsefrom darts.models import TFTModelfrom darts.utils import timeseries_generation as tg

6.1 实验设置

在我们的实验中,我们生成两个时间序列:一个完整的正弦波序列(标签 = smooth)和一个每隔一个周期就有一些不规则的正弦波序列(标签 = irregular,参见第 2 和第 4 个周期的斜坡)。

period = 20sine_series = tg.sine_timeseries( length=4 * period, value_frequency=1 / period, column_name=\"smooth\", freq=\"h\")sine_vals = sine_series.values()linear_vals = np.expand_dims(np.linspace(1, -1, num=19), -1)sine_vals[21:40] = linear_valssine_vals[61:80] = linear_valsirregular_series = TimeSeries.from_times_and_values( values=sine_vals, times=sine_series.time_index, columns=[\"irregular\"])sine_series.plot()irregular_series.plot()

img

我们将使用三种不同的设置进行训练和评估:

  1. 无静态协变量的拟合/预测
  2. 带二进制(数值)静态协变量的拟合/预测
  3. 带分类静态协变量的拟合/预测

对于每种设置,我们将在两个序列上训练模型,然后仅使用第 3 个周期(两个序列的正弦波)来预测第 4 个周期(“smooth”为正弦,“irregular”为斜坡)。

我们希望的是,没有静态协变量的模型表现比其他模型差。非静态模型应该无法识别 predict() 中使用的底层序列是 smooth 还是 irregular 序列,因为它只获得正弦波曲线作为输入(第 3 个周期)。这应导致预测介于平滑和不规则序列之间(通过最小化训练期间的全局损失来学习)。

这正是静态协变量真正有用的地方。例如,我们可以通过静态协变量将曲线类型的数据嵌入到目标序列中。有了这些信息,我们期望模型能够生成改进的预测。

首先,我们创建一些辅助函数,以将相同的实验条件应用于所有模型。

def test_case(model, train_series, predict_series): \"\"\"执行模型训练、预测和绘图的辅助函数\"\"\" model.fit(train_series) preds = model.predict(n=int(period / 2), num_samples=250, series=predict_series) for ts, ps in zip(train_series, preds): ts.plot() ps.plot() plt.show() return predsdef get_model_params(): \"\"\"生成带有新进度条对象的模型参数的辅助函数\"\"\" return { \"input_chunk_length\": int(period / 2), \"output_chunk_length\": int(period / 2), \"add_encoders\": { \"datetime_attribute\": {\"future\": [\"hour\"]} }, # TFTModel 需要未来输入,这样我们就不需要提供任何 future_covariates \"random_state\": 42, \"n_epochs\": 150, \"pl_trainer_kwargs\": { \"callbacks\": [TQDMProgressBar(refresh_rate=4)], }, }

6.2 无静态协变量的预测

让我们首先训练没有任何静态协变量的模型

train_series = [sine_series, irregular_series]for series in train_series: assert not series.has_static_covariatesmodel = TFTModel(**get_model_params())preds = test_case( model, train_series, predict_series=[series[:60] for series in train_series],)

img

img

从图中可以看出,预测在第 3 个周期后开始(~01-03-2022 - 12:00)。预测输入是最后 input_chunk_length=10 个值——两个序列相同(正弦波)。

正如预期的那样,模型无法确定底层预测序列的类型(平滑或不规则),并为两者生成了类似正弦波的预测。

6.3 使用 0/1 二进制静态协变量(数值)进行预测

现在让我们重复实验,但这次我们将曲线类型的信息作为二进制(数值)静态协变量 \"curve_type\" 添加。

sine_series_st_bin = sine_series.with_static_covariates( pd.DataFrame(data={\"curve_type\": [1]}))irregular_series_st_bin = irregular_series.with_static_covariates( pd.DataFrame(data={\"curve_type\": [0]}))train_series = [sine_series_st_bin, irregular_series_st_bin]for series in train_series: print(series.static_covariates)model = TFTModel(**get_model_params())preds_st_bin = test_case( model, train_series, predict_series=[series[:60] for series in train_series],)
static_covariates curve_typecomponentsmooth  1.0static_covariates curve_typecomponentirregular  0.0

img

img

这已经看起来好多了!模型能够从二进制静态协变量特征中识别曲线类型/类别。

6.4 使用分类静态协变量进行预测

最后一个实验已经显示出有希望的结果。那么为什么不仅仅使用二进制特征来表示分类数据呢?虽然对于我们的两个时间序列来说可能效果很好,但如果我们有更多的曲线类型,我们需要将特征一次性编码为每个类别的二进制变量。当类别很多时,这会导致大量特征/预测变量和多重共线性,从而降低模型的预测准确性。

作为最后一个实验,让我们将曲线类型用作分类特征。TFTModel 学习分类特征的嵌入。Darts 的 TorchForecastingModels(如 TFTModel)仅支持数值数据。在训练之前,我们需要使用 StaticCovariatesTransformer(参见第 5 节)将 \"curve_type\" 转换为整数值特征。

sine_series_st_cat = sine_series.with_static_covariates( pd.DataFrame(data={\"curve_type\": [\"smooth\"]}))irregular_series_st_cat = irregular_series.with_static_covariates( pd.DataFrame(data={\"curve_type\": [\"non_smooth\"]}))train_series = [sine_series_st_cat, irregular_series_st_cat]print(\"Static covariates before encoding:\")print(train_series[0].static_covariates)# 使用静态协变量转换器将分类静态协变量编码为数值数据scaler = StaticCovariatesTransformer()train_series = scaler.fit_transform(train_series)print(\"\\nStatic covariates after encoding:\")print(train_series[0].static_covariates)
Static covariates before encoding:static_covariates curve_typecomponentsmooth smoothStatic covariates after encoding:static_covariates curve_typecomponentsmooth  1.0

现在我们需要做的只是告诉 TFTModel \"curve_type\" 是一个需要嵌入的分类变量。我们可以通过模型参数 categorical_embedding_sizes 来实现,这是一个字典:{特征名称: (类别数, 嵌入大小)}

n_categories = 2 # \"smooth\" 和 \"non_smooth\"embedding_size = 2 # 将分类变量嵌入到大小为 2 的数值向量中categorical_embedding_sizes = {\"curve_type\": (n_categories, embedding_size)}model = TFTModel( categorical_embedding_sizes=categorical_embedding_sizes, **get_model_params())preds_st_cat = test_case( model, train_series, predict_series=[series[:60] for series in train_series],)

img

img

很好,这似乎也行得通!作为最后一步,让我们看看模型之间的比较表现如何。

for series, ps_no_st, ps_st_bin, ps_st_cat in zip( train_series, preds, preds_st_bin, preds_st_cat): series[-40:].plot(label=\"target\") ps_no_st.quantile(0.5).plot(label=\"no static covs\") ps_st_bin.quantile(0.5).plot(label=\"binary static covs\") ps_st_cat.quantile(0.5).plot(label=\"categorical static covs\") plt.show() print(\"Metric\") print( pd.DataFrame( { name: [rmse(series, ps)] for name, ps in zip(  [\"no st\", \"bin st\", \"cat st\"], [ps_no_st, ps_st_bin, ps_st_cat] ) }, index=[\"RMSE\"], ) )

img

Metric no st bin st cat stRMSE 0.16352 0.042527 0.050242

img

Metric no st bin st cat stRMSE 0.289051 0.138122 0.138631

这些结果很棒!与基线相比,使用静态协变量的两种方法都将两个序列的 RMSE 降低了一半以上!

请注意,我们只使用了一个静态协变量特征,但您可以根据需要使用任意数量的特征,包括混合数据类型(数值和分类)。

风险提示与免责声明
本文内容基于公开信息研究整理,不构成任何形式的投资建议。历史表现不应作为未来收益保证,市场存在不可预见的波动风险。投资者需结合自身财务状况及风险承受能力独立决策,并自行承担交易结果。作者及发布方不对任何依据本文操作导致的损失承担法律责任。市场有风险,投资须谨慎。