PyOD 实战入门:kNN 异常检测、模型组合与自适应阈值化的完整示例指南
2026/9/24 14:21:12 网站建设 项目流程

PyOD 实战入门:kNN 异常检测、模型组合与自适应阈值化的完整示例指南

【免费下载链接】pyodA Python library for anomaly detection across tabular, time series, graph, text, image, and audio data. 60+ detectors, benchmark-backed ADEngine orchestration, and an agentic workflow for AI agents.项目地址: https://gitcode.com/gh_mirrors/py/pyod

PyOD 是一套面向表格、时序、图、文本、图像与音频数据的 Python 异常检测工具库,其 examples 目录 提供了一整套可直接运行、可复现的入门示例。本文以官方示例文档 docs/example.rst 为核心骨架,完整讲解其中最核心的三类实战场景——kNN 异常检测、多检测器输出组合(Model Combination)与基于 FILTER 的自适应阈值化,并结合仓库源码剖析底层实现。读完本文,你将掌握 PyOD 标准的数据生成、模型训练、结果评估、可视化以及提升模型稳健性的组合与阈值化技巧,可以直接迁移到自己的异常检测任务中。

示例概览:从哪开始、如何运行

仓库的 examples 目录 中存放了大量配套示例脚本,每个脚本对应一个检测器或一项功能,命名规则统一为<模型名>_example.py。本文涉及的三个核心脚本为:

脚本对应文档章节演示内容
examples/knn_example.pykNN Example用 kNN 检测器完成"生成数据 → 训练 → 预测 → 评估 → 可视化"的完整流程
examples/comb_example.pyModel Combination Example用 20 个不同 k 的 kNN 检测器组合打分,比较 Average / Maximization / AOM / MOA
examples/threshold_example.pyThresholding Examplecontamination=FILTER()接入检测器,让阈值由算法自动确定

这些示例脚本头部都有一段本地运行时的路径兼容代码(sys.path.append(...)),用于在 PyOD 尚未安装到环境中时把仓库根目录加入导入路径;若已通过pip install pyod安装,则无需该行。运行方式很简单,在仓库根目录下直接执行即可:

python examples/knn_example.py python examples/comb_example.py python examples/threshold_example.py

kNN 异常检测完整示例

kNN 是异常检测中最直观的基线算法之一:对每个样本,将其到第 k 个最近邻的距离视为异常得分,距离越远说明该点所在区域越稀疏、越可疑。PyOD 的 kNN 实现位于 pyod/models/knn.py,完整示例见 examples/knn_example.py。

第一步:导入模型

from pyod.models.knn import KNN # kNN detector

kNN 检测器通过pyod.models.knn.KNN导入,它继承自 pyod/models/base.py 中的BaseDetector基类,因此天然具备fitpredictdecision_functionlabels_decision_scores_threshold_等统一接口。

第二步:生成合成样本数据

PyOD 通过 pyod/utils/data.py 中的generate_data函数生成带标签的合成数据,用于示例演示与算法验证:

from pyod.utils.data import generate_data contamination = 0.1 # percentage of outliers n_train = 200 # number of training points n_test = 100 # number of testing points X_train, X_test, y_train, y_test = generate_data( n_train=n_train, n_test=n_test, contamination=contamination)

generate_data的核心参数(依据 pyod/utils/data.py 源码)如下:

  • n_train/n_test:训练集与测试集样本数,默认分别为 1000 与 500;
  • n_features:特征维度,默认 2(便于可视化);
  • contamination:异常比例,取值在 (0, 0.5) 区间,默认 0.1,训练集与测试集中异常样本数分别为int(n_train * contamination)int(n_test * contamination)
  • train_only:若为True则只返回X_train, y_train,模型组合示例中即使用该模式;
  • offset:调节高斯分布与均匀分布的取值区间,默认 10;
  • behaviour:返回顺序,默认'new'返回X_train, X_test, y_train, y_test(旧版'old'顺序已被弃用);
  • random_state:随机种子,示例脚本中固定为 42 以保证可复现。

从源码看,正常样本由多元高斯分布生成(coef * random_state.randn(...) + offset),异常样本由均匀分布random_state.uniform(...)生成(见 pyod/utils/data.py),标签y中 0 表示内点、1 表示异常点。

第三步:初始化 kNN 检测器并完成训练与预测

# train kNN detector clf_name = 'KNN' clf = KNN() clf.fit(X_train) # get the prediction labels and outlier scores of the training data y_train_pred = clf.labels_ # binary labels (0: inliers, 1: outliers) y_train_scores = clf.decision_scores_ # raw outlier scores # get the prediction on the test data y_test_pred = clf.predict(X_test) # outlier labels (0 or 1) y_test_scores = clf.decision_function(X_test) # outlier scores # it is possible to get the prediction confidence as well y_test_pred, y_test_pred_confidence = clf.predict(X_test, return_confidence=True) # outlier labels (0 or 1) and confidence in the range of [0,1]

这里有几个关键概念:

  • clf.fit(X_train)是无监督拟合,训练时完全不使用标签;
  • labels_是训练数据的二值标签(0 内点 / 1 异常),由threshold_作用于decision_scores_得到;
  • decision_scores_是训练数据的原始异常得分,得分越高越异常;
  • predict(X_test)返回测试数据的二值标签,decision_function(X_test)返回测试数据的原始异常得分;
  • predict(..., return_confidence=True)额外返回取值在 [0, 1] 的预测置信度。

KNN的完整构造参数(依据 pyod/models/knn.py 源码)包括:

参数默认值说明
contamination0.1数据集中异常比例,用于拟合时确定decision_function的阈值
n_neighbors5k 近邻查询的邻居数量
method'largest'距离聚合方式:'largest'取到第 k 个邻居的距离、'mean'取 k 个邻居的平均距离、'median'取 k 个邻居距离的中位数
radius1.0radius_neighbors查询的参数空间范围
algorithm'auto'近邻搜索算法('auto'/'ball_tree'/'kd_tree'/'brute'),PyOD 已弃用该参数并统一使用 BallTree
leaf_size30BallTree 的叶子大小,影响建树与查询速度及内存占用
metric'minkowski'距离度量,支持 sklearn 与 scipy 的全部距离函数
p2Minkowski 距离的范数参数,p=1 即曼哈顿距离、p=2 即欧氏距离
n_jobs1近邻搜索的并行任务数,-1 表示使用全部 CPU 核心

三种method的底层实现可以在 pyod/models/knn.py 的_get_dist_by_method方法中看到:'largest'取距离矩阵最后一列(dist_arr[:, -1]),'mean'对距离矩阵按行求均值,'median'按行求中位数。fit内部先拟合NearestNeighbors,再通过kneighbors计算每个样本到邻居的距离矩阵,聚合后写入decision_scores_并调用_process_decision_scores()完成阈值计算。

第四步:用 ROC 与 Precision @ Rank n 评估

PyOD 的evaluate_print工具函数会一次性打印两个核心指标——ROC 与 Precision @ Rank n:

from pyod.utils.data import evaluate_print # evaluate and print the results print("\nOn Training Data:") evaluate_print(clf_name, y_train, y_train_scores) print("\nOn Test Data:") evaluate_print(clf_name, y_test, y_test_scores)

依据 pyod/utils/data.py 的实现,evaluate_print内部使用roc_auc_score(ROC 曲线下面积)与precision_n_scores(Top n 命中精度)两个指标,n 取实际异常样本数,衡量的是"得分最高的前 n 个样本中有多少确实是异常"。它要求传入的是原始异常得分decision_scores_/decision_function的输出)而非二值标签。

在 examples/knn_example.py 中运行可得到类似输出:

On Training Data: KNN ROC:1.0, precision @ rank n:1.0 On Test Data: KNN ROC:0.9989, precision @ rank n:0.9

训练集上 ROC 达到 1.0、Precision @ rank n 达到 1.0,说明 kNN 对合成数据几乎可以完美区分内点与异常点。

第五步:可视化训练与测试结果

所有示例脚本都内置了visualize可视化函数:

from pyod.utils.example import visualize visualize(clf_name, X_train, y_train, X_test, y_test, y_train_pred, y_test_pred, show_figure=True, save_figure=False)

visualize的实现位于 pyod/utils/example.py,它会生成 2×2 的四个子图:训练集真实标签、训练集预测标签、测试集真实标签、测试集预测标签,蓝/绿表示内点、橙/红三角表示异常点。注意该函数要求输入数据为二维(n_features=2),否则会抛出ValueErrorshow_figure控制是否弹窗展示,save_figure=True时会以<clf_name>.png保存到当前目录(300 dpi)。

模型组合示例:让检测结果更稳健

异常检测本质上是无监督任务,单一模型对数据的假设(密度、距离、分布形状等)往往带来不稳定性。PyOD 官方建议将多个检测器的输出进行组合(例如取平均)来提升稳健性,这属于异常集成(outlier ensembles)的范畴。完整示例见 examples/comb_example.py,配套的 Jupyter Notebook 为 notebooks/Model Combination.ipynb。

四种分数组合机制

示例演示了四种分数组合方式,它们都接收形状为(n_samples, n_estimators)的得分矩阵,返回形状为(n_samples,)的组合得分:

  1. Average(平均):对所有检测器的得分取平均;
  2. Maximization(最大化):取所有检测器得分的最大值;
  3. Average of Maximum(AOM):先将基检测器分成若干子组,对每个子组取最大得分作为子组得分,最后对所有子组得分取平均;
  4. Maximum of Average(MOA):先将基检测器分成若干子组,对每个子组取平均得分作为子组得分,最后对所有子组得分取最大值。

第一步:导入与加载数据

from pyod.models.knn import KNN # kNN detector from pyod.models.combination import aom, moa, average, maximization from pyod.utils.data import generate_data X, y = generate_data(train_only=True) # load data

generate_data(train_only=True)只返回X_train, y_train。在 examples/comb_example.py 中,数据加载逻辑更完整:它会优先尝试读取 examples/data/cardio.mat 真实数据集,文件缺失时自动回退到generate_data(train_only=True)生成的合成数据,随后用train_test_split(X, y, test_size=0.4)划分训练与测试集。

第二步:初始化 20 个不同 k 的 kNN 基检测器

# initialize 20 base detectors for combination k_list = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200] n_clf = len(k_list) # Number of classifiers being trained train_scores = np.zeros([X_train.shape[0], n_clf]) test_scores = np.zeros([X_test.shape[0], n_clf]) for i in range(n_clf): k = k_list[i] clf = KNN(n_neighbors=k, method='largest') clf.fit(X_train_norm) train_scores[:, i] = clf.decision_scores_ test_scores[:, i] = clf.decision_function(X_test_norm)

这里构建了 20 个 kNN 基检测器,k 从 10 变化到 200,均匀覆盖不同的邻域尺度。每个检测器的decision_scores_decision_function(X_test_norm)分别写入得分矩阵的一列,最终得到train_scorestest_scores两个(n_samples, 20)的得分矩阵。组合的多样性来自 k 的差异:k 越小越敏感于局部密度变化,k 越大越关注全局结构,二者互补。

第三步:得分标准化——组合前至关重要的一步

不同检测器输出的得分量纲与分布并不一致,直接组合会使某个高分模型的输出主导结果。因此在组合前必须将得分标准化为零均值、单位方差:

from pyod.utils.utility import standardizer # scores have to be normalized before combination train_scores_norm, test_scores_norm = standardizer(train_scores, test_scores)

standardizer的实现见 pyod/utils/utility.py:它在训练得分矩阵上拟合 sklearn 的StandardScaler(Z-score 标准化),然后用同一个标定器转换测试得分矩阵,保证训练与测试处于同一尺度。keep_scalar=True时可以额外返回标定器对象以便复用。

第四步:应用四种组合算法

comb_by_average = average(test_scores_norm) comb_by_maximization = maximization(test_scores_norm) comb_by_aom = aom(test_scores_norm, 5) # 5 groups comb_by_moa = moa(test_scores_norm, 5) # 5 groups

四种组合函数都定义在 pyod/models/combination.py 中,底层封装了combo库(需pip install combo)的同名实现:

  • aom(scores, n_buckets=5, method='static', bootstrap_estimators=False, random_state=None)n_buckets为子组数量,默认 5;method='dynamic'时子组大小随机、支持有放回抽样(bootstrap_estimators=True)并可用random_state固定随机性(见 pyod/models/combination.py);
  • moa(...):参数与aom完全一致(见 pyod/models/combination.py);
  • average(scores, estimator_weights=None):支持传入权重实现加权平均(见 pyod/models/combination.py);
  • maximization(scores):逐样本取最大值(见 pyod/models/combination.py)。

此外 pyod/models/combination.py 还提供了median(取中位数)与majority_vote(多数投票,默认二分类)两种组合方式,示例脚本中也会一并评估median的结果。

第五步:评估组合效果

evaluate_print('Combination by Average', y_test, comb_by_average) evaluate_print('Combination by Maximization', y_test, comb_by_maximization) evaluate_print('Combination by AOM', y_test, comb_by_aom) evaluate_print('Combination by MOA', y_test, comb_by_moa)

在 examples/comb_example.py 的注释输出中,组合 20 个 kNN 检测器后,AOM 与 MOA 的 ROC 表现通常优于单一模型的简单平均,示例输出示例如下:

Combining 20 kNN detectors Combination by Average ROC:0.9194, precision @ rank n:0.4531 Combination by Maximization ROC:0.9198, precision @ rank n:0.4688 Combination by AOM ROC:0.9257, precision @ rank n:0.4844 Combination by MOA ROC:0.9263, precision @ rank n:0.4688

注意这些数字依赖具体数据集与划分方式,更重要的是对比四种机制的相对表现:分桶策略(AOM / MOA)通过"组内取极值、组间聚合"的方式兼顾了多样性保持与噪声抑制,在示例中往往能取得更优的 ROC。该流程对任意 PyOD 检测器都成立——只需把KNN换成其他检测器类,收集各自得分后标准化、组合、评估即可。

阈值化示例:用 FILTER 自动确定异常判定阈值

传统做法中,检测器的contamination参数需要手动指定异常比例,模型据此把得分最高的n_samples * contamination个样本标记为异常。但当真实异常比例未知或分布形态复杂时,这个假设并不可靠。PyOD 支持传入阈值化器对象替代浮点数contamination,让阈值由算法从得分分布中自动推断。

完整示例见 examples/threshold_example.py(官方文档使用 kNN 演示,仓库脚本则以 KDE 检测器为例,二者接口完全一致):

from pyod.models.knn import KNN # kNN detector from pyod.models.thresholds import FILTER # Filter thresholder contamination = 0.1 # percentage of outliers n_train = 200 # number of training points n_test = 100 # number of testing points X_train, X_test, y_train, y_test = generate_data( n_train=n_train, n_test=n_test, contamination=contamination) # train kNN detector and apply FILTER thresholding clf_name = 'KNN' clf = KNN(contamination=FILTER()) clf.fit(X_train) # get the prediction labels and outlier scores of the training data y_train_pred = clf.labels_ # binary labels (0: inliers, 1: outliers) y_train_scores = clf.decision_scores_ # raw outlier scores

关键差异仅在一处:KNN(contamination=FILTER())。检测器在fit阶段不再按固定分位数切分,而是把decision_scores_交给FILTER阈值化器计算thresh_,得分超过该阈值的样本被判为异常。

FILTER的定义见 pyod/models/thresholds.py,底层基于pythresh库(需pip install pythresh),核心参数包括:

  • method:默认'savgol',可选'gaussian'(高斯滤波)、'savgol'(Savitzky-Golay 滤波)、'hilbert'(希尔伯特滤波)、'wiener'(维纳滤波)、'medfilt'(中值滤波)、'decimate'(降采样)、'detrend'(去趋势)、'resample'(重采样);
  • sigma:默认'auto'(自动取len(scores) * np.std(scores)),含义随滤波类型不同而变化——对'gaussian'是高斯核标准差,对'savgol'是滤波窗口大小,对'medfilt'是核大小,对'decimate'是降采样因子等。

阈值化器家族一览

PyOD 的 pyod/models/thresholds.py 还封装了二十余种来自pythresh的阈值化器,全部以contamination=<阈值化器>()的形式接入任意检测器,常见选项包括:

阈值化器原理概要
FILTER对得分做信号滤波后取最大值作为阈值
IQR四分位距法:第三四分位数 + 1.5×IQR
MAD中位数绝对偏差法
ZSCOREz 分数大于 1 即判为异常
AUCP基于 KDE 曲线下面积的非参数方法
BOOT自助抽样置信区间法,支持random_state参数
CLUST聚类法,未归属主簇即异常,支持十余种聚类算法(method参数)
GESD广义极端学生化偏差检验,支持max_outliersalpha
OCSVM用单类 SVM 学习阈值,支持poly/sgd两种模型
VAE变分自编码器法,支持epochsbatch_sizeloss等训练参数

这些阈值化器不依赖对异常比例的预设,适合标签稀少或分布未知的真实场景。选择建议:数据近似单峰分布时IQRZSCORE简单高效;得分分布复杂、存在多模态时,FILTERAUCPCLUST这类非参数方法通常更稳健;追求统计显著性检验时可选GESDMAD

总结与进一步探索

本文围绕官方示例文档 docs/example.rst 的三条主线展开:kNN 示例展示了 PyOD 标准五步流程(导入 → 生成数据 → 训练预测 → 评估 → 可视化);模型组合示例展示了通过 Average / Maximization / AOM / MOA 聚合多个异构检测器来提升稳健性的完整工程路径;阈值化示例展示了用FILTER等阈值化器替代固定contamination的自适应判定方案。三者的底层实现分别可追溯至 pyod/models/knn.py、pyod/models/combination.py、pyod/models/thresholds.py 与 pyod/utils 工具集。

如果想继续深入,可以关注仓库中的其他资源:

  • examples 目录下还有 60 余个检测器示例脚本(如 examples/iforest_example.py、examples/lof_example.py、examples/ecod_example.py),接口与本文完全一致,换类即可复用整套流程;
  • notebooks 目录提供了 Benchmark、Compare All Models、Model Combination 等交互式 Notebook,便于逐格运行与对比;
  • pyod/test 目录下的test_knn.pytest_combination.pytest_thresholds.py等测试用例可以作为接口行为与边界条件的权威参考;
  • 评估指标precision_n_scores的实现细节可查阅 pyod/utils/utility.py,合成数据生成函数generate_data_clusters(支持低密度模式与全局异常等困难场景)可查阅 pyod/utils/data.py。

【免费下载链接】pyodA Python library for anomaly detection across tabular, time series, graph, text, image, and audio data. 60+ detectors, benchmark-backed ADEngine orchestration, and an agentic workflow for AI agents.项目地址: https://gitcode.com/gh_mirrors/py/pyod

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询