基于中位数绝对偏差的异常检测
作者:Anais Dotis-Georgiou / 产品, 用例, 开发者
2020 年 7 月 7 日
导航至
当您想要发现容器、虚拟机 (VM)、服务器或传感器与其他设备行为不同时,您可以使用中位数绝对偏差 (MAD) 算法来识别时间序列何时“偏离群体”。在本教程中,我们将使用 mad()
— Flux 中 MAD 的实现 — 来自名为 anaisdg/anomalydetection 的 第三方 Flux 包 来识别异常主机。我们将找出这些时间序列中哪个序列是异常的
此数据集与 Linux KerneI 上的磁盘 IO 相似。但是,我生成此时间序列数据集合是因为我想展示 MAD 的强大功能。即使异常序列在视觉检查上不是立即或容易显现的,我们也可以使用 mad() 轻松检测到异常“主机”。
异常检测的货币价值
DevOps 监控使您的组织能够衡量关键绩效指标,这些指标对于维护您的 服务级别协议 (SLA) 至关重要。理想情况下,站点可靠性工程师 (SRE) 使用 Devops 监控来解决运营问题、提高可靠性并指导基础设施设计工作。MAD 等异常检测算法使 SRE 能够快速识别不健康的容器、虚拟机或服务器。SRE 越早发现可疑行为,他们就越快能够诊断和修复基础设施问题。根本原因分析是一种高度复杂的迭代问题解决类型。它涉及提出深入的问题并采用 五问法。找到问题仅仅是开始,解决运营问题可能更具挑战性,并且需要大量的创造力。虽然人工智能尚未先进到能够自主执行根本原因分析和解决基础设施问题的程度,但异常检测仍然具有价值。异常检测引导站点可靠性工程师 (SRE) 和系统管理员走上正确的道路,以缩短事件 解决时间 或 平均解决时间 (MTTR)。MTTR 的减少使公司能够遵守其 服务级别目标 (SLO),提供良好的用户体验并鼓励合同续签。通过允许您编写用户定义的函数并集成自定义 异常检测 算法,Flux 提供了这种价值。InfluxDB v2 警报和通知使 SRE 能够实时响应异常。
中位数绝对偏差算法的工作原理
MAD 算法通常用于此类异常检测,因为它非常有效和高效。在某个时间点,所有时间序列的中位数或“中间”值描述了该时间戳处所有时间序列的正常行为。每个单独时间序列和中位数的较大偏差表明序列是异常的。
以上描述是对算法工作原理的简化概述。实际上,一个点 通过以下公式被标记为异常
其中,
和 是中位数绝对偏差。(5)
其中, =1.4826 是一个 比例因子,它假设数据呈正态分布。
如果数学不是您最喜欢的科目,那么此算法可能看起来很复杂,但实际上非常简单。让我们通过一个数值示例来分解实际发生的情况。
中位数绝对偏差的数值示例
在此示例中,我们将查看来自不同主机的一些数据。我们的数据如下所示,带有图形(右)和表格视图(左)
![]() |
![]() |
请记住,MAD 将偏离中位数较大的点标记为异常点。因此,绿线 host3 在时间点 3 处显然存在异常。让我们看看算法如何确认这一点。
今天,我们将只关注时间点 3 发生的转换,因为 MAD 计算同一时间戳处所有序列的异常。MAD 的工作原理如下
- 按时间戳对数据进行分组并对数据进行排序
- 找到中位数或中间值
- 获取每个序列中位数之间的绝对差值
- 排序并找到中位数绝对偏差
- 乘以比例因子
以获得
= 0.14826。最后,我们取步骤 3 中计算的值,并将其除以 MAD。如果该值大于我们的阈值,那么我们就有一个异常点。
我们可以轻松识别主机 host3 在时间点 3 的异常点,因为输出超过了我们的阈值。
识别异常序列
异常检测的挑战之一是减少误报和嘈杂警报的数量。幸运的是,在时间序列数据上应用 MAD 时,有一个非常简单的解决方案可以解决此问题。与其针对每个异常点发出警报,不如在指定窗口中监控 mad()
的输出。如果单个序列在该窗口内输出一定百分比的异常点,则该序列在较长时间内表现出异常行为,这为将该序列分类为异常提供了信心。
使用 Flux 计算 MAD 并标记异常
我们正在分析的数据集包含 10 个常规时间序列,范围在 2020 年 1 月 1 日至 2020 年 5 月 1 日之间。您可能会检测到深紫色线代表的主机在 3 月初表现异常。
为了突出 MAD 的灵敏度,我们将重点关注最后一周的数据,其中异常行为通过目视检查并不明显。
以下 Flux 脚本识别哪些点是异常的
请注意:此 Flux 函数也是 第三方 Flux 包 的一部分,因此您不必自己编写该函数,但我想在此示例中分享该函数。在下一个示例中,我们将了解如何导入 MAD 第三方 Flux 函数 以便更轻松地使用。
import "experimental"
import "math"
mydata = from(bucket: "MAD_Example")
|> range(start: 2020-04-01, stop: 2020-05-01)
|> filter(fn: (r) => r["_measurement"] == "example_data")
mad = (table=<-, threshold=3.0) => {
// Step One: MEDiXi = med(x)
data = table |> group(columns: ["_time"], mode:"by")
med = data |> median(column: "_value")
// Step Two: diff = |Xi - MEDiXi| = math.abs(xi-med(xi))
diff = join(tables: {data: data, med: med}, on: ["_time"], method: "inner")
|> map(fn: (r) => ({ r with _value: math.abs(x: r._value_data - r._value_med) }))
|> drop(columns: ["_start", "_stop", "_value_med", "_value_data"])
// The constant k is needed to make the estimator consistent for the parameter of interest.
// In the case of the usual parameter a at Gaussian distributions b = 1.4826
k = 1.4826
// Step Three and Four: diff_med = MAD = k * MEDi * |Xi - MEDiXi|
diff_med =
diff
|> median(column: "_value")
|> map(fn: (r) => ({ r with MAD: k * r._value}))
|> filter(fn: (r) => r.MAD > 0.0)
output = join(tables: {diff: diff, diff_med: diff_med}, on: ["_time"], method: "inner")
// Step Five: Divide by MAD in Step Three and compare values against threshold
|> map(fn: (r) => ({ r with _value: r._value_diff/r._value_diff_med}))
|> map(fn: (r) => ({ r with
level:
if r._value >= threshold then "anomaly"
else "normal"
}))
return output
}
// Apply the mad() function to my data, specify the threshold and filter for anomalies.
mydata |> mad(threshold:3.0)
|> filter(fn: (r) => r.level == "anomaly")
输出产生以下图形
mad()
Flux 函数标记为异常的点的折线图我们看到一些序列表现出误报,例如,“主机”1
和 2
本不应该被标记为异常。但是,重要的是要记住以下注意事项
- 此时间序列特别棘手,因为所有序列的标准偏差相似。
- 请记住,灵敏的异常检测系统比无响应的异常检测系统更好。通常,误报比漏报更好。
我们有几种选择可以减少误报的数量
- 我们可以降低
mad()
阈值(即降至 2.5 或 2.0)。 - 我们可以使用 Flux 计算窗口内每个序列的异常点百分比。然后,我们为数据集定义一个异常百分比阈值。如果某个序列在一个窗口内具有较高百分比的异常点而超过阈值,则我们将该序列标记为异常。
- 我们可以只监控异常最多的序列。如果我们采用上面的查询并添加
mydata |> mad(threshold:3.0)
|> filter(fn: (r) => r.level == "anomaly")
|> count(column: "level")
|> sort(columns: ["level"], desc: true)
|> limit(n:1)
我们可以看到我们的异常值序列具有最多的异常。
mad()
Flux 函数正确识别了我们的异常值序列。大型数据集上的 MAD,以协助根本原因分析工作
现在我们确信 MAD 函数对异常非常敏感,让我们看看它在大型数据集上的性能。对于此示例,我们假设我们正在监控 Web 应用程序的响应时间。我们一直在收集包含各种用户响应时间的事件数据。标签描述了用户的区域和浏览器。在以下窗口中,我们大约有 9 万个点。
然后,我们应用 MAD 和以下脚本,该脚本在我的本地机器上仅用了 6 秒即可执行,并且还在运行其他进程。在此示例中,我在编译 Flux 后导入包。一旦 拉取请求合并,您应该能够像这样应用 MAD
import "contrib/anaisdg/anomalydetection"
mydata = from(bucket: "my-app-response")
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "my-app")
|> filter(fn: (r) => r["_field"] == "response_time")
mydata |>anomalydetection.mad(threshold:3.0)
|> filter(fn: (r) => r.level == "anomaly")
mad()
Flux 函数标记的异常响应时间散点图。异常按时间分组。我们可以看到,我们有很多响应时间超过 500 毫秒。现在,我们可以通过分析异常来确定可能导致这些响应时间缓慢的原因。按区域分组并使用以下命令计算值后
mydata |> mad(threshold:3.0)
|> filter(fn: (r) => r.level == "anomaly")
|> group(columns: ["region"])
|> count()
我们得出了一些结论。使用 IE6 的用户响应时间较慢,并且 us-east-2 区域可能存在一些问题,因为大多数异常都发生在该区域。
友善请求和结论
在这篇文章中,我们学习了如何使用 Flux 编写简单但功能强大的异常检测算法。我们了解到它既相当灵敏又高效。但是,如果我不概述后续的自然步骤和要利用的其他工具,我将是失职的。
要在 InfluxDB 2.0 和 Flux 中完成异常检测管道
- 编写一个 任务,使用 to() 函数将这些异常写入新存储桶。
- 警报异常。
- 利用 http.post() 函数触发脚本并自动执行纠正措施以响应警报。
重要提示:由于此函数执行按时间分组的计算,因此您可以创建一个频繁运行 MAD 函数的任务 - 您可以尝试镜像数据收集频率/间隔,而不是等待在更长的时间范围内运行它。更频繁地执行 MAD 函数会减少输入数据并提高性能,以适应更大的数据集。
使 Flux 和 InfluxDB 技术栈与众不同且功能强大的一个重要原因是它们是开源的。如果没有社区的帮助,InfluxDB 和 Telegraf 就不会有今天的成就。Flux 虽然在 beta 版本中已经非常强大,但也需要您的帮助。此处 anaisdg/anomalydetection Flux 包中的 MAD 函数之所以特别,是因为它是作为第三方 Flux 包贡献的。如果您编写了自定义 Flux 函数来执行异常检测或其他预测,我鼓励您贡献它们。让其他人从您的辛勤工作中受益,并赞扬您!
要了解更多关于贡献第三方 Flux 包的信息,请阅读这篇博客。与往常一样,请在评论区、我们的社区网站或我们的 Slack 频道中分享您的想法、疑虑或问题。我们很乐意获得您的反馈并帮助您解决遇到的任何问题!再次感谢您!