[python] 基於PyOD庫實現資料異常檢測

落痕的寒假發表於2024-10-01

PyOD是一個全面且易於使用的Python庫,專門用於檢測多變數資料中的異常點或離群點。異常點是指那些與大多數資料點顯著不同的資料,它們可能表示錯誤、噪聲或潛在的有趣現象。無論是處理小規模專案還是大型資料集,PyOD提供了50多種演算法以滿足使用者的需求。PyOD的特點包括:

  1. 統一且使用者友好的介面,適用於多種演算法。
  2. 豐富的模型選擇,從經典技術到最新的PyTorch深度學習方法。
  3. 高效能與高效率,利用numba和joblib實現即時編譯與並行處理。
  4. 快速的訓練和預測,透過SUOD框架實現。

PyOD官方倉庫地址為:pyod,官方文件地址為:pyod-doc。PyOD安裝命令如下:

pip install pyod

目錄
  • 1 使用說明
    • 1.1 PyOD背景介紹
    • 1.2 用法說明
      • 1.2.1 基於KNN實現異常檢測
      • 1.2.2 模型組合
      • 1.2.3 閾值處理
      • 1.2.4 模型儲存與載入
  • 2 參考

1 使用說明

1.1 PyOD背景介紹

PyOD作者釋出了一份長達45頁的預印論文,名為ADBench: Anomaly Detection Benchmark,以及提供ADBench開源倉庫對30種異常檢測演算法在57個基準資料集上的表現進行了比較。ADBench結構圖如下所示:

PyOD提供了這些演算法的介面類實現,具體演算法對應的介面見:pyod-implemented-algorithms。同時PyOD對於這些演算法提供了統一的API介面,如下所示:

  • pyod.models.base.BaseDetector.fit():訓練模型,對於無監督方法,目標變數y將被忽略。
  • pyod.models.base.BaseDetector.decision_function():使用已訓練的檢測器預測輸入資料的異常分數。
  • pyod.models.base.BaseDetector.predict():使用已訓練的檢測器預測特定樣本是否為異常點。
  • pyod.models.base.BaseDetector.predict_proba():使用已訓練的檢測器預測樣本為異常點的機率。
  • pyod.models.base.BaseDetector.predict_confidence():預測模型對每個樣本的置信度(可在predict和predict_proba中使用)。
  • pyod.models.base.BaseDetector.decision_scores_:訓練資料的異常分數。分數越高,越異常。
  • pyod.models.base.BaseDetector.labels_:訓練資料的二進位制標籤。0表示正常樣本,1表示異常樣本。

PyOD還提供了不同演算法的基準比較結果,詳見連結:benchmark。下圖展示了各種演算法的檢測結果與實際結果,並標出了識別錯誤樣本的數量:

1.2 用法說明

1.2.1 基於KNN實現異常檢測

本文以KNN為例說明PyOD實現異常點檢測的一般流程。KNN(K-Nearest Neighbors)是一種非常常用的機器學習方法,它的核心思想非常簡單直觀:在特徵空間中,如果一個資料點的K個最鄰近點大多數屬於某個特定類別,那麼這個資料點很可能也屬於該類別。

在異常檢測中,KNN演算法不需要假設資料的分佈,它透過計算每個樣本點與其它樣本點之間的距離,來確定樣本點是否為異常點。異常點通常是那些與大多數樣本點距離較遠的點。以下示例程式碼展示了透過PyOD庫建立KNN模型來實現異常檢測:

建立資料集

以下程式碼建立一個二維座標點資料集,正常資料透過多元高斯分佈生成,而異常值則透過均勻分佈生成。

from pyod.models.knn import KNN 
from pyod.utils.data import generate_data

# 設定異常值比例和訓練、測試樣本數量
contamination = 0.1  # 異常值的百分比
n_train = 200  # 訓練樣本數量
n_test = 100  # 測試樣本數量

# 生成訓練和測試資料集,包含正常資料和異常值,預設輸入資料特徵維度為2,標籤為二進位制標籤(0: 正常點, 1: 異常點)
# random_state為隨機種子,保證能夠復現結果
X_train, X_test, y_train, y_test = generate_data(n_train=n_train, n_test=n_test, contamination=contamination, random_state=42)
X_train.shape
(200, 2)

訓練KNN檢測器

# 訓練KNN檢測器
clf_name = 'KNN'  # 設定分類器的名稱
clf = KNN()  # 建立kNN模型例項
clf.fit(X_train)  # 使用訓練資料擬合模型

# 獲取訓練資料的預測標籤和異常分數
y_train_pred = clf.labels_  # 二進位制標籤(0: 正常點, 1: 異常點)
y_train_scores = clf.decision_scores_  # 訓練資料的異常分數

# 對測試資料進行預測
y_test_pred = clf.predict(X_test)  # 對測試資料的異常標籤(0或1)
y_test_scores = clf.decision_function(X_test)  # 測試資料的異常分數

# 獲取預測的置信度
y_test_pred, y_test_pred_confidence = clf.predict(X_test, return_confidence=True)  # 返回預測標籤和置信度(範圍[0,1])

評估結果

from pyod.utils.data import evaluate_print  # 匯入評估工具

# 評估並列印結果
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)  # 評估測試資料
On Training Data:
KNN ROC:0.9992, precision @ rank n:0.95

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

視覺化結果

以下程式碼展示了模型在訓練集和測試集上的異常標籤預測結果,其中inliers表示正常點,outliers表示異常點。

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)  # 顯示視覺化影像

png

模型替換

本文在1.1節提到,PyOD為不同的異常檢測演算法提供了統一的API介面,並附上了各類演算法的介面說明連結。在PyOD中,其他演算法的檢測流程與KNN演算法類似,這一點與sklearn的模型構建方式相似。以PCA為例,只需更改模型的初始化方式,即可輕鬆替換模型,具體操作如下:

from pyod.models.pca import PCA
# 訓練PCA檢測器
clf_name = 'PCA'  # 設定分類器的名稱
clf = PCA()  # 建立kNN模型例項
clf.fit(X_train)  # 使用訓練資料擬合模型
PCA(contamination=0.1, copy=True, iterated_power='auto', n_components=None,
  n_selected_components=None, random_state=None, standardization=True,
  svd_solver='auto', tol=0.0, weighted=True, whiten=False)

其他程式碼一樣:

# 獲取訓練資料的預測標籤和異常分數
y_train_pred = clf.labels_  # 二進位制標籤(0: 正常點, 1: 異常點)
y_train_scores = clf.decision_scores_  # 訓練資料的異常分數

# 對測試資料進行預測
y_test_pred = clf.predict(X_test)  # 對測試資料的異常標籤(0或1)
y_test_scores = clf.decision_function(X_test)  # 測試資料的異常分數

# 獲取預測的置信度
y_test_pred, y_test_pred_confidence = clf.predict(X_test, return_confidence=True)  # 返回預測標籤和置信度(範圍[0,1])

from pyod.utils.data import evaluate_print  # 匯入評估工具

# 評估並列印結果
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)  # 評估測試資料

# 視覺化結果
visualize(clf_name, X_train, y_train, X_test, y_test, y_train_pred, 
          y_test_pred, show_figure=True, save_figure=False)  # 顯示視覺化影像
On Training Data:
PCA ROC:0.8964, precision @ rank n:0.8

On Test Data:
PCA ROC:0.9033, precision @ rank n:0.8

png

1.2.2 模型組合

異常檢測由於其無監督特性,常常面臨模型不穩定的問題。因此,建議透過組合不同檢測器的輸出(例如,透過平均)來提高其穩健性。

本示例展示了四種評分組合機制:

  • 平均值:所有檢測器的平均分數。
  • 最大化:所有檢測器中的最高分數。
  • 最大值的平均(Average of Maximum,AOM):將基礎檢測器劃分為子組,並取每個子組的最高分數。最終得分為所有子組分數的平均值。
  • 平均值的最大(Maximum of Average,MOA):將基礎檢測器劃分為子組,並取每個子組的平均分數。最終得分為所有子組分數中的最高值。

以上組合機制的程式碼實現由combo庫提供。combo庫是一個用於機器學習模型組合(整合學習)的Python工具庫。它提供了多種模型合併方法,包括簡單的平均、加權平均、中位數、多數投票,以及更復雜的動態分類器選擇(Dynamic Classifier Selection)和堆疊(Stacking)等。combo庫支援多種不同的場景,如分類器合併、原始結果合併、聚類合併和異常檢測器合併。combo庫官方倉庫地址為:combo,安裝命令如下:

pip install combo

以下示例程式碼展示了透過PyOD庫和combo庫組合模型來實現異常檢測:

建立資料集

# 需要安裝combo庫,使用命令 pip install combo
from pyod.models.combination import aom, moa, median, average, maximization
from pyod.utils.data import generate_data, evaluate_print
from pyod.utils.utility import standardizer
from sklearn.model_selection import train_test_split
import numpy as np

# 匯入模型並生成樣本資料
# n_train:訓練樣本個數,n_features:樣本X的特徵維度,train_only:是否僅包含訓練集
X, y = generate_data(n_train=5000, n_features=2, train_only=True, random_state=42)  # 載入資料
# test_size:測試集比例
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)  # 劃分訓練集和測試集

# 標準化資料以便處理
X_train_norm, X_test_norm = standardizer(X_train, X_test)

建立檢測器

初始化10個KNN異常檢測器,設定不同的k值,並獲取異常分數。k值決定了在進行預測時考慮多少個最近鄰近點,較小的k值可能導致對噪聲敏感,而較大的k值可能會使得模型過於平滑,從而失去某些細節。當然這段程式碼也可以組合不同型別的檢測器,然後獲取異常分數。

from pyod.models.knn import KNN  
n_clf = 10  # 基礎檢測器的數量

# 初始化n_clf個基礎檢測器用於組合
k_list = list(range(1,100,n_clf))

train_scores = np.zeros([X_train.shape[0], n_clf])  # 建立訓練集得分陣列
test_scores = np.zeros([X_test.shape[0], n_clf])    # 建立測試集得分陣列

print('Combining {n_clf} kNN detectors'.format(n_clf=n_clf))  # 輸出組合的KNN檢測器數量

for i in range(n_clf):
    k = int(k_list[i])  # 獲取當前檢測器的鄰居數量

    clf = KNN(n_neighbors=k, method='largest')  # 初始化KNN檢測器
    clf.fit(X_train_norm)  # 擬合訓練資料

    train_scores[:, i] = clf.decision_scores_  # 記錄訓練得分
    test_scores[:, i] = clf.decision_function(X_test_norm)  # 記錄測試得分
Combining 10 kNN detectors

標準化檢測結果

各個檢測器的檢測結果需要被標準化為零均值和單位標準差,這是因為在進行模型結果組合時,如果各個模型的輸出得分範圍差異較大,直接組合可能會導致結果偏差。透過標準化,可以確保各個模型的得分在同一尺度上,從而進行有效的組合:

# 在組合之前,需要對檢測結果進行標準化
train_scores_norm, test_scores_norm = standardizer(train_scores, test_scores)

組合結果

使用combo組合結果:

# 使用平均值進行組合
y_by_average = average(test_scores_norm)
evaluate_print('Combination by Average', y_test, y_by_average)  # 輸出平均組合的評估結果

# 使用最大值進行組合
y_by_maximization = maximization(test_scores_norm)
evaluate_print('Combination by Maximization', y_test, y_by_maximization)  # 輸出最大值組合的評估結果

# 使用中位數進行組合
y_by_median = median(test_scores_norm)
evaluate_print('Combination by Median', y_test, y_by_median)  # 輸出中位陣列合的評估結果

# 使用AOM進行組合。n_buckets為子組個數
y_by_aom = aom(test_scores_norm, n_buckets=5)
evaluate_print('Combination by AOM', y_test, y_by_aom)  # 輸出AOM組合的評估結果

# 使用MOA進行組合,n_buckets為子組個數
y_by_moa = moa(test_scores_norm, n_buckets=5)
evaluate_print('Combination by MOA', y_test, y_by_moa)  # 輸出MOA組合的評估結果
Combination by Average ROC:0.9899, precision @ rank n:0.9497
Combination by Maximization ROC:0.9866, precision @ rank n:0.9447
Combination by Median ROC:0.99, precision @ rank n:0.9548
Combination by AOM ROC:0.9896, precision @ rank n:0.9447
Combination by MOA ROC:0.9884, precision @ rank n:0.9447

1.2.3 閾值處理

PyOD透過模型計算資料的異常機率,並根據設定的閾值篩選出異常資料。在這個過程中,閾值的選擇對異常檢測結果的準確性具有重要影響。

PyThresh是一個全面且可擴充套件的Python工具包,旨在自動設定和處理單變數或多變數資料中的異常檢測機率分數。它與PyOD庫相容,採用類似的語法和資料結構,但並不限於該庫。PyThresh包含超過30種閾值演算法,涵蓋了從簡單統計分析(如Z-score)到更復雜的圖論和拓撲數學方法的多種技術。PyThresh庫官方倉庫地址為:pythresh,安裝命令如下:

pip install pythresh

關於PyThresh的詳細使用,可以檢視其官方文件:pythresh-doc。以下示例程式碼展示了透過PyOD庫和PyThresh庫實現閾值處理的簡單示例:

使用閾值處理演算法

利用PyThresh與PyOD庫自動選擇閾值,可以提高識別精度。然而,請注意,使用PyThresh中的演算法來自動確定閾值並不保證在所有情況下都能獲得理想效果。

# 從pyod庫中匯入KNN模型、評估函式和資料生成函式
from pyod.models.knn import KNN
from pyod.utils.data import generate_data
from sklearn.metrics import accuracy_score

# 從pythresh庫中匯入KARCH閾值計算方法
from pythresh.thresholds.karch import KARCH

# 設定汙染率,即異常值的比例
contamination = 0.1  # percentage of outliers
# 設定訓練樣本的數量
n_train = 500  # number of training points
# 設定測試樣本的數量
n_test = 1000  # number of testing points

# 生成樣本資料,返回訓練和測試資料及其標籤
X_train, X_test, y_train, y_test = generate_data(n_train=n_train,
                    n_test=n_test,
                    n_features=2,  # 特徵數量
                    contamination=contamination,  # 異常值比例
                    random_state=42)  # 隨機種子,以確保結果可重複

# 初始化KNN異常檢測器
clf_name = 'KNN'  # 分類器名稱
clf = KNN()  # 建立KNN模型例項
clf.fit(X_train)  # 使用訓練資料擬合模型
thres = KARCH()  # 建立KARCH演算法建立閾值處理例項
# 對測試資料進行預測
y_test_scores = clf.decision_function(X_test)  # 計算測試集的異常分數
# 基於閾值clf.threshold_
y_test_pred = clf.predict(X_test)  # 獲取測試集,結果
y_test_pred_thre  = thres.eval(y_test_scores) # 對異常值結果進行處理

# 計算精度
accuracy = accuracy_score(y_test, y_test_pred)
print(f"閾值處理前精度: {accuracy:.4f}")

accuracy = accuracy_score(y_test, y_test_pred_thre)
print(f"閾值處理後精度: {accuracy:.4f}")
閾值處理前精度: 0.9940
閾值處理後精度: 0.9950

contamination引數

除了初始化PyThresh演算法模型例項,也可以在初始化PyOD模型時基於contamination引數指定閾值選擇演算法:

from pyod.models.kde import KDE  # 匯入KDE模型
from pyod.models.thresholds import FILTER 
from pyod.utils.data import generate_data 
from pyod.utils.data import evaluate_print 

contamination = 0.1  # 異常點的比例
n_train = 200  # 訓練資料點數量
n_test = 100  # 測試資料點數量

# 生成樣本資料
X_train, X_test, y_train, y_test = generate_data(n_train=n_train,
                    n_test=n_test,
                    n_features=2, 
                    contamination=contamination, 
                    random_state=42)  # 隨機種子

# 訓練KDE檢測器
clf_name = 'KDE'  # 模型名稱
clf = KDE(contamination=FILTER()) # 新增閾值選擇演算法
clf.fit(X_train)  # 使用訓練資料擬合模型

# 獲取訓練資料的預測標籤和異常分數
y_train_pred = clf.labels_  # 二元標籤(0: 正常點, 1: 異常點)
y_train_scores = clf.decision_scores_  #

# 獲取測試資料的預測結果
y_test_pred = clf.predict(X_test)  
y_test_scores = clf.decision_function(X_test) 

# 評估並列印結果
print("\n在訓練資料上:")
evaluate_print(clf_name, y_train, y_train_scores)  # 評估訓練資料
print("\n在測試資料上:")
evaluate_print(clf_name, y_test, y_test_scores)  # 評估測試資料
在訓練資料上:
KDE ROC:0.9992, precision @ rank n:0.95

在測試資料上:
KDE ROC:1.0, precision @ rank n:1.0

1.2.4 模型儲存與載入

PyOD使用joblibpickle來儲存和載入PyOD模型,如下所示:

from pyod.models.lof import LOF 
from pyod.utils.data import generate_data  
from pyod.utils.data import evaluate_print 
from pyod.utils.example import visualize 

from joblib import dump, load  # 從joblib庫匯入模型儲存和載入工具

contamination = 0.3  # 異常點的比例
n_train = 200  # 訓練資料點的數量
n_test = 100  # 測試資料點的數量

# 生成樣本資料
X_train, X_test, y_train, y_test = generate_data(n_train=n_train,
                    n_test=n_test,
                    n_features=2,  # 特徵數量為2
                    contamination=contamination,  # 異常比例
                    random_state=42)  # 隨機狀態設定

# 訓練LOF檢測器
clf_name = 'LOF'  # 分類器名稱
clf = LOF()  # 例項化LOF模型
clf.fit(X_train)  # 在訓練資料上擬合模型

# 獲取訓練資料的預測標籤和異常分數
y_train_pred = clf.labels_  # 二進位制標籤(0:正常點, 1:異常點)
y_train_scores = clf.decision_scores_  # 原始異常分數

# 儲存模型
dump(clf, 'clf.joblib')  # 將模型儲存到檔案
# 載入模型
clf_load = load('clf.joblib')  # 從檔案載入模型

# 獲取測試資料的預測
y_test_pred = clf_load.predict(X_test)  # 測試資料的異常標籤(0或1)
y_test_scores = clf_load.decision_function(X_test)  # 測試資料的異常分數

# 評估並列印結果
print("\n在訓練資料上的結果:")
evaluate_print(clf_name, y_train, y_train_scores)  # 評估訓練資料的結果
print("\n在測試資料上的結果:")
evaluate_print(clf_name, y_test, y_test_scores)  # 評估測試資料的結果

# 視覺化結果
visualize(clf_name, X_train, y_train, X_test, y_test, y_train_pred,
            y_test_pred, show_figure=True, save_figure=False)  # 視覺化訓練和測試結果
在訓練資料上的結果:
LOF ROC:0.5502, precision @ rank n:0.3333

在測試資料上的結果:
LOF ROC:0.4829, precision @ rank n:0.3333

png

2 參考

  • joblib
  • pyod
  • pyod-doc
  • pyod-implemented-algorithms
  • benchmark
  • combo
  • pythresh
  • pythresh-doc
  • pickle

相關文章