每個數(shù)據(jù)科學家都需要的3種簡單的異常檢測算法
深入了解離群值檢測以及如何在Python中實現(xiàn)3個簡單,直觀且功能強大的離群值檢測算法
> Photo By Scott.T on Flickr
我確定您遇到以下幾種情況:
- 您的模型表現(xiàn)不理想。
- 您不禁會注意到有些地方似乎與其他地方有很大的不同。
恭喜,因為您的數(shù)據(jù)中可能包含異常值!
什么是離群值?

> Photo can be found in StackExchange
在統(tǒng)計中,離群點是與其他觀察值有顯著差異的數(shù)據(jù)點。 從上圖可以清楚地看到,盡管大多數(shù)點都位于線性超平面內(nèi)或周圍,但可以看到單個點與其余超散點不同。 這是一個離群值。
例如,查看下面的列表:
- [1,35,20,32,40,46,45,4500]
在這里,很容易看出1和4500在數(shù)據(jù)集中是異常值。
為什么我的數(shù)據(jù)中有異常值?
通常,異常可能發(fā)生在以下情況之一:
- 有時可能由于測量錯誤而偶然發(fā)生。
- 有時它們可能會出現(xiàn)在數(shù)據(jù)中,因為在沒有異常值的情況下,數(shù)據(jù)很少是100%干凈的。
為什么離群值有問題?
原因如下:
線性模型
假設您有一些數(shù)據(jù),并且想使用線性回歸從中預測房價。 可能的假設如下所示:

> Source: http> Photo By Authors://arxiv.org/pdf/1811.06965.pdf
在這種情況下,我們實際上將數(shù)據(jù)擬合得太好(過度擬合)。 但是,請注意所有點的位置大致在同一范圍內(nèi)。
現(xiàn)在,讓我們看看添加異常值時會發(fā)生什么。

> Photo By Author
顯然,我們看到了假設的變化,因此,如果沒有異常值,推斷將變得更加糟糕。 線性模型包括:
- 感知器
- 線性+ Logistic回歸
- 神經(jīng)網(wǎng)絡
- 知識網(wǎng)絡
數(shù)據(jù)插補
常見的情況是缺少數(shù)據(jù),可以采用以下兩種方法之一:
- 刪除缺少行的實例
- 使用統(tǒng)計方法估算數(shù)據(jù)
如果我們選擇第二種方法,我們可能會得出有問題的推論,因為離群值會極大地改變統(tǒng)計方法的值。 例如,回到?jīng)]有異常值的虛構(gòu)數(shù)據(jù):
- # Data with no outliers
- np.array([35,20,32,40,46,45]).mean() = 36.333333333333336
- # Data with 2 outliers
- np.array([1,35,20,32,40,46,45,4500]).mean() = 589.875
顯然,這種類比是極端的,但是想法仍然相同。 我們數(shù)據(jù)中的異常值通常是一個問題,因為異常值會在統(tǒng)計分析和建模中引起嚴重的問題。 但是,在本文中,我們將探討幾種檢測和打擊它們的方法。
解決方案1:DBSCAN

> Photo By Wikipedia
像KMeans一樣,帶有噪聲(或更簡單地說是DBSCAN)的應用程序的基于密度的空間聚類實際上是一種無監(jiān)督的聚類算法。 但是,其用途之一還在于能夠檢測數(shù)據(jù)中的異常值。
DBSCAN之所以受歡迎,是因為它可以找到非線性可分離的簇,而KMeans和高斯混合無法做到這一點。 當簇足夠密集且被低密度區(qū)域隔開時,它會很好地工作。
DBSCAN工作原理的高級概述
該算法將群集定義為高密度的連續(xù)區(qū)域。 該算法非常簡單:
- 對于每個實例,它計算在距它的小距離ε(ε)內(nèi)有多少個實例。 該區(qū)域稱為實例的ε社區(qū)。
- 如果該實例在其ε鄰域中有多個min_samples個實例,則將其視為核心實例。 這意味著實例位于高密度區(qū)域(內(nèi)部有很多實例的區(qū)域)。
- 核心實例的ε鄰域內(nèi)的所有實例都分配給同一群集。 這可能包括其他核心實例,因此相鄰核心實例的單個長序列形成單個群集。
- 不是核心實例或不在任何核心實例的ε鄰居中的任何實例都是異常值。
DBSCAN實戰(zhàn)
借助Scikit-Learn直觀的API,DBSCAN算法非常易于使用。 讓我們看一個實際的算法示例:
- from sklearn.cluster import DBSCAN
- from sklearn.datasets import make_moons
- X, y = make_moons(n_samples=1000, noise=0.05)
- dbscan = DBSCAN(eps=0.2, min_samples=5)
- dbscan.fit(X)
在這里,我們將實例化一個具有ε鄰域長度為0.05的DBSCAN,并將5設為實例被視為核心實例所需的最小樣本數(shù)
請記住,我們不傳遞標簽,因為它是無監(jiān)督的算法。 我們可以使用以下命令查看標簽,即算法生成的標簽:
- dbscan.labels_
- OUT:array([ 0, 2, -1, -1, 1, 0, 0, 0, ..., 3, 2, 3, 3, 4, 2, 6, 3])
請注意一些標簽的值如何等于-1:這些是離群值。
DBSCAN沒有預測方法,只有fit_predict方法,這意味著它無法對新實例進行聚類。 相反,我們可以使用其他分類器進行訓練和預測。 在此示例中,我們使用KNN:
- from sklearn.neighbors import KNeighborsClassifier
- knn = KNeighborsClassifier(n_neighbors=50)
- knn.fit(dbscan.components_, dbscan.labels_[dbscan.core_sample_indices_])
- X_new = np.array([[-0.5, 0], [0, 0.5], [1, -0.1], [2, 1]])
- knn.predict(X_new)
- OUT:array([1, 0, 1, 0])
在這里,我們將KNN分類器適合核心樣本及其各自的鄰居。
但是,我們遇到了一個問題。 我們提供的KNN數(shù)據(jù)沒有任何異常值。 這是有問題的,因為它將迫使KNN為新實例選擇群集,即使新實例確實是異常值。
為了解決這個問題,我們利用了KNN分類器的kneighbors方法,該方法在給定一組實例的情況下,返回訓練集的k個最近鄰居的距離和索引。 然后,我們可以設置最大距離,如果實例超過該距離,我們會將其限定為離群值:
- y_dist, y_pred_idx = knn.kneighbors(X_new, n_neighbors=1)
- y_pred = dbscan.labels_[dbscan.core_sample_indices_][y_pred_idx]
- y_pred[y_dist > 0.2] = -1y_pred.ravel()
- OUT:array([-1, 0, 1, -1])
在這里,我們已經(jīng)討論并實現(xiàn)了用于異常檢測的DBSCAN。 DBSCAN很棒,因為它速度快,只有兩個超參數(shù)并且對異常值具有魯棒性。
解決方案2:IsolationForest

> Photo By Author
IsolationForest是一種集成學習異常檢測算法,在檢測高維數(shù)據(jù)集中的異常值時特別有用。 該算法基本上執(zhí)行以下操作:
- 它創(chuàng)建了一個隨機森林,其中決策樹是隨機增長的:在每個節(jié)點上,特征都是隨機選擇的,并且它選擇一個隨機閾值將數(shù)據(jù)集一分為二。
- 它會繼續(xù)砍掉數(shù)據(jù)集,直到所有實例最終相互隔離。
- 異常通常與其他實例相距甚遠,因此,平均而言(在所有決策樹中),與正常實例相比,異常隔離的步驟更少。
行動中的森林
同樣,借助Scikit-Learn直觀的API,我們可以輕松實現(xiàn)IsolationForest類。 讓我們看一個實際的算法示例:
- from sklearn.ensemble import IsolationForest
- from sklearn.metrics import mean_absolute_error
- import pandas as pd
我們還將導入mean_absolute_error來衡量我們的錯誤。 對于數(shù)據(jù),我們將使用可從Jason Brownlee的GitHub獲得的數(shù)據(jù)集:
- url='https://raw.githubusercontent.com/jbrownlee/Datasets/master/housing.csv'
- df = pd.read_csv(url, header=None)
- data = df.values
- # split into input and output elements
- X, y = data[:, :-1], data[:, -1]
在擬合隔離森林之前,讓我們嘗試在數(shù)據(jù)上擬合香草線性回歸模型并獲得MAE:
- from sklearn.linear_model import LinearRegression
- lr =LinearRegression()
- lr.fit(X,y)
- mean_absolute_error(lr.predict(X),y)
- OUT:3.2708628109003177
分數(shù)比較好。 現(xiàn)在,讓我們看看隔離林是否可以通過消除異常來提高得分!
首先,我們將實例化IsolationForest:
- iso = IsolationForest(contamination='auto',random_state=42)
該算法中最重要的超參數(shù)可能是污染參數(shù),該污染參數(shù)用于幫助估計數(shù)據(jù)集中的異常值。 這是介于0.0和0.5之間的值,默認情況下設置為0.1
但是,它本質(zhì)上是隨機的隨機森林,因此隨機森林的所有超參數(shù)也可以在算法中使用。
接下來,我們將數(shù)據(jù)擬合到算法中:
- y_pred = iso.fit_predict(X,y)
- mask = y_pred != -1
請注意,我們?nèi)绾我策^濾掉預測值= -1,就像在DBSCAN中一樣,這些被認為是離群值。
現(xiàn)在,我們將使用異常值過濾后的數(shù)據(jù)重新分配X和Y:
- X,y = X[mask,:],y[mask]
現(xiàn)在,讓我們嘗試將線性回歸模型擬合到數(shù)據(jù)中并測量MAE:
- lr.fit(X,y)
- mean_absolute_error(lr.predict(X),y)
- OUT:2.643367450077622
哇,成本大大降低了。 這清楚地展示了隔離林的力量。
解決方案3:Boxplots + Tuckey方法
雖然Boxplots是識別異常值的一種常見方法,但我確實發(fā)現(xiàn),后者可能是識別異常值的最被低估的方法。 但是在我們進入" Tuckey方法"之前,讓我們先談一下Boxplots:
箱線圖

> Photo By Wikipedia
箱線圖實質(zhì)上提供了一種通過分位數(shù)顯示數(shù)值數(shù)據(jù)的圖形方式,這是一種非常簡單但有效的可視化異常值的方式。
上下晶須顯示了分布的邊界,任何高于或低于此的值都被認為是異常值。 在上圖中,高于〜80和低于〜62的任何值都被認為是異常值。
Boxplots如何工作
本質(zhì)上,箱形圖通過將數(shù)據(jù)集分為5部分來工作:

> Photo from StackOverflow
- 最小值:分布中的最低數(shù)據(jù)點,不包括任何異常值。
- 最大值:分布中的最高數(shù)據(jù)點,不包括任何異常值。
- 中位數(shù)(Q2 / 50%):數(shù)據(jù)集的中間值。
- 第一個四分位數(shù)(Q1 / 25個百分點):是數(shù)據(jù)集下半部分的中位數(shù)。
- 第三四分位數(shù)(Q3 /第75個百分位數(shù)):是數(shù)據(jù)集上半部分的中位數(shù)。
四分位間距(IQR)很重要,因為它定義了異常值。 本質(zhì)上,它是以下內(nèi)容:
- IQR = Q3 - Q1
- Q3: third quartile
- Q1: first quartile
在箱圖中,測得的距離為1.5 * IQR,并包含數(shù)據(jù)集的較高觀測點。 類似地,在數(shù)據(jù)集的較低觀察點上測得的距離為1.5 * IQR。 這些距離之外的任何值都是異常值。 進一步來說:
- 如果觀測點低于(Q1-1.5 * IQR)或箱線圖下部晶須,則將其視為異常值。
- 同樣,如果觀測點高于(Q3 + 1.5 * IQR)或箱線圖上晶須,則它們也被視為離群值。

> Photo By Wikipedia
箱線圖在行動
讓我們看看如何在Python中使用Boxplots檢測離群值!
- import matplotlib.pyplot as plt
- import seaborn as sns
- import numpy as np
- X = np.array([45,56,78,34,1,2,67,68,87,203,-200,-150])
- y = np.array([1,1,0,0,1,0,1,1,0,0,1,1])
讓我們繪制數(shù)據(jù)的箱線圖:
- sns.boxplot(X)
- plt.show()

> Photo By Author
因此,根據(jù)箱線圖,我們看到我們的數(shù)據(jù)中位數(shù)為50和3個離群值。 讓我們擺脫這些要點:
- X = X[(X < 150) & (X > -50)]
- sns.boxplot(X)
- plt.show()

> Photo By Author
在這里,我基本上設置了一個閾值,以便將所有小于-50和大于150的點都排除在外。 結(jié)果 分布均勻!
Tukey方法離群值檢測
曲棍球方法離群值檢測實際上是箱形圖的非可視方法; 除了沒有可視化之外,方法是相同的。
我有時喜歡這種方法而不是箱線圖的原因是因為有時看一下可視化并粗略估計應將閾值設置為什么,實際上并沒有效果。
相反,我們可以編寫一種算法,該算法實際上可以返回它定義為異常值的實例。
該實現(xiàn)的代碼如下:
- import numpy as np
- from collections import Counter
- def detect_outliers(df, n, features):
- # list to store outlier indices
- outlier_indices = []
- # iterate over features(columns)
- for col in features:
- # Get the 1st quartile (25%)
- Q1 = np.percentile(df[col], 25)
- # Get the 3rd quartile (75%)
- Q3 = np.percentile(df[col], 75)
- # Get the Interquartile range (IQR)
- IQR = Q3 - Q1
- # Define our outlier step
- outlier_step = 1.5 * IQR
- # Determine a list of indices of outliers
- outlier_list_col = df[(df[col] < Q1 - outlier_step) | (df[col] > Q3 + outlier_step)].index
- # append outlier indices for column to the list of outlier indices
- outlier_indices.extend(outlier_list_col)
- # select observations containing more than 2 outliers
- outlier_indices = Counter(outlier_indices)
- multiple_outliers = list(k for k, v in outlier_indices.items() if v > n)
- return multiple_outliers
- # detect outliers from list of features
- list_of_features = ['x1', 'x2']
- # params dataset, number of outliers for rejection, list of features
- Outliers_to_drop = detect_outliers(dataset, 2, list_of_features)
基本上,此代碼執(zhí)行以下操作:
- 對于每個功能,它都會獲得:
- 第一四分位數(shù)
- 第三四分位數(shù)
- IQR
2.接下來,它定義離群值步驟,就像在箱圖中一樣,為1.5 * IQR
3.通過以下方式檢測異常值:
- 查看觀察點是否
- 查看觀察點是否為Q3 +離群步
4.然后檢查選擇的觀察值具有k個異常值(在這種情況下,k = 2)
結(jié)論
總而言之,存在許多離群值檢測算法,但是我們經(jīng)歷了3種最常見的算法:DBSCAN,IsolationForest和Boxplots。 我鼓勵您:
- 在"泰坦尼克號"數(shù)據(jù)集上嘗試這些方法。 哪一個最能檢測到異常值?
- 尋找其他異常檢測方法,看看它們的性能比最初嘗試得更好還是更差。
我真的很感謝我的追隨者,并希望不斷寫信并給予大家深思熟慮的食物。 但是,現(xiàn)在,我必須說再見;}