异常检测听起来很高大上,但我的经验是:现实里 80% 的场景用异常简单的方法就能解决。
问题定义
你有一个指标 — 收入、注册、报错数,随便哪种 — 想知道什么时候出了异常。不是”数字涨了”,而是”数字比预期涨得多”。
方法一:简单 Z-score
把今天的值与历史均值、标准差对比:
z_score = (current_value - historical_mean) / historical_std
is_anomaly = abs(z_score) > 3
适用:稳定、没有强季节性的指标。
局限:处理不了趋势和周内模式。
方法二:滚动统计
用滚动窗口让基线自适应:
rolling_mean = df["metric"].rolling(window=28).mean()
rolling_std = df["metric"].rolling(window=28).std()
z_score = (df["metric"] - rolling_mean) / rolling_std
适用:有缓慢趋势的指标。
局限:仍处理不好季节性。
方法三:季节性分解
把时间序列拆成趋势、季节、残差,对残差做检测:
from statsmodels.tsa.seasonal import seasonal_decompose
result = seasonal_decompose(df["metric"], period=7)
residuals = result.resid
# 对残差做 z-score
适用:周/月级别有明显周期的指标。
方法四:Prophet
Facebook Prophet 自带不确定区间:
from prophet import Prophet
model = Prophet(interval_width=0.99)
model.fit(df)
forecast = model.predict(df)
# 落在 yhat_upper/yhat_lower 之外的点视为异常
适用:复杂季节性、节假日效应。
实用建议
- 从简单开始:Z-score 能解决的问题比你想的多。我通常就从这里起步。
- 调阈值:3σ 是起点不是铁律。根据你能容忍的误报率调整。
- 处理缺失值:检测器最烦数据有洞,填补要讲究。
- 警报疲劳是真的:宁可漏报一些,也别天天喊狼来了。这是血泪经验。
- 先调查再报警:很多”异常”其实有平平无奇的原因。
元问题
异常检测最难的从来不是算法 — 而是定义什么样的异常值得采取行动。从”看到这条告警我们会做什么?“倒推阈值。如果答案是”什么也不做”,那也许这条告警根本不必存在。