3、基于统计的异常检测:均值-方差阈值法、移动平均与指数平滑、Z-Score与IQR离群点检测、CUSUM累积和算法实战

各位好,欢迎来到第三讲。今天我们要聊的,是流量分析里最基础也最实用的一类方法——基于统计的异常检测。

说白了,就是利用数学上的统计量,来判断当前流量是不是「不对劲」。我刚开始做安全运维那会儿,还没有什么花里胡哨的AI模型,全靠这些统计方法撑场面。你别看它们简单,用好了,效果一点不比深度学习差。

3.1 均值-方差阈值法:最朴素的直觉

这个方法,我估计你一听就懂。它的核心思想就一句话:正常流量应该集中在均值附近,离均值太远的,就是异常。

具体怎么做?我们先计算历史流量的均值 μ 和标准差 σ。然后设定一个阈值 k,通常是 2 或 3。如果当前流量值 x 满足:

|x - μ| > k * σ

那就判定为异常。

嗯,这里要注意:k 值怎么选?我个人的习惯是,对延迟敏感的业务用 k=2,对误报容忍度低的用 k=3。为什么?因为正态分布下,k=3 覆盖了 99.7% 的正常数据,误报率很低。

我的经验: 我在项目中遇到过一种情况——流量本身就有明显的周期性(比如白天高、晚上低)。这时候直接用全局均值和方差,白天全报异常,晚上全漏报。怎么办?按时间段分别统计!比如每5分钟一个窗口,分别计算均值和方差。

3.2 移动平均与指数平滑:让阈值「动」起来

均值-方差法有个硬伤:它假设流量是平稳的。但现实中的流量,往往有趋势和季节性。这时候,移动平均就派上用场了。

简单移动平均(SMA):取最近 N 个点的平均值作为基线。N 越大,曲线越平滑,但对突变的响应越慢。

指数加权移动平均(EWMA):给近期的数据更高的权重。公式是:

s_t = α * x_t + (1 - α) * s_{t-1}

其中 α 是平滑因子,一般在 0.1 到 0.3 之间。α 越大,对近期变化越敏感。

我建议你试试 EWMA。为什么?因为它计算简单,只需要记住上一个平滑值,内存占用几乎为零。在百万级 QPS 的场景下,这个优势非常明显。

实战要点: 移动平均法检测异常时,通常用「实际值 - 预测值」的残差来判断。残差超过某个阈值,就报警。阈值怎么定?可以用残差的均值-方差法,也可以直接用固定阈值。

3.3 Z-Score 与 IQR:两种离群点检测思路

这两种方法,都是用来找「离群点」的。但它们的思路完全不同。

Z-Score:基于均值和标准差。公式是:

z = (x - μ) / σ

z 的绝对值越大,说明 x 越异常。通常 |z| > 3 就认为是异常。

但 Z-Score 有个前提:数据要近似正态分布。如果数据本身偏态严重,Z-Score 就不太靠谱了。

IQR(四分位距法):不依赖正态分布假设。它用中位数和四分位数来定义正常范围:

IQR = Q3 - Q1
下限 = Q1 - 1.5 * IQR
上限 = Q3 + 1.5 * IQR

超出这个范围的就是异常。

我个人更偏爱 IQR。为什么?因为它对极端值不敏感。你想想看,如果数据里已经混入了几个巨大的异常值,均值和标准差都会被「带偏」,Z-Score 就失效了。但 IQR 用的是中位数和四分位数,那几个异常值基本不影响它。

避坑指南: 我曾经在一个项目中,用 Z-Score 检测 DDoS 攻击流量。结果发现,攻击流量太大,直接把均值和标准差都拉高了,导致正常流量反而被判为异常。后来换成 IQR,问题就解决了。所以,如果你的数据可能已经被污染,优先用 IQR。

3.4 CUSUM 累积和算法:捕捉微小偏移

前面几种方法,都是检测「大幅突变」的。但有些攻击,比如慢速扫描、低频数据窃取,流量变化非常微小,单点看根本看不出来。

这时候,CUSUM(累积和)算法就登场了。

CUSUM 的核心思想是:把微小的偏移累积起来,直到累积量超过阈值。

它的计算过程是这样的:

S_0 = 0
S_t = max(0, S_{t-1} + (x_t - μ - k))

其中 μ 是目标均值,k 是允许的偏移量(通常设为 0.5σ),h 是决策阈值(通常设为 4σ 或 5σ)。当 S_t > h 时,报警。

你可能会问:为什么要用 max(0, ...)?因为 CUSUM 只关心正向偏移。如果你想同时检测正向和负向偏移,需要两个方向的 CUSUM。

实战案例: 我记得有一次,客户说他们的服务器每天凌晨 3 点会有一波异常流量,但峰值只比平时高 5%,常规阈值根本抓不到。我上了 CUSUM,把 k 设为 0.2σ,h 设为 3σ。结果呢?累积了大约 10 分钟后,S_t 就超过了阈值,成功捕获了那个慢速扫描行为。

3.5 四种方法对比与选型建议

说了这么多,到底该用哪个?我整理了一个表格,方便你对照:

方法 适用场景 优点 缺点
均值-方差阈值法 平稳流量,快速检测大幅突变 简单直观,计算快 对趋势和周期性敏感,容易误报
移动平均/指数平滑 有趋势或季节性的流量 能自适应变化,内存占用低 对突变响应有延迟
Z-Score 近似正态分布的数据 标准化后易于比较 对极端值敏感,不适用于偏态分布
IQR 非正态分布、可能被污染的数据 鲁棒性强,不受极端值影响 对样本量有要求,小样本效果差
CUSUM 检测微小但持续的偏移 灵敏度高,能捕捉累积效应 参数调优较复杂,需要先验知识

我的建议是:不要只用一种方法。在实际项目中,我通常会把 IQR 和 CUSUM 结合起来用。IQR 负责抓大幅异常,CUSUM 负责抓微小偏移。两者互补,效果很好。

3.6 实战代码示例:用 Python 实现 CUSUM

光说不练假把式。我给你一段我常用的 CUSUM 实现代码,你可以直接拿去用:

import numpy as np

def cusum_detector(data, target_mean, k=0.5, h=4.0):
    """
    CUSUM 累积和异常检测
    :param data: 输入流量序列 (list or np.array)
    :param target_mean: 目标均值(正常流量均值)
    :param k: 允许的偏移量,通常设为 0.5 * std
    :param h: 决策阈值,通常设为 4 * std 或 5 * std
    :return: 报警点索引列表
    """
    s = 0
    alarms = []
    for i, x in enumerate(data):
        s = max(0, s + (x - target_mean - k))
        if s > h:
            alarms.append(i)
            s = 0  # 报警后重置
    return alarms

# 示例用法
np.random.seed(42)
normal = np.random.normal(100, 10, 200)  # 正常流量,均值100,标准差10
attack = np.random.normal(108, 10, 50)   # 攻击流量,均值偏移8
data = np.concatenate([normal, attack, normal])

alarms = cusum_detector(data, target_mean=100, k=5, h=40)
print(f"报警点索引: {alarms}")

这段代码里,我设了 k=5(0.5σ),h=40(4σ)。你跑一下就会发现,攻击流量开始后大约 10-15 个点,CUSUM 就开始报警了。这就是累积效应的威力。

调参小技巧: k 和 h 怎么设?我一般先用历史正常数据跑一遍,调整参数使得误报率在 1% 以下。然后注入模拟攻击,看检测率。反复迭代几次,就能找到合适的参数组合。

好了,这一讲的内容就到这里。统计方法虽然「古老」,但它们是流量分析的基石。你把这些方法吃透了,后面学机器学习模型会轻松很多。下一讲,我们会进入时间序列分解的世界,看看如何把流量拆解成趋势、季节性和残差三部分。到时候见。