快速入门

在本笔记本中,我们将介绍该库的主要功能:

我们这里只会展示一些最基本的“入门”示例。如需更深入的信息,您可以参考我们的 用户指南示例笔记本

安装 Darts

我们建议使用一些虚拟环境。然后主要有两种方式。

使用 pip:

pip install darts

使用 conda

conda install -c conda-forge -c pytorch u8darts-all

如果在安装过程中遇到问题或想要安装不同版本(避免某些依赖项),请参阅 详细安装指南

首先,让我们导入一些东西:

[1]:
%matplotlib inline

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

from darts import TimeSeries
from darts.datasets import AirPassengersDataset

构建和操作 TimeSeries

TimeSeries 是 Darts 中的主要数据类。一个 TimeSeries 表示一个单变量或多变量时间序列,具有适当的时间索引。时间索引可以是 pandas.DatetimeIndex 类型(包含日期时间),或者是 pandas.RangeIndex 类型(包含整数,用于表示没有特定时间戳的顺序数据)。在某些情况下,TimeSeries 甚至可以表示 概率 序列,例如为了获得置信区间。Darts 中的所有模型都消费 TimeSeries 并生成 TimeSeries

读取数据并构建一个 TimeSeries

TimeSeries 可以通过几个工厂方法轻松构建:

  • 从一个完整的 Pandas DataFrame 中,使用 TimeSeries.from_dataframe() (文档).

  • 从一个时间索引和相应的值数组,使用 TimeSeries.from_times_and_values() (文档).

  • 从一个 NumPy 数组值中,使用 TimeSeries.from_values() (文档).

  • 从 Pandas Series 中,使用 TimeSeries.from_series() (文档).

  • 从一个 xarray.DataArray 中,使用 TimeSeries.from_xarray() (文档).

  • 从CSV文件中,使用 TimeSeries.from_csv() (文档).

  • 通过 Pandas DataFrame 按组创建多个 TimeSeries,使用 TimeSeries.from_group_dataframe() (文档).

下面,我们通过直接从Darts中可用数据集之一加载航空乘客系列来获取 TimeSeries

[2]:
series = AirPassengersDataset().load()
series.plot()
[2]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_5_1.png

一些 TimeSeries 操作

TimeSeries 支持不同类型的操作 - 这里有一些例子。

分割

我们也可以在序列的一部分进行分割,可以在一个 pandas Timestamp 或者在一个整数索引值处进行。

[3]:
series1, series2 = series.split_after(0.75)
series1.plot()
series2.plot()
[3]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_7_1.png

切片:

[4]:
series1, series2 = series[:-36], series[-36:]
series1.plot()
series2.plot()
[4]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_9_1.png

算术运算:

[5]:
series_noise = TimeSeries.from_times_and_values(
    series.time_index, np.random.randn(len(series))
)
(series / 2 + 20 * series_noise - 10).plot()
[5]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_11_1.png

堆叠

将新维度连接起来以生成一个新的单一多元序列。

[6]:
(series / 50).stack(series_noise).plot()
[6]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_13_1.png

映射:

[7]:
series.map(np.log).plot()
[7]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_15_1.png

在时间戳和值上进行映射:

[8]:
series.map(lambda ts, x: x / ts.days_in_month).plot()
[8]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_17_1.png

添加一些datetime属性作为额外维度(生成一个多元序列):

[9]:
(series / 20).add_datetime_attribute("month").plot()
[9]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_19_1.png

添加一些二进制假期组件:

[10]:
(series / 200).add_holidays("US").plot()
[10]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_21_1.png

差分:

[11]:
series.diff().plot()
[11]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_23_1.png

填充缺失值(使用 ``utils`` 函数)。

缺失值用 np.nan 表示。

[12]:
from darts.utils.missing_values import fill_missing_values

values = np.arange(50, step=0.5)
values[10:30] = np.nan
values[60:95] = np.nan
series_ = TimeSeries.from_values(values)

(series_ - 10).plot(label="with missing values (shifted below)")
fill_missing_values(series_).plot(label="without missing values")
[12]:
<Axes: xlabel='time'>
../_images/quickstart_00-quickstart_25_1.png

创建训练和验证序列

接下来,我们将把 TimeSeries 分成一个训练系列和一个验证系列。注意:通常,保留一个测试系列并在整个过程中不接触它也是一个好的做法。这里,为了简单起见,我们只构建一个训练系列和一个验证系列。

训练系列将是一个包含直到1958年1月(不包括)的值的 TimeSeries,而验证系列是一个包含其余部分的 TimeSeries

[13]:
train, val = series.split_before(pd.Timestamp("19580101"))
train.plot(label="training")
val.plot(label="validation")
[13]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_27_1.png

训练预测模型并进行预测

玩玩具模型

在 Darts 中有一组“朴素”的基线模型,这些模型对于了解一个人可能期望的最低准确度非常有用。例如,NaiveSeasonal(K) 模型总是“重复”K 时间步之前出现的值。

在最简单的情况下,当 K=1 时,该模型只是简单地重复训练序列的最后一个值:

[14]:
from darts.models import NaiveSeasonal

naive_model = NaiveSeasonal(K=1)
naive_model.fit(train)
naive_forecast = naive_model.predict(36)

series.plot(label="actual")
naive_forecast.plot(label="naive forecast (K=1)")
[14]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_29_1.png

TimeSeries 上拟合模型并生成预测非常简单。所有模型都有一个 fit() 和一个 predict() 函数。这与 Scikit-learn 类似,只不过它是专门针对时间序列的。fit() 函数以训练时间序列作为参数来拟合模型,而 predict() 函数以预测的时间步数(在训练序列结束之后)作为参数。

检查季节性

我们上面的模型可能有点过于简单。我们可以通过利用数据中的季节性来改进。很明显,数据具有年度的季节性,我们可以通过查看自相关函数(ACF)并突出滞后 m=12 来确认这一点:

[15]:
from darts.utils.statistics import plot_acf, check_seasonality

plot_acf(train, m=12, alpha=0.05, max_lag=24)
../_images/quickstart_00-quickstart_31_0.png

ACF 在 x = 12 处呈现一个峰值,这表明存在年度季节性趋势(红色高亮显示)。蓝色区域确定了在置信水平为 \(\alpha = 5\%\) 时的统计显著性。我们还可以对每个候选周期 m 进行季节性的统计检验:

[16]:
for m in range(2, 25):
    is_seasonal, period = check_seasonality(train, m=m, alpha=0.05)
    if is_seasonal:
        print("There is seasonality of order {}.".format(period))
There is seasonality of order 12.

一个不那么天真的模型

让我们再次尝试使用季节性为12的 NaiveSeasonal 模型:

[17]:
seasonal_model = NaiveSeasonal(K=12)
seasonal_model.fit(train)
seasonal_forecast = seasonal_model.predict(36)

series.plot(label="actual")
seasonal_forecast.plot(label="naive forecast (K=12)")
[17]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_35_1.png

这更好了,但我们仍然缺少趋势。幸运的是,还有一个捕捉趋势的朴素基线模型,称为 NaiveDrift。该模型仅生成线性预测,其斜率由训练集的第一个和最后一个值决定:

[18]:
from darts.models import NaiveDrift

drift_model = NaiveDrift()
drift_model.fit(train)
drift_forecast = drift_model.predict(36)

combined_forecast = drift_forecast + seasonal_forecast - train.last_value()

series.plot()
combined_forecast.plot(label="combined")
drift_forecast.plot(label="drift")
[18]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_37_1.png

那里发生了什么?我们简单地拟合了一个朴素的漂移模型,并将其预测添加到我们之前得到的季节性预测中。我们还从结果中减去了训练集的最后一个值,以便生成的组合预测从正确的偏移量开始。

计算误差指标

这看起来已经像是一个相当不错的预测了,而且我们还没有使用任何非朴素模型。事实上 - 任何模型都应该能够超越这个。

那么我们将要解决的错误是什么?我们将使用 平均绝对百分比误差 (MAPE) (请注意,在实践中通常有充分的理由 使用 MAPE - 我们在这里使用它是因为它非常方便且与尺度无关)。在 Darts 中,这是一个简单的函数调用:

[19]:
from darts.metrics import mape

print(
    f"Mean absolute percentage error for the combined naive drift + seasonal: {mape(series, combined_forecast):.2f}%."
)
Mean absolute percentage error for the combined naive drift + seasonal: 5.66%.

darts.metrics 包含许多用于比较时间序列的指标。当两个序列不对齐时,指标将仅比较序列的公共切片,并在大量序列对上并行化计算 - 但我们不要操之过急。

快速尝试多个模型

Darts 旨在以统一的方式轻松训练和验证多个模型。让我们再训练几个模型,并计算它们在验证集上的各自 MAPE:

[20]:
from darts.models import ExponentialSmoothing, TBATS, AutoARIMA, Theta


def eval_model(model):
    model.fit(train)
    forecast = model.predict(len(val))
    print(f"model {model} obtains MAPE: {mape(val, forecast):.2f}%")


eval_model(ExponentialSmoothing())
eval_model(TBATS())
eval_model(AutoARIMA())
eval_model(Theta())
model ExponentialSmoothing() obtains MAPE: 5.11%
model TBATS() obtains MAPE: 5.87%
model AutoARIMA() obtains MAPE: 11.65%
model Theta() obtains MAPE: 8.15%

这里,我们只使用默认参数创建了这些模型。如果我们针对我们的问题进行微调,可能会做得更好。让我们尝试使用 Theta 方法。

使用Theta方法搜索超参数

模型 Theta 包含了 Assimakopoulos 和 Nikolopoulos 的 Theta 方法的实现。这种方法取得了一些成功,特别是在 M3 竞赛中。

尽管在应用中 Theta 参数的值通常设置为 0,但我们的实现支持可变的值,以便进行参数调优。让我们尝试找到一个合适的 Theta 值:

[21]:
# Search for the best theta parameter, by trying 50 different values
thetas = 2 - np.linspace(-10, 10, 50)

best_mape = float("inf")
best_theta = 0

for theta in thetas:
    model = Theta(theta)
    model.fit(train)
    pred_theta = model.predict(len(val))
    res = mape(val, pred_theta)

    if res < best_mape:
        best_mape = res
        best_theta = theta
[22]:
best_theta_model = Theta(best_theta)
best_theta_model.fit(train)
pred_best_theta = best_theta_model.predict(len(val))

print(f"Lowest MAPE is: {mape(val, pred_best_theta):.2f}, with theta = {best_theta}.")
Lowest MAPE is: 4.40, with theta = -3.5102040816326543.
[23]:
train.plot(label="train")
val.plot(label="true")
pred_best_theta.plot(label="prediction")
[23]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_48_1.png

我们可以观察到,在MAPE方面,带有 best_theta 的模型是目前为止我们拥有的最好的模型。

回测:模拟历史预测

所以在这一点上,我们有一个在验证集上表现良好的模型,这很好。但是,我们如何知道如果*过去使用这个模型*,我们*会获得*什么样的性能呢?

回测模拟了在给定模型下,历史时期可能获得的预测结果。由于每次模拟预测时间推进时,模型(默认情况下)都会重新训练,因此生成回测结果可能需要一些时间。

这种模拟预测总是相对于一个 预测范围 来定义的,这个范围是从预测时间到预测时间之间的时间步数。在下面的例子中,我们模拟了未来3个月的预测(相对于预测时间)。调用 historical_forecasts() 的结果(默认情况下)是一个 TimeSeries,它只包含每个3个月前预测的最后一个预测值:

[24]:
hfc_params = {
    "series": series,
    "start": pd.Timestamp("1956-01-01"),  # can also be a float for the fraction of the series to start at
    "forecast_horizon": 3,
    "verbose": True,
}
historical_fcast_theta = best_theta_model.historical_forecasts(last_points_only=True, **hfc_params)

series.plot(label="data")
historical_fcast_theta.plot(label="backtest 3-months ahead forecast (Theta)")
print(f"MAPE = {mape(series, historical_fcast_theta):.2f}%")
MAPE = 7.99%
../_images/quickstart_00-quickstart_51_2.png

我们还可以通过设置 last_points_only=False 来检索每个历史预测的所有预测值。通过 stride 参数,我们定义了两个连续预测之间移动的步数。我们将其设置为3个月,以便我们可以将这些预测连接成一个单一的 TimeSeries

[25]:
historical_fcast_theta_all = best_theta_model.historical_forecasts(last_points_only=False, stride=3, **hfc_params)

series.plot(label="data")
for idx, hfc in enumerate(historical_fcast_theta_all):
    hfc.plot(label=f"forecast {idx}")

from darts import concatenate
historical_fcast_theta_all = concatenate(historical_fcast_theta_all, axis=0)
print(f"MAPE = {mape(series, historical_fcast_theta_all):.2f}%")
MAPE = 5.61%
../_images/quickstart_00-quickstart_53_2.png

所以看来,我们在验证集上表现最好的模型在回测时表现不再那么出色了(我是不是听到了过拟合的声音 :D)

要更仔细地查看错误,我们还可以使用 backtest() 方法来获取模型可能获得的所有原始错误(例如,MAPE 错误):

[26]:
best_theta_model = Theta(best_theta)

raw_errors = best_theta_model.backtest(metric=mape, reduction=None, last_points_only=False, stride=1, **hfc_params)

from darts.utils.statistics import plot_hist

plot_hist(
    raw_errors,
    bins=np.arange(0, max(raw_errors), 1),
    title="Individual backtest error scores (histogram)",
)
../_images/quickstart_00-quickstart_55_1.png

最后,使用 backtest() 我们还可以得到历史预测中平均误差的简化视图:

[27]:
average_error = best_theta_model.backtest(
    metric=mape,
    reduction=np.mean,  # this is actually the default
    **hfc_params
)

print(f"Average error (MAPE) over all historical forecasts: {average_error:.2f}")
Average error (MAPE) over all historical forecasts: 6.30

我们也可以例如指定参数 reduction=np.median 来获取中位数MAPE。

上面,每次调用 backtest() 时都会重新计算历史预测。我们也可以使用一些预先计算的预测来更快地获得结果!

[28]:
hfc_precomputed = best_theta_model.historical_forecasts(last_points_only=False, stride=1, **hfc_params)
new_error = best_theta_model.backtest(historical_forecasts=hfc_precomputed, last_points_only=False, stride=1, **hfc_params)

print(f"Average error (MAPE) over all historical forecasts: {new_error:.2f}")
Average error (MAPE) over all historical forecasts: 6.30

让我们看一下当前 Theta 模型的拟合值残差,即在每个时间点上,通过在所有先前点上拟合模型获得的1步预测与实际观测值之间的差异:

[29]:
from darts.utils.statistics import plot_residuals_analysis

plot_residuals_analysis(best_theta_model.residuals(series))
../_images/quickstart_00-quickstart_61_0.png

我们可以看到分布并非以0为中心,这意味着我们的 Theta 模型存在偏差。我们还可以观察到滞后等于12时有一个较大的ACF值,这表明残差中包含了模型未使用的信息。

我们的 residuals 方法实际上更加强大!它可以用来计算 Darts 中的任何 *每时间步指标*(参见列表 这里),即使是多步预测也可以。它还支持类似于回测的预计算历史预测。

现在让我们检查在3个月预测中每个步骤的绝对误差分布。

[30]:
from darts.metrics import ae
residuals = best_theta_model.residuals(
    historical_forecasts=hfc_precomputed,
    metric=ae,  # the absolute error per time step
    last_points_only=False,
    values_only=True, # return a list of numpy arrays
    **hfc_params
)
residuals = np.concatenate(residuals, axis=1)[:, :, 0]

fig, ax = plt.subplots()
for forecast_step in range(len(residuals)):
    ax.hist(residuals[forecast_step], label=f"step {forecast_step}", alpha=0.5)
ax.legend()
ax.set_title("Absolute errors per forecast step")
[30]:
Text(0.5, 1.0, 'Absolute errors per forecast step')
../_images/quickstart_00-quickstart_63_1.png

我们可以清楚地看到,预测的时间越远,错误就越多。

我们能否用一个简单的 ExponentialSmoothing 模型做得更好?

[31]:
model_es = ExponentialSmoothing(seasonal_periods=12)
historical_fcast_es = model_es.historical_forecasts(**hfc_params)

series.plot(label="data")
historical_fcast_es.plot(label="backtest 3-months ahead forecast (Exp. Smoothing)")
print(f"MAPE = {mape(historical_fcast_es, series):.2f}%")
MAPE = 4.42%
../_images/quickstart_00-quickstart_66_2.png

这好多了!在这种情况下,当我们使用3个月的预测范围进行回测时,平均绝对百分比误差约为4-5%。

[32]:
plot_residuals_analysis(model_es.residuals(series, verbose=True))
../_images/quickstart_00-quickstart_68_1.png

残差分析也反映了性能的提升,因为我们现在有一个以值0为中心的残差分布,并且ACF值虽然不是不显著,但其幅度有所降低。

机器学习和全局模型

Darts 对机器学习和深度学习预测模型有丰富的支持;例如:

  • RegressionModel 可以包装任何与 sklearn 兼容的回归模型以生成预测(它有自己的 章节在下面)。

  • RNNModel 是一个灵活的 RNN 实现,可以像 DeepAR 一样使用。

  • NBEATSModel 实现了 N-BEATS 模型。

  • TFTModel 实现了时间融合变压器模型。

  • TCNModel 实现了时间卷积网络。

除了支持与其他模型相同的 fit()/predict() 接口外,这些模型也是 全局模型,因为它们支持在多个时间序列上进行训练(有时称为 元学习)。

这是使用基于机器学习(ML)模型进行预测的一个关键点:通常情况下,ML模型(尤其是深度学习模型)需要在大规模数据上进行训练,这通常意味着大量的单独但相关的时间序列。

在 Darts 中,指定多个 TimeSeries 的基本方法是使用 TimeSeriesSequence``(例如,一个简单的 ``TimeSeries 列表)。

一个包含两个系列的玩具示例

这些模型可以在数千个序列上进行训练。为了便于说明,我们将加载两个不同的序列——航空旅客人数和每月每头牛生产的牛奶磅数。我们还将其转换为 np.float32,因为这会稍微加快训练速度:

[33]:
from darts.datasets import AirPassengersDataset, MonthlyMilkDataset

series_air = AirPassengersDataset().load().astype(np.float32)
series_milk = MonthlyMilkDataset().load().astype(np.float32)

# set aside last 36 months of each series as validation set:
train_air, val_air = series_air[:-36], series_air[-36:]
train_milk, val_milk = series_milk[:-36], series_milk[-36:]

train_air.plot()
val_air.plot()
train_milk.plot()
val_milk.plot()
[33]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_71_1.png

首先,让我们将这两个序列缩放到0到1之间,因为这对大多数机器学习模型都有益。我们将为此使用一个 Scaler

[34]:
from darts.dataprocessing.transformers import Scaler

scaler = Scaler()
train_air_scaled, train_milk_scaled = scaler.fit_transform([train_air, train_milk])

train_air_scaled.plot()
train_milk_scaled.plot()
[34]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_73_1.png

注意我们如何一次性缩放多个系列。我们还可以通过指定 n_jobs 在多个处理器上并行化此类操作。

使用深度学习:N-BEATS 示例

注意:您可以在 这里 找到我们神经网络模型(TorchForecastingModels)的详细用户指南。

接下来,我们将构建一个 N-BEATS 模型。这个模型可以通过许多超参数(如堆栈数量、层数等)进行调整。为了简单起见,我们将使用默认的超参数。我们只需要提供的两个超参数是:

  • input_chunk_length: 这是模型的“回溯窗口” - 即,神经网络在一次前向传递中作为输入以产生输出的历史时间步数。

  • output_chunk_length: 这是模型的“前向窗口”——即,神经网络在一次前向传递中输出的未来时间步数的值。

random_state 参数仅用于获取可重复的结果。

Darts 中的大多数神经网络需要这两个参数。在这里,我们将使用季节性的倍数。我们现在准备好将我们的模型拟合到我们的两个序列上(通过将包含两个序列的列表传递给 fit()):

[35]:
from darts.models import NBEATSModel

model = NBEATSModel(input_chunk_length=24, output_chunk_length=12, random_state=42)

model.fit([train_air_scaled, train_milk_scaled], epochs=50, verbose=True);
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs

  | Name            | Type             | Params | Mode
-------------------------------------------------------------
0 | criterion       | MSELoss          | 0      | train
1 | train_criterion | MSELoss          | 0      | train
2 | val_criterion   | MSELoss          | 0      | train
3 | train_metrics   | MetricCollection | 0      | train
4 | val_metrics     | MetricCollection | 0      | train
5 | stacks          | ModuleList       | 6.2 M  | train
-------------------------------------------------------------
6.2 M     Trainable params
1.4 K     Non-trainable params
6.2 M     Total params
24.787    Total estimated model params size (MB)
`Trainer.fit` stopped: `max_epochs=50` reached.

现在让我们为我们的两个序列获取未来36个月的预测。我们可以使用 predict() 函数的 series 参数来告诉模型要预测哪个序列。重要的是,output_chunk_length 并不直接限制 predict() 的预测范围 n。在这里,我们使用 output_chunk_length=12 训练模型,并生成未来 n=36 个月的预测;这在幕后以自回归的方式简单地完成(网络递归地消费其先前的输出)。

[36]:
pred_air, pred_milk = model.predict(series=[train_air_scaled, train_milk_scaled], n=36)

# scale back:
pred_air, pred_milk = scaler.inverse_transform([pred_air, pred_milk])

plt.figure(figsize=(10, 6))
series_air.plot(label="actual (air)")
series_milk.plot(label="actual (milk)")
pred_air.plot(label="forecast (air)")
pred_milk.plot(label="forecast (milk)")
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
[36]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_78_3.png

考虑到我们使用**一个模型**和默认的超参数来捕捉航空乘客和牛奶生产,我们的预测实际上并没有那么糟糕!

该模型似乎很好地捕捉了年度的季节性,但未能捕捉到空气系列的趋势。在下一节中,我们将尝试使用外部数据(协变量)来解决这个问题。

协变量:使用外部数据

除了 目标 系列(我们感兴趣的预测系列),Darts 中的许多模型还接受 协变量 系列作为输入。协变量是我们不想预测的系列,但它们可以为模型提供有用的额外信息。目标和协变量都可以是多元或单变量的。

Darts 中有两种协变量时间序列:

  • past_covariates 是那些在预测时间之前不一定已知的序列。例如,它们可以代表必须测量且事先不知道的事物。模型在做出预测时不会使用 past_covariates 的未来值(仅在预测时由于自回归而使用 n > output_chunk_length 的全局模型)。有关过去/未来协变量的更多信息,请查看此 用户指南

  • future_covariates 是提前已知的序列,直到预测范围。它们可以代表日历信息、节假日、天气预报等。接受 future_covariates 的模型在做出预测时会查看未来的值(直到预测范围)。

  • static_covariates 是目标序列的特征,这些特征不会随时间变化。它们直接嵌入到目标序列中。它们可以代表产品类别、国家信息等。有关静态协变量的更多信息,请查看此 用户指南

|协变量|

每个协变量都可能是多元的。如果你有多个协变量序列(如月份和年份值),你应该使用 stack()concatenate() 将它们组合成一个多元序列。

您提供的协变量可能比必要的长。Darts 的模型将自动为您处理相关时间帧的提取! 不过,如果您的协变量没有足够的时间跨度,您将收到错误。

并非所有模型都支持每种协变量类型。你可以在 这里 找到一个模型列表,说明它们支持的类型。

现在,让我们为我们的空气和牛奶序列构建一些包含月度和年度值的外部协变量。在下面的单元格中,我们使用 darts.utils.timeseries_generation.datetime_attribute_timeseries() 函数生成包含月份和年份值的序列,并通过 concatenate() 函数沿着 "component" 轴(与 axis=1 相同)将这些序列连接起来,以便为每个目标序列获得一个包含两个分量(月份和年份)的协变量序列。为了简化,我们直接将月份和年份值缩放到 0 到 1 之间:

[37]:
from darts import concatenate
from darts.utils.timeseries_generation import datetime_attribute_timeseries as dt_attr

air_covs = concatenate(
    [
        dt_attr(series_air, "month", dtype=np.float32),
        dt_attr(series_air, "year", dtype=np.float32),
    ],
    axis="component",
)

milk_covs = concatenate(
    [
        dt_attr(series_milk, "month", dtype=np.float32),
        dt_attr(series_milk, "year", dtype=np.float32),
    ],
    axis="component",
)

air_covs, milk_covs = Scaler().fit_transform([air_covs, milk_covs])
air_covs.plot()
milk_covs.plot()
plt.title(
    "one multivariate time series of 2 dimensions, containing covariates for the air series:"
)
[37]:
Text(0.5, 1.0, 'one multivariate time series of 2 dimensions, containing covariates for the air series:')
../_images/quickstart_00-quickstart_81_1.png

NBEATSModel 仅支持 past_covariates。因此,尽管我们的协变量代表日历信息并且是提前已知的,我们仍将它们作为 past_covariates 与 N-BEATS 一起使用。要进行训练,我们只需将它们作为 past_covariates 传递给 fit() 函数,顺序与目标相同:

[38]:
model = NBEATSModel(input_chunk_length=24, output_chunk_length=12, random_state=42)

model.fit(
    [train_air_scaled, train_milk_scaled],
    past_covariates=[air_covs, milk_covs],
    epochs=50,
    verbose=True,
);
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs

  | Name            | Type             | Params | Mode
-------------------------------------------------------------
0 | criterion       | MSELoss          | 0      | train
1 | train_criterion | MSELoss          | 0      | train
2 | val_criterion   | MSELoss          | 0      | train
3 | train_metrics   | MetricCollection | 0      | train
4 | val_metrics     | MetricCollection | 0      | train
5 | stacks          | ModuleList       | 6.6 M  | train
-------------------------------------------------------------
6.6 M     Trainable params
1.7 K     Non-trainable params
6.6 M     Total params
26.314    Total estimated model params size (MB)
`Trainer.fit` stopped: `max_epochs=50` reached.

然后,为了生成预测,我们再次需要将我们的协变量作为 past_covariates 提供给 predict() 函数。

[39]:
preds = model.predict(series=[train_air_scaled, train_milk_scaled], past_covariates=[air_covs, milk_covs], n=36)

# scale back:
pred_air, pred_milk = scaler.inverse_transform(preds)

plt.figure(figsize=(10, 6))
series_air.plot(label="actual (air)")
series_milk.plot(label="actual (milk)")
pred_air.plot(label="forecast (air)")
pred_milk.plot(label="forecast (milk)")
`predict()` was called with `n > output_chunk_length`: using auto-regression to forecast the values after `output_chunk_length` points. The model will access `(n - output_chunk_length)` future values of your `past_covariates` (relative to the first predicted time step). To hide this warning, set `show_warnings=False`.
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
[39]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_85_3.png

现在看来,模型更好地捕捉了空气系列的趋势(这也稍微扰动了牛奶系列的预测)。

模型警告非常重要。它告诉我们,由于我们选择了水平 n > output_chunk_length,这激活了自回归,因此使用了我们 past_covariates 的未来值进行预测。然后,模型将使用其自身的预测作为下一次预测的输入。由于预测点向前移动,模型需要来自过去协变量的新值。然而,这些值只会从相对于预测点的过去提取(永远不会从预测水平本身提取)!

在我们的情况下,这样做是可以的,因为我们只使用我们提前知道的日历信息。如果我们使用的是我们只能在过去的某些特征,那么我们应该只用 n <= output_chunk_length 进行预测。

编码器:使用协变量以免费

使用与日历或时间轴相关的协变量(如上例中的月份和年份)非常频繁,因此Darts中所有支持协变量的模型都内置了开箱即用的生成功能。

要轻松地将此类协变量集成到您的模型中,您只需在创建模型时指定 add_encoders 参数。此参数必须是一个字典,其中包含有关应将哪些内容编码为额外协变量的信息。以下是一个示例,展示了支持过去和未来协变量的模型中此类字典的可能形式:

[40]:
def extract_year(idx):
    """Extract the year each time index entry and normalized it."""
    return (idx.year - 1950) / 50


encoders = {
    "cyclic": {"future": ["month"]},
    "datetime_attribute": {"future": ["hour", "dayofweek"]},
    "position": {"past": ["absolute"], "future": ["relative"]},
    "custom": {"past": [extract_year]},
    "transformer": Scaler(),
}

在上面的字典中,指定了以下内容:

  • 月份应作为未来协变量使用,采用循环(sin/cos)编码。

  • 应使用小时和星期几作为未来协变量。

  • 应使用绝对位置(序列中的时间步)作为过去协变量。

  • 应使用相对于预测时间的相对位置作为未来协变量。

  • 年度应使用额外的自定义函数作为过去协变量。

  • 所有上述协变量应使用 Scaler 进行缩放,该缩放器将在调用模型 fit() 函数时进行拟合,并在之后用于转换协变量。

我们参考 API文档 以获取更多关于如何使用编码器的信息。请注意,lambda函数不能使用,因为它们不可序列化。

要使用 N-BEATS 复制我们以月份和年份作为过去协变量的示例,我们可以使用一些编码器,如下所示:

[41]:
encoders = {"datetime_attribute": {"past": ["month", "year"]}, "transformer": Scaler()}

现在,使用这些协变量对 N-BEATS 模型进行整个训练的过程如下所示:

[42]:
model = NBEATSModel(
    input_chunk_length=24,
    output_chunk_length=12,
    add_encoders=encoders,
    random_state=42,
)

model.fit([train_air_scaled, train_milk_scaled], epochs=50, verbose=True);
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs

  | Name            | Type             | Params | Mode
-------------------------------------------------------------
0 | criterion       | MSELoss          | 0      | train
1 | train_criterion | MSELoss          | 0      | train
2 | val_criterion   | MSELoss          | 0      | train
3 | train_metrics   | MetricCollection | 0      | train
4 | val_metrics     | MetricCollection | 0      | train
5 | stacks          | ModuleList       | 6.6 M  | train
-------------------------------------------------------------
6.6 M     Trainable params
1.7 K     Non-trainable params
6.6 M     Total params
26.314    Total estimated model params size (MB)
`Trainer.fit` stopped: `max_epochs=50` reached.

并对航空旅客系列进行一些预测:

[43]:
preds = model.predict(series=[train_air_scaled, train_milk_scaled], n=36, show_warnings=False)

# scale back:
pred_air, pred_milk = scaler.inverse_transform(preds)

plt.figure(figsize=(10, 6))
series_air.plot(label="actual (air)")
series_milk.plot(label="actual (milk)")
pred_air.plot(label="forecast (air)")
pred_milk.plot(label="forecast (milk)")
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
[43]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_94_3.png

回归预测模型

注意:您可以在 这里 找到我们 RegressionModel 的详细示例笔记本。

回归模型 是围绕 sklearn 兼容回归模型包装的预测模型。内部回归模型用于根据目标的某些滞后、过去和未来的协变量来预测目标序列的未来值。在幕后,时间序列被表格化以构建正确格式的训练数据集。

默认情况下,RegressionModel 将执行线性回归。通过指定 model 参数,可以非常容易地使用任何兼容 sklearn 的回归模型,但为了方便起见,Darts 还提供了一些开箱即用的现成模型:

例如,这是将贝叶斯岭回归拟合到我们的玩具双系列问题上的样子:

[44]:
from darts.models import RegressionModel
from sklearn.linear_model import BayesianRidge

model = RegressionModel(lags=72, lags_future_covariates=[-6, 0], model=BayesianRidge())

model.fit(
    [train_air_scaled, train_milk_scaled], future_covariates=[air_covs, milk_covs]
);

上面发生了几件事:

  • lags=72 告诉 RegressionModel 查看目标过去 72 个滞后/月份的数据。

  • 此外,lags_future_covariates=[-6, 0] 意味着模型还将查看我们提供的 future_covariates 的滞后值。这里我们指定了希望模型考虑的确切滞后值;即“第-6个”和“第0个”滞后。“第0个”滞后意味着“当前”滞后(即在预测的时间步长);显然,知道这个滞后值需要提前知道数据(因此我们在使用 future_covariates)。同样,-6 表示我们还会查看预测时间步长前6个月的协变量值(如果我们在超过6步的范围内进行预测,这也需要提前知道协变量值)。

  • model=BayesianRidge() 提供了实际的内部回归模型。

现在让我们获取一些预测:

[45]:
preds = model.predict(
    series=[train_air_scaled, train_milk_scaled],
    future_covariates=[air_covs, milk_covs],
    n=36,
)

# scale back:
pred_air, pred_milk = scaler.inverse_transform(preds)

plt.figure(figsize=(10, 6))
series_air.plot(label="actual (air)")
series_milk.plot(label="actual (milk)")
pred_air.plot(label="forecast (air)")
pred_milk.plot(label="forecast (milk)")
[45]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_98_1.png

注意我们是如何一次性获取两个时间序列的预测的。同样地,我们也可以获取一些序列的度量指标:

[46]:
mape([series_air, series_milk], [pred_air, pred_milk])
[46]:
[3.4355457, 5.1290045]

或“所有”系列上的平均指标:

[47]:
mape([series_air, series_milk], [pred_air, pred_milk], series_reduction=np.mean)
[47]:
4.282275

顺便说一下:类似于 Scaler 这样的转换器,当通过指定 n_jobs=N 在许多序列对上执行时,计算指标可以在 N 个处理器上并行化。

看起来这个模型在航空交通系列上表现良好,当我们在这个单一系列上进行回测时,它的表现如何?

[48]:
bayes_ridge_model = RegressionModel(
    lags=72, lags_future_covariates=[0], model=BayesianRidge()
)

backtest = bayes_ridge_model.historical_forecasts(
    future_covariates=air_covs, **hfc_params
)

print(f"MAPE = {mape(series_air, backtest):.2f}%")
series_air.plot()
backtest.plot()
`enable_optimization=True` is ignored because `retrain` is not `False` or `0`. To hide this warning, set `show_warnings=False` or `enable_optimization=False`.
`enable_optimization=True` is ignored because `forecast_horizon > model.output_chunk_length`. To hide this warning, set `show_warnings=False` or `enable_optimization=False`.
MAPE = 3.76%
[48]:
<Axes: xlabel='time'>
../_images/quickstart_00-quickstart_104_4.png

我们迄今为止最好的模型!

样本权重

我们所有的全局模型(回归模型、集成模型和神经网络模型)都支持训练时的样本权重。它们按观测值、标签(output_chunk_length 中的每一步)和组件应用。这可以非常有用,例如:

  • 对某些时间段赋予更高的权重(例如,对过去的时间点赋予递减的权重,对具有更高业务价值的时间赋予更高的权重,…)

  • 减少/消除异常值或缺失值对模型的影响

让我们在航空乘客系列中引入一些异常值,并看看一个具有12个月回溯窗口(lags=12)的线性回归模型在这个数据上的表现如何。

[49]:
# convert to pandas DataFrame and add some outliers
air_df = series_air.pd_dataframe()
outlier_mask = (air_df.index.year >= 1950) & (air_df.index.year <= 1951)
air_df.loc[outlier_mask] = 600.
air_outlier = TimeSeries.from_dataframe(air_df)

from darts.models import LinearRegressionModel
model_lin = LinearRegressionModel(lags=12, lags_future_covariates=[0])
hist_fc_kwargs = {
    "series": air_outlier,
    "future_covariates": air_covs,
    "start": 0.6,
    "forecast_horizon": 3,
    "verbose": True,
    "show_warnings": False
}
backtest = model_lin.historical_forecasts(**hist_fc_kwargs)

print(f"MAPE = {mape(air_outlier, backtest):.2f}%")
air_outlier.plot(label="air series with outliers")
backtest.plot(label="non-weighted predictions")
MAPE = 26.40%
[49]:
<Axes: xlabel='time'>
../_images/quickstart_00-quickstart_107_3.png

这看起来不太好。如果我们能以某种方式忽略异常值就好了。而这正是样本权重发挥作用的地方!

在 Darts 中,样本权重被视为协变量——它们本身被定义为 TimeSeries。多亏了这一点,我们的模型可以自动为我们提取相关的时间框架!

让我们创建一个权重序列,其中我们将所有异常时间设置为 0.,其余时间设置为 1.。我们还将最后一个异常时间后的12个月设置为 0.,因为这些时间落入了我们模型的回溯窗口。否则,我们仍然会在模型输入中存在一些异常值。

[50]:
weight_mask = (air_df.index.year >= 1950) & (air_df.index.year <= 1952)
sample_weight = np.ones((len(air_df), 1))
sample_weight[weight_mask, 0] = 0.
sample_weight = air_outlier.with_values(sample_weight)

# and plot the results
air_outlier.plot(label="air series with outliers")
(sample_weight * 300).plot(label="sample weight * 300")
[50]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_109_1.png

现在我们可以使用样本权重通过方法 fit()historical_forecasts()backtest() 等来训练模型。

[51]:
model_lin = LinearRegressionModel(lags=12, lags_future_covariates=[0])
backtest_weighted = model_lin.historical_forecasts(sample_weight=sample_weight, **hist_fc_kwargs)

print(f"MAPE = {mape(series_air, backtest_weighted):.2f}%")
air_outlier.plot(label="air series with outliers")
backtest_weighted.plot(label="weighted predictions")
MAPE = 5.19%
[51]:
<Axes: xlabel='time'>
../_images/quickstart_00-quickstart_111_3.png

哇,这看起来很棒!我们成功地完全消除了异常值对我们模型的负面影响!

注意: 我们的样本权重还支持多时间范围预测、多时间序列、多变量序列,以及评估集的权重(如果模型支持)

预测开始转变

我们可能也对从目标序列结束后的偏移量开始的预测感兴趣。例如,这可能很有用:

  • 在(日前)市场中,我们需要为未来的某个时间点(次日)进行一些投标——我们并不真正关心现在到那个未来时间点之间会发生什么。通过只关注感兴趣的时间,我们可以降低模型复杂性。

  • 当协变量(或目标序列)被延迟报告时

我们所有的全局模型都通过模型创建参数 output_chunk_shift 支持这种预测,该参数表示将第一个预测步骤向未来偏移的步数。

通过输出偏移,模型无法再进行自回归。因此,让我们创建一个线性模型,直接预测未来12个月,偏移量为12个月。

[52]:
model_shifted = LinearRegressionModel(
    lags=12,
    lags_future_covariates=(0, 12),
    output_chunk_length=12,
    output_chunk_shift=12,
)

model_shifted.fit(series_air[:-24], future_covariates=air_covs)
preds = model_shifted.predict(n=12)

series_air[:-24].plot(label="train series")
series_air[-24:].plot(label="val_series")
preds.plot(label="shifted prediction")
[52]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_114_1.png

我们看到预测从训练序列结束后的12个月开始。

概率预报

Darts 中的大多数模型都支持概率预测(一些本地模型以及所有回归、集成和神经网络模型)。完整的支持列表可以在 Darts 的 README 页面 上找到。

使用本地模型

对于本地模型(ARIMAExponentialSmoothing 等),我们可以在调用 predict() 时简单地指定一个 num_samples 参数。返回的 TimeSeries 将包含 num_samples 个蒙特卡洛样本,描述时间序列值的分布。依赖蒙特卡洛样本(相对于显式置信区间)的优势在于,它们可以用来描述任何参数或非参数的联合分布,并计算任意分位数。

[53]:
model_es = ExponentialSmoothing()
model_es.fit(train)
probabilistic_forecast = model_es.predict(len(val), num_samples=500)

series.plot(label="actual")
probabilistic_forecast.plot(label="probabilistic forecast")
plt.legend()
plt.show()
../_images/quickstart_00-quickstart_117_0.png

使用神经网络模型(TorchForecastingModel)

在使用神经网络时,我们必须在模型创建时使用一个 Likelihood 对象。似然性指定了模型将尝试拟合的分布,以及分布参数的可能先验值。可用似然性的完整列表可以在 文档中找到

除了分布之外,我们还支持 QuantileRegression 作为似然函数,它可以估计目标序列的未来分位数值。

使用似然性很容易。例如,以下是如何训练一个 TCNModel 以适应拉普拉斯似然性的样子:

[54]:
from darts.models import TCNModel
from darts.utils.likelihood_models import LaplaceLikelihood

model = TCNModel(
    input_chunk_length=24,
    output_chunk_length=12,
    random_state=42,
    likelihood=LaplaceLikelihood(),
)

model.fit(train_air_scaled, epochs=400, verbose=True);
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs

  | Name            | Type             | Params | Mode
-------------------------------------------------------------
0 | criterion       | MSELoss          | 0      | train
1 | train_criterion | MSELoss          | 0      | train
2 | val_criterion   | MSELoss          | 0      | train
3 | train_metrics   | MetricCollection | 0      | train
4 | val_metrics     | MetricCollection | 0      | train
5 | res_blocks      | ModuleList       | 166    | train
-------------------------------------------------------------
166       Trainable params
0         Non-trainable params
166       Total params
0.001     Total estimated model params size (MB)
[W NNPACK.cpp:61] Could not initialize NNPACK! Reason: Unsupported hardware.
`Trainer.fit` stopped: `max_epochs=400` reached.

概率样本预测

然后,要获得概率预测,我们再次只需要指定一些 num_samples >> 1。这将从中采样预测分布。

[55]:
pred = model.predict(n=36, num_samples=500)

# scale back:
pred = scaler.inverse_transform(pred)

series_air.plot()
pred.plot()
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
Only 1 TimeSeries (lists) were provided which is lower than the number of series (n=2) used to fit Scaler. This can result in a mismatch between the series and the underlying transformers.
[55]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_121_4.png

直接参数预测

我们的似然性有一个很大的优点,那就是我们不需要从分布/分位数中采样,而是可以直接预测分布/分位数参数。为此,我们只需要设置 predict_likelihood_parameters=True

下面我们得到拉普拉斯分布的预测位置(mu)和尺度(b)(在0到1之间的缩放空间中)。

[56]:
pred = model.predict(n=12, predict_likelihood_parameters=True)

train_air_scaled.plot()
pred.plot(label="laplace_dist")
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
[56]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_123_3.png

此外,我们还可以例如指定我们对分布的尺度有一些先验信念,大约在变换域中为 \(0.1\),同时仍然捕捉到分布的一些时间依赖性,通过指定 prior_b=.1

在幕后,这将使用Kullback-Leibler散度项来正则化训练损失。

[57]:
model = TCNModel(
    input_chunk_length=24,
    output_chunk_length=12,
    random_state=42,
    likelihood=LaplaceLikelihood(prior_b=0.1),
)

model.fit(train_air_scaled, epochs=400, verbose=True);
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs

  | Name            | Type             | Params | Mode
-------------------------------------------------------------
0 | criterion       | MSELoss          | 0      | train
1 | train_criterion | MSELoss          | 0      | train
2 | val_criterion   | MSELoss          | 0      | train
3 | train_metrics   | MetricCollection | 0      | train
4 | val_metrics     | MetricCollection | 0      | train
5 | res_blocks      | ModuleList       | 166    | train
-------------------------------------------------------------
166       Trainable params
0         Non-trainable params
166       Total params
0.001     Total estimated model params size (MB)
`Trainer.fit` stopped: `max_epochs=400` reached.
[58]:
pred = model.predict(n=36, num_samples=500)

# scale back:
pred = scaler.inverse_transform(pred)

series_air.plot()
pred.plot()
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
Only 1 TimeSeries (lists) were provided which is lower than the number of series (n=2) used to fit Scaler. This can result in a mismatch between the series and the underlying transformers.
[58]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_126_4.png

默认情况下,TimeSeries.plot() 显示中位数以及第5和第95百分位数(如果 TimeSeries 是多元的,则为边际分布)。可以控制这一点:

[59]:
pred.plot(low_quantile=0.01, high_quantile=0.99, label="1-99th percentiles")
pred.plot(low_quantile=0.2, high_quantile=0.8, label="20-80th percentiles")
[59]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_128_1.png

分布类型

似然函数必须与时间序列值的域兼容。例如,PoissonLikelihood 可以用于离散正数值,ExponentialLikelihood 可以用于实正数值,而 BetaLikelihood 可以用于 \((0,1)\) 范围内的实数值。

也可以使用 QuantileRegression 来应用分位数损失并直接拟合一些所需的分位数。

评估概率预测

我们如何评估概率预测的质量?默认情况下,大多数指标函数(如 mape())将继续工作,但只查看中位数预测。也可以使用均值分位数损失指标 mql(),它量化了每个预测分位数的误差。对于分位数=0.5(中位数),它与平均绝对误差(MAE)相同:

[60]:
from darts.metrics import mql, mae

print(f"MAPE of median forecast: {mape(series_air, pred):.2f}")
print(f"MAE of median forecast: {mae(series_air, pred):.2f}")
for q in [0.05, 0.1, 0.5, 0.9, 0.95]:
    q_loss = mql(series_air, pred, q=q)
    print(f"quantile loss at quantile {q:.2f}: {q_loss:.2f}")
MAPE of median forecast: 12.16
MAE of median forecast: 51.73
quantile loss at quantile 0.05: 5.36
quantile loss at quantile 0.10: 13.24
quantile loss at quantile 0.50: 51.73
quantile loss at quantile 0.90: 20.74
quantile loss at quantile 0.95: 12.20

使用分位数损失

我们能否通过直接拟合这些分位数来做得更好?我们可以直接使用 QuantileRegression 似然:

[61]:
from darts.utils.likelihood_models import QuantileRegression

model = TCNModel(
    input_chunk_length=24,
    output_chunk_length=12,
    random_state=42,
    likelihood=QuantileRegression([0.05, 0.1, 0.5, 0.9, 0.95]),
)

model.fit(train_air_scaled, epochs=400, verbose=True);
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs

  | Name            | Type             | Params | Mode
-------------------------------------------------------------
0 | criterion       | MSELoss          | 0      | train
1 | train_criterion | MSELoss          | 0      | train
2 | val_criterion   | MSELoss          | 0      | train
3 | train_metrics   | MetricCollection | 0      | train
4 | val_metrics     | MetricCollection | 0      | train
5 | res_blocks      | ModuleList       | 208    | train
-------------------------------------------------------------
208       Trainable params
0         Non-trainable params
208       Total params
0.001     Total estimated model params size (MB)
`Trainer.fit` stopped: `max_epochs=400` reached.
[62]:
pred = model.predict(n=36, num_samples=500)

# scale back:
pred = scaler.inverse_transform(pred)

series_air.plot()
pred.plot()

print(f"MAPE of median forecast: {mape(series_air, pred):.2f}")
for q in [0.05, 0.1, 0.5, 0.9, 0.95]:
    q_loss = mql(series_air, pred, q=q)
    print(f"quantile loss at quantile {q:.2f}: {q_loss:.2f}")
GPU available: False, used: False
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
Only 1 TimeSeries (lists) were provided which is lower than the number of series (n=2) used to fit Scaler. This can result in a mismatch between the series and the underlying transformers.
MAPE of median forecast: 5.13
quantile loss at quantile 0.05: 9.27
quantile loss at quantile 0.10: 14.12
quantile loss at quantile 0.50: 20.85
quantile loss at quantile 0.90: 11.85
quantile loss at quantile 0.95: 7.14
../_images/quickstart_00-quickstart_133_4.png

使用回归模型

我们为 RegressionModels 提供的概率支持与神经网络类似。在创建模型时,我们必须指定一个 likelihood。除了提供一个 likelihood 对象外,我们还可以简单地选择 "quantile"``(带有某些 ``quantiles)和 "poisson" 中的一种。

[63]:
model = LinearRegressionModel(
    lags=24,
    lags_future_covariates=[0],
    likelihood="quantile",
    quantiles=[0.05, 0.1, 0.5, 0.9, 0.95],
)
model.fit(train_air, future_covariates=air_covs)
pred = model.predict(n=36, num_samples=500)

series_air.plot()
pred.plot()

print(f"MAPE of median forecast: {mape(series_air, pred):.2f}")
for q in [0.05, 0.1, 0.5, 0.9, 0.95]:
    q_loss = mql(series_air, pred, q=q)
    print(f"quantile loss at quantile {q:.2f}: {q_loss:.2f}")
MAPE of median forecast: 8.48
quantile loss at quantile 0.05: 20.20
quantile loss at quantile 0.10: 25.45
quantile loss at quantile 0.50: 35.19
quantile loss at quantile 0.90: 11.30
quantile loss at quantile 0.95: 6.25
../_images/quickstart_00-quickstart_135_1.png

模型集成

集成 是指将多个模型生成的预测结果结合起来,以获得一个最终的 - 并且希望是更好的预测结果。

例如,在我们上面的 一个不那么天真的模型 的例子中,我们手动将一个天真的季节性模型与一个天真的漂移模型结合起来。在这里,我们将展示如何自动组合模型预测——天真地使用 NaiveEnsembleModel,或通过 RegressionEnsembleModel 学习。

当然,也可以在集成模型中使用 past 和/或 future_covariates,但它们只会传递给支持这些功能的预测模型。

朴素集成

朴素集成只是取多个模型的预测平均值。Darts 的 NaiveEnsembleModel 正是这样做的,并且通过与预测模型相同的 API(拟合、预测、历史预测、回测等)来实现。

[64]:
from darts.models import NaiveEnsembleModel

models = [NaiveDrift(), NaiveSeasonal(12)]

ensemble_model = NaiveEnsembleModel(forecasting_models=models)

backtest = ensemble_model.historical_forecasts(**hfc_params)

print(f"MAPE = {mape(backtest, series_air):.2f}%")
series_air.plot()
backtest.plot()
MAPE = 11.93%
[64]:
<Axes: xlabel='time'>
../_images/quickstart_00-quickstart_138_3.png

学习集成

如预期,在这种情况下,简单的集成并没有给出很好的结果(尽管在某些情况下它可能会!)

如果我们把集成学习看作是一个监督回归问题,有时可以做得更好:给定一组预测(特征),找到一个模型,将它们结合起来以最小化目标上的误差。这就是 RegressionEnsembleModel 所做的。它接受参数:

  • forecasting_models 是我们想要进行集成预测的预测模型列表。

  • regression_train_n_points 是用于拟合“集成回归”模型(即,结合预测的内部模型)的时间步数。

  • regression_model 是可选的,可以是与 sklearn 兼容的回归模型或用于集成回归的 Darts RegressionModel。如果未指定,则使用线性回归。使用 sklearn 模型可以开箱即用,但使用 RegressionModel 可以潜在地将各个预测的任意滞后作为回归模型的输入。

  • 更多内容,请阅读我们的这个 集成模型用户指南

一旦这些元素就位,RegressionEnsembleModel 就可以像常规的预测模型一样使用:

[65]:
from darts.models import RegressionEnsembleModel
ensemble_model = RegressionEnsembleModel(
    forecasting_models=models, regression_train_n_points=12
)

backtest = ensemble_model.historical_forecasts(**hfc_params)

print(f"MAPE = {mape(backtest, series_air):.2f}")
series_air.plot()
backtest.plot()
MAPE = 4.77
[65]:
<Axes: xlabel='time'>
../_images/quickstart_00-quickstart_140_3.png

我们还可以检查用于在线性组合中加权两个内部模型的系数:

[66]:
ensemble_model.fit(series_air)
ensemble_model.regression_model.model.coef_
[66]:
array([0.01368849, 1.0980105 ], dtype=float32)

集成模型本身也可以是概率性的!你可以在 我们的集成模型指南 中阅读相关内容。

RegressionEnsembleModel 使用 堆叠 技术来训练和组合 forecasting_models:它们各自独立训练,然后使用它们的预测作为 future_covariates 来训练 regression_model

过滤模型

除了*预测*模型,能够预测序列的未来值,Darts还包含一些有用的*过滤*模型,可以建模样本内序列值的分布。

拟合卡尔曼滤波器

KalmanFilter 实现了一个 卡尔曼滤波器。该实现依赖于 nfoursid,因此可以提供一个包含转换矩阵、过程噪声协方差、观测噪声协方差等的 nfoursid.kalman.Kalman 对象。

也可以通过调用 fit() 来使用 N4SID 系统识别算法“训练”卡尔曼滤波器进行系统识别:

[67]:
from darts.models import KalmanFilter

kf = KalmanFilter(dim_x=3)
kf.fit(train_air_scaled)
filtered_series = kf.filter(train_air_scaled, num_samples=100)

train_air_scaled.plot()
filtered_series.plot()
[67]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_146_1.png

使用高斯过程推断缺失值

Darts 还包含一个 GaussianProcessFilter,可用于序列的概率建模:

[68]:
from darts.models import GaussianProcessFilter
from sklearn.gaussian_process.kernels import RBF

# create a series with holes:
values = train_air_scaled.values()
values[20:22] = np.nan
values[28:32] = np.nan
values[55:59] = np.nan
values[72:80] = np.nan
series_holes = TimeSeries.from_times_and_values(train_air_scaled.time_index, values)
series_holes.plot()

kernel = RBF()

gpf = GaussianProcessFilter(kernel=kernel, alpha=0.1, normalize_y=True)
filtered_series = gpf.filter(series_holes, num_samples=100)

filtered_series.plot()
[68]:
<Axes: xlabel='Month'>
../_images/quickstart_00-quickstart_148_1.png

警告

那么,基于牛奶生产的N-BEATS、指数平滑或贝叶斯岭回归是预测未来航空公司乘客数量的最佳方法吗?嗯,在这一点上,实际上很难确切地说哪一个是最好的。我们的时间序列很小,我们的验证集甚至更小。在这种情况下,很容易将整个预测练习过度拟合到如此小的验证集上。如果可用模型的数量及其自由度很高(例如深度学习模型),或者如果我们在单个测试集上尝试了许多模型(如本笔记本中所做的),这一点尤其正确。

作为数据科学家,我们有责任了解我们的模型在多大程度上可以被信任。因此,始终对结果持保留态度,尤其是在小数据集上,并在进行任何预测之前应用科学方法 :) 祝建模愉快!

异常检测

Darts 还有一个关于时间序列异常检测的完整模块。更多信息,请阅读我们的 异常检测用户指南

[ ]: