基于统计学的异常检测算法

应用背景

在数据测试中,通常测试多次,然后取平均值作为最终的参考值。

例如测试程序每次运行的时间数据,得到下面几组:

67.9109, 135.1102, 134.9953, 85.9153, 73.8646, 69.5817, 123.0897, 93.6666, 113.9593, 86.8969

111.9058, 126.425, 87.7191, 95.9987, 125.3884, 117.6314, 111.8465, 96.1856, 62.4525, 102.2092

107.4349, 91.2075, 105.0072, 98.953, 97.4755, 121.862, 42.1561, 97.66, 100.4861, 96.5518

但是可能由于一些额外影响因素,导致其中某些值过大,如果算入平均数,会拉大平均数。我们希望识别并剔除掉这种明显异常的数据。


一个实际例子:

例如下面的周期间隔数据:

pero: 3176
pero: 3018
pero: 3039
pero: 2785
pero: 2133
pero: 2394
pero: 2178
pero: 2809
pero: 3357
pero: 3466
pero: 716781
pero: 975873
pero: 2681
pero: 2880
pero: 2160
pero: 3890
pero: 3177
pero: 3792

从数据直观看出,时间周期正常都是3秒左右,有30%以内的误差,但是偶尔周期很长,达到300多倍的误差,如果算平均值,这种异常的误差会造成严重影响。可优化为取中位数。但是取中位数,只适合周期比较固定的情况(此处是波动小于30%),那种周期变化幅度非常大(例如几十上百倍差距),则不适合。


异常检测目标及思路

首先,什么是异常值,我们对异常值的定义是什么? 

例如上面的第三组数组中,42.1561算是异常吗?

异常的定义,其实是 跟大多数存在较大差别的数据

“大多数”用百分比来表示,可以自己定义,例如我定义大于80%,就代表上面10个数据,如果某个数据,跟其他80%以上的数据都在同一个范围,就认为正常。如果不在一个范围,就属于异常。

但是这个范围(差别)怎么定义?可以用平均数,四分位数,或标准方差等多种方式来定义。


最简单的是使用平均数,例如设定:

偏离(平均数的1.2倍)算作异常。

但是平均数这种方式“简单粗暴”,不一定准确。因为某些异常值会严重拉大或者拉小平均数,此时平均数已经不能代表大多数了。懂数学的人会想到,使用方差来表示。


方差表示法,例如:

  • 距离平均数1个标准差范围占比68%

  • 距离平均数2个标准差范围占比95%

  • 距离平均数3个标准差范围占比99.7%

因此,这个例子中,可以认为,偏离2个标准差范围的数据,属于异常数据

但是这种方式,也存在很大问题。以下面几组数据为例:

O67.91, 135.11, 134.99, 85.91, 73.86, 69.58, 123.09, 93.67, 113.96, 86.90, 
标准差/平均值=0.253, 标准差:24.922, 平均值:98.499

111.90, 126.42, O87.71, 95.99, 125.38, 117.63, 111.84, 96.18, O62.45, 102.20, 
标准差/平均值=0.177, 标准差:18.410, 平均值:103.776

107.43, 91.20, 85.00, 98.95, 97.47, 111.86, 82.15, 97.66, 100.48, 86.55, 
标准差/平均值=0.096, 标准差:9.169, 平均值:95.879

107.43, 91.20, 105.00, 98.95, 97.47, 121.86, O42.15, 97.66, 100.48, 96.55
标准差/平均值=0.204, 标准差:19.588, 平均值:95.879

看最后一组数据,最小值为42.15,最大值为121.86,相差了将近3倍。

说明:单看标准方差的绝对值,没有意义,因为数据大小范围不同,方差值也不同。但是,我们可以看 方差比例(标准差/平均值),这个值不会随着数据的变大而变大,只会随着数据的波动幅度而变化。

定义:方差比例 P = 标准差/平均值


方差(标准差)反映的是数据的波动幅度,数据有波动是正常的,方差有大有小,不代表数据一定有异常值。

但是我试图研究和证明两个问题:

  • 能不能说 方差比例(标准差/平均值) 大于多少,就代表数据有异常或者无规律?

  • 能不能说 方差比例(标准差/平均值) 小于多少,就代表数据波动很小,可以忽略异常值?


第二个问题,我已经通过实验证明了:如果 标准差/平均值 < 0.1,可以通过大数据实验证明数据波动幅度小,虽然不能证明没有异常数据,但是可以证明,即便有异常数据,也不能对平均值造成较大影响(如果异常数据已经达到了能对平均值造成影响的程度,那么标准差/平均值就不会 < 0.1)。因此可以说:

如果 标准差/平均值 < 0.1,那么平均值就能反映数据的情况,不需要看异常百分比。

举个极端例子:

例如 { 10, 10, 10, ……, 10, 20 },(一百个10里面混了一个20),方差比例=0.099,平均值:10.10,这个时候虽然有1个异常数据,但是对平均值的影响只有千分之一,可以忽略。


第一个问题,方差比例较大,有两种情况:

1种是 有少量异常数据,方差比例值越大 代表 异常数据对结果影响越大;

例如 { 10, 10, 10, ……, 10, 60 },一百个10里面混了一个60,P=0.474。

{ 10, 10, 10, ……, 10, 2100 },一百个10里面混了一个2100,P=6.73。

第二种情况是 没有异常数据,但是数据本身波动特别大

例如 { 2, 4, 6, 7, 8, 9, 12, 36, 10, 11 } P=0.856

{ 2, 14, 6, 77, 18, 99, 12, 36, 20, 990 } P=2.27

但是,能说明:如果方差比例较大,能说明数据有异常或者无规律吗?


要根据方差较大来判断,就要区分上面说的两种情况,如果有数据异常,找出异常的数据,如果数据没有异常,但是数据波动大,那么到底波动程度多大算是无规律?


我们以 P=标准差/平均值 > 0.1 为界,先看看能否找到异常数据。先看一组数据:

137.24, 123.0, 75.06, 86.57, 135.74, 105.12, 120.31, 102.77, 135.24, 124.57,
    标准差/平均值=0.178, 标准差:20.413, 平均值:114.562
    超过(50.0%)的数据落在 1 个标准差以内(94~135)
    超过(100.0%)的数据落在 2 个标准差以内(74~155)
  (超过80%后则停止计算)

这个数据,只有50%的数据落在1个标准差以内,说实话波动有点大,但是标准差也大,导致100%的数据都在2个标准差以内。针对这种情况,我们没用找到异常数据,但是75.06已经非常接近异常数据了。


改进算法,判断正常数据百分比的时候,标准差只考虑1~2倍的情况,超过2倍说明波动较大,倍数从1~2倍,以0.1为步长增加,这样平衡一些,避免上面出现的50%跳跃到100%,改进后重新计算结果如下:

标准差/平均值=0.178, 标准差:20.413, 平均值:114.562
超过(50.0%)的数据落在 1.0 个标准差以内
超过(70.0%)的数据落在 1.1 个标准差以内
超过(80.0%)的数据落在 1.2 个标准差以内
超过(80.0%)的数据落在 1.3 个标准差以内
超过(90.0%)的数据落在 1.4 个标准差以内
异常数据:75.06
(超过80%后则停止计算)

改进后,在1.4个标准差以内就包括了大于80%的数据,而且还找到了异常数据。说明这个算法还不错。

下面是另外两组数据:

第一组:2, 14, 6, 77, 18, 99, 12, 36, 20, 90
标准差/平均值=0.936, 标准差:34.989, 平均值:37.400
超过(60.0%)的数据落在 1.0 个标准差以内
超过(70.0%)的数据落在 1.1 个标准差以内
超过(80.0%)的数据落在 1.2 个标准差以内
超过(80.0%)的数据落在 1.3 个标准差以内
超过(80.0%)的数据落在 1.4 个标准差以内
超过(80.0%)的数据落在 1.5 个标准差以内
超过(90.0%)的数据落在 1.6 个标准差以内
异常数据:99.0

第二组:
{ 10, 10, 10, ……, 10, 114 } (共100个)
标准差/平均值=0.937, 标准差:10.348, 平均值:11.040
超过(99.0%)的数据落在 1.0 个标准差以内
异常数据:114.0

第一组数据,肉眼可见波动很大,按上面的算法,只找到一个异常数据。但实际上没什么意义,因为波动太大,很难判断谁是异常数据。

第二组数据,肉眼可见波动很小,但是(标准差/平均值)跟第一组数据的0.936几乎一样。

这个算法最大的问题就是,没办法区分上面的这两类数据。下面想办法继续改进算法。


核心思路:

剔除异常数据,再重新计算。但是不能无限制的剔除,最多剔除20%的数据,每剔除一次数据后,方差比例值应该会下降很多,直到<0.1为止。如果剔除超过20%的异常数据后,方差并没有明显下降到<0.1,说明数据本身波动很大,再剔除也没必要了,此时就要告警:如果此时方差比例 0.1<P<=0.2,此时数据为 轻度波动,由应用方决定是否采纳。如果 P>0.2 数据为严重波动,不建议应用方采纳。


标准方差异常检测算法 总结

经过我优化改进后的该算法,效果十分好。基本上,给定任意数据集,都可以得出准确的结论。

结论包括如下几种:

1、数据整体波动非常严重,不具备异常检测的基础。同时,算法会指出,哪些数据是严重之中的严重异常

2、数据整体波动比较严重,异常检测的结果仅供参考。同时,算法会指出,哪些数据是严重异常的

3、数据整体波动较小,有少量数据存在异常,算法指出了具体是哪些数据

同时,这个算法具有多个参数可以调节:

参数1:异常数据占总体数据 百分比的标准(例如80%的数据都符合条件,剩下20%就是异常数据)

参数2:数据波动较大时,允许剔除的总的异常数据量,占总数据量的比例(例如20%,就代表至多可以剔除20%的异常数据,如果剔除这些异常数据后,数据变正常了,波动变小了,那说明剔除对了,反之说明剔除了异常数据后,数据波动仍然没好转,数据不具备异常检测的基础)

参数3:在已经剔除了允许的最多异常数据之后,得到的方差比例值P,在P大于多少时,判断数据集是波动非常严重的、不具备异常检测基础的(默认设置为0.2,这是根据经验设置的,如果业务方确实想检测一些数据波动本来就非常大的数据集,可以调大这个值)。


下面给出了一些用该算法检测的结果:

(10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
    10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
    10, 10, 10, 114)
剔除异常数据: 114
标准差/平均值=0.000, 标准差:0.000, 平均值:10.000
数据正常

(2, 14, 6, 77, 18, 99, 12, 36, 20, 90)
剔除异常数据: 99,90,77
标准差/平均值=0.973, 标准差:22.502, 平均值:23.125
异常数据共3个, 占比多于0.8
P=0.973(标准差/平均值) > 0.2,重度异常!

(60.4638, 132.486, 95.5226, 135.8269, 109.8373, 95.1654, 139.3757,
   104.2131, 112.349, 123.3675)
剔除异常数据: 60.4638,139.3757,135.8269 
标准差/平均值=0.129, 标准差:14.648, 平均值:113.596 
异常数据共3个, 占比多于0.8
P=0.129(标准差/平均值) < 0.2,轻度异常!


(91.54, 97.91, 101.8, 89.5, 110.34, 90.91, 87.01, 105.05, 87.67, 113.84,
    112.92, 86.07, 98.52, 107.37, 110.58, 105.75, 81.46, 84.47, 110.41, 109.73, 104.07, 107.25, 98.59, 99.8,
    85.76, 105.08, 117.04, 102.4, 112.12, 89.65, 88.15, 92.66, 109.96, 105.23, 83.54, 80.43, 93.44, 80.3,
    103.29, 116.02, 110.57, 103.73, 113.14, 112.29, 114.23, 104.02, 87.52, 119.57, 109.34, 101.02, 87.36,
    103.93, 109.32, 80.23, 94.12, 113.87, 113.13, 103.64, 103.85, 90.66, 84.35, 86.45, 116.43, 93.18,
    116.23, 96.55, 113.98, 93.8, 106.16, 110.82, 109.84, 110.99, 97.76, 89.8, 111.65, 112.9, 109.1, 105.65,
    93.59, 86.16, 100.73, 95.3, 93.46, 97.05, 102.36, 90.01, 94.93, 84.51, 85.91, 90.4, 103.35, 97.15,
    119.26, 81.38, 98.16, 90.73, 140.14, 100.51, 101.52, 100.06)
剔除异常数据: 81.46 84.47 117.04 83.54 80.43 80.3 116.02 119.57 80.23 84.35 116.43 116.23 84.51 119.26 81.38 140.14
标准差/平均值=0.087, 标准差:8.781, 平均值:100.716
数据正常

算法调节:参数1 = 80%,参数2 = 20%,参数3 = 0.2


总结:该算法的效果非常、非常好,唯一的缺点是算法的复杂度高,每次要求方差,不适合大数据的场景。

(需要该算法的Java源代码的可以找我)


四分位法异常检测算法(箱线图)

为了便于比较,还是以上面的数据为例,例如这两组:

(2, 14, 6, 77, 18, 99, 12, 36, 20, 90)
异常数据: 99,90,77
标准差/平均值=0.973, 标准差:22.502, 平均值:23.125
异常数据共3个, 占比多于0.8
P=0.973(标准差/平均值) > 0.2,重度异常!

(60.46, 95.16, 95.52, 104.21, 109.83, 
  112.35, 123.36, 132.48, 135.82, 139.37)
剔除异常数据: 60.46,139.37,135.82 
标准差/平均值=0.129, 标准差:14.648, 平均值:113.596 
异常数据共3个, 占比多于0.8
P=0.129(标准差/平均值) < 0.2,轻度异常!

四分位中定义异常的思路是:

先把数据从小到大排序,然后切成4端:

  • QL: 称为下四分位数,表示全部观察值中有四分之一的数据取值比它小;

  • QU: 称为上四分位数,表示全部观察值中有四分之一的数据取值比它大;

  • IQR:称为四分位数间距,是上四分位数QU与下四分位数QL之差,其间包含了全部观察值的一半。

异常值通常被定义为 小于 QL-1.5IQR 或 大于 QU+1.5IQR 的值。( IQR = QU-QL )

所以,这个算法超级简单,就是确定上下四分位位置的数据值是多少,然后计算正常值的范围,超出范围就是异常。


对应上面第一组重度异常的数据,用四分位箱线法求得下、中、上三个分位的值为:

Q1=10.5

Q2=19

Q3=80.25

正常值取值范围为:(-94.125 , 184.857)

所以,得出的结论是,所有数据都正常。


同理,对于第二组数据,算得:

Q1=95.43

Q3=133.315

正常值取值范围为:(38.6025, 190.14)

得出的结论也是,所有数据都正常。


可见,这个算法太不靠谱了。由于只有4个分位,它只能检测那种数据波动非常小的数据中的异常。


© 2009-2020 Zollty.com 版权所有。渝ICP备20008982号