共计 9539 个字符,预计需要花费 24 分钟才能阅读完成。
本文作者: CDY,观远算法团队工程师,毕业于伦敦帝国理工学院计算机系,次要钻研方向为工夫序列算法及其落地利用,热衷于尝试各类离奇工具库,深耕于批发消费品场景,解决供应链的优化问题,为客户提供基于机器学习的 AI 解决方案。
概述
Kats 是 Facebook 公布的一个专门为了工夫序列服务的工具库。它作为一个 Toolkit 包,提供了四种繁难且轻量化的 API。
- 预测(封装了 10+models,次要是传统的工夫序列模型,这些模型有反对 ensemble 的 API,联合工夫序列特色的性能甚至能够做 meta-learning)
- 检测(官网叫做 detection,大抵是对工夫序列做相似于异样检测,change point detection 之类的检测)
- 工夫序列特色(API 非常简略,能够失去 65 个工夫序列相干的 features)
- 模仿(simulator,能够依照某些时序特色比方 seasonality 去发明工夫序列来不便试验)
针对这四种性能,上面说一下我本人的尝试和尝试过程中的了解。
上面的内容可能会比拟粗疏,如果想间接看总结的优缺点倡议拉到最初。
Basic Representation
首先这类库都会封装一个数据类型,用来作为它的 API 的调用输出。相似的库有 AutoGluon 的 TabularDataset,fastai 里的 TabularDataLoaders。
- 在 Kats 外面,所有的 pandas dataframe,series 甚至 numpy 都能够先转成一个叫做 TimeSeriesData 的货色
- 因为传统工夫序列并不是很关怀表格类数据其余的特色,而更专一于指标列,所以其实 TimeSeriesData 只须要两样货色,工夫和指标列
- 大抵的调用形式如下,试验的 dataset 是我司 (Guandata) 本人开发的一个机器学习工具库里的 data,是一个销量预测的数据,蕴含的列是 store,sku,date 和 sales。
from gaia.datasets.toy_datasets import load_interview_dataset
from kats.consts import TimeSeriesData
interview_df = load_interview_dataset()
# 首先简略的把某一个 store,sku 的数据拿进去做成工夫序列
single_ts_df = interview_df[(interview_df['store']==0)&(interview_df['sku']==114)]
# 第一种变成 Kats.TimeSeriesData 的办法
# 作为工夫序列只关怀指标列和工夫, TimeSeriesData(df), 其中 df 只包含两列, 一列叫做 time,一列叫做 value
single_ts_df = single_ts_df[['date', 'sales']].rename(columns={'date':'time', 'sales':'value'})
single_kats_ts = TimeSeriesData(single_ts_df)
# 第二种是显式的传入对应列(传入的能够是 dataframe,series 或者 numpy array)
single_kats_ts2 = TimeSeriesData(time = single_ts_df.time, value=single_ts_df.value)
那么目前 single_kats_ts 就是一个 kats.consts.TimeSeriesData 的 datatype 的货色了,这个数据类型的大部分操作以及 print 之后的输入都是和 pandas.DataFrame 截然不同,它还比一般的 pandas DataFrame 多反对了一些操作,最有用也是用的最多的就是画图:
import matplotlib.pyplot as plt
%matplotlib inline
single_kats_ts.plot(cols=['value'])
plt.show()
BTW, kats 当然反对 multi-variate timeseries,形式就是在初始化的时候 value 传入一个 dataframe,而后外面的列都成为 variate value 了。因为篇幅起因不再介绍,没有什么更非凡操作。
Simulator
在咱们理论应用过程中,咱们可能面对的更多的是一些表格类的数据,但在很多传统的工夫序列工作中,会更关怀指标列,并把它作为一条工夫序列,所以咱们首先先模拟出 N 条 univariate 的工夫序列,模仿实在销量预测场景中 by store by sku 的每一条销量预测曲线。
from kats.utils.simulator import Simulator
# simulate 90 days of data
sim = Simulator(n=90, freq="D", start = "2021-01-01")
# 目前 simulator 反对合成一些工夫序列
# 合成的办法包含依据 STL 合成,或者 Arima 的系数产生 ts,并且退出一些 trend, noise 等
# 简略介绍一下 arima_sim 是一个用 ARIMA model 来模仿 ts 的函数,参数包含 ar 的参数,ma 的参数,d 代表非安稳工夫序列的单位根 (unit root) 数。arima_sim_list = [sim.arima_sim(ar=[0.1, 0.05], ma = [0.04, 0.1], d = 1) for _ in range(10)]
# 通过 simulator,咱们还能在之前的序列根底上减少很多 drift,trend,seasonality 等
# 上面就是调用形式,强行在外面加一些 changepoint
# generate 10 TimeSeriesData with trend shifts
trend_sim_list = [
sim.trend_shift_sim(cp_arr = [30, 60, 75],
trend_arr=[3, 15, 2, 8],
intercept=30,
noise=50,
seasonal_period=7,
seasonal_magnitude=np.random.uniform(10, 100),
random_seed=random_seed
) for _ in range(10)
]
# generate 10 TimeSeriesData with level shifts
level_shift_list = [
sim.level_shift_sim(cp_arr = [30, 60, 75],
level_arr=[1.35, 1.05, 1.35, 1.2],
noise=0.05,
seasonal_period=7,
seasonal_magnitude=np.random.uniform(0.1, 1.0),
random_seed=random_seed
) for _ in range(10)
]
通过源码能够看到,在 arima_sim 的时候,它模仿了 arima 的输入作为一个 array,而后调用之前 TimeSeriesData 进行输入。
相似地,Simulator 还反对很多 add_noise, add_trend, add_seasonality 之类的函数,其实就是还在 trend_shift_sim 之类的函数里调用到了。
TsFeatures
这是我感觉这个库最爽的性能,靠这个性能咱们能够做很多的事件。
家喻户晓,目前表格类数据的时序预测场景树模型是比拟 NB 的,DL 和传统时序剖析仿佛差点意思,我集体认为次要是在理论场景下,大部分数据和特色工程适应性更加匹配树模型。比方实在数据的 seasonality 可能不显著,data drift 大,工业对训练模型的速度有肯定耗费要求等。
但回头想,也有一部分数据,可能就用简略的 arima 训练就能达到不错的成果,或者在开始你须要一个不错的 baseline 作为根底,转发一篇大佬的文章,用这些时序的特色进行分类,察看,剖析,是能够选取到不同的模型进行尝试的。那这时候,对 tsFeatures 的提取就异样要害了。
之前也接触过一些疾速且主动提取 time-series related features 的库,比方 tsfresh,然而在我无限的 tsfresh 应用尝试中,我的集体体验非常的差,总结为:
- 内存耗费大,速度慢
- 提取到大量特色(4000+)个特色,而且命名极其简单,特色名很长。
据说通过衍生规定能够缩小提取的特色进行减速,我也置信必定是我用法不精,然而作为一个主动提取时序特色的库,用起来的老本有点高了,我起初就再也没用过 tsfresh。
在 Kats 这个库中,提取就是效率很高。
import warnings
warnings.filterwarnings('ignore')
model = TsFeatures()
# 还是之前的那一单条数据
ts_feats = model.transform(single_kats_ts)```
失去的 ts_feats 是一个包含了 40 个特色的 dict:
通过这个 features 的提取会发现,它其实有很多种不同类型的特色分组,比方 length, mean, var 是统计值特色, 大量的 acf,pacf 等自相关性特色,entropy, stability 等稳定性特色,咱们通过源码能够看到,其实 Kats 外部对特色确实进行了分组。
并且依据分组,你还能够通过参数去管制你要抉择提取什么类的特色,或者不提取什么类型的特色。同理还有很多参数去管制提取特色时候的 window_size, lag_size, stl_period 等,这些参数大部分要看源码正文能力发现,目前还没有官网 API。
model = TsFeatures(selected_features=['stl_features'])
model.transform(single_kats_ts)
# 如果要反向抉择,则是将这个组或者一个特色的 value 设为 False
model = TsFeatures(stl_features = False)
model.transform(single_kats_ts)
那么这些特色对于咱们剖析工夫序列有什么作用呢,比方咱们能够依据这些统计值,对工夫序列进行分类,对于不同的分类做不同的模型,上面是一个例子:
fcst_ts_list = []
fcst_name_list = []
for name, group in interview_df.groupby(['store', 'sku']):
fcst_df = group[['date', 'sales']].rename(columns={'date':'time', 'sales':'value'})
fcst_ts_df = TimeSeriesData(fcst_df)
fcst_ts_list.append(fcst_ts_df)
fcst_name_list.append(name)
model = TsFeatures(selected_features=['stl_features','statistics'])
# 生成对应每个 pair 的特色
output_features = [model.transform(ts) for ts in fcst_ts_list]
# 将生成的特色和 store sku 分割回去
df_features = pd.DataFrame(output_features)
fcst_name_df = pd.DataFrame(fcst_name_list)
fcst_name_df.columns = ['store', 'sku']
df_features = pd.concat([fcst_name_df, df_features], axis=1)```
失去了每条工夫序列的时序相干特色之后,咱们就能够对它进行聚类操作:
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
X_feats = StandardScaler().fit_transform(df_features.values)
# 为了在一个坐标系外面画出这些工夫序列降维后的后果,暂且把它降到 2d
pca_res = PCA(n_components=2).fit_transform(X=X_feats)
df_features['pca_component_1'] = pca_res[:,0]
df_features['pca_component_2'] = pca_res[:,1]
# 画图
df_features.plot(x='pca_component_1', y='pca_component_2', kind='scatter')
# 聚类
kmeans = KMeans(n_clusters=3).fit(df_features[['pca_component_1', 'pca_component_2']])
# 看看每个类的 value_counts
print(np.unique(kmeans.labels_, return_counts=True))```
Detection
之前的两大 Features,Simulator 是为了更好的发明模仿工夫序列,然而在理论工程化我的项目中实用性比拟低,tsFeature 这块性能全面,然而很多我的项目可能不会这么细节的去剖析每一条序列的特色。
而 Detection 这一块是 Kats 这个库花了最大量的代码量的一个性能点,它的次要作用是在异样值的检测,复原,大幅度的 changepoint 的检测等。在之前如果在时序预测的场景中遇到要进行异样值检测的话,我个别都会用 Pyod。当初在看完这个库之后,对于 time-series specific 的场景,可能就会多一种抉择了。
咱们先从一个比拟高的维度看一下 Kats Detection 实现了什么性能
从源码来看 kats.detectors.detector 外面实现了一个 Detector 抽象类:
大量的具体实现的检测器都继承了这个类,通过办法和对应签名还是正文看应该就是 detector()去检测对应你想要检测的异样点,而后 remover()办法进行去除异样点操作,或者抉择 interpolate=True 应该能够有一些插值操作。
从具体检测器的分类上来看,检测器分为两大类,一类是专门用来检测离群点异样点,一类是专门用来检测渐变点(changepoint)
Outlier Detection
Outlier Detection 的用法是绝对简略的,它的原理实际上就是调用了 statsmodels.tsa.seasonal.seasonal_decompose 对工夫序列进行合成,如果剩下的序列 seasonality 或者 trend 很显著的话,再去除 seasonality 和 trend,在剩下绝对稳固,没有季节性,趋势性影响的序列中,如果还是一个超过 3 倍四分位距 (inter quartile range) 的点则为离群点。
# 该 outlier detector 只能解决 univariate time series, decomp 参数是指它被进行 seasonal_decompose 的办法, iqr_mult 是大于几倍的 iqr 被认为是离群点
ts_outlierDetection = OutlierDetector(single_kats_ts, decomp = 'additive', iqr_mult= 3)
ts_outlierDetection.detector() # apply OutlierDetector
# 查看对应所有 outliers 的 time_index
print(ts_outlierDetection.outliers[0])# 之前在基抽象类中看到的 remover,如果抉择 interpolate 的话,会是一个线性插值
ts_outliers_removed = ts_outlierDetection.remover(interpolate = False) # No interpolation
ts_outliers_interpolated = ts_outlierDetection.remover(interpolate = True) # With interpolation```
相似的 outlier detector 应该还有:
# 临时还没用过,用完之后如果感觉成果好补充相干代码
from kats.detectors.prophet_detector import ProphetDetectorModel
能够从源码中看到 ProphetDetectorModel 继承的基类是 DetectorModel,实现的办法是 fit_predict, 比方对于 ProphetDetectorModel,须要输出的是 historical_data 和将来心愿找 anomaly point 的 data
self, data: TimeSeriesData, historical_data: Optional[TimeSeriesData] = None
) -> AnomalyResponse:
"""Trains a model, and returns the anomaly scores.
Returns the AnomalyResponse, when data is passed to it.
Args:
data: TimeSeriesData on which detection is run.
historical_data: TimeSeriesData corresponding to history. History ends exactly where
the data begins.
Returns:
AnomalyResponse object. The length of this object is same as data. The score property
gives the score for anomaly.
"""
# train on historical, then predict on all data.```
changepoint detection
所谓 Changepoint Detection,是有定义的,Wikipedia:In statistical analysis, change detection or change point detection tries to identify times when the probability distribution of a stochastic process or time series changes,最简略的就可能是扭转了整个工夫序列的平均值。
那么 Kats 这个库在这一块退出了很多模型,看了一些简略的介绍一下。
CusumDetector
Cusum 的全程是 cumulative sum,是一种在信号处理畛域检测信号渐变的一种算法。
在 Kats 的实现里,它做了两件事件:
1、依据 cusum 的算法,找出一个 changepoint,这个 changepoint 的满足,在它之前和之后的工夫的累计和渐变最大。这是一个迭代的寻找,初始化为整个序列最核心的点,而后 iteratively 开始挪动寻找。
2、对这个点进行 hypothesis test,这里的 null hypothesis 是这个找到的 changepoint 没有平均值上的渐变,如果它 reject null hypothesis 则视为通过假如断定。
值得注意的是,这个算法只能找出一个 changpoint。
>>> # Univariate CUSUM
>>> timeseries = TimeSeriesData(...)>>> detector = CusumDetector(timeseries)
>>> #Run detector
>>> changepoints = detector.detector()
>>> # Plot the results
>>> detector.plot(changepoints)
之前说了这个 Detector 只能找出一个 changpoint,那如果一个工夫序列外面有超过一个 changepoint,或者你拿到一个实在销量曲线,发现 2020 年上半年都是疫情期间,销量很多异样,其实你在寻找渐变点的时候并不想思考那一段疫情的异样销量怎么办呢,这时候能够应用 interest_window 参数:
然而,如果你将这个参数选取到[40,60],你将看到:
这为什么没有找到 changepoint 呢,背地的起因还是和这个算法实现自身有关系的,在退出 interest window 之后,算法首先会在你的 window 内寻找 changepoint,还记得之前的算法有两步,首先找到 changepoint,而后通过 hypothesis test,也就是 reject null hypothesis。而后在 window 外面就有一个 changepoint 的后果了,接下来你还是要将这个后果在整个工夫序列的数据里一起做一个 hypothesis test,如果这个点在整个工夫序列上都通过了 hypothesis test,能力视为 changpoint,这也就是为什么在 40-60 的 window 上没有找到渐变点。
应用 interest point 这个参数,咱们拆分工夫序列去找到多个 changepoint。
Forecasting
这一块是我认为比拟鸡肋的一块,是对各种 ts 传统预测模型的封装(Prophet, Arima, LSTM, etc),然而惋惜的是仍然没有自适配多条工夫序列并行预测的 API,如果通过 python multiprocessing 去做多线程速度仍然很慢(lgb 3 分钟能跑完的数据跑了大略 50mins),后续可能会尝试另一个仿佛很厉害的 Ray 去试一试。\
总结
综合来看,总结一下 Kats 这个库的优缺点:
长处
- Easy to use
这一点次要体现在它的封装,集体认为它的 tsFeatures 性能比很多库(比方 tslearn,tsfresh)API 调用都要简略快捷;从它封装的模型上看,比方 Arima,Prophet,都有统一的对外接口,这一点对用户也是极好的。另外这个库相对来说比拟轻量,依赖包较少。
- 性能全面
全面的笼罩到工夫序列的大部分性能,从一开始的对工夫序列的剖析,画图,到 tsFeatures 的提取,通过 tsFeatures 能够去寻找一些工夫序列的 outlier, change point,而后通过后面失去的一些 seasonality, trend 的信息去做模型,甚至去做 meta learning 或者 hyper-parameter tuning,非常应答它在官网上说的”One stop shop for time series”。
毛病
- 只是封装了一堆模型并且退出了 ensemble 的 API,感觉在运行速度上和独自调用 Prophet/Arima 并没有什么晋升,也没有反对多条工夫序列的并行。
- 目前还不足比拟齐备的官网 API 文档
综合倡议,我感觉它是一个很好的疾速制订 baseline 的库,如果你手头没有工夫序列相干的轮子的话,他能够很好的辅助你。