超参数优化

深度学习模型是时间序列预测的最先进技术。它们在最近的大规模竞赛中,如 M 系列,超越了统计和树基方法,并且在工业中被越来越广泛地采用。然而,它们的性能受到超参数选择的重大影响。选择最佳配置的过程称为超参数调优,这对于实现最佳性能至关重要。

超参数调优的主要步骤是:

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

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

Auto 模型可以与两个后端一起使用:Ray 的 Tune 库和 Optuna,提供用户友好和简化的 API,并具备大部分功能。

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

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

在Colab中打开

1. 安装 Neuralforecast

%%capture
# !pip 安装 neuralforecast hyperopt

2. 加载数据

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

from neuralforecast.utils import AirPassengersDF

Y_df = AirPassengersDF
Y_df.head()
unique_id ds y
0 1.0 1949-01-31 112.0
1 1.0 1949-02-28 118.0
2 1.0 1949-03-31 132.0
3 1.0 1949-04-30 129.0
4 1.0 1949-05-31 121.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")                      # 提取默认的超参数设置
nhits_config["random_seed"] = tune.randint(1, 10)                                       # 随机种子
nhits_config["n_pool_kernel_size"] = tune.choice([[2, 2, 2], [16, 8, 1]])               # MaxPool 的核大小

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

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

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

nhits_config = {
       "max_steps": 100,                                                         # SGD步骤数
       "input_size": 24,                                                         # 输入窗口大小
       "learning_rate": tune.loguniform(1e-5, 1e-1),                             # 初始学习率
       "n_pool_kernel_size": tune.choice([[2, 2, 2], [16, 8, 1]]),               # MaxPool 的核大小
       "n_freq_downsample": tune.choice([[168, 24, 1], [24, 12, 1], [1, 1, 1]]), # 插值表达能力比率
       "val_check_steps": 50,                                                    # 每50步进行一次验证计算
       "random_seed": tune.randint(1, 10),                                       # 随机种子
    }
Important

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

3.b 实例化 Auto 模型

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

  • h: 预测的时间范围。
  • loss: 来自 neuralforecast.losses.pytorch 的训练和验证损失。
  • config: 超参数搜索空间。如果为 None,则 Auto 类将使用预定义的建议超参数空间。
  • search_alg: 搜索算法(来自 tune.search),默认是随机搜索。有关不同搜索算法选项的更多信息,请参阅 https://docs.ray.io/en/latest/tune/api_docs/suggestion.html。
  • backend: 要使用的后端,默认是 ray。如果是 optuna,则 Auto 类将使用 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)
Tip

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

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

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

from neuralforecast import NeuralForecast

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

%%capture
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()
loss time_this_iter_s done timesteps_total episodes_total training_iteration trial_id experiment_id date timestamp ... config/input_size config/learning_rate config/loss config/max_steps config/n_freq_downsample config/n_pool_kernel_size config/random_seed config/val_check_steps config/valid_loss logdir
0 21.173204 3.645993 False NaN NaN 2 e20dbd9b f62650f116914e18889bb96963c6b202 2023-10-03_11-19-14 1696346354 ... 24 0.000415 MAE() 100 [168, 24, 1] [16, 8, 1] 7 50 MAE() /Users/cchallu/ray_results/_train_tune_2023-10...
1 33.843426 3.756614 False NaN NaN 2 75e09199 f62650f116914e18889bb96963c6b202 2023-10-03_11-19-22 1696346362 ... 24 0.000068 MAE() 100 [24, 12, 1] [16, 8, 1] 4 50 MAE() /Users/cchallu/ray_results/_train_tune_2023-10...
2 17.750280 8.573898 False NaN NaN 2 0dc5925a f62650f116914e18889bb96963c6b202 2023-10-03_11-19-36 1696346376 ... 24 0.001615 MAE() 100 [1, 1, 1] [2, 2, 2] 8 50 MAE() /Users/cchallu/ray_results/_train_tune_2023-10...
3 24.573055 6.987517 False NaN NaN 2 352e03ff f62650f116914e18889bb96963c6b202 2023-10-03_11-19-50 1696346390 ... 24 0.003405 MAE() 100 [1, 1, 1] [2, 2, 2] 5 50 MAE() /Users/cchallu/ray_results/_train_tune_2023-10...
4 474221.937500 4.912362 False NaN NaN 2 289bdd5e f62650f116914e18889bb96963c6b202 2023-10-03_11-20-00 1696346400 ... 24 0.080117 MAE() 100 [168, 24, 1] [16, 8, 1] 5 50 MAE() /Users/cchallu/ray_results/_train_tune_2023-10...

5 rows × 29 columns

接下来,我们使用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_id ds AutoNHITS
0 1.0 1961-01-31 442.346680
1 1.0 1961-02-28 439.409821
2 1.0 1961-03-31 477.709930
3 1.0 1961-04-30 503.884064
4 1.0 1961-05-31 521.344421

4. Optuna 后端

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

4.a 定义超参数网格

每个 Auto 模型包含一个在多个大规模数据集上进行广泛测试的默认搜索空间。搜索空间是通过一个返回字典的函数来指定的,其中键对应于模型的超参数,值是一个 suggest 函数,用于指定超参数将如何被采样。例如,使用 suggest_int 来均匀采样整数,使用 suggest_categorical 来采样列表中的值。有关详细信息,请参见 https://optuna.readthedocs.io/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) # 使用此选项可禁用来自Optuna的训练打印信息。
nhits_default_config = AutoNHITS.get_default_config(h = 12, backend="optuna")                   # 提取默认超参数设置

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,                                                                                               # SGD步骤数
        "input_size": 24,                                                                                               # 输入窗口大小
        "learning_rate": trial.suggest_loguniform("learning_rate", 1e-5, 1e-1),                                         # 初始学习率
        "n_pool_kernel_size": trial.suggest_categorical("n_pool_kernel_size", [[2, 2, 2], [16, 8, 1]]),                 # MaxPool 的核大小
        "n_freq_downsample": trial.suggest_categorical("n_freq_downsample", [[168, 24, 1], [24, 12, 1], [1, 1, 1]]),    # 插值表达能力比率
        "val_check_steps": 50,                                                                                          # 每50步进行一次验证计算
        "random_seed": trial.suggest_int("random_seed", 1, 10),                                                         # 随机种子
    }

4.b 实例化 Auto 模型

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

  • h:预测视野。
  • loss:来自 neuralforecast.losses.pytorch 的训练和验证损失。
  • config:超参数搜索空间。如果为 NoneAuto 类将使用预定义的建议超参数空间。
  • search_alg:搜索算法(来自 optuna.samplers),默认是 TPESampler(树结构的 Parzen 估计器)。有关不同搜索算法选项的更多信息,请参阅 https://optuna.readthedocs.io/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)
Important

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

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

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

%%capture
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')
number value datetime_start datetime_complete duration params_learning_rate params_n_freq_downsample params_n_pool_kernel_size params_random_seed state
0 0 2.964735e+01 2023-10-23 19:13:30.251719 2023-10-23 19:13:33.007086 0 days 00:00:02.755367 0.000074 [24, 12, 1] [2, 2, 2] 2 COMPLETE
1 1 2.790444e+03 2023-10-23 19:13:33.007483 2023-10-23 19:13:35.823089 0 days 00:00:02.815606 0.026500 [24, 12, 1] [2, 2, 2] 10 COMPLETE
2 2 2.193000e+01 2023-10-23 19:13:35.823607 2023-10-23 19:13:38.599414 0 days 00:00:02.775807 0.000337 [168, 24, 1] [2, 2, 2] 7 COMPLETE
3 3 1.147799e+08 2023-10-23 19:13:38.600149 2023-10-23 19:13:41.440307 0 days 00:00:02.840158 0.059274 [1, 1, 1] [16, 8, 1] 5 COMPLETE
4 4 2.140740e+01 2023-10-23 19:13:41.440833 2023-10-23 19:13:44.184860 0 days 00:00:02.744027 0.000840 [168, 24, 1] [16, 8, 1] 5 COMPLETE
5 5 1.606544e+01 2023-10-23 19:13:44.185291 2023-10-23 19:13:46.945672 0 days 00:00:02.760381 0.005477 [1, 1, 1] [16, 8, 1] 8 COMPLETE
6 6 1.301640e+04 2023-10-23 19:13:46.946108 2023-10-23 19:13:49.805633 0 days 00:00:02.859525 0.056746 [1, 1, 1] [16, 8, 1] 3 COMPLETE
7 7 4.972713e+01 2023-10-23 19:13:49.806278 2023-10-23 19:13:52.577180 0 days 00:00:02.770902 0.000021 [24, 12, 1] [2, 2, 2] 9 COMPLETE
8 8 2.138879e+01 2023-10-23 19:13:52.577678 2023-10-23 19:13:55.372792 0 days 00:00:02.795114 0.007136 [1, 1, 1] [2, 2, 2] 9 COMPLETE
9 9 2.094145e+01 2023-10-23 19:13:55.373149 2023-10-23 19:13:58.125058 0 days 00:00:02.751909 0.004655 [1, 1, 1] [2, 2, 2] 6 COMPLETE

接下来,我们使用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_id ds AutoNHITS
0 1.0 1961-01-31 445.272858
1 1.0 1961-02-28 469.633423
2 1.0 1961-03-31 475.265289
3 1.0 1961-04-30 483.228516
4 1.0 1961-05-31 516.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()

参考文献

Give us a ⭐ on Github