🔍 异常检测 — Isolation Forest/Z-score
大海捞针,找出那些"不一样的存在"
📖 异常检测概述
异常检测(Anomaly Detection)是从数据中识别"异常"或"离群"样本的技术,广泛应用于欺诈检测、入侵检测、设备故障预警等场景。
异常检测三大类型:
1. 点异常 (Point Anomaly)
─────────────────────────
单个数据点异常(与整体分布偏离)
例: 信用卡单笔交易金额异常大
2. 上下文异常 (Contextual Anomaly)
──────────────────────────────────
在特定上下文中异常(时间/空间相关)
例: 夏天10°C温度正常,冬天10°C异常
3. 集合异常 (Collective Anomaly)
──────────────────────────────────
单个点正常,但一组点的组合异常
例: 同一IP短时间多次登录不同账号
异常检测方法分类:
┌────────────────────────────────────────┐
│ 统计方法 │ Z-Score / IQR / Grubbs │
│ 机器学习 │ IsolationForest / OCSVM │
│ 深度学习 │ AutoEncoder / VAE / GAN │
└────────────────────────────────────────┘
| 方法 | 类型 | 优点 | 缺点 | 适用 |
| Z-Score | 统计 | 简单快速 | 假设正态 | 低维 |
| IQR | 统计 | 鲁棒 | 单维度 | 低维 |
| IsolationForest | 集成 | 高维友好 | 随机性 | 通用 |
| One-Class SVM | 核方法 | 边界精确 | 慢 | 中小规模 |
| AutoEncoder | 深度 | 非线性 | 需大数据 | 复杂分布 |
| LOF | 密度 | 局部异常 | 参数敏感 | 密度变化 |
🔢 Z-Score方法
Z-Score衡量一个点离均值有多少个标准差,是最直觉的异常检测方法。
Z = (x - μ) / σ 阈值: |Z| > 2.5 或 |Z| > 3 → 异常
Z-Score原理:
正态分布下:
|Z| < 1: 68.3% 的数据 → 正常
|Z| < 2: 95.4% 的数据 → 正常
|Z| < 3: 99.7% 的数据 → 边界
|Z| > 3: 0.3% 的数据 → 异常! (3σ原则)
多维: 任一维度|Z|>阈值 → 异常
或: 马氏距离 (考虑特征间相关性)
D² = (x-μ)ᵀ Σ⁻¹ (x-μ)
from scipy import stats
import numpy as np
np.random.seed(42)
n_normal, n_anomaly = 300, 30
X_normal = np.random.randn(n_normal, 2)
X_anomaly = np.random.randn(n_anomaly, 2) * 3 + np.array([5, 5])
X = np.vstack([X_normal, X_anomaly])
y_true = np.array([1]*n_normal + [-1]*n_anomaly)
# Z-Score检测
z_scores = np.abs(stats.zscore(X, axis=0))
threshold = 2.5
z_anomaly = (z_scores > threshold).any(axis=1)
z_pred = np.where(z_anomaly, -1, 1)
# 各维度统计
for dim in range(2):
z_dim = np.abs(stats.zscore(X[:, dim]))
n_outlier = (z_dim > threshold).sum()
print(f"维度{dim}: Z>{threshold}的点={n_outlier}, 最大Z={z_dim.max():.2f}")
z_correct = (z_pred == y_true).sum()
print(f"\nZ-Score准确率: {z_correct/len(y_true)*100:.1f}%")
Z-Score假设数据服从正态分布!如果数据严重偏态,Z-Score可能误报或漏报。此时建议用IQR或非参数方法。
📊 IQR方法
IQR(四分位距)方法基于数据的分位数,不受极端值影响,比Z-Score更鲁棒。
IQR方法:
排序后: ───────[Q1]─────────[Q2/中位数]─────────[Q3]───────
IQR = Q3 - Q1 (中间50%数据的范围)
下界 = Q1 - 1.5 × IQR
上界 = Q3 + 1.5 × IQR
超出范围 → 异常!
┌──────────────────────────────────────────────┐
│ 为什么用1.5? 正态分布下: │
│ Q1-1.5×IQR ≈ μ-2.7σ │
│ Q3+1.5×IQR ≈ μ+2.7σ │
│ → 约99.3%的数据在范围内,0.7%被判异常 │
└──────────────────────────────────────────────┘
# IQR检测
iqr_anomaly = np.zeros(len(X), dtype=bool)
for dim in range(2):
Q1 = np.percentile(X[:, dim], 25)
Q3 = np.percentile(X[:, dim], 75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outlier_dim = (X[:, dim] < lower) | (X[:, dim] > upper)
iqr_anomaly |= outlier_dim
print(f"维度{dim}: Q1={Q1:.2f}, Q3={Q3:.2f}, IQR={IQR:.2f}, "
f"范围=[{lower:.2f}, {upper:.2f}]")
iqr_pred = np.where(iqr_anomaly, -1, 1)
iqr_correct = (iqr_pred == y_true).sum()
print(f"\nIQR准确率: {iqr_correct/len(y_true)*100:.1f}%")
🌲 Isolation Forest
Isolation Forest的核心思想:异常点"少而不同",更容易被孤立。随机切分空间,异常点只需很少次切分就能被隔离。
Isolation Forest原理:
正常点: 需要很多次切分才能孤立 异常点: 很少切分就能孤立
┌─────────────────┐ ┌─────────────────┐
│ · · · · │ │ · · · · │
│ · · · · │ │ · · · · │
│ · · ●· · │ │ · · · · │
│ · · · · │ │ · · · · │
│ · · · · │ │ · · ★ · │
└─────────────────┘ └─────────────────┘
需要8次切分才能孤立 只需2次切分就孤立!
异常分数:
s(x, n) = 2^(-E(h(x)) / c(n))
h(x) = 样本x的平均路径长度
c(n) = 归一化因子 (二叉搜索树平均路径)
s → 1: 异常 (路径短,容易被孤立)
s → 0.5: 正常 (路径与平均长度相当)
s → 0: 非常正常
from sklearn.ensemble import IsolationForest
# Isolation Forest
iso = IsolationForest(
contamination=0.1, # 异常比例估计
n_estimators=100, # 树的数量
random_state=42
)
iso_pred = iso.fit_predict(X) # 1=正常, -1=异常
# 异常分数 (越大越正常)
scores = iso.decision_function(X)
print(f"正常样本平均分数: {scores[y_true==1].mean():.3f}")
print(f"异常样本平均分数: {scores[y_true==-1].mean():.3f}")
iso_correct = (iso_pred == y_true).sum()
print(f"IsolationForest准确率: {iso_correct/len(y_true)*100:.1f}%")
🔬 方法全面对比
# 综合对比
from sklearn.svm import OneClassSVM
# One-Class SVM
ocsvm = OneClassSVM(nu=0.1, kernel='rbf', gamma='scale')
ocsvm_pred = ocsvm.fit_predict(X)
print(f"{'方法':>18} {'准确率':>8} {'精确率':>8} {'召回率':>8} {'F1':>8}")
print("=" * 55)
for name, pred in [("Z-Score", z_pred), ("IQR", iqr_pred),
("IsolationForest", iso_pred), ("OneClassSVM", ocsvm_pred)]:
acc = (pred == y_true).sum() / len(y_true)
tp = ((pred == -1) & (y_true == -1)).sum()
fp = ((pred == -1) & (y_true == 1)).sum()
fn = ((pred == 1) & (y_true == -1)).sum()
precision = tp/(tp+fp) if (tp+fp) > 0 else 0
recall = tp/(tp+fn) if (tp+fn) > 0 else 0
f1 = 2*precision*recall/(precision+recall) if (precision+recall) > 0 else 0
print(f"{name:>18} {acc:>8.3f} {precision:>8.3f} {recall:>8.3f} {f1:>8.3f}")
实际项目中,异常检测没有"最佳方法"——选择取决于数据特点、异常类型和业务需求。通常建议先用IQR/Z-Score做基线,再用Isolation Forest提升效果。
📐 2024-2025 异常检测前沿
- AutoEncoder异常检测:重构误差大的=异常,适合高维数据
- VAE (变分自编码器):概率框架,输出异常概率而非二元判断
- GANomaly:用GAN学习正常分布,生成器判别器联合检测
- Transformer异常检测:TrAnomaly、Anomaly Transformer
- 时序异常检测:LSTM-AE、DeepAnT,检测时序中的突变点
- 多模态异常检测:融合视觉+文本+时序信号
- 异常检测+大模型:LLM描述异常原因,可解释性提升
🧮 异常检测方法选择
异常检测选择指南:
数据特点?
│
├── 数据是否近似正态?
│ ├── 是 → Z-Score (简单快速)
│ └── 否 → IQR (鲁棒)
│
├── 数据维度?
│ ├── 低维(<10) → Z-Score / IQR / LOF
│ └── 高维 → Isolation Forest / AutoEncoder
│
├── 有标签数据?
│ ├── 无标签 → 无监督方法
│ ├── 少量标签 → 半监督(OCSVM)
│ └── 大量标签 → 监督分类
│
├── 需要可解释性?
│ └── Z-Score/IQR (直观)
│
└── 时序数据?
├── 统计 → ARIMA残差
└── 深度 → LSTM-AE
🏆 成就解锁:Isolation Forest/Z-score
四种异常检测方法同场竞技:IQR准确率98.2%(F1=0.900),IsolationForest准确率97.3%(F1=0.857),Z-Score准确率96.7%(F1=0.776),OneClassSVM准确率93.9%(F1=0.677)!IQR在本数据集上最优!
Python验证通过 — 异常检测4方法对比:Z-Score(96.7%, precision=1.0, recall=0.633, F1=0.776),IQR(98.2%, precision=0.9, recall=0.9, F1=0.9),IsolationForest(97.3%, precision=0.818, recall=0.9, F1=0.857),OneClassSVM(93.9%, precision=0.656, recall=0.7, F1=0.677)!
思考题:
1. Z-Score和IQR各有什么优缺点?什么场景下用哪个?
2. Isolation Forest为什么对异常点"天然敏感"?它的直觉是什么?
3. 如何设定异常检测的阈值?过高/过低各有什么后果?
4. 在实际欺诈检测中,你会优先保证精确率还是召回率?为什么?
📝 课后练习
- 用KDD Cup 1999网络入侵数据集做异常检测,对比各方法
- 实现LOF(局部异常因子),对比全局方法的效果
- 用AutoEncoder做异常检测(重构误差作为异常分数)
- 实现时间序列异常检测(滑动窗口+Z-Score)
- 实现马氏距离(考虑特征相关性),对比普通Z-Score
📚 参考资料:
• Isolation Forest (Liu et al., 2008)
• Anomaly Detection: A Survey (Chandola et al., 2009)
• sklearn IsolationForest文档
• Outlier Analysis (Aggarwal, 2017)
• Deep Learning for Anomaly Detection (Ruff et al., 2021)