当咱们思考工夫序列的加强树时,通常会想到 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 plt
import numpy as np
from tqdm import tqdm
import pandas as pd
from LazyProphet import LazyProphet as lp
train_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:
def smape(A, F):
return 100/len(A) * np.sum(2 * np.abs(F - A) / (np.abs(A) + np.abs(F)))
对于这个试验将取所有工夫序列的平均值与其余模型进行比拟。为了进行健全性查看,咱们还将取得的均匀 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 参数。
- scale:这个很简略,只是是否对数据进行缩放。默认值为 True。
- seasonal_period:此参数管制季节性的傅立叶基函数,因为这是咱们应用 52 的每周频率。
- n_basis:此参数管制加权分段线性基函数。这只是要应用的函数数量的整数。
- Fourier_order:用于季节性的正弦和余弦对的数量。
- ar:要应用的滞后指标变量值。能够获取多个列表 1-52。
- decay:衰减因子用于惩办咱们的基函数的“右侧”。设置为 0.99 示意斜率乘以 (1- 0.99) 或 0.01。
- linear_trend:树的一个次要毛病是它们无奈推断出后续数据的范畴。为了克服这个问题,有一些针对多项式趋势的现成测试将拟合线性回归以打消趋势。None 示意有测试,通过 True 示意总是去趋势,通过 False 示意不测试并且不应用线性趋势。
- decay_average:在应用衰减率时不是一个有用的参数。这是一个 trick 但不要应用它。传递 True 只是均匀基函数的所有将来值。这在与 elasticnet 程序拟合时很有用,但在测试中对 LightGBM 的用途不大。
上面持续解决数据:
train_df = pd.read_csv(r'm4-hourly-train.csv')
test_df = pd.read_csv(r'm4-hourly-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)
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(seasonal_period=[24,168],
n_basis=10,
fourier_order=10,
ar=list(range(1, 25)),
decay=.99)
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))
所以真正须要批改是 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
https://www.overfit.cn/post/00e8c0fc47ea47e4abfb49d5ae71707c
作者:Tyler Blume