循环神经网络模型

在这个笔记本中,我们展示了一个如何将RNNs与darts结合使用的示例。如果你是darts的新手,我们建议你首先跟随 快速开始 笔记本。

[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 torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import pandas as pd
import shutil
from sklearn.preprocessing import MinMaxScaler
from tqdm import tqdm_notebook as tqdm
import matplotlib.pyplot as plt

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

import warnings

warnings.filterwarnings("ignore")
import logging

logging.disable(logging.CRITICAL)

循环模型

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

RNNModel 是完全循环的,因为在预测时,输出是使用这些输入计算的:

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

  • 前一个隐藏状态

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

因此,具有预测范围 n 的预测是通过 RNNModel 预测的 n 次迭代创建的,并且需要知道 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("MAPE: {:.2f}%".format(mape(pred_series, val_transformed)))
    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