深度学习模型是时间序列预测的最新技术。在最近的大型竞赛(如 M 系列)中,它们已经超越了统计方法和基于树的方法,并且在工业界得到了越来越多的应用。然而,它们的性能受超参数选择的影响很大。选择最优配置(这个过程称为超参数调整)对于获得最佳性能至关重要。

超参数调整的主要步骤包括

  1. 定义训练集和验证集。
  2. 定义搜索空间。
  3. 使用搜索算法采样配置,训练模型,并在验证集上进行评估。
  4. 选择并保存最佳模型。

通过 Neuralforecast,我们使用 Auto 模型自动化和简化了超参数调整过程。库中的每个模型都有一个 Auto 版本(例如,AutoNHITSAutoTFT),可以在默认或用户定义的搜索空间上执行自动超参数选择。

Auto 模型可以使用两个后端:Ray 的 Tune 库和 Optuna,它们提供了用户友好和简化的 API,并包含了它们的大部分功能。

在本教程中,我们将详细展示如何使用 TuneOptuna 后端实例化和训练具有自定义搜索空间的 AutoNHITS 模型,安装和使用 HYPEROPT 搜索算法,并使用最优超参数的模型进行预测。

您可以使用 Google Colab 的 GPU 运行这些实验。

1. 安装 Neuralforecast

# !pip install neuralforecast hyperopt

2. 加载数据

在此示例中,我们将使用 AirPasengers,这是一个流行的数据集,包含 1949 年至 1960 年间美国每月的航空公司乘客数量。加载 utils 方法中所需格式的数据。有关数据输入格式的更多详细信息,请参阅 https://nixtla.github.io/neuralforecast/examples/data_format.html

from neuralforecast.utils import AirPassengersDF

Y_df = AirPassengersDF
Y_df.head()
unique_iddsy
01.01949-01-31112.0
11.01949-02-28118.0
21.01949-03-31132.0
31.01949-04-30129.0
41.01949-05-31121.0

3. Ray 的 Tune 后端

首先,我们展示如何使用 Tune 后端。此后端基于 Ray 的 Tune 库,这是一个可扩展的超参数调整框架。它是机器学习社区中流行的库,被许多公司和研究实验室使用。如果您计划使用 Optuna 后端,可以跳过此部分。

3.a 定义超参数网格

每个 Auto 模型都包含一个默认搜索空间,该空间已在多个大型数据集上进行了广泛测试。搜索空间使用字典指定,其中键对应于模型的超参数,值是一个 Tune 函数,用于指定如何对超参数进行采样。例如,使用 randint 均匀采样整数,使用 choice 采样列表中的值。

3.a.1 默认超参数网格

可以通过 Auto 模型的 get_default_config 函数访问默认搜索空间字典。如果您希望使用默认参数配置但想更改一个或多个超参数空间而不更改其他默认值,这会很有用。

要提取默认配置,您需要定义: * h: 预测范围。 * backend: 要使用的后端。 * n_series: 可选,唯一时间序列的数量,仅适用于多变量模型。

在此示例中,我们将使用 h=12 并使用 ray 作为后端。我们将使用默认超参数空间,但仅更改 random_seed 范围和 n_pool_kernel_size

from ray import tune
from neuralforecast.auto import AutoNHITS

nhits_config = AutoNHITS.get_default_config(h = 12, backend="ray")                      # Extract the default hyperparameter settings
nhits_config["random_seed"] = tune.randint(1, 10)                                       # Random seed
nhits_config["n_pool_kernel_size"] = tune.choice([[2, 2, 2], [16, 8, 1]])               # MaxPool's Kernelsize

3.a.2 自定义超参数网格

更一般地,用户可以通过完全指定超参数搜索空间字典来定义针对特定数据集和任务量身定制的完全自定义搜索空间。

在以下示例中,我们优化了 learning_rate 和两个 NHITS 特定超参数:n_pool_kernel_sizen_freq_downsample。此外,我们还使用搜索空间修改了默认超参数,例如 max_stepsval_check_steps

nhits_config = {
       "max_steps": 100,                                                         # Number of SGD steps
       "input_size": 24,                                                         # Size of input window
       "learning_rate": tune.loguniform(1e-5, 1e-1),                             # Initial Learning rate
       "n_pool_kernel_size": tune.choice([[2, 2, 2], [16, 8, 1]]),               # MaxPool's Kernelsize
       "n_freq_downsample": tune.choice([[168, 24, 1], [24, 12, 1], [1, 1, 1]]), # Interpolation expressivity ratios
       "val_check_steps": 50,                                                    # Compute validation every 50 steps
       "random_seed": tune.randint(1, 10),                                       # Random seed
    }

重要提示

配置字典在不同模型之间不可互换,因为它们具有不同的超参数。有关每个模型超参数的完整列表,请参阅 https://nixtla.github.io/neuralforecast/models.html

3.b 实例化 Auto 模型

要实例化一个 Auto 模型,您需要定义

  • h: 预测范围。
  • loss: 来自 neuralforecast.losses.pytorch 的训练和验证损失函数。
  • config: 超参数搜索空间。如果为 NoneAuto 类将使用预定义的建议超参数空间。
  • search_alg: 搜索算法(来自 tune.search),默认为随机搜索。有关不同搜索算法选项的更多信息,请参阅 https://docs.rayai.org.cn/en/latest/tune/api_docs/suggestion.html
  • backend: 要使用的后端,默认为 ray。如果为 optunaAuto 类将使用 Optuna 后端。
  • num_samples: 探索的配置数量。

在此示例中,我们将预测范围 h 设置为 12,使用 MAE 损失进行训练和验证,并使用 HYPEROPT 搜索算法。

from ray.tune.search.hyperopt import HyperOptSearch
from neuralforecast.losses.pytorch import MAE
from neuralforecast.auto import AutoNHITS
model = AutoNHITS(h=12,
                  loss=MAE(),
                  config=nhits_config,
                  search_alg=HyperOptSearch(),
                  backend='ray',
                  num_samples=10)

提示

样本数量 num_samples 是一个关键参数!较大的值通常会产生更好的结果,因为我们在搜索空间中探索了更多的配置,但这会增加训练时间。较大的搜索空间通常需要更多的样本。一般来说,我们建议将 num_samples 设置为高于 20。在此示例中,我们为演示目的设置了 10。

3.c 使用 Core 类训练模型和进行预测

接下来,我们使用 Neuralforecast 类训练 Auto 模型。在此步骤中,Auto 模型将自动执行超参数调整,训练多个具有不同超参数的模型,在验证集上生成预测,并评估它们。最佳配置根据验证集上的误差进行选择。仅存储并在推理期间使用最佳模型。

from neuralforecast import NeuralForecast

使用 fit 方法的 val_size 参数控制验证集的长度。在这种情况下,我们将验证集设置为预测范围的两倍。

nf = NeuralForecast(models=[model], freq='M')
nf.fit(df=Y_df, val_size=24)
Global seed set to 8

超参数调整的结果可在 Auto 模型的 results 属性中获取。使用 get_dataframe 方法将结果获取为 pandas 数据框。

results = nf.models[0].results.get_dataframe()
results.head()
losstime_this_iter_sdonetimesteps_totalepisodes_totaltraining_iterationtrial_idexperiment_id日期时间戳config/input_sizeconfig/learning_rateconfig/lossconfig/max_stepsconfig/n_freq_downsampleconfig/n_pool_kernel_sizeconfig/random_seedconfig/val_check_stepsconfig/valid_losslogdir
021.1732043.645993FalseNaNNaN2e20dbd9bf62650f116914e18889bb96963c6b2022023-10-03_11-19-141696346354240.000415MAE()100[168, 24, 1][16, 8, 1]750MAE()/Users/cchallu/ray_results/_train_tune_2023-10…
133.8434263.756614FalseNaNNaN275e09199f62650f116914e18889bb96963c6b2022023-10-03_11-19-221696346362240.000068MAE()100[24, 12, 1][16, 8, 1]450MAE()/Users/cchallu/ray_results/_train_tune_2023-10…
217.7502808.573898FalseNaNNaN20dc5925af62650f116914e18889bb96963c6b2022023-10-03_11-19-361696346376240.001615MAE()100[1, 1, 1][2, 2, 2]850MAE()/Users/cchallu/ray_results/_train_tune_2023-10…
324.5730556.987517FalseNaNNaN2352e03fff62650f116914e18889bb96963c6b2022023-10-03_11-19-501696346390240.003405MAE()100[1, 1, 1][2, 2, 2]550MAE()/Users/cchallu/ray_results/_train_tune_2023-10…
4474221.9375004.912362FalseNaNNaN2289bdd5ef62650f116914e18889bb96963c6b2022023-10-03_11-20-001696346400240.080117MAE()100[168, 24, 1][16, 8, 1]550MAE()/Users/cchallu/ray_results/_train_tune_2023-10…

接下来,我们使用 predict 方法使用最优超参数预测未来 12 个月。

Y_hat_df = nf.predict()
Y_hat_df = Y_hat_df.reset_index()
Y_hat_df.head()
Predicting DataLoader 0: 100%|██████████| 1/1 [00:00<00:00, 113.97it/s]
unique_iddsAutoNHITS
01.01961-01-31442.346680
11.01961-02-28439.409821
21.01961-03-31477.709930
31.01961-04-30503.884064
41.01961-05-31521.344421

4. Optuna 后端

在本节中,我们将展示如何使用 Optuna 后端。Optuna 是一个轻量级且多功能的超参数优化平台。如果您计划使用 Tune 后端,可以跳过此部分。

4.a 定义超参数网格

每个 Auto 模型都包含一个默认搜索空间,该空间已在多个大型数据集上进行了广泛测试。搜索空间通过一个返回字典的函数指定,其中键对应于模型的超参数,值是一个 suggest 函数,用于指定如何对超参数进行采样。例如,使用 suggest_int 均匀采样整数,使用 suggest_categorical 采样列表中的值。有关更多详细信息,请参阅 https://docs.optuna.cn/en/stable/reference/generated/optuna.trial.Trial.html

4.a.1 默认超参数网格

可以通过 Auto 模型的 get_default_config 函数访问默认搜索空间字典。如果您希望使用默认参数配置但想更改一个或多个超参数空间而不更改其他默认值,这会很有用。

要提取默认配置,您需要定义: * h: 预测范围。 * backend: 要使用的后端。 * n_series: 可选,唯一时间序列的数量,仅适用于多变量模型。

在此示例中,我们将使用 h=12 并使用 optuna 作为后端。我们将使用默认超参数空间,但仅更改 random_seed 范围和 n_pool_kernel_size

import optuna
optuna.logging.set_verbosity(optuna.logging.WARNING) # Use this to disable training prints from optuna
nhits_default_config = AutoNHITS.get_default_config(h = 12, backend="optuna")                   # Extract the default hyperparameter settings

def config_nhits(trial):
    config = {**nhits_default_config(trial)}
    config.update({
                   "random_seed": trial.suggest_int("random_seed", 1, 10), 
                   "n_pool_kernel_size": trial.suggest_categorical("n_pool_kernel_size", [[2, 2, 2], [16, 8, 1]])
                   })
    return config

3.a.2 自定义超参数网格

更一般地,用户可以通过完全指定超参数搜索空间函数来定义针对特定数据集和任务量身定制的完全自定义搜索空间。

在以下示例中,我们优化了 learning_rate 和两个 NHITS 特定超参数:n_pool_kernel_sizen_freq_downsample。此外,我们还使用搜索空间修改了默认超参数,例如 max_stepsval_check_steps

def config_nhits(trial):
    return {
        "max_steps": 100,                                                                                               # Number of SGD steps
        "input_size": 24,                                                                                               # Size of input window
        "learning_rate": trial.suggest_loguniform("learning_rate", 1e-5, 1e-1),                                         # Initial Learning rate
        "n_pool_kernel_size": trial.suggest_categorical("n_pool_kernel_size", [[2, 2, 2], [16, 8, 1]]),                 # MaxPool's Kernelsize
        "n_freq_downsample": trial.suggest_categorical("n_freq_downsample", [[168, 24, 1], [24, 12, 1], [1, 1, 1]]),    # Interpolation expressivity ratios
        "val_check_steps": 50,                                                                                          # Compute validation every 50 steps
        "random_seed": trial.suggest_int("random_seed", 1, 10),                                                         # Random seed
    }

4.b 实例化 Auto 模型

要实例化一个 Auto 模型,您需要定义

  • h: 预测范围。
  • loss: 来自 neuralforecast.losses.pytorch 的训练和验证损失函数。
  • config: 超参数搜索空间。如果为 NoneAuto 类将使用预定义的建议超参数空间。
  • search_alg: 搜索算法(来自 optuna.samplers),默认为 TPESampler (Tree-structured Parzen Estimator)。有关不同搜索算法选项的更多信息,请参阅 https://docs.optuna.cn/en/stable/reference/samplers/index.html
  • backend: 要使用的后端,默认为 ray。如果为 optunaAuto 类将使用 Optuna 后端。
  • num_samples: 探索的配置数量。
model = AutoNHITS(h=12,
                  loss=MAE(),
                  config=config_nhits,
                  search_alg=optuna.samplers.TPESampler(),
                  backend='optuna',
                  num_samples=10)

重要提示

TuneOptuna 的配置字典和搜索算法不可互换!请为每个后端使用适当类型的搜索算法和自定义配置字典。

4.c 使用 Core 类训练模型和进行预测

使用 fit 方法的 val_size 参数控制验证集的长度。在这种情况下,我们将验证集设置为预测范围的两倍。

nf = NeuralForecast(models=[model], freq='M')
nf.fit(df=Y_df, val_size=24)
Global seed set to 6
Global seed set to 6
Global seed set to 1
Global seed set to 1
Global seed set to 7
Global seed set to 4
Global seed set to 9
Global seed set to 8
Global seed set to 7
Global seed set to 7
Global seed set to 6

超参数调整的结果可在 Auto 模型的 results 属性中获取。使用 trials_dataframe 方法将结果获取为 pandas 数据框。

results = nf.models[0].results.trials_dataframe()
results.drop(columns='user_attrs_ALL_PARAMS')
编号开始时间完成时间持续时间params_learning_rateparams_n_freq_downsampleparams_n_pool_kernel_sizeparams_random_seed状态
002.964735e+012023-10-23 19:13:30.2517192023-10-23 19:13:33.0070860 days 00:00:02.7553670.000074[24, 12, 1][2, 2, 2]2COMPLETE
112.790444e+032023-10-23 19:13:33.0074832023-10-23 19:13:35.8230890 days 00:00:02.8156060.026500[24, 12, 1][2, 2, 2]10COMPLETE
222.193000e+012023-10-23 19:13:35.8236072023-10-23 19:13:38.5994140 days 00:00:02.7758070.000337[168, 24, 1][2, 2, 2]7COMPLETE
331.147799e+082023-10-23 19:13:38.6001492023-10-23 19:13:41.4403070 days 00:00:02.8401580.059274[1, 1, 1][16, 8, 1]5COMPLETE
442.140740e+012023-10-23 19:13:41.4408332023-10-23 19:13:44.1848600 days 00:00:02.7440270.000840[168, 24, 1][16, 8, 1]5COMPLETE
551.606544e+012023-10-23 19:13:44.1852912023-10-23 19:13:46.9456720 days 00:00:02.7603810.005477[1, 1, 1][16, 8, 1]8COMPLETE
661.301640e+042023-10-23 19:13:46.9461082023-10-23 19:13:49.8056330 days 00:00:02.8595250.056746[1, 1, 1][16, 8, 1]3COMPLETE
774.972713e+012023-10-23 19:13:49.8062782023-10-23 19:13:52.5771800 days 00:00:02.7709020.000021[24, 12, 1][2, 2, 2]9COMPLETE
882.138879e+012023-10-23 19:13:52.5776782023-10-23 19:13:55.3727920 days 00:00:02.7951140.007136[1, 1, 1][2, 2, 2]9COMPLETE
992.094145e+012023-10-23 19:13:55.3731492023-10-23 19:13:58.1250580 days 00:00:02.7519090.004655[1, 1, 1][2, 2, 2]6COMPLETE

接下来,我们使用 predict 方法使用最优超参数预测未来 12 个月。

Y_hat_df_optuna = nf.predict()
Y_hat_df_optuna = Y_hat_df_optuna.reset_index()
Y_hat_df_optuna.head()
Predicting DataLoader 0: 100%|██████████| 1/1 [00:00<00:00, 112.75it/s]
unique_iddsAutoNHITS
01.01961-01-31445.272858
11.01961-02-28469.633423
21.01961-03-31475.265289
31.01961-04-30483.228516
41.01961-05-31516.583496

5. 图表

最后,我们比较由 AutoNHITS 模型使用两个后端生成的预测结果。

import pandas as pd
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 1, figsize = (20, 7))
plot_df = pd.concat([Y_df, Y_hat_df]).reset_index()

plt.plot(plot_df['ds'], plot_df['y'], label='y')
plt.plot(plot_df['ds'], plot_df['AutoNHITS'], label='Ray')
plt.plot(Y_hat_df_optuna['ds'], Y_hat_df_optuna['AutoNHITS'], label='Optuna')

ax.set_title('AirPassengers Forecast', fontsize=22)
ax.set_ylabel('Monthly Passengers', fontsize=20)
ax.set_xlabel('Timestamp [t]', fontsize=20)
ax.legend(prop={'size': 15})
ax.grid()

参考文献