文中内容仅限技术学习与代码实践参考,市场存在不确定性,技术分析需谨慎验证,不构成任何投资建议。
Darts 是一个 Python 库,用于对时间序列进行用户友好型预测和异常检测。它包含多种模型,从 ARIMA 等经典模型到深度神经网络。所有预测模型都能以类似 scikit-learn 的方式使用 fit()
和 predict()
函数。该库还可以轻松地对模型进行回溯测试,将多个模型的预测结果结合起来,并将外部数据考虑在内。Darts 支持单变量和多变量时间序列和模型。基于 ML 的模型可以在包含多个时间序列的潜在大型数据集上进行训练,其中一些模型还为概率预测提供了丰富的支持。
时间序列深度编码器
本笔记本演示了如何使用 Darts 的 TiDEModel
并将其与 NHiTSModel
进行基准测试。
TiDE(Time-series Dense Encoder)是一种纯深度学习编码器-解码器架构。它的特殊之处在于,时间解码器可以帮助减轻异常样本对预测的影响(参见论文中的图 4)。
查看原始论文和模型描述:http://arxiv.org/abs/2304.08424。
# 如果在本地运行,修复 python 路径
from utils import fix_pythonpath_if_working_locally
fix_pythonpath_if_working_locally()
%matplotlib inline
%load_ext autoreload
%autoreload 2
%matplotlib inline
import warnings
import matplotlib.pyplot as plt
import pandas as pd
import torch
from pytorch_lightning.callbacks.early_stopping import EarlyStopping
from darts.dataprocessing.transformers.scaler import Scaler
from darts.datasets import AusBeerDataset
from darts.metrics import mae, mse
from darts.models import NHiTSModel, TiDEModel
warnings.filterwarnings("ignore")
import logging
logging.disable(logging.CRITICAL)
模型参数设置
样板代码并不有趣,尤其是在训练多个模型以比较性能的背景下。为了避免这种情况,我们使用一个通用配置,该配置可用于任何 Darts 的 TorchForecastingModel
。
关于这些参数的一些有趣之处:
-
梯度裁剪: 通过在批次的梯度上设置上限来缓解反向传播期间的梯度爆炸问题。
-
学习率: 模型的大部分学习发生在早期 epoch。随着训练的进行,降低学习率通常有助于微调模型。话虽如此,它也可能导致严重的过拟合。
-
早停: 为了避免过拟合,我们可以使用早停。它在验证集上监控某个指标,并在该指标基于自定义条件不再改善时停止训练。
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,
"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,
}
数据加载与准备
我们考虑澳大利亚以兆升为单位的季度啤酒销量。
在训练之前,我们将数据分成训练集、验证集和测试集。模型将从训练集中学习,使用验证集来确定何时停止训练,最后在测试集上进行评估。
为了避免从验证集和测试集中泄露信息,我们基于训练集的特性对数据进行缩放。
series = AusBeerDataset().load()
train, temp = series.split_after(0.6)
val, test = temp.split_after(0.5)
train.plot(label="train")
val.plot(label="val")
test.plot(label="test")
scaler = Scaler() # 默认使用 sklearn 的 MinMaxScaler
train = scaler.fit_transform(train)
val = scaler.transform(val)
test = scaler.transform(test)
模型配置
使用已建立的共享参数,我们可以看到 NHiTS 和 TiDE 使用了默认参数。唯一的例外是,TiDE 在有和没有可逆实例归一化的情况下都进行了测试。
然后我们遍历模型字典并训练所有模型。使用早停时,保存检查点很重要。这允许我们越过最佳模型配置继续训练,然后在训练完成后恢复最优权重。
# 创建模型
model_nhits = NHiTSModel(**common_model_args, model_name="hi")
model_tide = TiDEModel(
**common_model_args, use_reversible_instance_norm=False, model_name="tide0"
)
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,
}
# 训练模型并从最佳状态/检查点加载模型
for name, model in models.items():
# 早停需要为每个模型重置
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_input` 结束后接下来的 `pred_steps` 个点
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 的预测(请记住,这只是一个示例,并不总是保证性能会提高)。
results_df = pd.DataFrame.from_dict(result_accumulator, orient="index")
results_df.plot.bar()
<Axes: >
风险提示与免责声明
本文内容基于公开信息研究整理,不构成任何形式的投资建议。历史表现不应作为未来收益保证,市场存在不可预见的波动风险。投资者需结合自身财务状况及风险承受能力独立决策,并自行承担交易结果。作者及发布方不对任何依据本文操作导致的损失承担法律责任。市场有风险,投资须谨慎。