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

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

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

> Photo By Author
顯然,我們看到了假設(shè)的變化,因此,如果沒(méi)有異常值,推斷將變得更加糟糕。 線(xiàn)性模型包括:
- 感知器
 - 線(xiàn)性+ Logistic回歸
 - 神經(jīng)網(wǎng)絡(luò)
 - 知識(shí)網(wǎng)絡(luò)
 
數(shù)據(jù)插補(bǔ)
常見(jiàn)的情況是缺少數(shù)據(jù),可以采用以下兩種方法之一:
- 刪除缺少行的實(shí)例
 - 使用統(tǒng)計(jì)方法估算數(shù)據(jù)
 
如果我們選擇第二種方法,我們可能會(huì)得出有問(wèn)題的推論,因?yàn)殡x群值會(huì)極大地改變統(tǒng)計(jì)方法的值。 例如,回到?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
 
顯然,這種類(lèi)比是極端的,但是想法仍然相同。 我們數(shù)據(jù)中的異常值通常是一個(gè)問(wèn)題,因?yàn)楫惓V禃?huì)在統(tǒng)計(jì)分析和建模中引起嚴(yán)重的問(wèn)題。 但是,在本文中,我們將探討幾種檢測(cè)和打擊它們的方法。
解決方案1:DBSCAN

> Photo By Wikipedia
像KMeans一樣,帶有噪聲(或更簡(jiǎn)單地說(shuō)是DBSCAN)的應(yīng)用程序的基于密度的空間聚類(lèi)實(shí)際上是一種無(wú)監(jiān)督的聚類(lèi)算法。 但是,其用途之一還在于能夠檢測(cè)數(shù)據(jù)中的異常值。
DBSCAN之所以受歡迎,是因?yàn)樗梢哉业椒蔷€(xiàn)性可分離的簇,而KMeans和高斯混合無(wú)法做到這一點(diǎn)。 當(dāng)簇足夠密集且被低密度區(qū)域隔開(kāi)時(shí),它會(huì)很好地工作。
DBSCAN工作原理的高級(jí)概述
該算法將群集定義為高密度的連續(xù)區(qū)域。 該算法非常簡(jiǎn)單:
- 對(duì)于每個(gè)實(shí)例,它計(jì)算在距它的小距離ε(ε)內(nèi)有多少個(gè)實(shí)例。 該區(qū)域稱(chēng)為實(shí)例的ε社區(qū)。
 - 如果該實(shí)例在其ε鄰域中有多個(gè)min_samples個(gè)實(shí)例,則將其視為核心實(shí)例。 這意味著實(shí)例位于高密度區(qū)域(內(nèi)部有很多實(shí)例的區(qū)域)。
 - 核心實(shí)例的ε鄰域內(nèi)的所有實(shí)例都分配給同一群集。 這可能包括其他核心實(shí)例,因此相鄰核心實(shí)例的單個(gè)長(zhǎng)序列形成單個(gè)群集。
 - 不是核心實(shí)例或不在任何核心實(shí)例的ε鄰居中的任何實(shí)例都是異常值。
 
DBSCAN實(shí)戰(zhàn)
借助Scikit-Learn直觀(guān)的API,DBSCAN算法非常易于使用。 讓我們看一個(gè)實(shí)際的算法示例:
- 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)
 
在這里,我們將實(shí)例化一個(gè)具有ε鄰域長(zhǎng)度為0.05的DBSCAN,并將5設(shè)為實(shí)例被視為核心實(shí)例所需的最小樣本數(shù)
請(qǐng)記住,我們不傳遞標(biāo)簽,因?yàn)樗菬o(wú)監(jiān)督的算法。 我們可以使用以下命令查看標(biāo)簽,即算法生成的標(biāo)簽:
- dbscan.labels_
 - OUT:array([ 0, 2, -1, -1, 1, 0, 0, 0, ..., 3, 2, 3, 3, 4, 2, 6, 3])
 
請(qǐng)注意一些標(biāo)簽的值如何等于-1:這些是離群值。
DBSCAN沒(méi)有預(yù)測(cè)方法,只有fit_predict方法,這意味著它無(wú)法對(duì)新實(shí)例進(jìn)行聚類(lèi)。 相反,我們可以使用其他分類(lèi)器進(jìn)行訓(xùn)練和預(yù)測(cè)。 在此示例中,我們使用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分類(lèi)器適合核心樣本及其各自的鄰居。
但是,我們遇到了一個(gè)問(wèn)題。 我們提供的KNN數(shù)據(jù)沒(méi)有任何異常值。 這是有問(wèn)題的,因?yàn)樗鼘⑵仁筀NN為新實(shí)例選擇群集,即使新實(shí)例確實(shí)是異常值。
為了解決這個(gè)問(wèn)題,我們利用了KNN分類(lèi)器的kneighbors方法,該方法在給定一組實(shí)例的情況下,返回訓(xùn)練集的k個(gè)最近鄰居的距離和索引。 然后,我們可以設(shè)置最大距離,如果實(shí)例超過(guò)該距離,我們會(huì)將其限定為離群值:
- 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)討論并實(shí)現(xiàn)了用于異常檢測(cè)的DBSCAN。 DBSCAN很棒,因?yàn)樗俣瓤欤挥袃蓚€(gè)超參數(shù)并且對(duì)異常值具有魯棒性。
解決方案2:IsolationForest

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

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

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

> Photo By Wikipedia
箱線(xiàn)圖在行動(dòng)
讓我們看看如何在Python中使用Boxplots檢測(cè)離群值!
- 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ù)的箱線(xiàn)圖:
- sns.boxplot(X)
 - plt.show()
 

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

> Photo By Author
在這里,我基本上設(shè)置了一個(gè)閾值,以便將所有小于-50和大于150的點(diǎn)都排除在外。 結(jié)果 分布均勻!
Tukey方法離群值檢測(cè)
曲棍球方法離群值檢測(cè)實(shí)際上是箱形圖的非可視方法; 除了沒(méi)有可視化之外,方法是相同的。
我有時(shí)喜歡這種方法而不是箱線(xiàn)圖的原因是因?yàn)橛袝r(shí)看一下可視化并粗略估計(jì)應(yīng)將閾值設(shè)置為什么,實(shí)際上并沒(méi)有效果。
相反,我們可以編寫(xiě)一種算法,該算法實(shí)際上可以返回它定義為異常值的實(shí)例。
該實(shí)現(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í)行以下操作:
- 對(duì)于每個(gè)功能,它都會(huì)獲得:
 - 第一四分位數(shù)
 - 第三四分位數(shù)
 - IQR
 
2.接下來(lái),它定義離群值步驟,就像在箱圖中一樣,為1.5 * IQR
3.通過(guò)以下方式檢測(cè)異常值:
- 查看觀(guān)察點(diǎn)是否
 - 查看觀(guān)察點(diǎn)是否為Q3 +離群步
 
4.然后檢查選擇的觀(guān)察值具有k個(gè)異常值(在這種情況下,k = 2)
結(jié)論
總而言之,存在許多離群值檢測(cè)算法,但是我們經(jīng)歷了3種最常見(jiàn)的算法:DBSCAN,IsolationForest和Boxplots。 我鼓勵(lì)您:
- 在"泰坦尼克號(hào)"數(shù)據(jù)集上嘗試這些方法。 哪一個(gè)最能檢測(cè)到異常值?
 - 尋找其他異常檢測(cè)方法,看看它們的性能比最初嘗試得更好還是更差。
 
我真的很感謝我的追隨者,并希望不斷寫(xiě)信并給予大家深思熟慮的食物。 但是,現(xiàn)在,我必須說(shuō)再見(jiàn);}















 
 
 










 
 
 
 