时间序列深度编码器
本笔记本介绍了如何使用Darts的TiDEModel
,并将其与NHiTSModel
进行了基准测试。
TiDE(时间序列密集编码器)是一种纯深度学习编码器-解码器架构。它的特殊之处在于时间解码器可以帮助减轻异常样本对预测的影响(论文中的图4)。
请参阅原始论文和模型描述:http://arxiv.org/abs/2304.08424。
# 导入fix_pythonpath_if_working_locally函数
from utils import fix_pythonpath_if_working_locally
# 调用fix_pythonpath_if_working_locally函数,用于修复本地Python路径
fix_pythonpath_if_working_locally()
# 在Jupyter Notebook中使用matplotlib绘图时,需要使用%matplotlib inline命令
%matplotlib inline
# 加载autoreload扩展,可以在代码修改后自动重新加载模块
%load_ext autoreload
# 设置autoreload的模式为2,即在代码修改后自动重新加载模块
%autoreload 2
# 设置matplotlib在notebook中显示图形
%matplotlib inline
# 导入所需的库
import torch
import numpy as np
import pandas as pd
import shutil
import matplotlib.pyplot as plt
import warnings
import logging
# 禁用警告和日志
warnings.filterwarnings("ignore")
logging.disable(logging.CRITICAL)
# 导入Darts库中的模型和数据集
from darts.models import NHiTSModel, TiDEModel
from darts.datasets import AusBeerDataset
# 导入Darts库中的数据处理工具
from darts.dataprocessing.transformers.scaler import Scaler
# 导入PyTorch Lightning库中的EarlyStopping回调函数
from pytorch_lightning.callbacks.early_stopping import EarlyStopping
# 导入Darts库中的评估指标
from darts.metrics import mae, mse
# 加载澳大利亚啤酒数据集
data = AusBeerDataset()
# 将数据集划分为训练集和测试集
train, val = data.split_before(pd.Timestamp('01-01-2010'))
# 对数据进行标准化处理
scaler = Scaler()
train_transformed = scaler.fit_transform(train)
val_transformed = scaler.transform(val)
# 使用TiDE模型进行时间序列预测
model = TiDEModel(input_chunk_length=24, output_chunk_length=12, n_rnn_layers=2, n_dense_layers=2, n_cells=32, cell_type='GRU')
es = EarlyStopping(monitor='val_loss', patience=5)
model.fit(train_transformed, epochs=100, batch_size=32, verbose=True, callbacks=[es], val_split=0.2)
# 对测试集进行预测
pred_series = model.predict(n=len(val_transformed))
# 将预测结果反向标准化
pred_series = scaler.inverse_transform(pred_series)
# 计算MAE和MSE指标
mae_val = mae(val, pred_series)
mse_val = mse(val, pred_series)
# 输出MAE和MSE指标
print(f'MAE: {mae_val:.4f}')
print(f'MSE: {mse_val:.4f}')
# 绘制预测结果和实际结果的对比图
plt.plot(val, label='actual')
plt.plot(pred_series, label='forecast')
plt.legend()
plt.show()
模型参数设置
模板代码很无聊,特别是在训练多个模型以比较性能的情况下。为了避免这种情况,我们使用一个通用配置,可以与任何Darts TorchForecastingModel
一起使用。
这些参数有一些有趣的地方:
-
**梯度裁剪:**通过为批处理设置梯度上限来减轻反向传播期间的梯度爆炸。
-
**学习率:**模型学习的大部分内容都在早期时期完成。随着训练的进行,减少学习率通常有助于微调模型。话虽如此,它也可能导致显着的过度拟合。
-
**提前停止:**为了避免过度拟合,我们可以使用提前停止。它监视验证集上的指标,并根据自定义条件在指标不再改善时停止训练。
# 定义优化器参数
optimizer_kwargs = {
"lr": 1e-3, # 学习率
}
# 定义 PyTorch Lightning Trainer 参数
pl_trainer_kwargs = {
"gradient_clip_val": 1, # 梯度裁剪值
"max_epochs": 200, # 最大迭代次数
"accelerator": "auto", # 自动选择加速器
"callbacks": [], # 回调函数
}
# 定义学习率调度器
lr_scheduler_cls = torch.optim.lr_scheduler.ExponentialLR # 指数学习率调度器
lr_scheduler_kwargs = {
"gamma": 0.999, # 学习率衰减系数
}
# 定义早停法则(需要在后面的模型中重新设置)
# 当验证集损失连续10个epoch没有下降超过1e-3时,停止训练
early_stopping_args = {
"monitor": "val_loss", # 监控指标
"patience": 10, # 忍耐次数
"min_delta": 1e-3, # 最小变化量
"mode": "min", # 模式
}
# 定义通用模型参数
common_model_args = {
"input_chunk_length": 12, # 输入数据的时间步长
"output_chunk_length": 12, # 输出数据的时间步长
"optimizer_kwargs": optimizer_kwargs, # 优化器参数
"pl_trainer_kwargs": pl_trainer_kwargs, # PyTorch Lightning Trainer 参数
"lr_scheduler_cls": lr_scheduler_cls, # 学习率调度器
"lr_scheduler_kwargs": lr_scheduler_kwargs, # 学习率调度器参数
"likelihood": None, # 概率预测的似然函数
"save_checkpoints": True, # 是否保存检查点
"force_reset": True, # 是否强制重置模型
"batch_size": 256, # 批次大小
"random_state": 42, # 随机种子
}
数据加载和准备
我们考虑澳大利亚每季度啤酒销售量(单位为兆升)。
在训练之前,我们将数据分为训练集、验证集和测试集。模型将从训练集中学习,使用验证集确定何时停止训练,并最终在测试集上进行评估。
为了避免从验证集和测试集中泄漏信息,我们根据训练集的属性对数据进行缩放。
# 导入AusBeerDataset数据集类
# 从该类中加载数据集并存储在series变量中
series = AusBeerDataset().load()
# 将series数据集按照0.6的比例分割成训练集和临时集
# 训练集存储在train变量中,临时集存储在temp变量中
train, temp = series.split_after(0.6)
# 将temp临时集按照0.5的比例分割成验证集和测试集
# 验证集存储在val变量中,测试集存储在test变量中
val, test = temp.split_after(0.5)
# 绘制训练集的图像,并添加标签为"train"
train.plot(label="train")
# 绘制验证集的图像,并添加标签为"val"
val.plot(label="val")
# 绘制测试集的图像,并添加标签为"test"
test.plot(label="test")
# 创建一个Scaler对象,使用默认的sklearn的MinMaxScaler进行数据缩放
scaler = Scaler()
# 对训练数据进行缩放,并将缩放后的结果赋值给train变量
train = scaler.fit_transform(train)
# 对验证数据进行缩放,并将缩放后的结果赋值给val变量
val = scaler.transform(val)
# 对测试数据进行缩放,并将缩放后的结果赋值给test变量
test = scaler.transform(test)
模型配置
使用已经建立的共享参数,我们可以看到NHiTS和TiDE的默认参数被使用。唯一的例外是,TiDE被测试了一下,包括和不包括可逆实例归一化。
然后我们遍历模型字典并训练所有的模型。当使用早期停止时,保存检查点非常重要。这使我们能够继续超过最佳模型配置,然后在训练完成后恢复最佳权重。
# 创建模型
# 创建NHiTS模型,使用common_model_args参数,模型名称为"hi"
model_nhits = NHiTSModel(**common_model_args, model_name="hi")
# 创建TiDE模型,使用common_model_args参数,不使用可逆实例归一化,模型名称为"tide0"
model_tide = TiDEModel(
**common_model_args, use_reversible_instance_norm=False, model_name="tide0"
)
# 创建TiDE模型,使用common_model_args参数,使用可逆实例归一化,模型名称为"tide1"
model_tide_rin = TiDEModel(
**common_model_args, use_reversible_instance_norm=True, model_name="tide1"
)
# 将三个模型放入字典中,键为模型名称,值为对应的模型对象
models = {
"NHiTS": model_nhits,
"TiDE": model_tide,
"TiDE+RIN": model_tide_rin,
}
# train the models and load the model from its best state/checkpoint
# 训练模型并从最佳状态/检查点加载模型
for name, model in models.items():
# 对于每个模型,需要重置early stopping
pl_trainer_kwargs["callbacks"] = [
EarlyStopping(
**early_stopping_args,
)
]
# 使用训练数据集和验证数据集对模型进行训练
model.fit(
series=train,
val_series=val,
verbose=False,
)
# 从检查点加载模型返回一个新的模型对象,将其存储在models字典中
models[name] = model.load_from_checkpoint(model_name=model.model_name, best=True)
# 预测接下来的 `pred_steps` 个点,这些点是在 `pred_input` 结束后的
pred_steps = common_model_args["output_chunk_length"] * 2
pred_input = test[:-pred_steps]
# 创建一个图形窗口和一个坐标轴对象
fig, ax = plt.subplots(figsize=(15, 5))
# 绘制输入数据的图像
pred_input.plot(label="input")
# 绘制测试数据的图像
test[-pred_steps:].plot(label="ground truth", ax=ax)
# 创建一个字典来存储每个模型的预测结果
result_accumulator = {}
# 对于每个模型,进行预测并计算/存储指标
for model_name, model in models.items():
# 预测未来的数据
pred_series = model.predict(n=pred_steps, series=pred_input)
# 绘制预测结果的图像
pred_series.plot(label=model_name, ax=ax)
# 存储模型的指标
result_accumulator[model_name] = {
"mae": mae(test, pred_series),
"mse": mse(test, pred_series),
}
结果
在这种情况下,普通的TiDE与NHiTs的准确性相似。包括可逆实例归一化(RINorm
)极大地帮助改善了TiDE的预测(请记住,这只是一个例子,并不总是保证提高性能)。
# 导入pandas库
import pandas as pd
# 将result_accumulator字典转换为DataFrame
results_df = pd.DataFrame.from_dict(result_accumulator, orient="index")
# 绘制DataFrame的条形图
results_df.plot.bar()