长周期预测具有挑战性,因为预测结果具有波动性计算复杂度高。为了解决这个问题,我们创建了 NHITS 模型,并在 NeuralForecast 库中提供了代码。NHITS 通过分层插值和多速率输入处理,使其部分输出专注于时间序列的不同频率。

在本 notebook 中,我们将展示如何在 ETTm2 基准数据集上使用 NHITS。该数据集包含 2 个变电站的 2 个变压器的数据点,包括负载、油温等信息。

我们将向您展示如何加载数据、训练模型并执行自动超参数调优,以实现 SoTA 性能,其计算成本仅为最新 Transformer 架构的一小部分(快 50 倍),但性能甚至超越它们。

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

1. 安装 NeuralForecast

!pip install neuralforecast datasetsforecast

2. 加载 ETTm2 数据

LongHorizon 类会自动下载完整的 ETTm2 数据集并进行处理。

它返回三个 Dataframe:Y_df 包含目标变量的值,X_df 包含外部日历特征,S_df 包含每个时间序列的静态特征(ETTm2 中没有)。在此示例中,我们将仅使用 Y_df

如果您想使用自己的数据,只需替换 Y_df 即可。请确保使用长格式,并与我们的数据集具有相似的结构。

import pandas as pd
from datasetsforecast.long_horizon import LongHorizon

# Change this to your own data to try the model
Y_df, _, _ = LongHorizon.load(directory='./', group='ETTm2')
Y_df['ds'] = pd.to_datetime(Y_df['ds'])

# For this excercise we are going to take 20% of the DataSet
n_time = len(Y_df.ds.unique())
val_size = int(.2 * n_time)
test_size = int(.2 * n_time)

Y_df.groupby('unique_id').head(2)
unique_iddsy
0HUFL2016-07-01 00:00:00-0.041413
1HUFL2016-07-01 00:15:00-0.185467
57600HULL2016-07-01 00:00:000.040104
57601HULL2016-07-01 00:15:00-0.214450
115200LUFL2016-07-01 00:00:000.695804
115201LUFL2016-07-01 00:15:000.434685
172800LULL2016-07-01 00:00:000.434430
172801LULL2016-07-01 00:15:000.428168
230400MUFL2016-07-01 00:00:00-0.599211
230401MUFL2016-07-01 00:15:00-0.658068
288000MULL2016-07-01 00:00:00-0.393536
288001MULL2016-07-01 00:15:00-0.659338
345600OT2016-07-01 00:00:001.018032
345601OT2016-07-01 00:15:000.980124
import matplotlib.pyplot as plt

# We are going to plot the temperature of the transformer 
# and marking the validation and train splits
u_id = 'HUFL'
x_plot = pd.to_datetime(Y_df[Y_df.unique_id==u_id].ds)
y_plot = Y_df[Y_df.unique_id==u_id].y.values

x_val = x_plot[n_time - val_size - test_size]
x_test = x_plot[n_time - test_size]

fig = plt.figure(figsize=(10, 5))
fig.tight_layout()

plt.plot(x_plot, y_plot)
plt.xlabel('Date', fontsize=17)
plt.ylabel('HUFL [15 min temperature]', fontsize=17)

plt.axvline(x_val, color='black', linestyle='-.')
plt.axvline(x_test, color='black', linestyle='-.')
plt.text(x_val, 5, '  Validation', fontsize=12)
plt.text(x_test, 5, '  Test', fontsize=12)

plt.grid()

3. 超参数选择和预测

AutoNHITS 类将使用 Tune 库自动执行超参数调优,探索用户定义或默认的搜索空间。模型根据在验证集上的误差进行选择,然后存储最佳模型并在推理过程中使用。

AutoNHITS.default_config 属性包含一个建议的超参数空间。这里,我们按照论文中的超参数指定了一个不同的搜索空间。请注意,*1000 次随机梯度步*就足以实现 SoTA 性能。您可以随意尝试调整这个空间。

from ray import tune
from neuralforecast.auto import AutoNHITS
from neuralforecast.core import NeuralForecast
horizon = 96 # 24hrs = 4 * 15 min.

# Use your own config or AutoNHITS.default_config
nhits_config = {
       "learning_rate": tune.choice([1e-3]),                                     # Initial Learning rate
       "max_steps": tune.choice([1000]),                                         # Number of SGD steps
       "input_size": tune.choice([5 * horizon]),                                 # input_size = multiplier * horizon
       "batch_size": tune.choice([7]),                                           # Number of series in windows
       "windows_batch_size": tune.choice([256]),                                 # Number of windows in batch
       "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
       "activation": tune.choice(['ReLU']),                                      # Type of non-linear activation
       "n_blocks":  tune.choice([[1, 1, 1]]),                                    # Blocks per each 3 stacks
       "mlp_units":  tune.choice([[[512, 512], [512, 512], [512, 512]]]),        # 2 512-Layers per block for each stack
       "interpolation_mode": tune.choice(['linear']),                            # Type of multi-step interpolation
       "val_check_steps": tune.choice([100]),                                    # Compute validation every 100 epochs
       "random_seed": tune.randint(1, 10),
    }

提示

有关不同空间选项(例如列表和连续区间)的更多信息,请参阅 https://docs.rayai.org.cn/en/latest/tune/index.html

要实例化 AutoNHITS,您需要定义:

  • h: 预测范围 (forecasting horizon)
  • loss: 训练损失。使用 DistributionLoss 来生成概率预测。
  • config: 超参数搜索空间。如果为 NoneAutoNHITS 类将使用预定义的建议超参数空间。
  • num_samples: 探索的配置数量。
models = [AutoNHITS(h=horizon,
                    config=nhits_config, 
                    num_samples=5)]

通过实例化一个 NeuralForecast 对象并使用以下必需参数来拟合模型:

  • models: 模型列表。

  • freq: 表示数据频率的字符串。(参见 panda's 可用频率。)

cross_validation 方法允许您模拟多个历史预测,通过用 fitpredict 方法替换 for 循环,极大地简化了流程。

对于时间序列数据,交叉验证是通过在历史数据上定义一个滑动窗口并预测其后的时期来完成的。这种形式的交叉验证使我们能够在更广泛的时间实例范围内更好地估计模型的预测能力,同时保持训练集中的数据连续,这是我们的模型所要求的。

cross_validation 方法将使用验证集进行超参数选择,然后为测试集生成预测。

nf = NeuralForecast(
    models=models,
    freq='15min')

Y_hat_df = nf.cross_validation(df=Y_df, val_size=val_size,
                               test_size=test_size, n_windows=None)

4. 评估结果

AutoNHITS 类包含一个 results tune 属性,用于存储每个探索的配置信息。它包含验证损失和最佳验证超参数。

nf.models[0].results.get_best_result().config
{'learning_rate': 0.001,
 'max_steps': 1000,
 'input_size': 480,
 'batch_size': 7,
 'windows_batch_size': 256,
 'n_pool_kernel_size': [2, 2, 2],
 'n_freq_downsample': [24, 12, 1],
 'activation': 'ReLU',
 'n_blocks': [1, 1, 1],
 'mlp_units': [[512, 512], [512, 512], [512, 512]],
 'interpolation_mode': 'linear',
 'val_check_steps': 100,
 'random_seed': 8,
 'h': 96,
 'loss': MAE(),
 'valid_loss': MAE()}
y_true = Y_hat_df.y.values
y_hat = Y_hat_df['AutoNHITS'].values

n_series = len(Y_df.unique_id.unique())

y_true = y_true.reshape(n_series, -1, horizon)
y_hat = y_hat.reshape(n_series, -1, horizon)

print('Parsed results')
print('2. y_true.shape (n_series, n_windows, n_time_out):\t', y_true.shape)
print('2. y_hat.shape  (n_series, n_windows, n_time_out):\t', y_hat.shape)
Parsed results
2. y_true.shape (n_series, n_windows, n_time_out):   (7, 11425, 96)
2. y_hat.shape  (n_series, n_windows, n_time_out):   (7, 11425, 96)
fig, axs = plt.subplots(nrows=3, ncols=1, figsize=(10, 11))
fig.tight_layout()

series = ['HUFL','HULL','LUFL','LULL','MUFL','MULL','OT']
series_idx = 3

for idx, w_idx in enumerate([200, 300, 400]):
  axs[idx].plot(y_true[series_idx, w_idx,:],label='True')
  axs[idx].plot(y_hat[series_idx, w_idx,:],label='Forecast')
  axs[idx].grid()
  axs[idx].set_ylabel(series[series_idx]+f' window {w_idx}', 
                      fontsize=17)
  if idx==2:
    axs[idx].set_xlabel('Forecast Horizon', fontsize=17)
plt.legend()
plt.show()
plt.close()

最后,我们计算两个关注指标的测试误差:

MAE=1WindowsHorizonτyτy^τ\qquad MAE = \frac{1}{Windows * Horizon} \sum_{\tau} |y_{\tau} - \hat{y}_{\tau}| \qquad and MSE=1WindowsHorizonτ(yτy^τ)2\qquad MSE = \frac{1}{Windows * Horizon} \sum_{\tau} (y_{\tau} - \hat{y}_{\tau})^{2} \qquad

from neuralforecast.losses.numpy import mae, mse

print('MAE: ', mae(y_hat, y_true))
print('MSE: ', mse(y_hat, y_true))
MAE:  0.24862242128243706
MSE:  0.17257850996828134

作为参考,我们可以对照 NHITS 论文中先前的“最先进”基于 Transformer 的长周期预测方法来检查性能。要重现或改进论文结果,请尝试在超参数调优中设置 hyperopt_max_evals=30

平均绝对误差 (MAE)

预测范围NHITSAutoFormerInFormerARIMA
960.2490.3390.4530.301
1920.3050.3400.5630.345
3360.3460.3720.8870.386
7200.4260.4191.3880.445

均方误差 (MSE)

预测范围NHITSAutoFormerInFormerARIMA
960.1730.2550.3650.225
1920.2450.2810.5330.298
3360.2950.3391.3630.370
7200.4010.4223.3790.478

参考文献

Cristian Challu, Kin G. Olivares, Boris N. Oreshkin, Federico Garza, Max Mergenthaler-Canseco, Artur Dubrawski (2021). NHITS: 用于时间序列预测的神经分层插值. Accepted at AAAI 2023.