4、异常检测基础:统计方法、距离方法与孤立森林
各位同学,今天我们来聊聊异常检测。说白了,就是怎么从一堆数据里,把那些「不对劲」的样本揪出来。内幕交易检测里,异常交易行为就是我们要找的「不对劲」。我做了这么多年反欺诈,发现很多新手一上来就上深度学习,其实没必要。先打好基础,统计方法和距离方法往往就能解决80%的问题。
4.1 统计方法:Z-score 与 IQR
统计方法是最直观的。它假设正常数据服从某种分布,偏离太远的就算异常。我个人习惯先用统计方法快速扫一遍数据,心里有个底。
4.1.1 Z-score 方法
Z-score 衡量一个数据点偏离均值多少个标准差。公式很简单:
Z = (x - μ) / σ
其中 μ 是均值,σ 是标准差。通常我们取阈值 3,也就是 |Z| > 3 就算异常。为什么是3?因为正态分布下,99.7% 的数据都在3个标准差内。
举个例子,某只股票过去100天的日均成交量是100万股,标准差20万。今天突然成交了200万股。算一下 Z = (200-100)/20 = 5,远超3,明显异常。嗯,这里要注意,如果数据量太小(比如少于30个样本),Z-score 就不太靠谱了。
4.1.2 IQR 方法
IQR 方法对数据分布要求没那么严格。它基于四分位数:
- Q1:下四分位数(25%分位)
- Q3:上四分位数(75%分位)
- IQR = Q3 - Q1
- 异常阈值:低于 Q1 - 1.5×IQR 或高于 Q3 + 1.5×IQR
为什么是1.5倍?这是统计学上的经验值。你想想看,这个系数其实可以调的。在金融反欺诈场景下,我经常把系数调到2甚至3,因为金融数据波动大,太敏感会误报太多。
4.2 距离方法:KNN 与 LOF
统计方法假设数据分布是规则的,但现实中的数据往往不是。这时候距离方法就派上用场了。它的核心思想很简单:离群点周围邻居少,或者离邻居远。
4.2.1 KNN 异常检测
KNN 做异常检测,不是用来分类的,而是计算每个点到其第 k 个最近邻居的距离。距离越大,越可能是异常。
from sklearn.neighbors import NearestNeighbors
import numpy as np
# 假设 X 是特征矩阵
nbrs = NearestNeighbors(n_neighbors=5)
nbrs.fit(X)
distances, indices = nbrs.kneighbors(X)
# 取第5近的距离作为异常分数
anomaly_scores = distances[:, -1]
# 设定阈值,比如取95%分位数
threshold = np.percentile(anomaly_scores, 95)
outliers = np.where(anomaly_scores > threshold)[0]
我个人习惯用 k=5 或 k=10。k 太小容易受局部噪声影响,k 太大又会把局部异常给平滑掉。我在做内幕交易检测时,发现 k=20 效果更好,因为交易数据本身噪声大,需要多看几个邻居才能判断。
4.2.2 LOF(局部异常因子)
LOF 比 KNN 更聪明。它不光看距离,还看局部密度。如果一个点的密度远低于其邻居的密度,那它就是异常。
为什么需要 LOF?举个例子,一个交易活跃的营业部,每天有上千笔交易,其中一笔交易金额稍大,但放在这个营业部里很正常。而一个冷清营业部,突然来了一笔大单,那就很可疑。LOF 能捕捉这种局部差异。
from sklearn.neighbors import LocalOutlierFactor
lof = LocalOutlierFactor(n_neighbors=20, contamination=0.05)
# contamination 是预期异常比例
y_pred = lof.fit_predict(X)
# -1 表示异常,1 表示正常
anomaly_scores = -lof.negative_outlier_factor_
4.3 孤立森林原理
孤立森林是我最喜欢的方法之一。它不显式定义「正常」或「异常」,而是通过随机切割来孤立每个点。异常点因为特征值特殊,更容易被孤立出来。
你想想看,正常交易就像人群中的普通人,很难一眼找出来。而异常交易就像人群中穿奇装异服的人,一眼就能看到。孤立森林做的就是这件事——用随机切割的方式,快速把「奇装异服」的人找出来。
4.3.1 算法核心思想
- 随机选一个特征,再随机选一个切割值
- 把数据分成左右两个子空间
- 递归切割,直到每个点都被孤立
- 异常点需要的切割次数少(路径短)
- 正常点需要的切割次数多(路径长)
为什么会这样?因为异常点往往特征值极端,随机切割很容易就把它们分出来了。而正常点聚集在一起,需要切很多刀才能分开。
4.3.2 代码实现
from sklearn.ensemble import IsolationForest
iso_forest = IsolationForest(
n_estimators=100, # 树的数量
max_samples='auto', # 每棵树用的样本数
contamination=0.05, # 预期异常比例
random_state=42
)
iso_forest.fit(X)
# 预测:1 正常,-1 异常
y_pred = iso_forest.predict(X)
# 异常分数,越负越异常
anomaly_scores = iso_forest.decision_function(X)
4.4 三种方法对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Z-score | 简单快速,可解释性强 | 要求正态分布,对异常值敏感 | 单变量、数据量大的场景 |
| IQR | 不受极端值影响,无需分布假设 | 只考虑全局分布,忽略局部模式 | 偏态分布、有离群点的数据 |
| KNN | 无需分布假设,可处理多变量 | 计算量大,对k值敏感 | 中小规模数据集 |
| LOF | 能检测局部异常,适应不同密度 | 参数调优复杂,计算量大 | 密度不均匀的数据 |
| 孤立森林 | 高效,适合高维数据,无需缩放 | 对无关特征敏感,结果难解释 | 大规模、高维数据 |
4.5 知识体系总览
下面这张图总结了本章的核心逻辑。你可以看到,从统计方法到距离方法再到孤立森林,是一个从简单到复杂、从全局到局部、从假设驱动到数据驱动的演进过程。
好了,以上就是异常检测的三种基础方法。每种方法都有自己的脾气,没有银弹。我个人的工作流是:先用 IQR 快速扫一遍,剔除明显异常;然后用孤立森林做精细检测;最后用 LOF 验证局部异常。三个方法交叉验证,基本不会漏掉真正的内幕交易。