先决条件

本指南假设您对 NeuralForecast 有深入了解。

我们强烈建议您先阅读“入门”和“NeuralForecast Map”教程!

此外,请参阅CONTRIBUTING 指南,了解如何为 NeuralForecast 贡献代码的基础知识。

引言

本教程面向希望向 NeuralForecast 库添加新模型的贡献者。该库现有模块负责深度学习模型的优化、训练、选择和评估。core 类简化了在任何数据集上构建完整管道的过程,无论是工业界还是学术界,都提供了诸如 fitpredict 之类的用户友好方法。

向 NeuralForecast 添加新模型比从头开始构建新的 PyTorch 模型更简单。您只需编写 forward 方法即可。

它具有以下额外优势

  • NeuralForecast 中现有模块已实现了深度学习模型的基本训练和评估方面。
  • 与 PyTorch-Lightning 和 Tune 库集成,可实现高效优化和分布式计算。
  • BaseModel 类提供了通用的优化组件,例如早停和学习率调度器。
  • Github 上安排了自动性能测试,以确保质量标准。
  • 用户可以轻松比较新模型与现有模型的性能和计算。
  • 有机会接触庞大的用户和贡献者社区。

示例:简化 MLP 模型

我们将通过一个示例来介绍如何添加当前MLP模型的简化版本,该版本不包含外生协变量。

在给定时间戳 ttMLP模型将使用最后 LL 个历史值 YtL:tY_{t-L:t} 作为输入,预测单变量目标时间序列的未来 hh 个值 Yt+1:t+hY_{t+1:t+h}。下图展示了模型的图示。

0. 前期准备

按照我们的贡献教程(点击此处)来设置您的开发环境。

以下是最重要步骤的简要列表

  1. 创建 neuralforecast 库的分支 (fork)。
  2. 将分支克隆到您的计算机。
  3. 设置一个环境,包含 neuralforecast 库、核心依赖项和 nbdev 包,以便在交互式笔记本中编写模型代码。

1. 继承基类 (BaseModel)

该库包含一个基模型类:BaseModel。使用类属性,我们可以使该模型递归或非递归,多元或单变量,或允许使用外生输入。

a. 采样过程

训练期间,基类从TimeSeriesLoader模块接收数据集的时间序列样本。BaseModel 模型将从随机时间戳开始,采样大小为 input_size+h 的单个窗口。

b. BaseModel 的超参数

熟悉基类中指定的超参数,包括 h(预测期)、input_size 以及诸如 learning_ratemax_steps 等优化超参数。以下列表介绍了与窗口采样相关的超参数

  • h (h):要预测的未来值的数量。
  • input_size (L):用作模型输入的历史值的数量。
  • batch_size (bs):训练期间加载器采样的时间序列数量。
  • valid_batch_size (v_bs):推理(验证和测试)期间加载器采样的时间序列数量。
  • windows_batch_size (w_bs):训练期间(从先前的时间序列中)采样的单个窗口数量,用于组成批次。
  • inference_windows_batch_size (i_bs):推理期间采样的单个窗口数量,用于形成每个批次。用于控制 GPU 内存。

c. 输入和输出批次形状

forward 方法接收一个字典形式的数据批次,包含以下键:

  • insample_y:时间序列的历史值。
  • insample_mask:指示时间序列可用值的掩码(可用为 1,缺失为 0)。
  • futr_exog:未来的外生协变量(如果存在)。
  • hist_exog:历史外生协变量(如果存在)。
  • stat_exog:静态外生协变量(如果存在)。

如果属性 MULTIVARIATE = False 已设置,下表显示了每个张量的形状

张量BaseModel
insample_y(w_bs, L, 1)
insample_mask(w_bs, L)
futr_exog(w_bs, L+h, n_f)
hist_exog(w_bs, L, n_h)
stat_exog(w_bs,n_s)

forward 函数应返回一个张量,其中包含每个窗口未来 h 个时间戳的预测结果。使用 loss 类的属性可以自动将输出解析为正确的形状(参见下面的示例)。

提示

由于我们使用 nbdev,您可以轻松地在代码中添加打印语句,并在训练期间查看张量的形状。

d. BaseModel 的方法

BaseModel 类包含所有基于窗口的模型的几个常用方法,通过防止代码重复简化了新模型的开发。该类最重要的方法是:

  • _create_windows:将TimeSeriesLoader中的时间序列解析为大小为 input_size+h 的单个窗口。
  • _normalization:根据 scaler 类型规范化每个窗口。
  • _inv_normalization:对预测结果进行逆规范化。
  • training_step:模型的训练步骤,在训练期间由 PyTorch-Lightning 的 Trainer 类调用(fit 方法)。
  • validation_step:模型的验证步骤,在验证期间由 PyTorch-Lightning 的 Trainer 类调用。
  • predict_step:模型的预测步骤,在推理期间由 PyTorch-Lightning 的 Trainer 类调用(predict 方法)。

2. 创建模型文件和类

熟悉 BaseModel 类的基础知识后,下一步是创建您的特定模型。

主要步骤如下

  1. nbs 文件夹 (https://github.com/Nixtla/neuralforecast/tree/main/nbs) 中创建文件。应命名为 models.YOUR_MODEL_NAME.ipynb
  2. 添加 nbdev 文件的头部。
  3. 在文件中导入库。
  4. 定义 __init__ 方法,包含模型继承的特有超参数,并实例化架构。
  5. 设置以下模型属性
    • EXOGENOUS_FUTR:模型是否可以处理未来的外生变量(True 为是,False 为否)
    • EXOGENOUS_HIST:模型是否可以处理历史的外生变量(True 为是,False 为否)
    • EXOGENOUS_STAT:模型是否可以处理静态的外生变量(True 为是,False 为否)
    • MULTIVARIATE:如果模型产生多元预测(True 为是)或单变量预测(False 为否)
    • RECURRENT:如果模型递归地生成预测(True 为是)或直接生成预测(False 为否)
  6. 定义 forward 方法,该方法接收输入批次字典并返回预测结果。

a. 模型类

首先,在 nbdev 文件的顶部添加以下两个单元格

#| default_exp models.mlp

重要

mlp 更改为您的模型名称,使用小写字母和下划线。稍后运行 nbdev_export 时,它将在 neuralforecast/models/ 目录中创建一个 YOUR_MODEL.py 脚本。

#| hide
%load_ext autoreload
%autoreload 2

接下来,添加模型的依赖项。

#| export
from typing import Optional

import torch
import torch.nn as nn

from neuralforecast.losses.pytorch import MAE
from neuralforecast.common._base_model import BaseModel

提示

不要忘记在此单元格上添加 #| export 标签。

接下来,创建包含 initforward 方法的类。以下示例展示了简化的MLP模型。我们将在代码之后解释重要细节。

#| export
class MLP(BaseModel): # <<---- Inherits from BaseModel
    # Set class attributes to determine this model's characteristics
    EXOGENOUS_FUTR = False   # If the model can handle future exogenous variables
    EXOGENOUS_HIST = False   # If the model can handle historical exogenous variables
    EXOGENOUS_STAT = False   # If the model can handle static exogenous variables
    MULTIVARIATE = False    # If the model produces multivariate forecasts (True) or univariate (False)
    RECURRENT = False       # If the model produces forecasts recursively (True) or direct (False)

    def __init__(self,
                 # Inhereted hyperparameters with no defaults
                 h,
                 input_size,
                 # Model specific hyperparameters
                 num_layers = 2,
                 hidden_size = 1024,
                 # Inhereted hyperparameters with defaults
                 futr_exog_list = None,
                 hist_exog_list = None,
                 stat_exog_list = None,                 
                 exclude_insample_y = False,
                 loss = MAE(),
                 valid_loss = None,
                 max_steps: int = 1000,
                 learning_rate: float = 1e-3,
                 num_lr_decays: int = -1,
                 early_stop_patience_steps: int =-1,
                 val_check_steps: int = 100,
                 batch_size: int = 32,
                 valid_batch_size: Optional[int] = None,
                 windows_batch_size = 1024,
                 inference_windows_batch_size = -1,
                 start_padding_enabled = False,
                 step_size: int = 1,
                 scaler_type: str = 'identity',
                 random_seed: int = 1,
                 drop_last_loader: bool = False,
                 optimizer = None,
                 optimizer_kwargs = None,
                 lr_scheduler = None,
                 lr_scheduler_kwargs = None,
                 dataloader_kwargs = None,
                 **trainer_kwargs):
    # Inherit BaseWindows class
    super(MLP, self).__init__(h=h,
                              input_size=input_size,
                              ..., # <<--- Add all inhereted hyperparameters
                              random_seed=random_seed,
                              **trainer_kwargs)

    # Architecture
    self.num_layers = num_layers
    self.hidden_size = hidden_size

    # MultiLayer Perceptron
    layers = [nn.Linear(in_features=input_size, out_features=hidden_size)]
    layers += [nn.ReLU()]
    for i in range(num_layers - 1):
        layers += [nn.Linear(in_features=hidden_size, out_features=hidden_size)]
        layers += [nn.ReLU()]
    self.mlp = nn.ModuleList(layers)

    # Adapter with Loss dependent dimensions
    self.out = nn.Linear(in_features=hidden_size, 
                         out_features=h * self.loss.outputsize_multiplier) ## <<--- Use outputsize_multiplier to adjust output size

    def forward(self, windows_batch): # <<--- Receives windows_batch dictionary
        # Parse windows_batch
        insample_y = windows_batch['insample_y'].squeeze(-1)                            # [batch_size, input_size]
        # MLP
        hidden = self.mlp(insample_y)                                                   # [batch_size, hidden_size]
        y_pred = self.out(hidden)                                                       # [batch_size, h * n_outputs]
        
        # Reshape
        y_pred = y_pred.reshape(batch_size, self.h, self.loss.outputsize_multiplier)    # [batch_size, h, n_outputs]

        return y_pred

提示

  • 不要忘记在每个单元格上添加 #| export 标签。
  • 更大的架构,例如 Transformers,可能需要通过使用中间函数来拆分 forward 方法。

重要说明

基类有许多超参数,模型必须为所有超参数设置默认值(hinput_size 除外)。如果您不确定使用什么默认值,我们建议从现有模型中复制大多数优化和采样超参数的默认值。您可以随时更改默认值。

forward 步骤末尾的 reshape 方法用于调整输出形状。loss 类包含一个 outputsize_multiplier 属性,可根据 loss 自动调整预测输出大小。例如,对于多分位数损失 (MQLoss),模型需要为每个预测期输出每个分位数。

b. 测试和文档

nbdev 允许在开发过程中测试和记录模型。它允许用户在笔记本中迭代开发,并在同一环境中测试代码。请参阅现有模型,例如此处的完整 MLP 模型。这些文件已包含开发过程中使用的测试、文档和使用示例。

c. 使用 nbdev 将新模型导出到库

按照 CONTRIBUTING 指南,下一步是将新模型从开发笔记本导出到包含实际脚本的 neuralforecast 文件夹。

要导出模型,请在终端中运行 nbdev_export。您应该在 neuralforecast/models/ 文件夹中看到包含您模型的新文件。

3. Core 类和其他文件

最后,将模型添加到 core 类和其他文件

  1. 手动将模型添加到以下init 文件中。

  2. 使用此处nbdev 文件将模型添加到 core 类中

    1. 将模型添加到初始模型列表
    from neuralforecast.models import (
    GRU, LSTM, RNN, TCN, DilatedRNN,
    MLP, NHITS, NBEATS, NBEATSx,
    TFT, VanillaTransformer,
    Informer, Autoformer, FEDformer,
    StemGNN, PatchTST
    )
    
    1. 将模型添加到 MODEL_FILENAME_DICT 字典(用于 saveload 函数)。

4. 将模型添加到文档

将模型添加到必要的文档页面非常重要,以便所有人都能找到文档

  1. 将模型添加到模型概述表中。
  2. 将模型添加到 API 参考的侧边栏
  3. 将模型添加到mint.json

5. 上传到 GitHub

恭喜!按照上述步骤,模型已可用于库中。

按照我们的贡献指南的最后步骤将模型上传到 GitHub:点击此处

其中一位维护者将审查 PR,如有必要会请求更改,并将其合并到库中。

快速清单

  • 熟悉 BaseModel 类的超参数以及 forward 方法的输入/输出形状。
  • nbs 文件夹中创建包含您的模型类的笔记本:models.YOUR_MODEL_NAME.ipynb
  • 添加头部和导入库。
  • 实现 initforward 方法并设置类属性。
  • 使用 nbdev_export 导出模型。
  • 将模型添加到此init 文件中。
  • 在此处将模型添加到core
  • 按照 CONTRIBUTING 指南创建 PR 以上传模型。