应用背景
在数据测试中,通常测试多次,然后取平均值作为最终的参考值。
例如测试程序每次运行的时间数据,得到下面几组:
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个分位,它只能检测那种数据波动非常小的数据中的异常。