当我们考虑时间序列的增强树时,通常会想到 M5 比赛,其中前十名中有很大一部分使用了 LightGBM。 但是当在单变量情况下使用增强树时,由于没有大量的外生特征可以利用,它的性能非常的糟糕。 首先需要明确的是M4 比赛的亚军 DID 使用了增强树。但是它作为一个元模型来集成其他更传统的时间序列方法。 在 M4 上公开的代码中,所有标准增强树的基准测试都相当糟糕,有时甚至还达不到传统的预测方法。 下面是Sktime 包和他们的论文所做的出色工作[1]: ![]() 任何带有“XGB”或“RF”的模型都使用基于树的集成。 在上面的列表中 Xgboost 在每小时数据集中提供了 10.9 的最佳结果! 然后,但是这些模型只是Sktime 在他们框架中做过的简单尝试,而 M4 的获胜者在同一数据集上的得分是 9.3 分……。在该图表中我们需要记住一些数字,例如来自 XGB-s 的每小时数据集的 10.9 和每周数据集中的树性模型的“最佳”结果:来自 RF-t-s 的 9.0。 从上图中就引出了我们的目标:创建一个基于LightGBM并且适合个人使用的时间序列的快速建模程序,并且能够绝对超越这些数字,而且在速度方面可与传统的统计方法相媲美。 听起来很困难,并且我们的第一个想法可能是必须优化我们的树。 但是提升树非常复杂,改动非常费时,并且结果并不一定有效。但是有一点好处是我们正在拟合是单个数据集,是不是可从特征下手呢? 特征在查看单变量空间中树的其他实现时都会看到一些特征工程,例如分箱、使用目标的滞后值、简单的计数器、季节性虚拟变量,也许还有傅里叶函数。这对于使用传统的指数平滑等方法是非常棒的。但是我们今天目的是必须对时间元素进行特征化并将其表示为表格数据以提供给树型模型,LazyProphet这时候就出现了。除此以外,LazyProphet还包含一个额外的特征工程元素:将点”连接”起来。 很简单,将时间序列的第一个点连接起来,并将一条线连接到中途的另一个点,然后将中途的点连接到最后一个点。重复几次,同时更改将哪个点用作“kink”(中间节点),这就是我们所说的“连接”。 下面张图能很好地说明这一点。蓝线是时间序列,其他线只是“连接点”: ![]() 事实证明,这些只是加权分段线性基函数。 这样做的一个缺点是这些线的外推可能会出现偏差。 为了解决这个问题,引入一个惩罚从中点到最后点的每条线的斜率的“衰减”因子。 在这个基础上加滞后的目标值和傅里叶基函数,在某些问题上就能够接近最先进的性能。因为要求很少,因因此我们把它称作“LazyProphet”。 下面我们看看实际的应用结果。 代码这里使用的数据集都是开源的,并在M-competitions github上发布。数据已经被分割为训练和测试集,我们直接使用训练csv进行拟合,而测试csv用于使用SMAPE进行评估。现在导入LazyProphet: pip install LazyProphet 安装后,开始编码: import matplotlib.pyplot as pltimport numpy as npfrom tqdm import tqdmimport pandas as pdfrom LazyProphet import LazyProphet as lptrain_df = pd.read_csv(r'm4-weekly-train.csv')test_df = pd.read_csv(r'm4-weekly-test.csv')train_df.index = train_df['V1']train_df = train_df.drop('V1', axis = 1)test_df.index = test_df['V1']test_df = test_df.drop('V1', axis = 1) 导入所有必要的包,后并将读入每周数据。创建 SMAPE 函数,它将返回给定预测和实际值的 SMAPE:
对于这个实验将取所有时间序列的平均值与其他模型进行比较。 为了进行健全性检查,我们还将获得的平均 SMAPE,这样可以确保所做的与比赛中所做的一致。 smapes = []naive_smape = []j = tqdm(range(len(train_df)))for row in j:y = train_df.iloc[row, :].dropna()y_test = test_df.iloc[row, :].dropna()j.set_description(f'{np.mean(smapes)}, {np.mean(naive_smape)}')lp_model = LazyProphet(scale=True,seasonal_period=52,n_basis=10,fourier_order=10,ar=list(range(1, 53)),decay=.99,linear_trend=None,decay_average=False)fitted = lp_model.fit(y)predictions = lp_model.predict(len(y_test)).reshape(-1)smapes.append(smape(y_test.values, pd.Series(predictions).clip(lower=0)))naive_smape.append(smape(y_test.values, np.tile(y.iloc[-1], len(y_test)))) print(np.mean(smapes))print(np.mean(naive_smape)) 在查看结果之前,快速介绍一下 LazyProphet 参数。
下面继续处理数据:
所以真正需要修改是seasonal_period 和ar 参数。 将list传递给seasonal_period 时,它将为列表中的所有内容构建季节性基函数。 ar 进行了调整以适应新的主要季节 24。 结果对于上面的 Sktime 结果,表格如下: ![]() LazyProphet 击败了 Sktime 最好的模型,其中包括几种不同的基于树的方法。 在每小时数据集上输给给了 M4 的获胜者,但平均而言总体上优于 ES-RNN。 这里要意识到的重要一点是,只使用默认参数进行了此操作…… boosting_params = {'objective': 'regression','metric': 'rmse','verbosity': -1,'boosting_type': 'gbdt','seed': 42,'linear_tree': False,'learning_rate': .15,'min_child_samples': 5,'num_leaves': 31,'num_iterations': 50} 可以在创建 LazyProphet 类时传递你参数的字典,可以针对每个时间序列进行优化,以获得更多收益。 对比一下我们的结果和上面提到的目标:
目前看是非常成功的,但是成功可能无法完全的复制,因为他数据集的数据量要少得多,因此我们的方法往往会显着降低性能。根据测试LazyProphet 在高频率和大量数据量上表现的更好,但是LazyProphet还是一个时间序列建模的很好选择,我们不需要花多长时间进行编码就能够测试,这点时间还是很值得。 引用: [1] Markus Löning, Franz Király: “Forecasting with sktime: Designing sktime’s New Forecasting API and Applying It to Replicate and Extend the M4 Study”, 2020; arXiv:2005.08067 |
|