異常檢測--基於類似度的方法

#異常檢測——基於類似度的方法html

主要內容包括:python

  • 基於距離的度量
  • 基於密度的度量

 

文章目錄

    • 一、概述
    • 二、基於距離的度量
      • 2.1 基於單元的方法
      • 2.2 基於索引的方法
    • 三、基於密度的度量
      • 3.1 k-距離(k-distance(p)):
      • 3.2 k-鄰域(k-distance neighborhood):
      • 3.3 可達距離(reachability distance):
      • 3.4 局部可達密度(local reachability density):
      • 3.5 局部異常因子:
    • 四、練習
    • 參考資料:

 

一、概述

  「異常」一般是一個主觀的判斷,什麼樣的數據被認爲是「異常」的,須要結合業務背景和環境來具體分析肯定。
  實際上,數據一般嵌入在大量的噪聲中,而咱們所說的「異常值」一般指具備特定業務意義的那一類特殊的異常值。噪聲能夠視做特性較弱的異常值,沒有被分析的價值。噪聲和異常之間、正常數據和噪聲之間的邊界都是模糊的。異常值一般具備更高的離羣程度分數值,同時也更具備可解釋性。
  在普通的數據處理中,咱們經常須要保留正常數據,而對噪聲和異常值的特性則基本忽略。但在異常檢測中,咱們弱化了「噪聲」和「正常數據」之間的區別,專一於那些具備有價值特性的異常值。在基於類似度的方法中,主要思想是異常點的表示與正常點不一樣算法

二、基於距離的度量

  基於距離的方法是一種常見的適用於各類數據域的異常檢測算法,它基於最近鄰距離來定義異常值。 此類方法不只適用於多維數值數據,在其餘許多領域,例如分類數據,文本數據,時間序列數據和序列數據等方面也有普遍的應用。
  基於距離的異常檢測有這樣一個前提假設,即異常點的 k kk 近鄰距離要遠大於正常點。解決問題的最簡單方法是使用嵌套循環。 第一層循環遍歷每一個數據,第二層循環進行異常判斷,須要計算當前點與其餘點的距離,一旦已識別出多於 k kk 個數據點與當前點的距離在 D DD 以內,則將該點自動標記爲非異常值。 這樣計算的時間複雜度爲O ( N 2 ) O(N^{2})O(N2),當數據量比較大時,這樣計算是及不划算的。 所以,須要修剪方法以加快距離計算。dom

2.1 基於單元的方法

  在基於單元格的技術中,數據空間被劃分爲單元格,單元格的寬度是閾值D和數據維數的函數。具體地說,每一個維度被劃分紅寬度最多爲 D 2 ⋅ d \frac{D}{{2 \cdot \sqrt d }}2dD 單元格。在給定的單元以及相鄰的單元中存在的數據點知足某些特性,這些特性可讓數據被更有效的處理。ide

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PrxOTupS-1611237077193)(img/UWiX5C7kCHx5yX7O9yQm9F1ndg-QgMqS3BAwIWPB40k.original.fullsize-1609839833441.png)]svg

  以二維狀況爲例,此時網格間的距離爲 D 2 ⋅ d \frac{D}{{2 \cdot \sqrt d }}2dD ,須要記住的一點是,網格單元的數量基於數據空間的分區,而且與數據點的數量無關。這是決定該方法在低維數據上的效率的重要因素,在這種狀況下,網格單元的數量可能很少。 另外一方面,此方法不適用於更高維度的數據。對於給定的單元格,其 L 1 L_{1}L1 鄰居被定義爲經過最多1個單元間的邊界可從該單元到達的單元格的集合。請注意,在一個角上接觸的兩個單元格也是 L 1 L_{1}L1 鄰居。 L 2 L_{2}L2 鄰居是經過跨越2個或3個邊界而得到的那些單元格。 上圖中顯示了標記爲 X XX的特定單元格及其 L 1 L_{1}L1 和 L 2 L_{2}L2 鄰居集。 顯然,內部單元具備8個 L 1 L_{1}L1 鄰居和40個 L 2 L_{2}L2 鄰居。 而後,能夠當即觀察到如下性質:函數

  • 單元格中兩點之間的距離最多爲 D / 2 D/2D/2。
  • 一個點與 L 1 L_{1}L1 鄰接點之間的距離最大爲 D DD。
  • 一個點與它的 L r LrLr 鄰居(其中r rr > 2)中的一個點之間的距離至少爲D DD。

  惟一沒法直接得出結論的是 L 2 L_{2}L2 中的單元格。 這表示特定單元中數據點的不肯定性區域。 對於這些狀況,須要明確執行距離計算。 同時,能夠定義許多規則,以便當即將部分數據點肯定爲異常值或非異常值。 規則以下:學習

  • 若是一個單元格中包含超過 k kk 個數據點及其 L 1 L_{1}L1 鄰居,那麼這些數據點都不是異常值。
  • 若是單元 A AA 及其相鄰 L 1 L_{1}L1 和 L 2 L_{2}L2 中包含少於 k kk 個數據點,則單元A中的全部點都是異常值。

  此過程的第一步是將部分數據點直接標記爲非異常值(若是因爲第一個規則而致使它們的單元格包含 k kk 個點以上)。 此外,此類單元格的全部相鄰單元格僅包含非異常值。 爲了充分利用第一條規則的修剪能力,肯定每一個單元格及其 L 1 L_{1}L1 鄰居中點的總和。 若是總數大於 k kk ,則全部這些點也都標記爲非離羣值。測試

  接下來,利用第二條規則的修剪能力。 對於包含至少一個數據點的每一個單元格 A AA,計算其中的點數及其 L 1 L_{1}L1 和 L 2 L_{2}L2 鄰居的總和。 若是該數字不超過 k kk,則將單元格A AA 中的全部點標記爲離羣值。 此時,許多單元可能被標記爲異常值或非異常值。ui

  對於此時仍未標記爲異常值或非異常值的單元格中的數據點須要明確計算其 k kk 最近鄰距離。即便對於這樣的數據點,經過使用單元格結構也能夠更快地計算出 k kk 個最近鄰的距離。考慮到目前爲止還沒有被標記爲異常值或非異常值的單元格A AA。這樣的單元可能同時包含異常值和非異常值。單元格 A AA 中數據點的不肯定性主要存在於該單元格的 L 2 L_{2}L2 鄰居中的點集。沒法經過規則知道 A AA 的 L 2 L_{2}L2 鄰居中的點是否在閾值距離 D DD 內,爲了肯定單元 A AA 中數據點與其L 2 L_{2}L2 鄰居中的點集在閾值距離 D DD 內的點數,須要進行顯式距離計算。對於那些在 L 1 L_{1}L1 和 L 2 L_{2}L2 中不超過 k kk 個且距離小於 D DD 的數據點,則聲明爲異常值。須要注意,僅須要對單元 A AA 中的點到單元A AA的L 2 L_{2}L2鄰居中的點執行顯式距離計算。這是由於已知 L 1 L_{1}L1 鄰居中的全部點到 A AA 中任何點的距離都小於 D DD,而且已知 L r LrLr 中 ( r > 2 ) (r> 2)(r>2) 的全部點與 A AA上任何點的距離至少爲 D DD。所以,能夠在距離計算中實現額外的節省。

2.2 基於索引的方法

  對於一個給定數據集,基於索引的方法利用多維索引結構(如 R \mathrm{R}R 樹、k − d k-dkd 樹)來搜索每一個數據對象 A AA 在半徑 D DD 範圍 內的相鄰點。設 M MM 是一個異常值在其 D DD -鄰域內容許含有對象的最多個數,若發現某個數據對象 A AA 的 D DD -鄰域內出現 M + 1 M+1M+1 甚至更多個相鄰點, 則斷定對象 A AA 不是異常值。該算法時間複雜度在最壞狀況下爲 O ( k N 2 ) , O\left(k N^{2}\right),O(kN2), 其中 k kk 是數據集維數, N NN 是數據集包含對象的個數。該算法在數據集的維數增長時具備較好的擴展性,可是時間複雜度的估算僅考慮了搜索時間,而構造索引的任務自己就須要密集複雜的計算量。

三、基於密度的度量

  基於密度的算法主要有局部離羣因子(LocalOutlierFactor,LOF),以及LOCI、CLOF等基於LOF的改進算法。下面咱們以LOF爲例來進行詳細的介紹和實踐。

  基於距離的檢測適用於各個集羣的密度較爲均勻的狀況。在下圖中,離羣點B容易被檢出,而若要檢測出較爲接近集羣的離羣點A,則可能會將一些集羣邊緣的點看成離羣點丟棄。而LOF等基於密度的算法則能夠較好地適應密度不一樣的集羣狀況。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6tzwRYEo-1611237077198)(img/圖4.1距離檢測的困境-離羣點A-1609839836032.png)]

   那麼,這個基於密度的度量值是怎麼得來的呢?仍是要從距離的計算開始。相似k近鄰的思路,首先咱們也須要來定義一個「k-距離」。

3.1 k-距離(k-distance§):

  對於數據集D中的某一個對象o,與其距離最近的k個相鄰點的最遠距離表示爲k-distance§,定義爲給定點p和數據集D中對象o之間的距離d(p,o),知足:

  • 在集合D中至少有k個點 o’,其中o ′ ∈ D { p } o'∈D\{p\}oD{p},知足d ( p , o ′ ) ≤ d ( p , o ) d(p,o')≤d(p,o)d(p,o)d(p,o)
  • 在集合D中最多有k-1個點o’,其中o ′ ∈ D { p } o'∈D\{p\}oD{p},知足d ( p , o ; ) < d ( p , o ) d(p,o;)<d(p,o)d(p,o;)<d(p,o)

  直觀一些理解,就是以對象o爲中心,對數據集D中的全部點到o的距離進行排序,距離對象o第k近的點p與o之間的距離就是k-距離。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Q7RjZsK3-1611237077201)(img/定義3-鄰域半徑-1609839837759.png)]

3.2 k-鄰域(k-distance neighborhood):

  由k-距離,咱們擴展到一個點的集合——到對象o的距離小於等於k-距離的全部點的集合,咱們稱之爲k-鄰域:$N_{k − d i s t a n c e ( p )}( P ) = { q ∈ D \backslash{ p } ∣ d ( p , q ) ≤ k − d i s t a n c e ( p )}
$。

  在二維平面上展現出來的話,對象o的k-鄰域實際上就是以對象o爲圓心、k-距離爲半徑圍成的圓形區域。就是說,k-鄰域已經從「距離」這個概念延伸到「空間」了。

3.3 可達距離(reachability distance):

  有了鄰域的概念,咱們能夠按照到對象o的距離遠近,將數據集D內的點按照到o的距離分爲兩類:

  • p i p_ipi在對象o的k-鄰域內,則可達距離就是給定點p關於對象o的k-距離;
  • p i p_ipi在對象o的k-鄰域外,則可達距離就是給定點p關於對象o的實際距離。

  給定點p關於對象o的可達距離用數學公式能夠表示爲:r e a c h − d i s t k ( p , o ) = m a x { k − d i s t a n c e ( o ) , d ( p , o ) } r e a c h−d i s t_ k ( p , o ) = m a x \{k−distance( o ) , d ( p , o )\}reachdistk(p,o)=max{kdistance(o),d(p,o)} 。
  這樣的分類處理能夠簡化後續的計算,同時讓獲得的數值區分度更高。

3.4 局部可達密度(local reachability density):

  咱們能夠將「密度」直觀地理解爲點的彙集程度,就是說,點與點之間距離越短,則密度越大。在這裏,咱們使用數據集D中給定點p與對象o的k-鄰域內全部點的可達距離平均值的倒數(注意,不是導數)來定義局部可達密度。
  給定點p的局部可達密度計算公式爲:l r d M i n P t s ( p ) = 1 / ( ∑ o ∈ N M i n P t s ( p ) r e a c h − d i s t M i n P t s ( p , o ) ∣ N M i n P t s ( p ) ∣ ) lrd_{MinPts}(p)=1/(\frac {\sum\limits_{o∈N_{MinPts}(p)} reach-dist_{MinPts}(p,o)} {\left\vert N_{MinPts}(p) \right\vert})lrdMinPts(p)=1/(NMinPts(p)oNMinPts(p)reachdistMinPts(p,o))

  由公式能夠看出,這裏是對給定點p進行度量,計算其鄰域內的全部對象o到給定點p的可達距離平均值。給定點p的局部可達密度越高,越可能與其鄰域內的點 屬於同一簇;密度越低,越多是離羣點。

3.5 局部異常因子:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gRM2ox1m-1611237077204)(img/局部異常因子公式-1609839840015.png)]

  表示點p的鄰域N k ( p ) N_k(p)Nk(p)內其餘點的局部可達密度與點p的局部可達密度之比的平均數。若是這個比值越接近1,說明o的鄰域點密度差很少,o可能和鄰域同屬一簇;若是這個比值小於1,說明o的密度高於其鄰域點密度,o爲密集點;若是這個比值大於1,說明o的密度小於其鄰域點密度,o多是異常點。

  最終得出的LOF數值,就是咱們所須要的離羣點分數。在sklearn中有LocalOutlierFactor庫,能夠直接調用。下面來直觀感覺一下LOF的圖像呈現效果。

  LocalOutlierFactor庫能夠用於對單個數據集進行無監督的離羣檢測,也能夠基於已有的正常數據集對新數據集進行新穎性檢測。在這裏咱們進行單個數據集的無監督離羣檢測。

import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.neighbors import LocalOutlierFactor plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus']=False pd.set_option('display.max_columns', None) pd.set_option('display.max_rows', None) 

  首先構造一個含有集羣和離羣點的數據集。該數據集包含兩個密度不一樣的正態分佈集羣和一些離羣點。可是,這裏咱們手工對數據點的標註實際上是不許確的,可能有一些隨機點會散落在集羣內部,而一些集羣點因爲正態分佈的特性,會與其他點的距離相對遠一些。在這裏咱們沒法進行區分,因此按照生成方式統一將它們標記爲「集羣內部的點」或者「離羣點」。

np.random.seed(61) # 構造兩個數據點集羣 X_inliers1 = 0.2 * np.random.randn(100, 2) X_inliers2 = 0.5 * np.random.randn(100, 2) X_inliers = np.r_[X_inliers1 + 2, X_inliers2 - 2] # 構造一些離羣的點 X_outliers = np.random.uniform(low=-4, high=4, size=(20, 2)) # 拼成訓練集 X = np.r_[X_inliers, X_outliers] n_outliers = len(X_outliers) ground_truth = np.ones(len(X), dtype=int) # 打標籤,羣內點構造離羣值爲1,離羣點構造離羣值爲-1 ground_truth[-n_outliers:] = -1 
plt.title('構造數據集 (LOF)') plt.scatter(X[:-n_outliers, 0], X[:-n_outliers, 1], color='b', s=5, label='集羣點') plt.scatter(X[-n_outliers:, 0], X[-n_outliers:, 1], color='orange', s=5, label='離羣點') plt.axis('tight') plt.xlim((-5, 5)) plt.ylim((-5, 5)) legend = plt.legend(loc='upper left') legend.legendHandles[0]._sizes = [10] legend.legendHandles[1]._sizes = [20] plt.show() 

在這裏插入圖片描述
  而後使用LocalOutlierFactor庫對構造數據集進行訓練,獲得訓練的標籤和訓練分數(局部離羣值)。爲了便於圖形化展現,這裏對訓練分數進行了一些轉換。

# 訓練模型(找出每一個數據的實際離羣值) clf = LocalOutlierFactor(n_neighbors=20, contamination=0.1) # 對單個數據集進行無監督檢測時,以1和-1分別表示非離羣點與離羣點 y_pred = clf.fit_predict(X) # 找出構造離羣值與實際離羣值不一樣的點 n_errors = y_pred != ground_truth X_pred = np.c_[X,n_errors] X_scores = clf.negative_outlier_factor_ # 實際離羣值有正有負,轉化爲正數並保留其差別性(不是直接取絕對值) X_scores_nor = (X_scores.max() - X_scores) / (X_scores.max() - X_scores.min()) X_pred = np.c_[X_pred,X_scores_nor] X_pred = pd.DataFrame(X_pred,columns=['x','y','pred','scores']) X_pred_same = X_pred[X_pred['pred'] == False] X_pred_different = X_pred[X_pred['pred'] == True] # 直觀地看一看數據 X_pred.head() 

在這裏插入圖片描述
  將訓練分數(離羣程度)用圓直觀地表示出來,並對構造標籤與訓練標籤不一致的數據用不一樣顏色的圓進行標註。

plt.title('局部離羣因子檢測 (LOF)') plt.scatter(X[:-n_outliers, 0], X[:-n_outliers, 1], color='b', s=5, label='集羣點') plt.scatter(X[-n_outliers:, 0], X[-n_outliers:, 1], color='orange', s=5, label='離羣點') # 以標準化以後的局部離羣值爲半徑畫圓,以圓的大小直觀表示出每一個數據點的離羣程度 plt.scatter(X_pred_same.values[:,0], X_pred_same.values[:, 1], s=1000 * X_pred_same.values[:, 3], edgecolors='c', facecolors='none', label='標籤一致') plt.scatter(X_pred_different.values[:, 0], X_pred_different.values[:, 1], s=1000 * X_pred_different.values[:, 3], edgecolors='violet', facecolors='none', label='標籤不一樣') plt.axis('tight') plt.xlim((-5, 5)) plt.ylim((-5, 5)) legend = plt.legend(loc='upper left') legend.legendHandles[0]._sizes = [10] legend.legendHandles[1]._sizes = [20] plt.show() 

在這裏插入圖片描述

  能夠看出,模型成功區分出了大部分的離羣點,一些由於隨機緣由散落在集羣內部的「離羣點」也被識別爲集羣內部的點,可是一些與集羣略爲分散的「集羣點」則被識別爲離羣點。
  同時能夠看出,模型對於不一樣密度的集羣有着較好的區分度,對於低密度集羣與高密度集羣使用了不一樣的密度閾值來區分是否離羣點。
  所以,咱們從直觀上能夠獲得一個印象,即基於LOF模型的離羣點識別在某些狀況下,可能比基於某種統計學分佈規則的識別更加符合實際狀況。

四、練習

1.學習使用PyOD庫生成toy example並調用LOF算法

import os import sys 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 # 使用生成樣本數據pyod.utils.data.generate_data(): contamination = 0.1 # percentage of outliers n_train = 200 # number of training points n_test = 100 # number of testing points X_train, y_train, X_test, y_test = generate_data( n_train=n_train, n_test=n_test, contamination=contamination) # 初始化pyod.models.lof.LOF檢測器,擬合模型,而後進行預測。 # train LOF detector clf_name = 'LOF' """ LOF()構造器參數列表: n_neighbors=20, algorithm='auto', metric='minkowski', # 計算距離 p=2, # Parameter for the Minkowski metric from sklearn.metrics.pairwise.pairwise_distances. metric_params=None, contamination=0.1, # 污染比例: the proportion(比例) of outliers in the data set. n_jobs=1 # 並行做業數 """ clf = LOF() 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 # 使用ROC和Precision @ Rank n評估預測pyod.utils.data.evaluate_print()。 from pyod.utils.data import evaluate_print # evaluate and print the results print("\nLOF On Training Data:") evaluate_print(clf_name, y_train, y_train_scores) print("\nLOF On 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=True) 

在這裏插入圖片描述
在這裏插入圖片描述

參考資料:

  1. LOF: Identifying Density-Based Local Outliers
  2. https://scikit-learn.org/stable/auto_examples/neighbors/plot_lof_outlier_detection.html?highlight=lof

關於Datawhale:

Datawhale是一個專一於數據科學與AI領域的開源組織,聚集了衆多領域院校和知名企業的優秀學習者,聚合了一羣有開源精神和探索精神的團隊成員。Datawhale以「for the learner,和學習者一塊兒成長」爲願景,鼓勵真實地展示自我、開放包容、互信互助、勇於試錯和敢於擔當。同時Datawhale 用開源的理念去探索開源內容、開源學習和開源方案,賦能人才培養,助力人才成長,創建起人與人,人與知識,人與企業和人與將來的聯結。

相關文章
相關標籤/搜索