异常检测算法(一):基于统计与基于距离的方法

大家好,我是老张。今天我们来聊聊异常检测里最基础、也最实用的两类方法——统计方法和距离方法。说实话,这两类方法我用了十几年,从最早做服务器监控,到后来搞AI运维,它们始终是我的“看家本领”。

你可能会问:为什么先讲这些?因为在实际运维中,80%的异常场景用这些方法就能搞定。剩下的20%,才需要上深度学习那些重型武器。嗯,咱们先把基本功练扎实。

核心观点:异常检测的本质是“找不同”。统计方法找的是“不符合分布规律”的点,距离方法找的是“离群太远”的点。两者思路不同,但目标一致。

一、基于统计的方法

统计方法的核心假设是:正常数据服从某种分布。如果某个点出现的概率极低,那它就是异常。我个人习惯先看数据分布,再选具体方法。

1. 3-Sigma 法则

这是最简单粗暴的方法。假设数据服从正态分布,那么99.7%的数据会落在均值±3个标准差范围内。超出这个范围的,就是异常。

import numpy as np

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

# 示例
data = [10, 12, 11, 13, 12, 11, 100, 10, 12]
print(detect_3sigma(data))  # 输出: [100]

我的经验:3-Sigma对数据量有要求,至少30个样本以上才靠谱。我在监控CPU使用率时常用它,但注意——如果数据本身不是正态分布,比如网络延迟这种长尾分布,3-Sigma会漏报很多。

2. Grubbs 检验

Grubbs检验是3-Sigma的升级版。它不只算阈值,还会做假设检验,判断这个异常点是否“显著”。说白了,它告诉你:这个点异常的概率有多大。

from scipy import stats
import numpy as np

def grubbs_test(data, alpha=0.05):
    n = len(data)
    mean = np.mean(data)
    std = np.std(data)
    # 计算G值
    g = max(abs(x - mean) for x in data) / std
    # 查临界值
    t_crit = stats.t.ppf(1 - alpha / (2 * n), n - 2)
    g_crit = ((n - 1) / np.sqrt(n)) * np.sqrt(t_crit**2 / (n - 2 + t_crit**2))
    return g > g_crit

data = [10, 12, 11, 13, 12, 11, 100, 10, 12]
print(grubbs_test(data))  # 输出: True (存在异常)

注意:Grubbs检验一次只能检测一个异常点。如果你怀疑有多个异常,需要反复迭代——每次剔除一个异常,再重新检验。我曾经在磁盘IO监控中踩过这个坑,一次检测多个异常导致误报率飙升。

3. MAD(中位数绝对偏差)

MAD是3-Sigma的“稳健版”。为什么说它稳健?因为中位数不受极端值影响,而均值会被异常点拉偏。你想想看,如果数据里已经混入了异常,用均值算出来的标准差还能准吗?

import numpy as np

def detect_mad(data, threshold=3.5):
    median = np.median(data)
    mad = np.median(np.abs(data - median))
    modified_z_scores = 0.6745 * (data - median) / mad
    anomalies = data[np.abs(modified_z_scores) > threshold]
    return anomalies

data = np.array([10, 12, 11, 13, 12, 11, 100, 10, 12])
print(detect_mad(data))  # 输出: [100]

避坑指南:MAD的阈值一般取3.5,但这不是铁律。我曾经处理过网络流量数据,分布特别集中,阈值调到2.5才抓到真正的异常。建议你先画个箱线图看看数据分布,再调阈值。

二、基于距离的方法

统计方法有个硬伤——它假设数据服从特定分布。但现实中的数据,鬼知道它是什么分布。这时候,距离方法就派上用场了。它的思路很简单:如果一个点离它的邻居们都很远,那它大概率是异常。

1. KNN(K近邻)异常检测

KNN的思路是:计算每个点到它第K个最近邻居的距离。如果这个距离很大,说明它周围很空旷,那它就是异常。

from sklearn.neighbors import NearestNeighbors
import numpy as np

def knn_anomaly_detection(data, k=5, threshold=2.0):
    nbrs = NearestNeighbors(n_neighbors=k).fit(data)
    distances, _ = nbrs.kneighbors(data)
    # 取第k个最近邻的距离
    k_distances = distances[:, -1]
    mean_dist = np.mean(k_distances)
    std_dist = np.std(k_distances)
    anomalies = data[k_distances > mean_dist + threshold * std_dist]
    return anomalies

# 示例:二维数据
data = np.array([[1, 2], [2, 3], [1, 1], [2, 2], [10, 10], [1.5, 2.5]])
print(knn_anomaly_detection(data))  # 输出: [[10, 10]]

关键参数:K值的选择很讲究。K太小,容易把局部稀疏的正常点误判为异常;K太大,又会漏掉真正的异常。我个人习惯K取5-10之间,然后根据业务场景微调。

2. LOF(局部异常因子)

LOF是KNN的进阶版。它不光看距离,还看“密度”。如果一个点的密度远低于它邻居的密度,那它就是异常。这有什么用?举个例子:假设有两个簇,一个很密集,一个很稀疏。稀疏簇边缘的点,用KNN可能判为正常,但LOF能识别出来——因为它的邻居密度比它高。

from sklearn.neighbors import LocalOutlierFactor
import numpy as np

def lof_anomaly_detection(data, contamination=0.1):
    lof = LocalOutlierFactor(n_neighbors=20, contamination=contamination)
    y_pred = lof.fit_predict(data)
    anomalies = data[y_pred == -1]
    return anomalies

data = np.array([[1, 2], [2, 3], [1, 1], [2, 2], [10, 10], [1.5, 2.5], [100, 100]])
print(lof_anomaly_detection(data))  # 输出: [[100, 100]]

注意:LOF的contamination参数表示你预期数据中异常的比例。设得太高,会把正常点误判为异常;设得太低,又会漏报。我在做服务器日志异常检测时,一般设0.01-0.05,因为真正的异常其实很少。

三、方法对比与选择

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

方法 适用场景 优点 缺点
3-Sigma 数据近似正态分布 简单快速,容易理解 对非正态分布效果差
Grubbs检验 单变量、少量异常 有统计显著性检验 一次只能检测一个异常
MAD 数据有离群值干扰 稳健,不受极端值影响 阈值需要调参
KNN 多变量、分布未知 无需假设分布 计算量大,K值难选
LOF 密度不均匀的数据 能处理局部异常 参数敏感,解释性差

我的建议:刚开始做异常检测时,先用MAD或KNN试试水。它们对数据分布要求低,调参也简单。等业务稳定了,再上LOF这种更精细的方法。别一上来就搞复杂的,容易翻车。

四、知识体系图

下面这张图总结了本章的核心逻辑,帮你理清思路:

异常检测算法体系 基于统计的方法 基于距离的方法 3-Sigma Grubbs检验 MAD(中位数绝对偏差) KNN LOF 核心思想:找“不一样”的点 选择建议:数据分布已知→统计方法;分布未知→距离方法 注:实际应用中,建议多种方法组合使用,取长补短

嗯,以上就是本章的全部内容。统计方法和距离方法,说白了就是异常检测的两条腿。走稳了,后面的深度学习、时序预测才能跑起来。记住:没有最好的方法,只有最合适的方法。多试、多调、多总结,你也能成为异常检测的老司机。