异常值检测-滑动均值实现智能告警

来源:互联网 发布:imp命令导入远程数据库 编辑:程序博客网 时间:2024/06/07 16:07

前言

  当前的分析对象是一段 timestamp-value 的时间序列,该时间序列可能是cpu使用率、磁盘使用率等数据。对于这些序列实现智能告警功能(也就是检测出一段序列中的异常值),因此需要我们在计算前,首先要定义什么样的值是异常值。
  基于移动平均的方法,其朴素思想是在直观上来看图形,认为相近一段时间内的数据值,有相似的走向趋势。因此判断一个值是否是异常值,可通过判断该值是否对数据趋势造成了破坏,以此来得出结论。
  如下图所示,其中某一点明显波动异常。所以直观上来看,就可以判断出该点是异常点。
这里写图片描述
  那么如何通过某些方法,对异常点进行更加科学地判断以及代码实现。即本文主要工作内容。

移动平均方法介绍

简单移动平均

  简单移动平均线(Simple Moving Average,SMA),是某变量之前 n 个数值的未作加权的算术平均。若计算收盘价的10日 SMA,是指“之前10日”收市价的平均值。假设,收市价为 p=(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10),则 SMA 公式为:
这里写图片描述
推广到一般情况为:
这里写图片描述

加权移动平均

  加权移动平均(Weighted Moving Average,WMA),是指计算平均值时将各个数据分别乘以不同权重,其基本思想是赋予近期数据更高的行为权重。WMA是线性的。
  若计算n日WMA,假设收市价为 p= (p1, p2, … , pn, pn+1, … , pm, …),则为若干天的收市价及依倒数顺序递减一的不同比例,来加权计算的移动平均线,公式如下:
这里写图片描述

指数加权移动平均

  指数加权移动平均(EWMA:exponentially weighted moving average),公式如下:
这里写图片描述
  x是实际值,s是ewma计算出来的平均值。也就是下一点的平均值是由上一点的平均值,加上当前点的实际值修正而来。这个修正的比例,就取决月这个alpha的decay factor的大小。视觉上来说就是ewma曲线是否紧跟实际曲线,也就是平滑程度。
  要避免由于简单移动平均导致的缺陷,最简单的方法是对近期的数据赋予更高的权重。这也是指数加权移动平均法(EWMA)背后的基本思想

Python 实现

python pandas包中,对于ewma和sma有现成的实现方法。但是对于wma没有找到实现,在此,自己写了个简单地wma实现函数。

# coding:utf-8from __future__ import divisionimport pandas as pdimport matplotlib.pyplot as pltclass WMA(object):    """    加权移动平均实现    """    @staticmethod    def get_wma_weights(span, flag=True):        """        计算每个数值点的wma权重值        """        paras = range(1, span + 1)        count = sum(paras)        if flag:            return [float(para) / count for para in paras]        else:            return [float(para) / count for para in paras][::-1]    def get_wma_values(self, datas):        """        计算wma数值        """        wma_values = []        wma_keys = datas.index        for length in range(1, len(datas) + 1):            wma_value = 0            weights = self.get_wma_weights(length)            for index, weight in zip(datas.index, weights):                wma_value += datas[index] * weight            wma_values.append(wma_value)        return pd.Series(wma_values, wma_keys)'''1. 首先构造测试数据'''# TODO 以后改成读取数据,直接生成 pandas Series 对象data_dict_series = {    '1490707920': 19.8219660272,    '1490707980': 20.0681534509,    '1490708040': 20.1842385903,    '1490708100': 19.7650368611,    '1490708160': 19.9269200861,    '1490708220': 18.470530654,    '1490708280': 18.2211077462,    '1490708340': 19.3140179366,    '1490708400': 20.5632611228,    '1490708460': 20.6341463476,    '1490708520 ': 8.9789650937,    '1490708580': 19.3494873891,    '1490708640': 20.0160623292,    '1490708700': 19.6702364186,    '1490708760': 19.624609674,    '1490708820': 20.456564799,    '1490708880': 17.6035744548,    '1490708940': 17.1818237007,    '1490709000': 20.600406309,    '1490709060': 19.9717502842,    '1490709120': 20.0490164061,    '1490709180': 20.1280070096,    '1490709240': 20.579920421,    '1490709300': 19.0682847972,    '1490709360': 20.6150588592,    '1490709420': 19.8059894244,    '1490709480': 19.7459333887,    '1490709540': 20.1206678947,    '1490709600': 21.0094336718,    '1490709660': 19.8375952393}dps = pd.Series(data_dict_series)'''2. ewma进行拟合'''ewma_line = pd.ewma(dps, span=4)'''3. 简单移动平均'''sma_line = pd.rolling_mean(dps, window=4)'''4. wma加权移动平均'''wma_line = WMA().get_wma_values(dps)'''5. 对原始数据和拟合结果数据进行画图'''# plt.figure(figsize=(10, 4))dps.plot(label='sample data')ewma_line.plot(label='sample data EMA result')sma_line.plot(label='sample data SMA result')wma_line.plot(label='sample data EWMA result')plt.legend()plt.show()

结果展示

SMA和原始数据对比

SMA和原始数据对比

WMA和原始数据对比

WMA和原始数据对比

EWMA和原始数据对比

EMA和原始数据对比

总体平滑效果对比

总体对比

异常点检测

  上面通过滑动平均算法,得到了滑动均值,通过均值可计算方差,方差乘以一定的倍数可以得出对于振幅的容忍范围。比较实际的值是否超出了这个范围就可以知道是否可以告警了。
  在本文的示例中,告警的正常的浮动范围设为一倍的方差。可以正常浮动范围可以根据实际业务进行调整。对于异常点监测通过如下代码实现。

# coding:utf-8from __future__ import divisionimport pandas as pdclass WMA(object):    """    加权移动平均实现    """    @staticmethod    def get_wma_weights(span, flag=True):        """        计算每个数值点的wma权重值        """        paras = range(1, span + 1)        count = sum(paras)        if flag:            return [float(para) / count for para in paras]        else:            return [float(para) / count for para in paras][::-1]    def get_wma_values(self, datas):        """        计算wma数值        """        wma_values = []        wma_keys = datas.index        for length in range(1, len(datas) + 1):            wma_value = 0            weights = self.get_wma_weights(length)            for index, weight in zip(datas.index, weights):                wma_value += datas[index] * weight            wma_values.append(wma_value)        return pd.Series(wma_values, wma_keys)def calculate_variance(dps, moving_average):    variance = 0    flag_list = moving_average.isnull()    count = 0    for index in range(len(dps)):        if flag_list[index]:            count += 1            continue        variance += (dps[index] - moving_average[index]) ** 2    variance /= (len(dps) - count)    return variance'''1. 首先构造测试数据'''# TODO 以后改成读取数据,直接生成 pandas Series 对象data_dict_series = {    '1490707920': 19.8219660272,    '1490707980': 20.0681534509,    '1490708040': 20.1842385903,    '1490708100': 19.7650368611,    '1490708160': 19.9269200861,    '1490708220': 18.470530654,    '1490708280': 18.2211077462,    '1490708340': 19.3140179366,    '1490708400': 20.5632611228,    '1490708460': 20.6341463476,    '1490708520 ': 8.9789650937,    '1490708580': 19.3494873891,    '1490708640': 20.0160623292,    '1490708700': 19.6702364186,    '1490708760': 19.624609674,    '1490708820': 20.456564799,    '1490708880': 17.6035744548,    '1490708940': 17.1818237007,    '1490709000': 20.600406309,    '1490709060': 19.9717502842,    '1490709120': 20.0490164061,    '1490709180': 20.1280070096,    '1490709240': 20.579920421,    '1490709300': 19.0682847972,    '1490709360': 20.6150588592,    '1490709420': 19.8059894244,    '1490709480': 19.7459333887,    '1490709540': 20.1206678947,    '1490709600': 21.0094336718,    '1490709660': 19.8375952393}dps = pd.Series(data_dict_series)'''2. ewma进行拟合'''ewma_line = pd.ewma(dps, span=4)'''3. 简单移动平均'''sma_line = pd.rolling_mean(dps, window=4)'''4. wma加权移动平均'''wma_line = WMA().get_wma_values(dps)'''5. 计算告警'''# 计算每种平滑下原始数据的标准差sma_var = calculate_variance(dps, sma_line)wma_var = calculate_variance(dps, wma_line)ewma_var = calculate_variance(dps, ewma_line)flag_list = sma_line.isnull()for index in sma_line.index:    if not (sma_line[index] - sma_var <= dps[index] <= sma_line[index] + sma_var):        if not flag_list[index]:            print "异常点", dps[index]print "=================================="for index in wma_line.index:    if not (wma_line[index] - wma_var <= dps[index] <= wma_line[index] + wma_var):        print "异常点", dps[index]print "=================================="for index in ewma_line.index:    if not (wma_line[index] - ewma_var <= dps[index] <= wma_line[index] + ewma_var):        print "异常点", dps[index]

输出结果如下图所示:
这里写图片描述

小结和参考

小结

 本文使用了各种平滑算法对数据进行处理,但是算法见性能和效果,并没有进行科学地度量。这部分工作准备日后进行的。不过目前就从图上来看EWMA效果感觉还是不错的~~over

参考

告警方式介绍:
https://segmentfault.com/a/1190000003021919
滑动平均介绍:
http://www.cnblogs.com/liuning8023/p/3548770.html
https://en.wikipedia.org/wiki/Exponential_smoothing
相关代码实现:
http://stackoverflow.com/questions/9621362/how-do-i-compute-a-weighted-moving-average-using-pandas
http://stackoverflow.com/questions/18517722/weighted-moving-average-in-python
pandas用户手册:
http://pandas.pydata.org/pandas-docs/version/0.17.0/api.html#standard-moving-window-functions

0 0
原创粉丝点击