时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用

时间:2022-11-24 11:54:38


1.实验目的

此笔记本用作以下目的:

  • 在多个时间序列上训练单个模型
  • 使用预训练模型获取训练期间未见的任何时间序列的预测
  • 使用协变量训练和使用模型

2.导库

# fix python path if working locally
from utils import fix_pythonpath_if_working_locally

fix_pythonpath_if_working_locally()

import pandas as pd
import numpy as np
import torch
import matplotlib.pyplot as plt
from darts.models import NBEATSModel
from darts.models import *
from darts import TimeSeries
from darts.utils.timeseries_generation import (
gaussian_timeseries,
linear_timeseries,
sine_timeseries,
)
from darts.models import (
RNNModel,
TCNModel,
TransformerModel,
NBEATSModel,
BlockRNNModel,
)
from darts.metrics import mape, smape
from darts.dataprocessing.transformers import Scaler
from darts.utils.timeseries_generation import datetime_attribute_timeseries
from darts.datasets import AirPassengersDataset, MonthlyMilkDataset
%load_ext autoreload
%autoreload 2
%matplotlib inline

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

from darts import TimeSeries
from darts.datasets import AirPassengersDataset,MonthlyMilkDataset


# for reproducibility
torch.manual_seed(1)
np.random.seed(1)

3.读取数据

从阅读两个时间序列开始——一个包含每月的航空乘客数量,另一个包含每头奶牛每月的产奶量。这些时间序列彼此没有太大关系,只是它们都具有明显的年度周期性和上升趋势的月频率,并且(完全巧合)它们包含可比较数量级的值。

series_air = AirPassengersDataset().load()
series_milk = MonthlyMilkDataset().load()

series_air.plot(label="Number of air passengers")
series_milk.plot(label="Pounds of milk produced per cow")
plt.legend()

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用

4. 预处理

通常,神经网络倾向于在标准化/标准化数据上工作得更好。在这里,将使用Scaler该类来规范化 0 和 1 之间的时间序列:

from darts.dataprocessing.transformers import Scaler

scaler_air, scaler_milk = Scaler(), Scaler()
series_air_scaled = scaler_air.fit_transform(series_air)
series_milk_scaled = scaler_milk.fit_transform(series_milk)

series_air_scaled.plot(label="air")
series_milk_scaled.plot(label="milk")
plt.legend()

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用

5.拆分数据集【训练集、验证集】

保留这两个系列的最后 36 个月作为验证:

train_air, val_air = series_air_scaled[:-36], series_air_scaled[-36:]
train_milk, val_milk = series_milk_scaled[:-36], series_milk_scaled[-36:]

6.全局预测模型

Darts 包含许多预测模型,但并非所有模型都可以在多个时间序列上进行训练。支持多系列训练的模型称为全局模型。目前有 5 个全局模型:

  • BlockRNN模型
  • RNN模型
  • 时间卷积网络 (TCN)
  • N-Beats 模型
  • Transformer 模型

将区分两种时间序列:

  • 目标时间序列是我们有兴趣预测的时间序列(鉴于其历史)
  • 协变量时间序列是可能有助于预测目标序列的时间序列,但对预测不感兴趣。有时也称为外部数据。

进一步区分协变量系列,这取决于它们是否可以提前知道:

  • 过去协变量表示其过去值在预测时已知的时间序列。这些通常是必须测量或观察的事情。
  • 未来协变量表示其未来值在预测时间范围内已知的时间序列。例如,这些可以代表已知的未来假期或天气预报。

有些模型只使用过去的协变量,有些只使用未来的协变量,有些模型可能同时使用两者。

  • ​BlockRNNModel, TCNModel,NBEATSModel和TransformerModel​​​都使用​​past_covariates​​。
  • ​RNNModel​​​使用​​future_covariates​​。

上面列出的所有全局模型都支持多个序列的训练。此外,它们还都支持多变量序列。这意味着它们可以无缝地用于多维时间序列;目标系列可以包含一个(通常是这种情况)或多个维度。具有多个维度的时间序列实际上只是一个常规时间序列,其中每个时间戳的值是向量而不是标量。

例如,支持​​past_covariates​​的4个模型遵循 “块”架构 。它们包含一个神经网络,该网络以时间序列的块作为输入,并输出(预测的)未来时间序列的块。输入维数是目标序列的维数(组件),加上所有协变量的组件数——堆叠在一起。输出维数为目标序列的维数:

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用


时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用

RNNModel的工作方式不同,以一种循环的方式(这也是它们支持未来协变量的原因)。 好消息是,作为用户,我们不需要太担心不同的模型类型和输入/输出维度。维度由模型基于训练数据自动推断出来,对过去或未来协变量的支持由​​past_covariates​​​或​​future_covariates​​参数简单地处理。

在构建模型时,仍然需要指定两个重要参数:

  • input_chunk_length:这是模型的回溯窗口的长度;因此模型将通过读取前面的input_chunk_length点来计算每个输出。
  • ​Output_chunk_length​​​:这是内部模型产生的输出(预测)的长度。然而**,“outer” dart模型** (例如,​​NBEATSModel、TCNModel等的模型​​​)的​​predict()​​​方法可以在更长的时间范围内被调用。在这些情况下,如果在超过​​output_chunk_length的​​​范围内调用​​predict()​​​,内部模型将被重复调用,以一种自回归的方式依赖于它自己以前的输出。如果使用了​​past_covariates​​,它需要这些协变量提前足够长的时间被知道。

7.训练-预测-单变量

构建一个 N-BEATS 模型,该模型具有 ​​24 个点 ( input_chunk_length=24)​​​ 的回溯窗口并预测接下来的 ​​12 个点 ( output_chunk_length=12)​​​。选择这些值,这样模型就会在过去的两年里,每次产生连续的预测。
这个模型可以像任何其他dart预测模型一样使用,适用于单个时间序列:

model_air = NBEATSModel(
input_chunk_length=24, output_chunk_length=12, n_epochs=200, random_state=0
)
model_air.fit(train_air, verbose=True)

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用


和其他dart预测模型一样,可以通过调用​​predict()​​​来获得预测。注意,下面,调用的窗口为36的​​predict()​​​,比模型内部​​output_chunk_length为12​​的视界要长。这在这里不是问题——正如上面所解释的,在这种情况下,内部模型将被简单地称为自回归输出。在本例中,它将被调用三次,以便三个12点输出构成最后的36点预测——但所有这些都是在幕后透明地完成的。

pred = model_air.predict(n=36)

series_air_scaled.plot(label="actual")
pred.plot(label="forecast")
plt.legend()
print("MAPE = {:.2f}%".format(mape(series_air_scaled, pred)))

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用

(1).训练过程【后台训练过程】

调用model_air.fit()时发生了什么呢?

为了训练内部神经网络,dart首先从提供的时间序列(在本例中为series_air_scaled)中创建一个输入/输出示例数据集。有几种方法可以做到这一点,并且dart在dart .utils.data包中包含一些不同的数据集实现。

默认情况下,NBEATSModel将实例化 一个 ​​darts.utils.data.PastCovariatesSequentialDataset​​,它只是构建序列中存在的所有连续的输入/输出子序列对(长度为​​input_chunk_length​​​和​​output_chunk_length​​)。

对于长度为14的序列,​​input_chunk_length=4, output_chunk_length=2​​,它看起来如下:

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用


对于这样的数据集,一系列长度为N的数据集会得到 ​​N - input_chunk_length - output_chunk_length + 1​​​个样本的“训练集”。在上面的示例中,我们有​​N=14​​​, ​​input_chunk_length=4和output_chunk_length=2​​​,所以用于训练的样本数量是​​K = 9​​。在这种情况下,训练阶段包含对所有样本的完全遍历(可能包含几个小批量)。

请注意,默认情况下,不同的模型容易使用不同的数据集。例如,​​darts.utils.data.HorizonBasedDataset​​​受到​​N-BEATS​​ 论文的启发,并生成了“接近”系列结尾的样本,甚至可能忽略了序列的开头。

如果您需要控制从实例生成训练样本的方式,可以通过继承​​抽象类TimeSeries​​​来实现自己的训练数据集。​​darts.utils.data.TrainingDatasetDarts​​​ 数据集继承自 ​​torch Dataset​​​,这意味着很容易实现不会一次将所有数据加载到内存中的惰性版本。一旦有了自己的数据集实例,就可以直接调用该​​fit_from_dataset()​​方法,所有全球预测模型都支持该方法。

8.在多个时间序列(多变量)上训练模型

所有这些机器都可以与多个时间序列无缝使用。下面是一个顺序数据集如何查找两个长度为 N 和 M 的序列 ​​input_chunk_length=4​​​ 和 ​​output_chunk_length=2​

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用


注意几点:

  • 不同的系列不需要具有相同的长度,甚至不需要共享相同的时间戳。
  • 事实上,它们甚至不需要具有相同的频率。
  • 训练数据集中的样本总数将是每个系列中包含的所有训练样本的并集;所以一个训练时期现在将跨越所有系列的所有样本。

(1). 训练 空中交通数据集 和牛奶数据序列

在两个时间序列(航空乘客和牛奶生产)上拟合另一个模型实例。由于使用(大致)相同长度的两个系列(大致)会使训练数据集大小翻倍,因此我们将使用一半的 epoch 数:

train_air, val_air = series_air_scaled[:-36], series_air_scaled[-36:]
train_milk, val_milk = series_milk_scaled[:-36], series_milk_scaled[-36:]

model_air_milk = NBEATSModel(
input_chunk_length=24, output_chunk_length=12, n_epochs=100, random_state=0
)

在两个(或更多)序列上拟合模型,就像在​​fit()​​函数的参数中给出一个序列列表(而不是单个序列)一样简单:

model_air_milk.fit([train_air, train_milk], verbose=True)

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用

(2).在训练结束后生成预测

重要的是,在计算预测时,必须指定要预测未来的时间序列:之前没有这个约束。当仅在一个系列上拟合模型时,模型会在内部记住该系列,如果​​predict()​​​在没有​​series参数​​​的情况下调用,它会返回(唯一)训练系列的预测。一旦模型适用于多个系列,这将不再起作用 - 在这种情况下,​​series参数​​​成为​​predict()​​强制性的。

假设想预测空中交通的未来。在本例中,为​​predict()函数​​​指定了​​series=train_air​​​,目的是为了预测​​train_air​​之后的内容:

pred = model_air_milk.predict(n=36, series=train_air)

series_air_scaled.plot(label="actual")
pred.plot(label="forecast")
plt.legend()
print("MAPE = {:.2f}%".format(mape(series_air_scaled, pred)))

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用

(3).牛奶消耗是否真的有助于预测空中交通?

在这个模型的这个特殊例子中,情况(有助于)似乎就是这样(至少在 MAPE 错误方面)。不过,如果你仔细想想,这并不奇怪。空中交通的特点是年度季节性和上升趋势。牛奶系列也表现出这两个特征,在这种情况下,它可能有助于模型捕捉它们。

请注意,这指向了预训练预测模型的可能性;一劳永逸地训练模型,然后使用它们来预测不在训练集中的序列,可以预测一个变量的未来,也可以预测多个变量的未来。使用模型,可以真正预测任何其他系列的未来值,甚至是训练期间从未见过的系列。

举个例子,假设要预测一些任意正弦波序列的未来:【使用训练好的模型,预测多个变量的未来】
这个预测并不好(sin甚至没有每年的季节性),但您可以理解。

any_series = sine_timeseries(length=50, freq="M")
pred = model_air_milk.predict(n=36, series=any_series)

any_series.plot(label='"any series, really"')
pred.plot(label="forecast")
plt.legend()

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用

与​​fit()函数​​​所支持的类似,也可以在参数中为​​predict()函数​​提供一个序列列表,在这种情况下,它将返回一个预测序列列表。例如,可以一次得到空中交通和牛奶系列的预测,这两个序列分别对应于train_air和train_milk结束后的预测:

pred_list = model_air_milk.predict(n=36, series=[train_air, train_milk])
for series, label in zip(pred_list, ["air passengers", "milk production"]):
series.plot(label=f"forecast {label}")
plt.legend()

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用

9. 协变量序列

到目前为止,一直使用的模型只使用目标序列的历史 来 预测其未来。然而,如上所述,全局dart模型也支持使用协变量时间序列。这些是“外部数据” 的时间序列,也许不一定对预测感兴趣,但仍然希望将其作为模型的输入,因为它们可能包含有价值的信息。

(1)构建协变量

看一个关于空气和牛奶系列的简单例子,在这个例子中,将尝试使用年份和月份作为协变量:

# 构建年和月系列:
air_year = datetime_attribute_timeseries(series_air_scaled, attribute="year")
air_month = datetime_attribute_timeseries(series_air_scaled, attribute="month")

milk_year = datetime_attribute_timeseries(series_milk_scaled, attribute="year")
milk_month = datetime_attribute_timeseries(series_milk_scaled, attribute="month")

# 堆叠年和月,得到二维(年和月)序列:
air_covariates = air_year.stack(air_month)
milk_covariates = milk_year.stack(milk_month)

# 缩放到 0和1之间:
scaler_dt_air = Scaler()
air_covariates = scaler_dt_air.fit_transform(air_covariates)

scaler_dt_milk = Scaler()
milk_covariates = scaler_dt_milk.fit_transform(milk_covariates)

# 拆分训练/验证集:
air_train_covariates, air_val_covariates = air_covariates[:-36], air_covariates[-36:]
milk_train_covariates, milk_val_covariates = (
milk_covariates[:-36],
milk_covariates[-36:],
)

# 绘制协变量:
plt.figure()
air_covariates.plot()
plt.title("Air traffic covariates (year and month)")

plt.figure()
milk_covariates.plot()
plt.title("Milk production covariates (year and month)")

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用


很好,因此对于每个目标序列(空气和牛奶),我们已经构建了具有相同时间轴并包含年和月的协变量系列。

注意,这里的协变量序列是多元时间序列:它们包含两个维度——一个维度代表年份,一个维度代表月份。

(2)训练【协变量+特征、目标数据】

构建一个BlockRNNModel 训练:

model_cov = BlockRNNModel(
model="LSTM",
input_chunk_length=24,
output_chunk_length=12,
n_epochs=300,
random_state=0,
)

现在,要用协变量训练模型,只需将协变量(以匹配目标序列的列表的形式)作为​​future_covariates参数​​​提供给​​fit()函数​​​即可。参数名为​​future_covariates​​,以提醒模型可以使用这些协变量的未来值来进行预测。

model_cov.fit(
series=[train_air, train_milk],
past_covariates=[air_train_covariates, milk_train_covariates],
verbose=True,
)

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用

(3)使用协变量进行预测未来【无需输入未来的真实值】

类似地,获得预测现在只需要为​​predict()​​​函数指定​​future_covariates参数​​。

pred_cov = model_cov.predict(n=36, series=train_air, past_covariates=air_covariates)

series_air_scaled.plot(label="actual")
pred_cov.plot(label="forecast")
plt.legend()

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用


注意,这里调用了​​predict()​​​,其​​预测范围n​​​大于训练模型时使用的​​output_chunk_length​​​。之所以能够做到这一点,是因为即使​​BlockRNNModel​​​使用了过去的协变量,在这种情况下,这些协变量也知道未来的情况,所以dart能够对未来的​​n个时间步​​进行自回归计算预测。

(4)使用协变量进行回测

可以使用协变量对模型进行回测。例如,评估 12 个月的运行精度,从 75% 的空气系列开始:

backtest_cov = model_cov.historical_forecasts(
series_air_scaled,
past_covariates=air_covariates,
start=0.6,
forecast_horizon=12,
stride=1,
retrain=False,
verbose=True,
)

series_air_scaled.plot(label="actual")
backtest_cov.plot(label="forecast")
plt.legend()
print("MAPE (using covariates) = {:.2f}%".format(mape(series_air_scaled, backtest_cov)))

时间序列 工具库学习(5) Darts模块-多个时间序列、预训练模型和协变量的概念和使用