在本教程中,我们将详细探讨 neuralforecast 中的交叉验证函数。

1. 库

请确保已安装 neuralforecast 以跟着操作。

!pip install neuralforecast
import logging
import matplotlib.pyplot as plt
import pandas as pd
from utilsforecast.plotting import plot_series

from neuralforecast import NeuralForecast
from neuralforecast.models import NHITS
logging.getLogger("pytorch_lightning").setLevel(logging.ERROR)

2. 读取数据

本教程使用小时级 M4 数据集的一部分。为了提高效率,它存储在 parquet 文件中。您可以使用普通的 pandas 操作读取其他格式的数据,例如 .csv

NeuralForecast 的输入始终是一个采用长格式的数据框,包含三列:unique_iddsy

  • unique_id(字符串、整数或类别)表示序列的标识符。

  • ds(日期时间戳或整数)列应为时间索引整数或日期时间戳,理想情况下日期格式为 YYYY-MM-DD,时间戳格式为 YYYY-MM-DD HH:MM:SS。

  • y(数值)表示我们希望预测的度量值。

根据您的互联网连接情况,此步骤大约需要 10 秒。

Y_df = pd.read_parquet('https://datasets-nixtla.s3.amazonaws.com/m4-hourly.parquet')
Y_df.head()
unique_iddsy
0H11605.0
1H12586.0
2H13586.0
3H14559.0
4H15511.0

为了简化,我们仅使用单个序列来详细探讨交叉验证功能。此外,让我们使用前 700 个时间步,以便使用整数,从而更容易可视化和理解交叉验证。

Y_df = Y_df.query("unique_id == 'H1'")[:700]
Y_df.head()
unique_iddsy
0H11605.0
1H12586.0
2H13586.0
3H14559.0
4H15511.0
plot_series(Y_df)

3. 使用交叉验证

3.1 使用 n_windows

要使用 cross_validation 方法,我们可以选择: - 设置验证集和测试集的大小 - 设置交叉验证窗口的数量

让我们看一个最小的例子来了解其工作原理。在这里,我们使用 NHITS 模型,并将预测范围 (horizon) 设置为 100,输入大小 (input size) 设置为 200。

首先,让我们使用 n_windows = 4

我们还将 step_size 设置为等于预测范围 (horizon)。此参数控制每个交叉验证窗口之间的距离。将其设置为等于预测范围时,我们执行链式交叉验证,其中窗口不重叠。

h = 100
nf = NeuralForecast(models=[NHITS(h=h, input_size=2*h, max_steps=500, enable_progress_bar=False, logger=False)], freq=1);
cv_df = nf.cross_validation(Y_df, n_windows=4, step_size=h, verbose=0)
cv_df.head()
Seed set to 1
unique_idds截止点NHITSy
0H1301300490.048950485.0
1H1302300537.713867525.0
2H1303300612.900635585.0
3H1304300689.346313670.0
4H1305300760.153992747.0
cutoffs = cv_df['cutoff'].unique()

plt.figure(figsize=(15,5))
plt.plot(Y_df['ds'], Y_df['y'])
plt.plot(cv_df['ds'], cv_df['NHITS'], label='NHITS', ls='--')

for cutoff in cutoffs:
    plt.axvline(x=cutoff, color='black', ls=':')

plt.xlabel('Time steps')
plt.ylabel('Target [H1]')
plt.legend()
plt.tight_layout()

在上图中,我们看到有 4 个截止点,对应于我们的四个交叉验证窗口。当然,请注意这些窗口是从数据集的末尾设置的。这样,模型就可以根据过去的数据进行训练,从而预测未来数据。

重要提示

我们从 0 开始计数,因此从 0 到 99 共有 100 个数据点。

因此,模型最初使用时间步 0 到 299 进行训练。然后,为了进行预测,它使用时间步 100 到 299(输入大小为 200),并预测时间步 300 到 399(预测范围为 100)。

然后,使用时间步 200 到 399 的实际值(因为我们的模型 input_size 为 200)来生成下一窗口的预测,即从 400 到 499。

这个过程会重复进行,直到窗口用尽为止。

3.2 使用验证集和测试集

除了设置窗口数量外,我们还可以定义验证集和测试集。在这种情况下,我们必须将 n_windows 设置为 None

cv_df_val_test = nf.cross_validation(Y_df, val_size=200, test_size=200, step_size=h, n_windows=None)
cutoffs = cv_df_val_test['cutoff'].unique()
plt.figure(figsize=(15,5))

# Plot the original data and NHITS predictions
plt.plot(Y_df['ds'], Y_df['y'])
plt.plot(cv_df_val_test['ds'], cv_df_val_test['NHITS'], label='NHITS', ls='--')

# Add highlighted areas for validation and test sets
plt.axvspan(Y_df['ds'].iloc[300], Y_df['ds'].iloc[499], alpha=0.2, color='yellow', label='Validation Set')
plt.axvspan(Y_df['ds'].iloc[500], Y_df['ds'].iloc[699], alpha=0.2, color='red', label='Test Set')

# Add vertical lines for cutoffs
for cutoff in cutoffs:
    plt.axvline(x=cutoff, color='black', ls=':')

# Set labels and legend
plt.xlabel('Time steps')
plt.ylabel('Target [H1]')
plt.legend()

plt.tight_layout()
plt.show()

在这里,我们仅预测测试集,它对应于最后的 200 个时间步。由于模型的预测范围为 100,且 step_size 也设置为 100,因此测试集中只有两个交叉验证窗口 (200/100 = 2)。因此,我们只看到两个截止点。

3.3 带 refit 的交叉验证

在前面的章节中,我们只训练了一次模型,并在多个交叉验证窗口上进行了预测。然而,在实际应用中,我们通常会在进行下一组预测之前,使用新的观测数据重新训练模型。

我们可以使用 refit=True 来模拟该过程。这样,模型会在交叉验证过程的每个步骤中重新训练。换句话说,训练集会随着新的观测值逐渐扩大,并且在进行下一组预测之前,模型会重新训练。

cv_df_refit = nf.cross_validation(Y_df, n_windows=4, step_size=h, refit=True)
cutoffs = cv_df_refit['cutoff'].unique()

plt.figure(figsize=(15,5))
plt.plot(Y_df['ds'], Y_df['y'])
plt.plot(cv_df_refit['ds'], cv_df_refit['NHITS'], label='NHITS', ls='--')

for cutoff in cutoffs:
    plt.axvline(x=cutoff, color='black', ls=':')

plt.xlabel('Time steps')
plt.ylabel('Target [H1]')
plt.legend()
plt.tight_layout()

注意,当我们运行带 refit=True 的交叉验证时,完成了 4 个训练循环。这是预期的,因为模型现在会针对交叉验证中的每个折叠(fold)用新数据重新训练: - 折叠 1:在前 300 个时间步上训练,预测接下来的 100 个时间步 - 折叠 2:在前 400 个时间步上训练,预测接下来的 100 个时间步 - 折叠 3:在前 500 个时间步上训练,预测接下来的 100 个时间步 - 折叠 4:在前 600 个时间步上训练,预测接下来的 100 个时间步

3.4 交叉验证中的重叠窗口

step_size 小于预测范围 (horizon) 的情况下,我们会得到重叠的窗口。这意味着我们对某些时间步进行了多次预测。

这对于在更多预测窗口上测试模型很有用,并且提供了更可靠的评估,因为模型在序列的不同段上进行了测试。

然而,它会带来更高的计算成本,因为我们对某些时间步进行了多次预测。

cv_df_refit_overlap = nf.cross_validation(Y_df, n_windows=2, step_size=50, refit=True)
cutoffs = cv_df_refit_overlap['cutoff'].unique()

fold1 = cv_df_refit_overlap.query("cutoff==550")
fold2 = cv_df_refit_overlap.query("cutoff==600")

plt.figure(figsize=(15,5))
plt.plot(Y_df['ds'], Y_df['y'])
plt.plot(fold1['ds'], fold1['NHITS'], label='NHITS (fold 1)', ls='--', color='blue')
plt.plot(fold2['ds'], fold2['NHITS'], label='NHITS (fold 2)', ls='-.', color='red')

for cutoff in cutoffs:
    plt.axvline(x=cutoff, color='black', ls=':')

plt.xlabel('Time steps')
plt.ylabel('Target [H1]')
plt.xlim(500, 700)
plt.legend()
plt.tight_layout()

在上图中,我们看到我们的两个折叠在时间步 601 和 650 之间重叠,因为步长是 50。这是由于: - 折叠 1:模型使用时间步 0 到 550 进行训练,并预测 551 到 650 (h=100) - 折叠 2:模型使用时间步 0 到 600 (step_size=50) 进行训练,并预测 601 到 700

请注意,当评估使用重叠交叉验证窗口训练的模型时,某些时间步会有不止一个预测值。这可能会影响您的评估指标,因为重复的时间步在计算指标时会被多次考虑。