(ARMS-AIOps)一文教你用Attributor算法实现多维下钻分析

本文涉及的产品
应用实时监控服务-用户体验监控,每月100OCU免费额度
应用实时监控服务-应用监控,每月50GB免费额度
简介: 常见的AIOps应用路径为:对监控的各种关键性能指标(KPI)进行实时异常检测;对多维指标进行根源分析,快速下钻到异常维度和元素;基于应用拓扑和实时Trace,实现根因定位;结合CMDB、关联等、构建异常根因上下文,帮助快速修复问题。 作为KPI指标, 往往包含了很多维度和元素,最显而易见的则是对每一个维度的元素都进行实时异常检测。 对于维度组合笛卡尔集数量很长的场景, 该方案的成本则有点难以承受

常见的AIOps应用路径为:对监控的各种关键性能指标(KPI)进行实时异常检测;对多维指标进行根源分析,快速下钻到异常维度和元素;基于应用拓扑和实时Trace,实现根因定位;结合CMDB、关联等、构建异常根因上下文,帮助快速修复问题。 

作为KPI指标, 往往包含了很多维度和元素,最显而易见的则是对每一个维度的元素都进行实时异常检测。 对于维度组合笛卡尔集数量很长的场景, 该方案的成本则有点难以承受。 此外,还有很多业务场景更多的是关心业务总体指标,总体指标出现问题了才会关心具体哪个维度下有问题。 这俩类问题都可以通过多维下钻分析得到很好的解决。 本文主要是介绍多维分析中最常用的Attributor算法,以及基于该算法的实践。 

1. 背景知识

KL散度 & JS散度导入

1.KL散度

用来衡量两个分布之间的差异,等于一个交叉熵减去一个信息熵(交叉熵损失函数的由来)

1.1 KL散度的性质

  • 非负性(用Jenson‘s inequality 证明)
  • 不对称性,即KL(P||Q)≠KL(Q||P)

1.2 KL散度的问题即JS散度的引出

正是由于KL散度的不对称性问题使得在训练过程中可能存在一些问题,为了解决这个问题,我们在KL散度基础上引入了JS散度

JS如下:

  1. JS(Jenson’s Shannon)散度

一般地,JS散度是对称的,其取值是 0 到 1 之间。如果两个分布 P,Q 离得很远,完全没有重叠的时候,那么KL散度值是没有意义的,而JS散度值是一个常数。这在学习算法中是比较致命的,这就意味这这一点的梯度为 0。梯度消失了。

2. 算法解析

背景

论文:https://www.usenix.org/system/files/conference/nsdi14/nsdi14-paper-bhagwan.pdf

本文主要针对论文《Adtributor: Revenue Debugging in Advertising Systems》做简单介绍。这篇论文主要介绍如何定位造成广告收入波动的根本因素。

问题定义

问题:某个月的预测广告总收入为100万,实际广告总收入为50万

数据:如果每个月的广告总收入可以从数据中心,广告商,终端设备这3个维度分别进行统计。其收入分布情况具体如下图所示:

广告收入分布图

需求:如何定位出造成这次收入突降的根本因素,即找到对广告收入波动影响最大的因素(某个维度下的某个具体因素)

论文关键思路

(1)定义

度量指标: 论文中用广告收入

维度: 论文中主要包含数据中心、广告商、终端设备3个维度

维度下具体因素: 数据中心这个维度主要包含2个因素,X和Y

后续公式中会使用的标识具体如下图:

(2)核心思路

第一步:计算某维度下某因素的真实值和预测值占总收入的差异性----Surprise(惊奇性

A : 计算 。其中 表示i维度i下j因素对应的预测收入占总预测收入的比率,具体公式为:

其中m是某种度量指标如广告收入,i表示维度,j表维度下具体因素。F(m)表示预测总收入如100万, 表示维度i下j因素对应的预测收入,如数据中心X对的预测收入为94万,则对应的  ; A(m)表示实际总收入50万,则数据中心X的实际收入为47万,则对应的 

B: 计算 的差异度,论文中利用js散度进行计算, 直接的差异越大,则js散度值越大

第二步:计算某维度下某因素波动占总体波动的比率----Explanatory power(解释力)

其中波动表示真实值和异常值的差异性,具体公式入下:

如终端设备PC的EP值为  ,Tablet的EP值为 

(3)率值指标处理

率值KPI的计算方式不同于量值KPI,因为率值KPI不能像量值KPI一样在不同维度和元素上进行加减操作,率值KPI的波动变化也不能通过加减反映在各个维度和元素上。此时,偏导数和有限差分便派上用场了。在离散场景下, 衡量对于这种形式函数的波动变化情况的偏导数可以简化为:

最后文中推导出的EP值如下:

根据相对熵理论,对于需要首先计算f(.)和g(.)函数的联合概率分布,然后计算联合概率分布函数的相对熵,计算十分复杂。论文作出近似假设,认为f和g函数之间相互独立,则的联合概率分布相对熵就是f(.)和g(.)的概率分布函数相对熵之和。因此,率值KPI的S值等于组成率值KPI定义公式的分子KPI和分母KPI的S值之和。省略下标ij,S值计算公式如下

S = sum(Sf, Sg)

具体的算法如下:

总结

多维定位:

Adtributor假设所有根因都是一维的提出了解释力(Explanatory power)和惊奇性(Surprise)来量化根因的定义。通过计算维度的惊奇性(维度内所有元素惊奇性之和)对维度进行排序,确定根因所在的维度(例如省份)。在维度内部计算每个元素的解释力,当元素的解释力之和(例如北京+上海)超过阈值时,这些元素就被认为是根因。

思考:

(1) 指标类型的泛化:对于基本类型的KPI的计算公式(例如PV、交易量),直接走正常的S值和EP值计算公式, 对于率值类指标(例如成功率,失败率),则需要结合偏导数和有限差分, 推导出对应的率值公式。 

(2)维度之间的关系:将根因限定在一维的假设不太符合我们的实际场景,同时用解释性和惊奇性的大小来衡量根因也不完全合理。因为其没有考虑到维度之间的相互影响以及「外部根因」的可能。

(3)对于参考值的选型:Adtributor的根因分析严重依赖于整体KPI的变化情况,对于整体变化不大,但是内部波动较为剧烈的数据表现不好

优化建议

  • 异常检测,标注正常和异常范围, 为提供正常和异常对比提供依据
  • 率值指标转化为量值指标进行,避免复杂公式推导。

3. 算法实践

Attributor算法构建:

import numpy as np
import pandas as pd
import scipy.stats


def js_divergence(p, q):
    p = np.array(p)
    q = np.array(q)
    m = (p + q) / 2
    js = 0.5 * np.sum(p * np.log(p / m)) + 0.5 * np.sum(q * np.log(q / m))
    return round(float(js), 6)


def get_exp(pv_pred, pv_actual, pred_sum, actual_sum):
    if actual_sum - pred_sum == 0:
        return pv_actual - pv_pred
    else:
        return (pv_actual - pv_pred) / (actual_sum - pred_sum)


def root_cause_analysis_for_single_dimension(dframe,dimension_2_check):
    EP_threshold = 0.1
    pv_sum = dframe.sum(numeric_only=True)

    # 先计算isp维度的
    group_isp = dframe.groupby(dimension_2_check).sum()

    group_isp = group_isp.reset_index()
    group_isp['pred_sum'] = pv_sum['pv_pred']
    group_isp['actual_sum'] = pv_sum['pv_actual']

    group_isp['p'] = group_isp['pv_pred'] / group_isp['pred_sum']
    group_isp['q'] = group_isp['pv_actual'] / group_isp['actual_sum']

    # 第一步:计算当前维度下Surprise
    group_isp['surprise'] = group_isp[['p', 'q']].apply(lambda x: js_divergence(x['p'], x['q']), axis=1)

    # 第二步:计算当前维度下每个元素值的EP值
    group_isp['EP'] = group_isp[['pv_pred', 'pv_actual', 'pred_sum', 'actual_sum']].apply(
        lambda x: get_exp(x['pv_pred'], x['pv_actual'] , x['pred_sum'], x['actual_sum']), axis=1)

    # 第三步:计算每个维度下元素的变化占比:
    group_isp['change'] = group_isp[['pv_pred', 'pv_actual', 'pred_sum', 'actual_sum']].apply(
        lambda x: (x['pv_actual'] - x['pv_pred']) / (x['actual_sum'] ), axis=1)

    isp_surprise = group_isp['surprise'].sum()

    group_isp = group_isp[group_isp['EP'] > EP_threshold]
    
    return [dimension_2_check, isp_surprise, group_isp]


单维度分析主函数

import pandas as pd
import numpy as np
from attributor import root_cause_analysis_for_single_dimension


def data_from_KPI_to_pd(df, axis, anomaly_start_time, anomaly_end_time, normal_start_time, normal_end_time):
    data = df[axis]
    axis_list = []
    pred_list = []
    actual_list = []
    for key, item in data.items():
        pred = np.mean(item[normal_start_time:normal_end_time])
        actual = np.mean(item[anomaly_start_time:anomaly_end_time])
        actual_list.append(actual)
        pred_list.append(pred)
        axis_list.append(key)

    result = pd.DataFrame({"elements": axis_list,
                           "pv_pred": pred_list,
                           "pv_actual": actual_list, })
    return result


# 单维度分析
def single_axis_analysis(df, anomaly_start_time, anomaly_end_time, normal_start_time, normal_end_time, axis_2_check):
    """
    进行指标数据的单维度下钻分析
    params:
    df: 包含总指标、各维度/元素指标
    axis_2_check: 分析的维度
    anomaly_start_time: 总指标的异常开始
    anomaly_end_time: 总指标的异常结束
    normal_start_time: 总指标的正常开始
    normal_end_time:总指标的正常结束
    """
	check_list = []
    df_total = []
    dimension_list = []
    axis_after_check = []
    print("we will check these axis: ", axis_2_check)
    for a_to_c in axis_2_check:
        result = data_from_KPI_to_pd(df, a_to_c, anomaly_start_time, anomaly_end_time, normal_start_time, normal_end_time)
        result.columns = ['elements', 'pv_pred', 'pv_actual']

        # None值处理
        result = result.fillna(1)
        res = root_cause_analysis_for_single_dimension(result, ['elements'])

        # print(res)
        group_isp = res[2]
        group_isp['axis'] = a_to_c
        group_isp['suprise_sum'] = res[1]
        df_total.append(res[2])
        check_list.extend(list(group_isp['elements']))
        if res[1] > 0:
            axis_after_check.append(a_to_c)
            dimension_list.append([a_to_c, res[1]])

    df_final = pd.concat(df_total, axis=0)

    df_final = df_final.sort_values(by=['change'], ascending=False)

    dimension_list = sorted(dimension_list, key=lambda x: x[1], reverse=True)
    dimension_list = [i[0] for i in dimension_list]
    print("these axis exists problems ", dimension_list)

    return {"axis_error":dimension_list,
            "elements":df_final['elements'].to_list()}

测试数据

# -*- coding: utf-8 -*
# 功能说明: ## 测试通过http方式去获取异常排序结果
import json
import requests
import numpy as np
import pandas as pd
import random

def make_data():
    # 构造总的KPI
    KPI = []
    for i in range(97):
        tmp = random.randint(5, 10)
        KPI.append(tmp)

    KPI.extend([100, 200, 100])

    # 构造各个维度:
    # 维度1: region,包含3个元素
    KPI_region_1 = []
    for i in range(100):
        tmp = random.randint(1, 4)
        KPI_region_1.append(tmp)

    KPI_region_2 = []
    for i in range(100):
        tmp = random.randint(1, 4)
        KPI_region_2.append(tmp)

    KPI_region_3 = []
    for i in range(100):
        tmp = KPI[i] - KPI_region_1[i] - KPI_region_2[i]
        KPI_region_3.append(tmp)

    # 维度2: api,包含2个元素
    KPI_api_1 = []
    for i in range(97):
        tmp = random.randint(1, 4)
        KPI_api_1.append(tmp)
    KPI_api_1.extend([20, 40, 20])

    KPI_api_2 = []
    for i in range(100):
        tmp = KPI[i] - KPI_api_1[i]
        KPI_api_2.append(tmp)

    # 输入数据
    # df为总指标和各维度/元素的下指标,组合成的dict类型。
    # axis_2_check中的元素, 必须是df中的key,否则该元素无法进行分析。
    # 异常时间段、正常时间段。 这个一般是和检测进行结合, 得到俩个段的开始和结束时间。
    df = {
        "metric": KPI,
        "region": {"sh": KPI_region_1,
                   "sz": KPI_region_2,
                   "bj": KPI_region_3,
                   },
        "api": {
            "api1": KPI_api_1,
            "api2": KPI_api_2,
        },
    }
    axis_2_check = ['region', 'api']
    anomaly_start_time = 97
    anomaly_end_time = 100
    normal_start_time = 0
    normal_end_time = 90
    return df, anomaly_start_time, anomaly_end_time, normal_start_time, normal_end_time, axis_2_check


if __name__ == '__main__':
    df, anomaly_start_time, anomaly_end_time, normal_start_time, normal_end_time, axis_2_check = make_data()
	result = single_axis_analysis(df, anomaly_start_time, anomaly_end_time, normal_start_time, normal_end_time, axis_2_check)

实验结果

Case1:突增

  1. 总数据

total为汇总的KPI,

包含了region和api俩个维度,各个维度的元素可见图

  1. 分析结果:

Case2:下跌

  1. 总数据

total为汇总的KPI,

包含了region和api俩个维度,各个维度的元素可见图

  1. 分析结果:

Reference

https://blog.csdn.net/weixin_44441131/article/details/105878383

https://hyper.ai/wiki/4274

https://zhuanlan.zhihu.com/p/89266916

论文地址:https://www.usenix.org/system/files/conference/nsdi14/nsdi14-paper-bhagwan.pdf

https://cloud.tencent.com/developer/article/1644348

相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
相关文章
|
3月前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
97 3
|
5月前
|
人工智能 算法 BI
第一周算法设计与分析 D : 两面包夹芝士
这篇文章介绍了解决算法问题"两面包夹芝士"的方法,通过找出两个数组中的最大最小值,计算这两个值之间的整数个数,包括特判不存在整数的情况。
|
2月前
|
缓存 监控 前端开发
优化 Flutter 应用启动速度的策略,涵盖理解启动过程、资源加载优化、减少初始化工作、界面布局优化、异步初始化、预加载关键数据、性能监控与分析等方面
本文探讨了优化 Flutter 应用启动速度的策略,涵盖理解启动过程、资源加载优化、减少初始化工作、界面布局优化、异步初始化、预加载关键数据、性能监控与分析等方面,并通过案例分析展示了具体措施和效果,强调了持续优化的重要性及未来优化方向。
74 10
|
6天前
|
存储 算法 安全
基于哈希表的文件共享平台 C++ 算法实现与分析
在数字化时代,文件共享平台不可或缺。本文探讨哈希表在文件共享中的应用,包括原理、优势及C++实现。哈希表通过键值对快速访问文件元数据(如文件名、大小、位置等),查找时间复杂度为O(1),显著提升查找速度和用户体验。代码示例展示了文件上传和搜索功能,实际应用中需解决哈希冲突、动态扩容和线程安全等问题,以优化性能。
|
15天前
|
缓存 算法 搜索推荐
Java中的算法优化与复杂度分析
在Java开发中,理解和优化算法的时间复杂度和空间复杂度是提升程序性能的关键。通过合理选择数据结构、避免重复计算、应用分治法等策略,可以显著提高算法效率。在实际开发中,应该根据具体需求和场景,选择合适的优化方法,从而编写出高效、可靠的代码。
27 6
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
69 1
|
3月前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
3月前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
4月前
|
算法 搜索推荐 开发者
别再让复杂度拖你后腿!Python 算法设计与分析实战,教你如何精准评估与优化!
在 Python 编程中,算法的性能至关重要。本文将带您深入了解算法复杂度的概念,包括时间复杂度和空间复杂度。通过具体的例子,如冒泡排序算法 (`O(n^2)` 时间复杂度,`O(1)` 空间复杂度),我们将展示如何评估算法的性能。同时,我们还会介绍如何优化算法,例如使用 Python 的内置函数 `max` 来提高查找最大值的效率,或利用哈希表将查找时间从 `O(n)` 降至 `O(1)`。此外,还将介绍使用 `timeit` 模块等工具来评估算法性能的方法。通过不断实践,您将能更高效地优化 Python 程序。
76 4
|
4月前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
82 1