55·无监督学习进阶

异常检测方法:Isolation Forest 与 LOF

anomaly-detectionunsupervised

第55课 异常检测方法:Isolation Forest 与 LOF

课程: 机器学习:从入门到实战
所属模块: 无监督学习
难度: Intermediate
标签: anomaly-detection, unsupervised


学习目标

学完本课后,你将能够:

  1. 解释 Isolation Forest (iForest) 和 Local Outlier Factor (LOF) 的核心思想与差异。
  2. 使用 Scikit-learn 库实现 Isolation Forest 和 LOF 模型,对数据集进行异常检测。
  3. 分析 模型输出(如异常分数)的含义,并基于阈值做出判断。
  4. 比较 这两种方法在不同数据分布下的表现,并理解其各自的适用场景。

核心概念

1. Isolation Forest (孤立森林)

  • 核心思想: 一个异常点(Outlier)通常很“孤单”,在特征空间中很容易被隔离。Isolation Forest 通过随机选择特征和切分点来“孤立”数据点。异常点因为其稀疏性和特殊性,通常只需要很少的几次切分就能被隔离出来,而正常点则需要更多次切分。
  • 通俗比喻: 想象在一个大房间里,我们随机地砌墙来分割空间。那些站在角落里、明显与人群分离的人(异常点),很快就会被单独隔开。而站在人群中心的人(正常点),则需要很多次分割才能被隔离。
  • 关键参数:
    • n_estimators: 森林中树的数量。
    • max_samples: 用来构建每棵树的样本数量。
    • contamination: 数据集中预期的异常值比例(用于自动设定阈值)。

2. Local Outlier Factor (局部离群因子)

  • 核心思想: 异常与否是相对的,依赖于数据点周围的“局部密度”。LOF 通过比较一个点的局部密度与其邻居的局部密度来判断其异常程度。如果一个点的密度远低于其邻居的密度,那么它很可能是一个局部异常点。
  • 通俗比喻: 在一个社区中,大多数人住在密集的公寓楼里(高密度区域)。某人住在独栋别墅里(局部密度低),虽然整个城市可能有很多别墅,但相对于这个公寓楼社区,他就是一个“局部”异常点。
  • 关键参数:
    • n_neighbors: 用于计算局部密度的邻居数量。
    • contamination: 同 Isolation Forest。

代码示例

下面,我们使用 Scikit-learn 生成一个包含明显异常点的二维数据集,并分别应用这两种方法。

# 导入必要的库
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import IsolationForest
from sklearn.neighbors import LocalOutlierFactor
from sklearn.datasets import make_moons, make_blobs
from sklearn.preprocessing import StandardScaler

# 1. 生成模拟数据
# 生成两个半月形聚类(正常数据)
X_normal, _ = make_moons(n_samples=300, noise=0.05, random_state=42)
# 在外圈添加一些异常点
rng = np.random.RandomState(42)
X_outliers = rng.uniform(low=-3, high=4, size=(20, 2))
# 合并数据集
X = np.vstack([X_normal, X_outliers])

# 2. 数据标准化(虽然这两种算法对尺度不敏感,但这是个好习惯)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 3. Isolation Forest
# 设置异常比例为5%((20/320) ≈ 6.25%,这里假设一个先验值)
iso_forest = IsolationForest(n_estimators=100, contamination=0.06, random_state=42)
# 拟合模型并预测,标签为-1表示异常,1表示正常
iso_predictions = iso_forest.fit_predict(X_scaled)
# 获取异常分数(越小越异常)
iso_scores = iso_forest.decision_function(X_scaled)

# 4. Local Outlier Factor
# LOF 需要设置邻居数,对于半月形数据,20个邻居可能比较合适
lof = LocalOutlierFactor(n_neighbors=20, contamination=0.06)
# LOF没有`fit`过程,直接预测
lof_predictions = lof.fit_predict(X_scaled)
# LOF的负离群因子(越小越异常,通常以负值表示)
lof_scores = lof.negative_outlier_factor_

# 5. 可视化结果
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(18, 5))

# 绘制原始数据
ax1.scatter(X_scaled[:, 0], X_scaled[:, 1], c='b', s=20, label='数据点')
ax1.set_title('原始数据')
ax1.legend()

# 绘制 Isolation Forest 结果
# 正常点用蓝色,异常点用红色
colors_iso = np.where(iso_predictions == 1, 'b', 'r')
ax2.scatter(X_scaled[:, 0], X_scaled[:, 1], c=colors_iso, s=20)
ax2.set_title(f'Isolation Forest 检测结果\n(预测异常点数: {sum(iso_predictions == -1)})')

# 绘制 LOF 结果
colors_lof = np.where(lof_predictions == 1, 'b', 'r')
ax3.scatter(X_scaled[:, 0], X_scaled[:, 1], c=colors_lof, s=20)
ax3.set_title(f'LOF 检测结果\n(预测异常点数: {sum(lof_predictions == -1)})')

plt.tight_layout()
plt.show()

# 打印部分异常分数信息
print("Isolation Forest 异常分数 (前5个异常点):", iso_scores[iso_predictions == -1][:5])
print("LOF 负离群因子 (前5个异常点):", lof_scores[lof_predictions == -1][:5])

运行说明: 运行上述代码后,你将看到三幅图。左边是原始数据,中间和右边分别显示 Isolation Forest 和 LOF 检测出的异常点(红色)。你可以观察两种方法识别出的异常点有何不同。

实践练习

练习1:基础应用 使用上面代码中的数据集 X,分别尝试调整 IsolationForestcontamination 参数(例如 0.030.1),观察检测出的异常点数量有何变化,并解释原因。

练习2:算法比较 创建一个新的数据集,其中包含一个密集的核心聚类和一个稀疏的外围环状分布(提示:可以使用 make_blobsmake_circles 组合)。在这个数据集上,你认为 Isolation Forest 和 LOF 谁的检测结果更合理?为什么?请通过代码和可视化进行验证。

练习3:真实数据探索 加载 Scikit-learn 内置的 breast_cancer 数据集(它主要用于分类,但我们可以将其视为一个正常/异常的场景)。假设良性样本为“正常”,恶性样本为“异常”。使用 IsolationForest 对其进行异常检测,并计算检测结果与真实标签的“准确率”(注意:这里只是概念性练习,真实的无监督异常检测没有真实标签)。思考:为什么结果可能不理想?

常见错误

  1. 忽略数据预处理: 虽然树模型和LOF对数据尺度相对不敏感,但在应用LOF计算距离时,标准化(如StandardScaler)通常能带来更稳定、可解释的结果。直接在原始未标准化数据上训练可能导致基于距离的方法表现不佳。
  2. 误解算法假设: Isolation Forest 假设异常点是“少且不同”的。如果数据中存在大量异常点或异常点形成密集簇,其效果会大打折扣。LOF 则假设异常点位于低密度区域,对于存在于不同密度区域间的异常点可能失效。
  3. 盲目使用默认参数: n_neighbors (LOF) 和 n_estimators (iForest) 对结果有显著影响。n_neighbors 设置得太小,结果对噪声敏感;设置得太大,可能无法捕捉局部结构。需要根据数据规模和领域知识进行调整。
  4. 过度依赖自动化阈值: contamination 参数用于自动设定预测阈值。如果这个先验比例设置错误(例如,真实异常率是1%,你设为5%),会导致大量误报或漏报。更好的做法是使用 decision_functionnegative_outlier_factor_ 获取连续分数,然后自行分析分布来设定阈值。

小结

  • Isolation Forest 通过“随机隔离”的难度来识别异常点,核心思想是异常点更容易被分离。它计算高效,对高维数据和大型数据集友好。
  • Local Outlier Factor (LOF) 通过比较数据点与其邻居的局部密度来识别异常,核心思想是异常点的局部密度显著低于其周围点。它更适合检测局部异常,即可能在全局不突出,但在其局部环境中非常异常的点。
  • 两者都是强大的无监督异常检测工具,各有侧重。在实践中,建议结合使用并观察不同算法的结果,以获得更全面的见解。
  • 理解算法的核心思想和假设,比单纯调用API更重要,这能帮助你选择合适的方法并正确解读结果。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「关联规则挖掘:Apriori 与 FP-Growth」 以巩固所学知识。