循环神经网络模型

在本 Notebook 中,我们将展示如何在 darts 中使用 RNN 的示例。如果您是 darts 的新手,我们建议您首先学习 快速入门 Notebook。

[1]:
# fix python path if working locally
from utils import fix_pythonpath_if_working_locally

fix_pythonpath_if_working_locally()
[2]:
%load_ext autoreload
%autoreload 2
%matplotlib inline
[3]:
import warnings

import matplotlib.pyplot as plt
import pandas as pd

from darts.dataprocessing.transformers import Scaler
from darts.datasets import AirPassengersDataset, SunspotsDataset
from darts.metrics import mape
from darts.models import BlockRNNModel, ExponentialSmoothing, RNNModel
from darts.utils.statistics import check_seasonality, plot_acf
from darts.utils.timeseries_generation import datetime_attribute_timeseries

warnings.filterwarnings("ignore")
import logging

logging.disable(logging.CRITICAL)

循环模型

Darts 包含两种循环预测模型类:RNNModelBlockRNNModel

RNNModel 是完全循环的,在预测时,输出是使用以下输入计算的:

  • 前一个目标值,对于第一次预测,它将被设置为最后一个已知目标值;对于所有其他预测,它将被设置为前一个预测值

  • 前一个隐藏状态

  • 当前的协变量(如果模型是使用协变量训练的)

因此,预测长度为 n 的预测需要进行 nRNNModel 预测迭代,并且需要已知 n 个未来的协变量。此模型适用于目标序列高度依赖于已知未来的协变量的预测问题。

BlockRNNModel 有一个循环编码器阶段,用于编码其输入,以及一个全连接神经网络解码器阶段,该阶段根据编码器阶段的最后一个隐藏状态生成长度为 output_chunk_length 的预测“块”。因此,此模型会生成“块”状预测,并且仅限于查看与输入目标序列具有相同时间索引的协变量。

航空旅客示例

这是一个高度依赖于协变量的数据集。知道月份可以告诉我们很多关于季节性成分的信息,而年份则决定了趋势成分的影响。这些协变量在未来都是已知的,因此 RNNModel 类是解决此问题的首选。

[4]:
# Read data:
series = AirPassengersDataset().load()

# Create training and validation sets:
train, val = series.split_after(pd.Timestamp("19590101"))

# Normalize the time series (note: we avoid fitting the transformer on the validation set)
transformer = Scaler()
train_transformed = transformer.fit_transform(train)
val_transformed = transformer.transform(val)
series_transformed = transformer.transform(series)

# create month and year covariate series
year_series = datetime_attribute_timeseries(
    pd.date_range(start=series.start_time(), freq=series.freq_str, periods=1000),
    attribute="year",
    one_hot=False,
)
year_series = Scaler().fit_transform(year_series)
month_series = datetime_attribute_timeseries(
    year_series, attribute="month", one_hot=True
)
covariates = year_series.stack(month_series)
cov_train, cov_val = covariates.split_after(pd.Timestamp("19590101"))

让我们训练一个 LSTM 神经网络。要改用普通 RNN 或 GRU,只需将 'LSTM' 分别替换为 'RNN''GRU'

[5]:
my_model = RNNModel(
    model="LSTM",
    hidden_dim=20,
    dropout=0,
    batch_size=16,
    n_epochs=300,
    optimizer_kwargs={"lr": 1e-3},
    model_name="Air_RNN",
    log_tensorboard=True,
    random_state=42,
    training_length=20,
    input_chunk_length=14,
    force_reset=True,
    save_checkpoints=True,
)

接下来,我们可以直接将整个 covariates 序列作为 future_covariates 参数提供给模型;模型将切分这些协变量,并仅使用训练预测目标序列 train_transformed 所需的部分

[6]:
my_model.fit(
    train_transformed,
    future_covariates=covariates,
    val_series=val_transformed,
    val_future_covariates=covariates,
    verbose=True,
)
[6]:
<darts.models.forecasting.rnn_model.RNNModel at 0x7f800691edc0>

查看验证集上的预测

使用“当前”模型 - 即训练过程结束时的模型

[7]:
def eval_model(model):
    pred_series = model.predict(n=26, future_covariates=covariates)
    plt.figure(figsize=(8, 5))
    series_transformed.plot(label="actual")
    pred_series.plot(label="forecast")
    plt.title(f"MAPE: {mape(pred_series, val_transformed):.2f}%")
    plt.legend()


eval_model(my_model)
../_images/examples_04-RNN-examples_12_1.png

使用在训练过程中获得的最佳模型(根据验证损失)

[8]:
best_model = RNNModel.load_from_checkpoint(model_name="Air_RNN", best=True)
eval_model(best_model)
../_images/examples_04-RNN-examples_14_1.png

回测

让我们回测我们的 RNN 模型,看看它在 6 个月预测期内的表现如何

[9]:
backtest_series = my_model.historical_forecasts(
    series_transformed,
    future_covariates=covariates,
    start=pd.Timestamp("19590101"),
    forecast_horizon=6,
    retrain=False,
    verbose=True,
)
[10]:
plt.figure(figsize=(8, 5))
series_transformed.plot(label="actual")
backtest_series.plot(label="backtest")
plt.legend()
plt.title("Backtest, starting Jan 1959, 6-months horizon")
print(
    "MAPE: {:.2f}%".format(
        mape(
            transformer.inverse_transform(series_transformed),
            transformer.inverse_transform(backtest_series),
        )
    )
)
MAPE: 2.71%
../_images/examples_04-RNN-examples_17_1.png

月度太阳黑子

现在让我们尝试一个更具挑战性的时间序列:自 1749 年以来的月度太阳黑子数量。首先,我们从数据构建时间序列,并检查其周期性。

[11]:
series_sunspot = SunspotsDataset().load()

series_sunspot.plot()
check_seasonality(series_sunspot, max_lag=240)
[11]:
(True, 125)
../_images/examples_04-RNN-examples_19_1.png
[12]:
plot_acf(series_sunspot, 125, max_lag=240)  # ~11 years seasonality
../_images/examples_04-RNN-examples_20_0.png
[13]:
train_sp, val_sp = series_sunspot.split_after(pd.Timestamp("19401001"))

transformer_sunspot = Scaler()
train_sp_transformed = transformer_sunspot.fit_transform(train_sp)
val_sp_transformed = transformer_sunspot.transform(val_sp)
series_sp_transformed = transformer_sunspot.transform(series_sunspot)
[14]:
my_model_sun = BlockRNNModel(
    model="GRU",
    input_chunk_length=125,
    output_chunk_length=36,
    hidden_dim=10,
    n_rnn_layers=1,
    batch_size=32,
    n_epochs=100,
    dropout=0.1,
    model_name="sun_GRU",
    nr_epochs_val_period=1,
    optimizer_kwargs={"lr": 1e-3},
    log_tensorboard=True,
    random_state=42,
    force_reset=True,
)

my_model_sun.fit(train_sp_transformed, val_series=val_sp_transformed, verbose=True)
[14]:
<darts.models.forecasting.block_rnn_model.BlockRNNModel at 0x7f80095963d0>

为了评估我们的模型,我们将模拟历史预测,预测期为 3 年,覆盖验证集。为了加快速度,我们只查看每第 10 个预测。为了进行比较,我们还将拟合一个指数平滑模型。

[15]:
# Compute the backtest predictions with the two models
pred_series = my_model_sun.historical_forecasts(
    series_sp_transformed,
    start=pd.Timestamp("19401001"),
    forecast_horizon=36,
    stride=10,
    retrain=False,
    last_points_only=True,
    verbose=True,
)

pred_series_ets = ExponentialSmoothing(seasonal_periods=120).historical_forecasts(
    series_sp_transformed,
    start=pd.Timestamp("19401001"),
    forecast_horizon=36,
    stride=10,
    retrain=True,
    last_points_only=True,
    verbose=True,
)
[16]:
val_sp_transformed.plot(label="actual")
pred_series.plot(label="our RNN")
pred_series_ets.plot(label="ETS")
plt.legend()
print("RNN MAPE:", mape(pred_series, val_sp_transformed))
print("ETS MAPE:", mape(pred_series_ets, val_sp_transformed))
RNN MAPE: 73.19010018398568
ETS MAPE: 116.63584309419007
../_images/examples_04-RNN-examples_25_1.png