指标检测(一):绝对值/离群值异常检测
天苍苍,野茫茫,风吹草地见牛羊。
文章目录
**系列文章导航:**
一、概述
背景思考
- 数据应用中,指标与业务息息相关。指标是数据的数值体现,数据驱动业务的思想基础上,指标的价值必然不会止于业务分析人员依靠业务积累而探寻出来。
- 笔者看来,有意义的指标的价值客观存在,数据驱动–即需要让数据说话,让数据主动推动业务。其中一大价值点在于,把业务问题和业务亮点主动发现,并推动业务人员及时处理问题和推广亮点,积累正确的点,解决错误的点。
- 业务发展,指标数量越来越多。如何能够快速识别系统各项异常指标,发现问题根因,并推动业务进行调改,是一项很重要的能力。如果只通过业务专家基于过往业务经验,手动的设置各类阈值,那么非常耗时并且容易疏漏。
- 我们进而设计了一套自动化方法,实现如下能力目标:
- 自动:不依赖用户设置阈值监控规则,自动化识别异常。
- 通用:适应多种数值分布的指标。
- 时效:实现天、小时级别甚至部分指标分钟级别的监测和归因。
- 主动:数据找人,数据驱动业务。
其中,时效性和主动性,体现在应用产品功能设计上,契合这个目标去做简洁、易用、好用的功能点设计,更关键能力在于自动化、通用的识别异常。
异常分类
首先,指标的异常检测,需要定义出检测出哪类异常。即数据科学工作首先需要对问题进行清晰定义,那么指标的异常分类,有如下三类:
- 绝对值异常:统计学也称之为离群点,不遵循指标分布。反应业务状态。
- 波动异常:环比过大的突增或突降点。反应业务变化。
- 趋势异常:中长期呈现出显著的上升或下降趋势。反应业务可能潜在风险。
如下图示例
三种异常互相独立,不同场景可能对应不同的异常,需要辅以业务情况判断。
基于上述异常类型,结合公开论文和分享资源,首先使用Java语言设计实现了一套基于统计检验的无监督检测框架,并开源在github:
- Github: ad4j:Anomaly Detection For Java 。
- 以轻量级jar形式或源码形式引入业务工程,简洁API调用即可进行异常检测。
- 开源项目首页有详细算法介绍,欢迎各位star、fork参与优化共建。有问题欢迎提issue一起讨论。
接下来,我将一一介绍不同异常类型的检测框架。
二、绝对值/离群值异常检测
3-Sigma是可以检测出显著性异常,但检出率太低,故没有加入算法库。
1.基于GESD的绝对值异常检测
a.原理
计算广义极端学生化偏差统计量寻找异常点
R
i
=
max
i
∣
x
i
−
x
ˉ
∣
s
i
=
1
,
2
,
…
,
r
λ
i
=
(
n
−
i
)
t
p
,
n
−
i
−
1
(
n
−
i
−
1
+
t
p
,
n
−
i
−
1
2
)
(
n
−
i
+
1
)
p
=
1
−
α
2
(
n
−
i
+
1
)
o
u
t
l
i
e
r
=
w
h
e
r
e
(
R
i
>
λ
i
)
\begin{aligned} &\ R_{i} =\frac{\max _{i}\left|x_{i}-\bar{x}\right|}{s} i=1,2, \ldots, r \\ &\ \lambda_{i} =\frac{(n-i) t_{p, n-i-1}}{\sqrt{\left(n-i-1+t_{p, n-i-1}^{2}\right)(n-i+1)}} \\ &\ p =1-\frac{\alpha}{2(n-i+1)} \\ &\ { outlier } =w h e r e \left(R_{i}>\lambda_{i}\right) \end{aligned}
Ri=smaxi∣xi−xˉ∣i=1,2,…,r λi=(n−i−1+tp,n−i−12)(n−i+1)(n−i)tp,n−i−1 p=1−2(n−i+1)α outlier=where(Ri>λi)
这是一个迭代的计算过程:
- 假设有 r 个异常,那么最大需要迭代 r 轮
- 计算当前序列的最大偏离值 R i R_i Ri
- 计算当前序列的临界值 λ i λ_i λi
- 如果 R i > λ i R_i > λ_i Ri>λi 则 序号为i的指标就是异常值,那么i++,进行下一轮迭代,下一轮迭代过程中,指标序列会排除此前已经找到了的异常指标。如果本轮没有找到异常,那么结束异常计算过程。
b.计算过程
-
准备数据集:设定数据集 X = x 1 , x 2 , … , x n X={x_1,x_2,…,x_n} X=x1,x2,…,xn,数据大小为 n。
-
参数设置:
- 最大可检测的异常值个数:r个
- 显著性水平:α,通常取 0.05
-
迭代计算:
-
1)计算均值和标准差:对本轮迭代的数据集X 计算均值 x ˉ \bar{x} xˉ 和方差 s s s
x ˉ = 1 n ∑ i = 1 n x i s = ∑ i = 1 n ( x i − x ˉ ) 2 n − 1 \bar{x} = \frac{1}{n} \sum_{i=1}^{n} x_i \\ \\ \\ s = \sqrt{\frac{\sum_{i=1}^{n} (x_i - \bar{x})^2}{n-1}} xˉ=n1i=1∑nxis=n−1∑i=1n(xi−xˉ)2 -
2)计算偏离程度: 对每个数据点 x i x_i xi ,计算偏离程度 R i R_i Ri:
R i = ∣ x i − x ˉ ∣ s R_i = \frac{|x_i - \bar{x}|}{s} Ri=s∣xi−xˉ∣
找出最大偏离值 R m a x R_{max} Rmax 及对应的点 x m a x x_{max} xmax:
R m a x = max ( R 1 , R 2 , … , R n ) R_{max} = \max(R_1, R_2, \ldots, R_n) Rmax=max(R1,R2,…,Rn) -
3)计算临界值: 使用 t 分布计算临界值 λ k λ_k λk:
λ k = ( n − k ) ⋅ t p , n − k − 1 ( n − k − 1 + t p , n − k − 1 2 ) ⋅ ( n − k + 1 ) \lambda_k = \frac{(n-k) \cdot t_{p, n-k-1}}{\sqrt{(n-k-1 + t_{p, n-k-1}^2) \cdot (n-k+1)}} λk=(n−k−1+tp,n−k−12)⋅(n−k+1)(n−k)⋅tp,n−k−1
其中:- t p , n − k − 1 t_{p, n-k-1} tp,n−k−1是自由度为 n − k − 1 n - k - 1 n−k−1 ,置信水平 p = 1 − α / ( 2 ⋅ ( n − k + 1 ) ) p=1−α/(2⋅(n−k+1)) p=1−α/(2⋅(n−k+1)) 的 t 分布值。
- k k k:当前迭代次数(也可理解为迭代轮数)。
-
4)判断是否为异常值: 若 R m a x > λ k R_{max}>λ_k Rmax>λk,则 x m a x x_{max} xmax 被认为是异常值,将其从数据集中移除;否则,停止检测。
-
-
结果输出:
迭代 r m a x r_{max} rmax次,输出所有检测到的异常值。
c.实际数据演示
数据集:
X
=
10
,
12
,
12
,
13
,
12
,
11
,
50
X={10,12,12,13,12,11,50}
X=10,12,12,13,12,11,50
参数设置:
r
m
a
x
=
2
r_{max} = 2
rmax=2(最多检测2个异常值),
α
=
0.05
α=0.05
α=0.05
迭代计算:
迭代次数K | 本轮数据集 | 均值 x ˉ \bar x xˉ | 标准差 s s s | R m a x R_{max} Rmax及对应 x m a x x_{max} xmax | 临界值 λ k \lambda_k λk | 判断异常值 |
---|---|---|---|---|---|---|
1 | 10,12,12,13,12,11,50 | 17.14 | 14.32 | R m a x = 2.29 x m a x = 50 R_{max}=2.29 \\ x_{max}=50 Rmax=2.29xmax=50 | λ 1 = ( 7 − 1 ) ⋅ t p , 5 ( 5 + t p , 5 2 ) ⋅ 7 其中 t p , 5 对应 p = 0.9964 , 查表得 t p , 5 ≈ 4.382 即 λ 1 ≈ 2.02 \lambda_1 = \frac{(7-1) \cdot t_{p, 5}}{\sqrt{(5 + t_{p, 5}^2) \cdot 7}} \\ 其中t_{p, 5}对应p=0.9964,查表得t_{p, 5}≈4.382 \\ 即 \lambda_1≈2.02 λ1=(5+tp,52)⋅7(7−1)⋅tp,5其中tp,5对应p=0.9964,查表得tp,5≈4.382即λ1≈2.02 | 2.29>2.02,则对应的 x m a x = 50 x_{max}=50 xmax=50是异常值 |
2 | 10,12,12,13,12,11 | 11.67 | 1.03 | R m a x = 1.62 x m a x = 10 R_{max}=1.62 \\ x_{max}=10 Rmax=1.62xmax=10 | λ 2 = ( 7 − 2 ) ⋅ t p , 4 ( 4 + t p , 4 2 ) ⋅ 6 其中 t p , 4 对应 p = 0.9958 , 查表得 t p , 5 ≈ 4.851 即 λ 1 ≈ 1.887 \lambda_2 = \frac{(7-2) \cdot t_{p, 4}}{\sqrt{(4 + t_{p, 4}^2) \cdot 6}} \\ 其中t_{p, 4}对应p=0.9958,查表得t_{p, 5}≈4.851 \\ 即 \lambda_1≈1.887 λ2=(4+tp,42)⋅6(7−2)⋅tp,4其中tp,4对应p=0.9958,查表得tp,5≈4.851即λ1≈1.887 | 1.62<1.887,则对应的 x m a x = 10 x_{max}=10 xmax=10不是异常值,结束迭代 |
注:t分布值可通过如上开源工程中的相关代码模块计算得出,或者去查表(统计表)
d.代码示例
代码来自Github开源项目 ad4j:Anomaly Detection For Java 欢迎star,欢迎提issue交流。
关键计算过程代码:完整代码可去项目中查看ADM_GESD.java
protected List<Integer> gesd(List<IndicatorSeries> dataList, int maxOutliers, double alpha) {
List<Integer> outlierIndexes = new ArrayList<>();
List<IndicatorSeries> originalData = new ArrayList<>(dataList);
DescriptiveStatistics stats = null;
int n = dataList.size();
int r = maxOutliers;
for (int i = 1; i <= r; i++) { // i = 1,2,3...r
// init statistic
stats = IndicatorCalculateUtil.initStatistic(stats, originalData, outlierIndexes);
// calculate mean and std
double mean = stats.getMean();
double stdDev = stats.getStandardDeviation();
// calculate Max(Ri)
double maxRi = -1;
int maxRiIndex = -1;
for (int j = 0; j < originalData.size(); j++) {
if (outlierIndexes.contains(j)) {
continue;
}
double deviation = Math.abs(originalData.get(j).getValue() - mean);
if (maxRi < deviation) {
maxRi = deviation;
maxRiIndex = j;
}
}
// Lambda i
double lambdaI = calculateLambdaI(n, i, alpha);
// judge anomaly: where(Ri > LambdaI)
if ((maxRi / stdDev) > lambdaI) {
outlierIndexes.add(maxRiIndex);
} else {
break;
}
}
return outlierIndexes;
}
// calculate lambda (critical-value)
public double calculateLambdaI(int n, int i, double alpha) {
TDistribution tDistribution = new TDistribution(n - i - 1);
double p = 1 - alpha / (2 * (n - i + 1)); // both sid t-distribution
double tValue = tDistribution.inverseCumulativeProbability(p); // Probability to T
return (n - i) * tValue / Math.sqrt((n - i - 1 + Math.pow(tValue, 2)) * (n - i + 1));
}
测试代码:ADMTest.java
@Test
public void testADM_GESD(){
double[] data = new double[]{10.0,12.0,12.0,13.0,12.0,11.0,50.0};
List<IndicatorSeries> indicatorSeries = IndicatorSeriesUtil.transferFromArray(data);
AbstractADM model = new ADM_GESD();
model.init(AnomalyDetectionContext.createDefault());
model.checkCompatibility(indicatorSeries, log);
IndicatorEvaluateInfo evaluate = model.evaluate(indicatorSeries, log);
IndicatorSeriesUtil.print(evaluate);
}
输出结果:如预期检测出50.0数值异常
IndicatorEvaluateInfo{
anomalyDetectionModel='MODEL_ADM_GESD',
anomalyType=TYPE_OUTLIERS_VALUE,
hasAnomaly=true,
anomalySeriesList=[[INFLUENCE_POSITIVE:6, 50.0, 6]]
}
2. 基于Quantile的绝对值异常检测
a.原理
Quantile-based异常检测是一种基于分位数(Quantile)的统计方法,用于识别数据集中绝对值异常的数据点。通过计算数据分布的上下分位数,结合一定的离散性范围(如 IQR,四分位距),判断哪些数据点偏离整体分布较大。
关键概念
- 分位数(Quantile):
- 数据按照从小到大的顺序排列后,取第 q% 的位置值为第 q 分位数。
- 常用分位数:
- Q1(第 25% 分位数):排序后第 25% 的值。
- Q3(第 75% 分位数):排序后第 75% 的值。
- 四分位距(IQR, Interquartile Range):
I Q R = Q 3 − Q 1 IQR=Q3-Q1 IQR=Q3−Q1
表示数据分布中间 50% 的范围。 - 异常值范围: 通常根据经验公式定义异常值为:
上界 = Q 3 + k ⋅ I Q R 下界 = Q 1 − k ⋅ I Q R 上界=Q3+k⋅IQR \\下界=Q1−k⋅IQR 上界=Q3+k⋅IQR下界=Q1−k⋅IQR
其中 k k k 是异常值判定系数,常取 k = 1.5 或 k = 3 (更严格) k=1.5或k=3(更严格) k=1.5或k=3(更严格)。
b.计算过程
(1). 排序数据: 将数据从小到大排序。
(2). 计算分位数:
- 计算 Q 1 Q1 Q1和 Q 3 Q3 Q3。
- 计算四分位距 I Q R IQR IQR。
(3). 计算上下界:
- 下界: Q 1 − k ⋅ I Q R Q1−k⋅IQR Q1−k⋅IQR。
- 上界: Q 3 + k ⋅ I Q R Q3+k⋅IQR Q3+k⋅IQR。
(4). 判断异常值: 如果数据点小于下界或大于上界,则认为是异常值。
c.实际数据计算示例
数据集:
X
=
10
,
12
,
12
,
13
,
12
,
11
,
50
X={10,12,12,13,12,11,50}
X=10,12,12,13,12,11,50
(1). 排序数据:
X
=
10
,
11
,
12
,
12
,
12
,
13
,
50
X={10,11,12,12,12,13,50}
X=10,11,12,12,12,13,50
(2). 计算分位数:
-
计算 Q 1 Q1 Q1和 Q 3 Q3 Q3:
- Q 1 Q1 Q1(第 25% 分位数):即第 2 个与第 3 个值的平均: Q 1 = 11 + 12 2 = 11.5 Q1=\frac {11+12}{2}=11.5 Q1=211+12=11.5
- Q 3 Q3 Q3(第 75% 分位数):即第 5 个与第 6 个值的平均: Q 1 = 12 + 13 2 = 12.5 Q1=\frac {12+13}{2}=12.5 Q1=212+13=12.5
-
计算四分位距 I Q R IQR IQR: I Q R = Q 3 − Q 1 = 12.5 − 11.5 = 1 IQR=Q3-Q1=12.5-11.5=1 IQR=Q3−Q1=12.5−11.5=1
(3). 计算上下界:假设 k = 1.5 k=1.5 k=1.5
- 下界: Q 1 − k ⋅ I Q R = 11.5 − 1.5 ⋅ 1 = 10 Q1−k⋅IQR=11.5−1.5⋅1=10 Q1−k⋅IQR=11.5−1.5⋅1=10
- 上界: Q 3 + k ⋅ I Q R = 12.5 + 1.5 ⋅ 1 = 14 Q3+k⋅IQR=12.5+1.5⋅1=14 Q3+k⋅IQR=12.5+1.5⋅1=14。
(4). 判断异常值: 找出小于10或大于14的点, X X X中,50数值超过上界,即为异常值。
d.代码示例
代码来自Github开源项目 ad4j:Anomaly Detection For Java 欢迎star,欢迎提issue交流。
关键代码:
// calculate bound
double[] quantileBound = IndicatorCalculateUtil.quantileBound(data, iqrMultiplier, 0.25, 0.75);
double lowerBound = quantileBound[0];
double upperBound = quantileBound[1];
// find anomaly indicator series
List<AnomalyIndicatorSeries> anomalyList = data.stream()
.filter(v -> v.getValue() > upperBound || v.getValue() < lowerBound)
.map(v -> new AnomalyIndicatorSeries(v.getValue() > upperBound ? AnomalyDictType.INFLUENCE_POSITIVE : AnomalyDictType.INFLUENCE_NEGATIVE, v))
.collect(Collectors.toList());
其中:IndicatorCalculateUtil.quantileBound方法如下:
public static double[] quantileBound(List<IndicatorSeries> data, double iqrMultiplier, double lowerQuantile, double upperQuantile){
// sort
List<IndicatorSeries> sortList = sortAsc(data);
// calculate quantile value
double Q1 = quantile(sortList, lowerQuantile);
double Q3 = quantile(sortList, upperQuantile);
// calculate IQR
double IQR = Q3 - Q1;
// calculate bound
double lowerBound = Q1 - iqrMultiplier * IQR;
double upperBound = Q3 + iqrMultiplier * IQR;
return new double[]{lowerBound, upperBound};
}
测试代码:
@Test
public void testADM_Quantile(){
double[] data = new double[]{10,11,12,12,12,13,50};
List<IndicatorSeries> indicatorSeries = IndicatorSeriesUtil.transferFromArray(data);
JFreeChartUtil.drawLineChart("TestADM_Quantile_Line", indicatorSeries);
JFreeChartUtil.drawScatterChart("TestADM_Quantile_Scatter", indicatorSeries);
AbstractADM model = new ADM_Quantile();
model.init(AnomalyDetectionContext.createDefault());
model.checkCompatibility(indicatorSeries, log);
IndicatorEvaluateInfo evaluate = model.evaluate(indicatorSeries, log);
IndicatorSeriesUtil.print(evaluate);
}
输出:
IndicatorEvaluateInfo{
anomalyDetectionModel='MODEL_ADM_Quantile',
anomalyType=TYPE_OUTLIERS_VALUE,
hasAnomaly=true,
anomalySeriesList=[[INFLUENCE_POSITIVE:6, 50.0, 6]]
}
3.总结
- GESD
- 根据原理过程可知,需要均值和方差,以及T分布的查表,那么对数据集要求符合正态分布。
- 适合处理少量异常点的场景,因为迭代计算。正常业务一天/一小时一次检测,异常点理论上就是少量的。
- Quantile
- Quantile简单有效,适合所有分布的数据集。
- 对小样本数据敏感。
- 可以补充GESD的不能对非正态分布数据集的检测场景。
- 一般用做补充检测,即兜底检测方案。
原文地址:https://blog.csdn.net/maoyuanming0806/article/details/144119657
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!