KPI异常检测【一】- 时间序列分解算法

目录

1、相关概念

2 、常见的时间序列

3、时间序列分解

3.1 方法介绍

3.2 经典方法

3.3 Holt Winter 指数平滑

3.4 STL分解

4 、异常准则

5、 异常检测算法


1、相关概念

1.1 异常

时序异常检测通常形式化为根据某种标准或正常信号寻找离群数据点。有很多异常类型,但本文只关注那些从商业角度来说最重要的类型,包括意料之外的峰谷、趋势变动、水平变化(level shift)。

数学上表示为:

|预测值-真实值| > 阈值

KPI异常检测【一】- 时间序列分解算法_第1张图片

1.2 时间序列

时间序列又称时间数列或动态数列,是按照时问的先后顺序排列的某一现象的一系列观测值。

1.2.1 组成

(1)现象所属的时间;

(2)现象在不同时间上的观测值。现象在不同时间上的观测值的表现形式,有绝对数、相对数和平均数。

1.2.2 分类

时间序列分为随机性时间序列非随机性时间序列。非随机性时间序列包括:平稳性时间序列、趋势性时间序列和季节性时间序列三种。

不平稳的时间序列称为非平稳。金融市场研究中用到的日数据、周数据序列,一般是非平稳的,比如期货市场中日价格数据构成的时间序列基本土是非平稳的。

2、常见的时间序列

2.1 恒值

KPI异常检测【一】- 时间序列分解算法_第2张图片

y = np.zeros(20)
plt.figure(figsize=(8, 3))
plt.plot(y, color='b', marker='o')
plt.tight_layout(pad=0.5, w_pad=0.5, h_pad=2.0)
plt.show()

2.2 趋势

KPI异常检测【一】- 时间序列分解算法_第3张图片

 

n = 20
x = np.arange(n)
y_s = list(x)
u, sigma = 0.0, 1.0
e = np.random.normal(loc=u, scale=sigma, size=n)
y = y_s + e
plt.figure(figsize=(8, 3))
plt.plot(y, color='b', marker='o')
plt.tight_layout(pad=0.5, w_pad=0.5, h_pad=2.0)
plt.show()

2.3 白噪声

KPI异常检测【一】- 时间序列分解算法_第4张图片

n = 20
x = np.arange(n)
y = np.random.normal(0, 0.5, 20)
plt.figure(figsize=(8, 3))
plt.plot(y, color='b', marker='o')
plt.tight_layout(pad=0.5, w_pad=0.5, h_pad=2.0)
plt.show()

 2.4 周期

KPI异常检测【一】- 时间序列分解算法_第5张图片

n = 20
x = np.arange(n)
noise = np.random.normal(0, 0.2, 20)
season = np.array([math.sin(i/3*math.pi) for i in x])
y = season + noise
plt.figure(figsize=(8, 3))
plt.plot(y, color='b', marker='o')
plt.tight_layout(pad=0.5, w_pad=0.5, h_pad=2.0)
plt.show()

2.5 周期+趋势

KPI异常检测【一】- 时间序列分解算法_第6张图片

np.random.seed(0)
x = np.arange(start=0, step=0.04, stop=2)
trend = [(a-1)*(a-2)*(a-3) for a in x]
noise = np.random.normal(0, 0.3, len(x))
season = np.array([math.sin(i/3*math.pi) for i in range(len(x))])
y = trend + season
plt.figure(figsize=(10, 4))
plt.plot(y, color='b', marker='o')
plt.tight_layout(pad=0.5, w_pad=0.5, h_pad=2.0)
plt.show()

3、时间序列分解

3.1 方法介绍

3.1.1 目的

  1. 寻找序列中的周期性和趋势性
  2. 进行时序预测
  3. 异常检测

3.1.2 主要组成

趋势部分、周期部分、随机部分

3.1.3 模型

当预测对象依时间变化呈现某种上升或下降的趋势,并且有季节波动时,构造时间序列分解模型:

加法模型:Y_{t}=S_{t}+T_{t}+R_{t}

乘法模型Y_{t}=S_{t}\times T_{t}\times R_{t}

  •      Y——需求预测
  •   T——趋势
  •   S——季节
  •   R——残差

选用准则:

  • 当周期性不随趋势而变化时,选加法模型,否则选乘法模型;
  • 当目标有负值时,选加法模型;
  • 当处理的是经济相关数据时,选乘法模型,增加可解释性。

3.1.4 方法

  1. 经典方法
  2. Holt-Winters方法
  3. STL(Seasonal-Trend decomposition procedure based on Loess)方法

3.2 经典方法

滑动平均值是从一个有n项的时间序列中来计算多个连续m项序列的平均值。

以3年滑动平均值举例:

有1、2、3、4、5共5个数,计算过程为:(1+2+3)/3=2,(2+3+4)/3=3,(3+4+5)/3=4

3.2.1 算法步骤

  1. 使用滑动平均,减去滑动平均的均值,得到趋势;
  2. 计算趋势序列
  3. 在趋势序列中,间隔为周期的序列求均值,得到周期;
  4. 标准化周期数据,即和为0;
  5. 求残差

KPI异常检测【一】- 时间序列分解算法_第7张图片

# 数据准备-变周期
np.random.seed(0)
x = np.arange(start=0, step=0.02, stop=4)
trend = [(a - 1) * (a - 2) * (a - 3) for a in x]
season = np.array([math.sin(i / 3 * math.pi) for i in range(len(x))])
noise = np.random.normal(0, 0.2, len(x))
y = trend + season + noise
plt.figure(figsize=(12, 5))
pd.Series(data=y, index=x).plot(color='r', linestyle='-')

KPI异常检测【一】- 时间序列分解算法_第8张图片

# 模型展示
from statsmodels.tsa import seasonal
decompose_model = seasonal.seasonal_decompose(y, freq=12, model='additive')
fig, ax_arr = plt.subplots(4, sharex=True)
fig.set_size_inches(12, 8)

pd.Series(data=y, index=x).plot(ax=ax_arr[0], color="b", linestyle='-')
ax_arr[0].set_title("Original sequence")

pd.Series(data=decompose_model.trend, index=x).plot(ax=ax_arr[1], color="c", linestyle='-')
ax_arr[1].set_title("Trend section")

pd.Series(data=decompose_model.seasonal, index=x).plot(ax=ax_arr[2], color="g", linestyle='-')
ax_arr[2].set_title("Seasonal section")

pd.Series(data=decompose_model.resid, index=x).plot(ax=ax_arr[3], color="r", linestyle='-')
ax_arr[3].set_title("Residual section")

plt.tight_layout(pad=0.5, w_pad=0.5, h_pad=2.0)
plt.show()

 3.2.2 优缺点

优点:

  • 简单,算法研究的基础

缺点:

  • 前后时刻的值有缺失,无法预测
  • 不能识别变周期
  • 对异常点敏感

3.3 Holt Winter 指数平滑

指数平滑法(exponential smoothing)是一种简单的计算方案,可以有效的避免上述问题。按照模型参数的不同,指数平滑的形

式可以分为一次指数平滑法、二次指数平滑法、三次指数平滑法

  • 一次指数平滑法——>针对没有趋势和季节性的序列
  • 二次指数平滑法——>针对有趋势但是没有季节特性的时间序列
  • 三次指数平滑法——>可以预测具有趋势和季节性的时间序列。术语“Holt-Winter”指的是三次指数平滑。

3.3.1  一次指数平滑

指数平滑法是一种结合当前信息和过去信息的方法,新旧信息的权重由一个可调整的参数控制,各种变形的区别之处在于其“混

合”的过去信息量的多少和参数的个数。

一次指数平滑的递推关系公式:

其中,s_i是第i步经过平滑的值,x_i是这个时间的实际数据。alpha是加权因子,取值范围为[0,1],它控制着新旧信息之间的权重平衡。当alpha接近1时,我们就只保留当前数据点(即完全没有对序列做平滑操作),当alpha接近0时,我们只保留前面的平滑值,整个曲线是一条水平的直线。在该方法中,越早的平滑值作用越小,从这个角度看,指数平滑法像拥有无限记忆且权值呈指数级递减的移动平均法。 

一次指数平滑法的预测公式为:

因此,一次指数平滑法得到的预测结果在任何时候都是一条直线。并不适合于具有总体趋势的时间序列,如果用来处理有总体趋势的序列,平滑值将滞后于原始数据,除非alpha的值非常接近1,但这样使得序列不够平滑。

3.2.2 二次指数平滑法

二次指数平滑法保留了平滑信息和趋势信息,使得模型可以预测具有趋势的时间序列。二次指数平滑法有两个等式和两个参数:

t_i代表平滑后的趋势,当前趋势的未平滑值是当前平滑值s_i和上一个平滑值s_{i-1}的差。s_i为当前平滑值,是在一次指数平滑基础上加入了上一步的趋势信息t_{i-1}。利用这种方法做预测,就取最后的平滑值,然后每增加一个时间步长,就在该平滑值上增加一个t_{i}:

在计算的形式上这种方法与三次指数平滑法类似,因此,二次指数平滑法也被称为无季节性的Holt-Winter平滑法。

3.2.3 Holt-Winter指数平滑法

三次指数平滑法相比二次指数平滑,增加了第三个量来描述季节性。累加式季节性对应的等式为:

累乘式季节性对应的等式为:

KPI异常检测【一】- 时间序列分解算法_第9张图片

其中p_i为周期性的分量,代表周期的长度。x_{i+h}为模型预测的等式。

3.2.4 优缺点

优点:

  • 拟合趋势和周期
  • 快速响应趋势和周期的变化

缺点:

  • 异常点影响大

KPI异常检测【一】- 时间序列分解算法_第10张图片

# 数据同上
# 模型展示
from statsmodels.tsa.api import ExponentialSmoothing
holt_winter_model = ExponentialSmoothing(y, seasonal_periods=6, trend="add", seasonal="add", damped=False).fit(use_boxcox=False)
fig, ax_arr = plt.subplots(4, sharex=True)
fig.set_size_inches(12, 8)

pd.Series(data=y, index=x).plot(ax=ax_arr[0], color="b", linestyle='-')
ax_arr[0].set_title("Original sequence")

pd.Series(data=holt_winter_model.level, index=x).plot(ax=ax_arr[1], color="c", linestyle='-')
ax_arr[1].set_title("Trend section")

pd.Series(data=holt_winter_model.season, index=x).plot(ax=ax_arr[2], color="g", linestyle='-')
ax_arr[2].set_title("Seasonal section")

pd.Series(data=holt_winter_model.resid, index=x).plot(ax=ax_arr[3], color="r", linestyle='-')
ax_arr[3].set_title("Residual section")

plt.tight_layout(pad=0.5, w_pad=0.5, h_pad=2.0)
plt.show()

3.4 STL分解

 STL(’Seasonal and Trend decomposition using Loess‘ )是以鲁棒局部加权回归作为平滑方法的时间序列分解方法。

       其中Loess(locally weighted scatterplot smoothing,LOWESS or LOESS)为局部多项式回归拟合,是对两维散点图

进行平滑的常用方法,它结合了传统线性回归的简洁性和非线性回归的灵活性。当要估计某个响应变量值时,先从其预

测变量附近取一个数据子集,然后对该子集进行线性回归或二次回归,回归时采用加权最小二乘法,即越靠近估计点的

值其权重越大,最后利用得到的局部回归模型来估计响应变量的值。用这种方法进行逐点运算得到整条拟合曲线。

STL的具体流程如下:

outer loop:
    计算robustness weight;
    inner loop:
        Step 1 去趋势;
        Step 2 周期子序列平滑;
        Step 3 周期子序列的低通量过滤;
        Step 4 去除平滑周期子序列趋势;
        Step 5 去周期;
        Step 6 趋势平滑;

基于LOESS将某时刻的数据Yv分解为趋势分量(trend component)、周期分量(seasonal component)和余项(remainder component):

Yv=Tv+Sv+Rv=1,⋯,N

STL分为内循环(inner loop)与外循环(outer loop),其中内循环主要做了趋势拟合与周期分量的计算。假定T_{v}^{(k)}S_{v}^{(k)}为内循环中第k-1次pass结束时的趋势分量、周期分量,初始时T_{v}^{(k)}=0;并有以下参数:

  • n(i)内层循环数,
  • n(o)外层循环数,
  • n(p)为一个周期的样本数,
  • n(s)为Step 2中LOESS平滑参数,
  • n(l)n为Step 3中LOESS平滑参数,
  • n(t)为Step 6中LOESS平滑参数。

每个周期相同位置的样本点组成一个子序列(subseries),容易知道这样的子序列共有n(p)个,我们称其为cycle-subseries。内循环主要分为以下6个步骤:

  • Step 1: 去趋势(Detrending),减去上一轮结果的趋势分量,Y_{{v}}-T_{v}^{(k)}
  • Step 2: 周期子序列平滑(Cycle-subseries smoothing),用LOESS (q=n_{n(s)},d=1)对每个子序列做回归,并向前向后各延展一个周期;平滑结果组成temporary seasonal series,记为C_{v}^{(k+1)};
  • Step 3: 周期子序列的低通量过滤(Low-Pass Filtering),对上一个步骤的结果序列C_{v}^{(k+1)}依次做长度为2*n(p)、3的滑动平均(moving average),然后做LOESS (q=n_{n(l)},d=1)回归,得到结果序列L_{v}^{(k+1)};
  • Step 4: 去除平滑周期子序列趋势(Detrending of Smoothed Cycle-subseries),得到周期S_{v}^{(k+1)}=C_{v}^{(k+1)} - L_{v}^{(k+1)}
  • Step 5: 去周期(Deseasonalizing),减去周期分量,Y_{v}-S_{v}^{(k+1)}
  • Step 6: 趋势平滑(Trend Smoothing),对于去除周期之后的序列做LOESS (q=n_{n(t)},d=1)回归,得到趋势分量T_{v}^{(k+1)}

KPI异常检测【一】- 时间序列分解算法_第11张图片

外层循环主要用于调节robustness weight。如果数据序列中有outlier,则余项会较大。定义

h=6\ast median(\left | R_{v} \right |)

对于位置为v的数据点,其robustness weight为

ρv=B(|Rv|/h)ρv=B(\left |frac{R_{v}}{h}\right |)\rho _{v} = B(R_{v}/h)

其中B函数为bisquare函数:

B(u)=\left\{\begin{matrix} (1-u^{2})^{2} , 0\leqslant u< 1\\ 0 , u\geqslant 1 \end{matrix}\right.

然后每一次迭代的内循环中,在Step 2与Step 6中做LOESS回归时,邻域权重(neighborhood weight)需要乘以ρv,以减少outlier对回归的影响。

为了使得算法具有足够的robustness,所以设计了内循环与外循环。特别地,当n(i)足够大时,内循环结束时趋势分量与周期分量已收敛;若时序数据中没有明显的outlier,可以将n(o)设为0。

KPI异常检测【一】- 时间序列分解算法_第12张图片

# pip install git+https://github.com/statsmodels/statsmodels.git
from statsmodels.tsa.seasonal import STL 
stl = STL(y, freq=6).fit()
fig = stl.plot()
fig.set_size_inches(12, 8)
fig.set_tight_layout(True)
plt.show()

4、异常准则

4.1 n-sigma准则

n-sigma准则基于目标分布是正态或近似正态分布。一般地,选取n=3。根据正态分布的概率密度公式可以算出,样本取值几乎全部(99.7%)集中在\left ( \mu -3\sigma ,\mu +3\sigma \right ) (\mu -3\sigma ,\mu +3\sigma )区间内,其中,

超出这个范围的可能性仅占不到0.3%,可以认为是小概率事件。因此,检测的方法也比较简单,如下:

  1. 检测突增异常:异常区间为 X> \mu +3\sigma ;
  2. 检测突降异常:异常区间为 X< \mu -3\sigma ;
  3. 检测双向(突增或突降)异常:  X< \mu -3\sigmaX> \mu +3\sigma, 如下图所示;

 

KPI异常检测【一】- 时间序列分解算法_第13张图片

 

优点:计算简单、效率高且有很强的理论支撑

缺点:需要近似正态的假设,且均值和标准差的计算用到了全部的数据,因此,受异常点的影响较大。

KPI异常检测【一】- 时间序列分解算法_第14张图片

# 数据准备
np.random.seed(0)
x = np.arange(40)
y = np.random.normal(loc=5, scale=1, size=len(x))
y[20] += 10
plt.figure(figsize=(8, 3))
pd.Series(data=y, index=x).plot(color='b', linestyle='-')

KPI异常检测【一】- 时间序列分解算法_第15张图片

# 计算上下线
n = 3
mean = y.mean()
sigma = y.std()
resid = y - mean
up_threshold = n * sigma
low_threshold = -n * sigma
outlier_indexs = [i for i in range(len(resid)) if resid[i] > up_threshold or resid[i] < low_threshold]
outlier_values = resid[outlier_indexs]

plt.title("Abnormal point")
plt.figure(figsize=(8, 3))
pd.Series(data=resid, index=x).plot(color='b', linestyle='-')  # 残差
plt.plot(outlier_indexs, outlier_values, "ro", label="point")  # 异常点
plt.hlines(up_threshold, x[0], x[-1], colors="r", linestyles="dashed")   # 上线
plt.hlines(low_threshold, x[0], x[-1], colors="r", linestyles="dashed")  # 下线
plt.show()

4.2 boxplot 准则

为了降低异常点的影响,boxplot准则被提出。boxplot(箱线图)是一种用作显示一组数据分散情况的统计图,经常用于异常检测。BoxPlot的核心在于计算一组数据的中位数、两个四分位数、上限和下限,基于这些统计值画出箱线图。

记 Q1、Q3分别表示一组数据的下四分位数和上四分位数,则

KPI异常检测【一】- 时间序列分解算法_第16张图片

根据上面的统计值就可以画出下面的图,超过上限的点或这个低于下限的点都可以认为是异常点。KPI异常检测【一】- 时间序列分解算法_第17张图片

 

KPI异常检测【一】- 时间序列分解算法_第18张图片

# 数据准备
np.random.seed(0)
x = np.arange(40)
y = np.random.normal(loc=5, scale=1, size=len(x))
y[20] += 10
mean = y.mean()
sigma = y.std()
resid = y - mean

Q1 = np.percentile(resid, 25)
Q3 = np.percentile(resid, 75)
IQR = Q3 - Q1
up_threshold = Q3 + 1.5*IQR
low_threshold = Q1 - 1.5*IQR
outlier_indexs = [i for i in range(len(resid)) if resid[i] > up_threshold or resid[i] < low_threshold]
outlier_values = resid[outlier_indexs]

plt.figure(figsize=(8, 3))
plt.title("Abnormal point")
pd.Series(data=resid, index=x).plot(color='b', linestyle='-')  # 残差
plt.plot(outlier_indexs, outlier_values, "ro", label="point")  # 异常点
plt.hlines(up_threshold, x[0], x[-1], colors="r", linestyles="dashed")   # 上线
plt.hlines(low_threshold, x[0], x[-1], colors="r", linestyles="dashed")  # 下线
plt.show()

5、 异常检测算法

异常检测过程

KPI异常检测【一】- 时间序列分解算法_第19张图片

 序列拟合与预测+异常准则

基于“STL算法+boxplot准则”的异常检测算法,详细步骤如下:

Step1: 使用 STL 算法分解时序,得到残差;

Step2: 基于 box-plot 准则求得残差的上界和下界;

Step3: 当残差小于Step2中的下界,或者残差大于Step2中的上界,则认为是异常点。

__________________________________

参考文献:

https://www.jianshu.com/p/0aec95162cc6

https://www.cnblogs.com/en-heng/p/7390310.html

https://www.cnblogs.com/runner-ljt/p/5245080.html

https://zhuanlan.zhihu.com/p/142904065

你可能感兴趣的:(异常检测,机器学习,算法,人工智能,机器学习,python)