4、异常检测基础:统计方法、距离方法与孤立森林

各位同学,今天我们来聊聊异常检测。说白了,就是怎么从一堆数据里,把那些「不对劲」的样本揪出来。内幕交易检测里,异常交易行为就是我们要找的「不对劲」。我做了这么多年反欺诈,发现很多新手一上来就上深度学习,其实没必要。先打好基础,统计方法和距离方法往往就能解决80%的问题。

4.1 统计方法:Z-score 与 IQR

统计方法是最直观的。它假设正常数据服从某种分布,偏离太远的就算异常。我个人习惯先用统计方法快速扫一遍数据,心里有个底。

4.1.1 Z-score 方法

Z-score 衡量一个数据点偏离均值多少个标准差。公式很简单:

Z = (x - μ) / σ

其中 μ 是均值,σ 是标准差。通常我们取阈值 3,也就是 |Z| > 3 就算异常。为什么是3?因为正态分布下,99.7% 的数据都在3个标准差内。

我的经验: 我在项目中遇到过,有些数据分布不是正态的,比如交易金额往往长尾分布。这时候直接用 Z-score 会漏掉很多异常。我建议先做对数变换,把数据拉近正态分布再算。

举个例子,某只股票过去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,因为金融数据波动大,太敏感会误报太多。

避坑指南: 我曾经在检测大额交易时,直接用 IQR 方法,结果把很多正常的机构大单都标记成了异常。后来发现,机构交易和散户交易应该分开建模。所以,做异常检测前,先做好数据分层。

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_
关键点: LOF 的 contamination 参数需要根据业务场景设定。内幕交易的真实比例可能只有万分之一,但模型训练时设成 0.05 是为了让模型学到更多模式。实际部署时再调低阈值。

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)
我的经验: 孤立森林对高维数据效果很好。我在做内幕交易检测时,特征维度有50多个,包括交易时间、金额、频率、账户关联度等。孤立森林跑得飞快,而且不需要做特征缩放。不过要注意,如果数据中有大量无关特征,效果会下降。我建议先用特征选择筛一遍。

4.4 三种方法对比

方法 优点 缺点 适用场景
Z-score 简单快速,可解释性强 要求正态分布,对异常值敏感 单变量、数据量大的场景
IQR 不受极端值影响,无需分布假设 只考虑全局分布,忽略局部模式 偏态分布、有离群点的数据
KNN 无需分布假设,可处理多变量 计算量大,对k值敏感 中小规模数据集
LOF 能检测局部异常,适应不同密度 参数调优复杂,计算量大 密度不均匀的数据
孤立森林 高效,适合高维数据,无需缩放 对无关特征敏感,结果难解释 大规模、高维数据

4.5 知识体系总览

下面这张图总结了本章的核心逻辑。你可以看到,从统计方法到距离方法再到孤立森林,是一个从简单到复杂、从全局到局部、从假设驱动到数据驱动的演进过程。

异常检测方法知识体系 异常检测基础 统计方法 Z-score:基于标准差 IQR:基于四分位数 距离方法 KNN:k近邻距离 LOF:局部密度对比 孤立森林 随机切割孤立 路径越短越异常 核心逻辑:从全局假设 → 局部密度 → 数据驱动 内幕交易检测:建议先用IQR快速过滤,再用孤立森林精筛

好了,以上就是异常检测的三种基础方法。每种方法都有自己的脾气,没有银弹。我个人的工作流是:先用 IQR 快速扫一遍,剔除明显异常;然后用孤立森林做精细检测;最后用 LOF 验证局部异常。三个方法交叉验证,基本不会漏掉真正的内幕交易。


专注资料整理