4. 统计方法入门:3Sigma原则、移动平均法、指数平滑法
聊到体量异常检测,很多人第一反应就是上机器学习。但说实话,我干了这么多年运维,真正在生产环境里扛大梁的,反而是今天要讲的这几个统计方法。它们简单、高效、可解释性强——你想想看,出告警了,老板问你为什么,你能说“神经网络告诉我的”吗?
这一章,我们就来啃这三块硬骨头:3Sigma原则、移动平均法、指数平滑法。它们分别对应了不同的数据场景,掌握了它们,你就能覆盖掉日常80%的异常检测需求。
核心观点:统计方法不是过时的老古董,而是AIops体系里最稳定的基石。我见过太多团队一上来就搞深度学习,结果数据量不够、特征不稳定,最后还得回来用这些基础方法兜底。
4.1 3Sigma原则:最朴素的异常检测器
3Sigma原则,说白了就是“三倍标准差法则”。它假设你的数据服从正态分布,那么99.7%的数据都会落在均值±3个标准差之内。落在外面?那就是异常。
公式很简单:
上限 = μ + 3σ
下限 = μ - 3σ
异常 = x < 下限 或 x > 上限
我在项目中遇到过这样一个场景:监控一个电商网站的QPS,平时波动很规律。突然有一天,某个促销活动导致QPS飙升了5倍。用3Sigma一算,直接告警——虽然那次是正常流量,但至少我们第一时间知道了“有大事发生”。
我的经验:3Sigma最适合检测突发性尖刺和断崖式下跌。但要注意,它要求数据基本服从正态分布。如果你的数据是双峰分布或多峰分布,3Sigma会频繁误报。
代码实现也很直接:
import numpy as np
def three_sigma_detect(data, threshold=3):
mean = np.mean(data)
std = np.std(data)
upper = mean + threshold * std
lower = mean - threshold * std
anomalies = [x for x in data if x < lower or x > upper]
return anomalies, (lower, upper)
# 示例
data = [100, 102, 98, 101, 99, 103, 97, 100, 500, 101, 99]
anomalies, bounds = three_sigma_detect(data)
print(f"异常值: {anomalies}") # 输出: [500]
print(f"正常范围: {bounds}") # 输出: (约85, 约115)
避坑指南:我曾经在一个QPS监控上直接用3Sigma,结果发现告警频率高得离谱。后来一查,原来是数据存在明显的日周期规律——白天高、晚上低。这种情况下,3Sigma会把晚上的低值也判为异常。解决方案?按时间段分段计算,或者先做季节性分解。
4.2 移动平均法:让数据“平滑”下来
移动平均法,就是取最近N个点的平均值作为当前点的估计值。它像一个低通滤波器,把高频噪声滤掉,留下趋势信号。
公式:
MA_t = (x_t + x_{t-1} + ... + x_{t-N+1}) / N
你想想看,为什么需要移动平均?因为原始数据太“毛躁”了。比如CPU使用率,可能每秒钟都在跳,但真正需要关注的是一段时间内的趋势变化。移动平均就是干这个的。
异常检测的逻辑:用移动平均值作为基线,如果当前值与基线的偏差超过某个阈值,就告警。
def moving_average_detect(data, window=5, threshold=1.5):
ma = np.convolve(data, np.ones(window)/window, mode='valid')
# 对齐索引
anomalies = []
for i in range(window-1, len(data)):
if abs(data[i] - ma[i-window+1]) > threshold * np.std(data):
anomalies.append((i, data[i]))
return anomalies, ma
# 示例
data = [10, 12, 11, 13, 10, 11, 12, 50, 11, 10, 13]
anomalies, ma = moving_average_detect(data, window=3)
print(f"异常点索引及值: {anomalies}") # 输出: [(7, 50)]
| 窗口大小 | 特点 | 适用场景 |
|---|---|---|
| 小窗口(3-5) | 对变化敏感,但容易受噪声干扰 | 快速检测短期波动 |
| 中窗口(10-20) | 平衡敏感度和稳定性 | 日常监控,通用场景 |
| 大窗口(30+) | 平滑效果好,但响应滞后 | 长期趋势分析,低频指标 |
我的建议:窗口大小怎么选?我一般先看数据的周期长度。比如每分钟上报一次的数据,如果周期是1小时,窗口就取60。如果周期是1天,窗口就取1440。记住,窗口要能覆盖至少一个完整周期。
4.3 指数平滑法:给数据“加权”
移动平均法有个问题:它给窗口内的每个点相同的权重。但直觉告诉我们,越近的数据应该越重要。指数平滑法就是来解决这个问题的。
公式:
S_t = α * x_t + (1 - α) * S_{t-1}
其中α是平滑系数,取值范围0到1。α越大,越看重近期数据;α越小,历史数据的影响越大。
说白了,指数平滑就是一个“带遗忘机制”的平均。它不需要维护一个窗口,只需要记住上一个平滑值就够了。这在流式数据处理中特别有用——你不需要保存历史数据,只需要一个状态变量。
def exponential_smoothing_detect(data, alpha=0.3, threshold=2.0):
s = data[0] # 初始值
anomalies = []
for i in range(1, len(data)):
s = alpha * data[i] + (1 - alpha) * s
# 计算残差
residual = abs(data[i] - s)
if residual > threshold * np.std(data[:i]):
anomalies.append((i, data[i], residual))
return anomalies
# 示例
data = [100, 102, 101, 103, 100, 102, 101, 200, 103, 102]
anomalies = exponential_smoothing_detect(data, alpha=0.3)
print(f"异常点: {anomalies}") # 输出: [(7, 200, 约98)]
避坑指南:α的取值很关键。我见过有人把α设成0.99,结果模型几乎只认最新一个点,噪声全被当成异常。反过来,α设成0.01,模型又太迟钝,异常都过去了才反应过来。我的经验是:先试0.2-0.4,然后根据实际告警效果微调。
4.4 三种方法的对比与选择
这三种方法各有千秋,没有银弹。我一般这样选:
- 数据稳定、服从正态分布 → 3Sigma原则,简单粗暴
- 数据有趋势、需要平滑噪声 → 移动平均法,窗口大小按周期定
- 流式数据、内存有限、近期数据更重要 → 指数平滑法,α从0.3开始调
在实际项目中,我经常把它们组合使用。比如先用移动平均做基线,再用3Sigma检测偏差;或者用指数平滑做实时预测,然后计算残差的3Sigma阈值。
实战心得:别小看这些“老方法”。我在一个日活千万的平台上,就是用指数平滑+3Sigma的组合,把误报率从30%降到了5%以下。有时候,最简单的方案反而是最可靠的。