4、异常检测基础:统计方法(3Sigma、箱线图)、时间序列分解、移动平均法

聊到异常检测,很多新手一上来就上深度学习模型。我个人习惯是,先拿统计方法试试水。为什么?因为简单、可解释、还快。你想想看,一个线上系统每天几亿条指标,你不可能每条都跑神经网络。统计方法就是那个“先开枪后瞄准”的利器。

今天咱们就掰扯清楚四个最常用的统计异常检测手段:3Sigma、箱线图、时间序列分解、移动平均法。这些方法我几乎在每个项目里都用过,踩过坑,也填过坑。

4.1 3Sigma法则:最朴素的“离群点”判断

3Sigma,说白了就是假设你的数据服从正态分布。然后呢,均值加减三个标准差,落在这个范围之外的,我就认为它是异常。

公式很简单:

上限 = μ + 3σ
下限 = μ - 3σ

我在项目中遇到过一个问题:某电商平台的QPS监控,平时很平稳,但一到双十一大促,流量暴涨,3Sigma直接报警报疯了。其实那不是异常,那是业务高峰。所以用3Sigma之前,你得先确认数据是不是真的“正常波动”。

⚠️ 我曾经踩过的坑: 3Sigma对极端值非常敏感。如果数据里已经混入了几个异常点,均值和标准差都会被拉偏,导致正常点被误判为异常。建议先用中位数和MAD(绝对中位差)做一次粗筛。

代码实现也很直接:

import numpy as np

def detect_3sigma(data):
    mean = np.mean(data)
    std = np.std(data)
    upper = mean + 3 * std
    lower = mean - 3 * std
    anomalies = [x for x in data if x > upper or x < lower]
    return anomalies, upper, lower

4.2 箱线图:不依赖分布的“稳健派”

3Sigma要求正态分布,但现实世界的数据哪有那么乖?这时候箱线图就派上用场了。它基于四分位数,不假设任何分布。

核心概念:

  • Q1:下四分位数(25%分位)
  • Q3:上四分位数(75%分位)
  • IQR:四分位距 = Q3 - Q1
  • 异常判定:小于 Q1 - 1.5×IQR 或大于 Q3 + 1.5×IQR

嗯,这里要注意。1.5这个系数是经验值,不是铁律。我在做服务器CPU使用率监控时,发现业务特性导致数据偏态严重,1.5倍IQR会漏掉很多异常。后来我调成了2.0倍,效果反而更好。说白了,参数得根据你的业务场景调。

💡 我的建议: 箱线图特别适合做“一次性”的离线异常检测。比如每天凌晨跑一次批处理,把昨天的日志指标扫一遍。它快,而且不容易被极端值带偏。
import numpy as np

def detect_boxplot(data, multiplier=1.5):
    q1 = np.percentile(data, 25)
    q3 = np.percentile(data, 75)
    iqr = q3 - q1
    lower = q1 - multiplier * iqr
    upper = q3 + multiplier * iqr
    anomalies = [x for x in data if x < lower or x > upper]
    return anomalies, lower, upper

4.3 时间序列分解:把趋势和周期拆开看

前面两种方法都是“静态”的,但运维指标是随时间变化的。比如每天的访问量有周期性,周末低、工作日高。如果你直接用3Sigma去检测,周末的数据全被判成异常了。

时间序列分解就是干这个的。它把一条原始序列拆成三部分:

  • 趋势分量(Trend):长期上升或下降
  • 季节分量(Seasonal):周期性波动
  • 残差分量(Residual):剩下的随机波动

异常检测的核心,就是盯着残差分量看。因为趋势和周期都是“正常”的部分,只有残差里蹦出来的大尖刺,才是真正的异常。

我记得有一次帮一个金融客户做交易量监控。他们的数据有明显的日周期和周周期。直接用原始数据做阈值,误报率高达40%。分解之后只看残差,误报率降到了5%以下。效果立竿见影。

import statsmodels.api as sm

def decompose_and_detect(series, period=24):
    # period=24 表示以24小时为周期
    result = sm.tsa.seasonal_decompose(series, model='additive', period=period)
    residual = result.resid.dropna()
    # 对残差用3Sigma检测
    anomalies, _, _ = detect_3sigma(residual.values)
    return anomalies, result
⚠️ 注意: 时间序列分解要求数据是等间隔采样的。如果你的监控数据有缺失,记得先插值。我曾经因为没处理缺失值,分解出来的残差全是NaN,排查了半天才发现是数据对齐的问题。

4.4 移动平均法:平滑掉“毛刺”再看异常

移动平均法,说白了就是拿一个滑动窗口,算窗口内的平均值。这样可以把短期的随机抖动平滑掉,露出真正的趋势。

常用的有两种:

  • 简单移动平均(SMA):窗口内所有点权重一样
  • 指数加权移动平均(EWMA):越近的点权重越大

我个人更偏爱EWMA。为什么?因为它对最近的变化更敏感。比如服务器突然CPU飙升,EWMA能更快地跟上这个变化,而SMA反应会慢半拍。

异常检测的做法是:用移动平均预测下一个点的值,然后看实际值和预测值的偏差。如果偏差超过某个阈值,就报警。

import pandas as pd

def ewma_detect(series, span=10, threshold=3):
    # span是窗口大小,threshold是标准差倍数
    ewma = series.ewm(span=span).mean()
    residuals = series - ewma
    std = residuals.std()
    anomalies = series[abs(residuals) > threshold * std]
    return anomalies, ewma
💡 实战技巧: 移动平均法的窗口大小怎么选?我一般遵循“业务周期”原则。比如监控的是每分钟的请求量,业务有明显的日周期,那窗口就设成1440(一天1440分钟)。这样平滑出来的曲线刚好能反映一天的规律。

4.5 四种方法怎么选?一张表说清楚

方法 适用场景 优点 缺点
3Sigma 数据近似正态分布 简单、计算快 对极端值敏感、依赖正态假设
箱线图 分布未知、有偏态 稳健、不依赖分布 对周期性数据不敏感
时间序列分解 有明显周期和趋势 能分离出真正的异常 需要等间隔数据、计算稍复杂
移动平均法 实时流式数据 适合在线检测、反应快 窗口大小难调、滞后性

4.6 知识体系总览

下面这张图,我把这四种方法的核心逻辑和适用场景串起来了。你可以把它当作一个“选型指南”。

异常检测统计方法知识体系 异常检测统计方法 3Sigma法则 正态分布假设 μ ± 3σ 判定异常 箱线图法 基于四分位数 Q1 - 1.5×IQR / Q3 + 1.5×IQR 时间序列分解 趋势 + 季节 + 残差 对残差做异常检测 移动平均法 SMA / EWMA 预测值与实际值偏差 核心思路:先看数据特性,再选方法 静态数据 → 3Sigma/箱线图 | 动态数据 → 分解/移动平均

好了,这四种方法讲完了。你可能会问,到底用哪个?我的回答是:别纠结,先跑一遍箱线图看看数据长什么样。如果数据有明显的周期,就上时间序列分解。如果是实时流,就用EWMA。记住,没有银弹,只有最合适的工具。


专注资料整理