本文作者: 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_datasetfrom kats.consts import TimeSeriesDatainterview_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,一列叫做valuesingle_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 inlinesingle_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 datasim = 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 shiftstrend_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 shiftslevel_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 warningswarnings.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设为Falsemodel = 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 PCAfrom sklearn.preprocessing import StandardScalerfrom sklearn.cluster import KMeansX_feats = StandardScaler().fit_transform(df_features.values)# 为了在一个坐标系外面画出这些工夫序列降维后的后果,暂且把它降到2dpca_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_countsprint(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_indexprint(ts_outlierDetection.outliers[0])# 之前在基抽象类中看到的remover,如果抉择interpolate的话,会是一个线性插值ts_outliers_removed = ts_outlierDetection.remover(interpolate = False) # No interpolationts_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 的库,如果你手头没有工夫序列相干的轮子的话,他能够很好的辅助你。