自学内容网 自学内容网

指标检测(一):绝对值/离群值异常检测

天苍苍,野茫茫,风吹草地见牛羊。


**系列文章导航:**

一、概述

背景思考

  • 数据应用中,指标与业务息息相关。指标是数据的数值体现,数据驱动业务的思想基础上,指标的价值必然不会止于业务分析人员依靠业务积累而探寻出来。
  • 笔者看来,有意义的指标的价值客观存在,数据驱动–即需要让数据说话,让数据主动推动业务。其中一大价值点在于,把业务问题和业务亮点主动发现,并推动业务人员及时处理问题和推广亮点,积累正确的点,解决错误的点。
  • 业务发展,指标数量越来越多。如何能够快速识别系统各项异常指标,发现问题根因,并推动业务进行调改,是一项很重要的能力。如果只通过业务专家基于过往业务经验,手动的设置各类阈值,那么非常耗时并且容易疏漏。
  • 我们进而设计了一套自动化方法,实现如下能力目标:
    • 自动:不依赖用户设置阈值监控规则,自动化识别异常。
    • 通用:适应多种数值分布的指标。
    • 时效:实现天、小时级别甚至部分指标分钟级别的监测和归因。
    • 主动:数据找人,数据驱动业务。

其中,时效性和主动性,体现在应用产品功能设计上,契合这个目标去做简洁、易用、好用的功能点设计,更关键能力在于自动化、通用的识别异常。

异常分类

首先,指标的异常检测,需要定义出检测出哪类异常。即数据科学工作首先需要对问题进行清晰定义,那么指标的异常分类,有如下三类:

  • 绝对值异常:统计学也称之为离群点,不遵循指标分布。反应业务状态。
  • 波动异常:环比过大的突增或突降点。反应业务变化。
  • 趋势异常:中长期呈现出显著的上升或下降趋势。反应业务可能潜在风险。
    如下图示例
    在这里插入图片描述
    三种异常互相独立,不同场景可能对应不同的异常,需要辅以业务情况判断。

基于上述异常类型,结合公开论文和分享资源,首先使用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=smaxixixˉi=1,2,,r λi=(ni1+tp,ni12)(ni+1) (ni)tp,ni1 p=12(ni+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.计算过程

  1. 准备数据集:设定数据集 X = x 1 , x 2 , … , x n X={x_1,x_2,…,x_n} X=x1,x2,,xn,数据大小为 n。

  2. 参数设置:

    • 最大可检测的异常值个数:r个
    • 显著性水平:α,通常取 0.05
  3. 迭代计算:

    • 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=1nxis=n1i=1n(xixˉ)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=sxixˉ
      找出最大偏离值 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=(nk1+tp,nk12)(nk+1) (nk)tp,nk1
      其中:

      • t p , n − k − 1 t_{p, n-k-1} tp,nk1是自由度为 n − k − 1 n - k - 1 nk1 ,置信水平 p = 1 − α / ( 2 ⋅ ( n − k + 1 ) ) p=1−α/(2⋅(n−k+1)) p=1α/(2(nk+1)) 的 t 分布值。
      • k k k:当前迭代次数(也可理解为迭代轮数)。
    • 4)判断是否为异常值: 若 R m a x > λ k R_{max}>λ_k Rmax>λk,则 x m a x x_{max} xmax​ 被认为是异常值,将其从数据集中移除;否则,停止检测。

  4. 结果输出:
    迭代 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判断异常值
110,12,12,13,12,11,5017.1414.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 (71)tp,5其中tp,5对应p=0.9964,查表得tp,54.382λ12.022.29>2.02,则对应的 x m a x = 50 x_{max}=50 xmax=50是异常值
210,12,12,13,12,1111.671.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 (72)tp,4其中tp,4对应p=0.9958,查表得tp,54.851λ11.8871.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=Q3Q1
    表示数据分布中间 50% 的范围。
  • 异常值范围: 通常根据经验公式定义异常值为:
    上界 = Q 3 + k ⋅ I Q R 下界 = Q 1 − k ⋅ I Q R 上界=Q3+k⋅IQR \\下界=Q1−k⋅IQR 上界=Q3+kIQR下界=Q1kIQR

其中 k k k 是异常值判定系数,常取 k = 1.5 或 k = 3 (更严格) k=1.5或k=3(更严格) k=1.5k=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 Q1kIQR
  • 上界: Q 3 + k ⋅ I Q R Q3+k⋅IQR Q3+kIQR

(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=Q3Q1=12.511.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 Q1kIQR=11.51.51=10
  • 上界: Q 3 + k ⋅ I Q R = 12.5 + 1.5 ⋅ 1 = 14 Q3+k⋅IQR=12.5+1.5⋅1=14 Q3+kIQR=12.5+1.51=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)!