常见异常检测方法简介
聊到异常检测,很多新手的第一反应就是「设个阈值不就行了?」。嗯,这个想法很直接,但现实往往没那么简单。我在早期做服务器监控时,也这么干过——给 CPU 使用率设了个 90% 的硬阈值。结果呢?大促期间流量翻了三倍,阈值天天被触发,告警刷屏,运维兄弟差点把我拉黑。
为什么会这样?
因为真实的系统行为是动态的、有模式的。单一的阈值根本抓不住那种「明明没超限,但行为就是不对劲」的异常。
下面我带你过一遍工业界常用的几种异常检测方法。每种方法都有自己的脾气,选对了才是好工具。
基于统计的方法
这是最经典的一类。核心假设是:正常数据服从某个已知的概率分布,异常点则是小概率事件。
- 3-Sigma 原则:假设数据服从正态分布,超出均值 ±3σ 的点标记为异常。
- Grubbs 检验:单变量场景下,逐个检测离群点。
- 箱线图(四分位距):不依赖正态分布,用 Q1-1.5×IQR 和 Q3+1.5×IQR 画边界。
这类方法计算快,可解释性强。但局限也很明显——它假设数据是独立同分布的。说白了,如果你的数据是时序的,有趋势有周期,用纯统计方法就容易误报。
基于距离的方法
核心思路:异常点离它最近的邻居都比较远。典型的代表是 k-最近邻距离法。
我给一个简单的检测框架:
from sklearn.neighbors import NearestNeighbors
import numpy as np
def knn_anomaly_score(X, k=5):
nbrs = NearestNeighbors(n_neighbors=k).fit(X)
distances, _ = nbrs.kneighbors(X)
# 用第k近的距离作为异常分数
scores = distances[:, -1]
return scores
# 设定百分位阈值
scores = knn_anomaly_score(data, k=5)
threshold = np.percentile(scores, 95)
anomalies = np.where(scores > threshold)[0]
距离法比较容易理解,不受分布假设的约束。但它有两个硬伤:第一,计算复杂度高——数据量一大,全量计算 k-近邻基本跑不动;第二,它对「密度不均」的数据集效果很差——假设正常数据本身就有稀疏区域,你按距离一刀切,那些正常的稀疏点也会被误判为异常。
基于密度的方法
为了解决上面那个「稀疏区域 vs 真实异常」的问题,有人提出了局部异常因子(LOF, Local Outlier Factor)。
LOF 不看距离,而是看「局部可达密度」。什么意思呢?它计算一个点跟邻居的紧密程度,再跟邻居们的紧密程度做个对比。通俗点说:你的邻居们如果都住得挺挤,就你一个人孤零零的,那你大概率有问题。
LOF 的优势在于能处理局部异常。我在生产环境里用它检测过一波流量异常——同样是 2000 QPS,放在 A 机房的正常业务上算正常,放在 B 机房的静默业务上就是异常。LOF 能区分出这种「局部上下文」的差异。
LOF 的调参压力比较大——邻居数 k 选不好,结果天差地别。一般建议先跑一下小规模数据试一下。
基于树的方法
「孤立森林」是这类方法的代表。它不试图建模「正常是什么样」,反过来去想——异常点往往更容易被「孤立」出来。
算法的做法很暴力:随机选一个特征,随机选一个分裂值,把数据不断切分。正常点需要很多次分裂才能被隔离,异常点没几下就被单独分出来了。
from sklearn.ensemble import IsolationForest
model = IsolationForest(
n_estimators=100,
max_samples='auto',
contamination=0.05, # 预期异常比例
random_state=42
)
model.fit(X)
# outlier 标签:1 正常,-1 异常
labels = model.predict(X)
# 异常分数(越小越异常)
scores = model.decision_function(X)
孤立森林速度快,几乎不需要什么假设约束,高维数据也不怵。但我个人习惯提醒一句:它的随机性很强,建议多跑几次取均值,结果会更稳定。
基于聚类的方法
这种方法也很好理解:先把数据聚成几团,然后看哪些点「不属于任何一团」。常见的做法有 DBSCAN 和 K-Means 的变体。
拿 DBSCAN 举例,它天然可以识别噪声点——每个样本如果落不到任何核心点的邻域半径内,就直接标记为 -1(噪声),这不就是异常嘛。
聚类方法的明显局限:如果异常本身自己也能聚成一团(比如某条链路持续掉包形成的线形轨迹),那它反而会被当成正常的簇。这一点需要警惕。
方法速查对比
我把这几类方法的关键差异列了个表,方便你查阅选型:
| 方法类别 | 核心思想 | 适用场景 | 典型上限(样本数) | 调参复杂程度 |
|---|---|---|---|---|
| 统计方法 | 分布假设 + 低概率 | 单变量、数据量大且稳定 | 百万级 | 低 |
| 距离方法 | 到最近邻居的距离 | 低维、小数据集 | 万级 | 中 |
| 密度方法(LOF) | 局部密度对比 | 密度不均匀、中低维 | 万级 | 高 |
| 孤立森林 | 随机切分,孤立难度 | 高维、大样本、混合类型 | 十万级 | 低 |
| 聚类方法 | 远离所有簇 | 簇形状规整、有先验 | 万级~十万级 | 中~高 |
方法选择框架图
我整理了一张选择逻辑图,帮你快速定位用哪一种方法:
这张图的思路是从数据规模出发做第一层过滤,再从「是否需要解释性」做第二层。我个人习惯先拿小数据集快速跑一轮对比,看看各方法的召回率分布,再定最终方案——别一上来就调大参数,浪费时间。
小结
异常检测没有银弹。每种方法都有自己的脾气,也都有自己的盲区。你想想看——统计方法怕分布假设不满足,距离法怕局部密度不均匀,聚类法怕异常自己抱成团,孤立森林又带随机性。
我的建议是:至少掌握 2-3 种方法,能根据数据特征灵活切换。下一轮我们再聊如何把这些方法封装成可复用的 SDK 模块。