了解更多关于离群值检测以及如何在Python中实现3种简单、直观和强大的离群值检测算法>照片由Scott.T在Flickr上情况:您的模型表现不佳。您会情不自禁地注意到某些地方似乎与其他地方截然不同。恭喜,因为您的数据可能包含异常值!什么是异常值?>照片可以在StackExchange中找到在统计中,离群值是与其他观察值明显不同的数据点。从上图可以清楚地看出,虽然大多数点都位于线性超平面内或周围,但可以看出单个点与超散射的其余部分不同。这是异常值。例如,查看下面的列表:[1,35,20,32,40,46,45,4500]在这里,很容易看出1和4500是数据集中的异常值。为什么我的数据中有异常值?通常,异常会在以下情况之一中发生:有时它们可??能由于测量误差而偶然发生。有时它们会出现在数据中,因为没有异常值的数据很少是100%干净的。为什么离群值有问题?原因如下:线性模型假设您有一些数据,并且您想要使用线性回归从中预测房价。一个可能的假设是这样的:>来源:http>PhotoByAuthors://arxiv.org/pdf/1811.06965.pdf在这种情况下,我们实际上对数据拟合得太好了(过度拟合)。但是请注意,所有点都大致位于同一范围内。现在,让我们看看添加异常值时会发生什么。>作者照片显然,我们看到了假设的变化,因此如果没有异常值,推论会更糟。线性模型包括:感知器线性+逻辑回归神经网络知识网络数据插补一个常见的情况是缺失数据,这可以通过以下两种方式之一来完成:删除缺失行的实例使用统计方法来估计数据如果我们选择第二种方法,我们可能会得出有问题的推论,因为异常值会极大地改变统计方法的价值。例如,回到没有异常值的虚构数据:#Datawithnooutliersnp.array([35,20,32,40,46,45]).mean()=36.333333333333336#Datawith2outliersnp.array([1,35,20,32,40,46,45,4500]).mean()=589.875显然类比偏激了,但是思路还是一样的。我们数据中的异常值通常是一个问题,因为异常值会导致统计分析和建模中出现严重问题。但是,在本文中,我们将探索几种检测和对抗它们的方法。解决方案1:DBSCAN>图片来自维基百科与KMeans一样,基于密度的空间聚类与噪声应用(或更简单的DBSCAN)实际上是一种无监督聚类算法。但是,它的用途之一还在于能够检测数据中的异常值。DBSCAN很受欢迎,因为它可以找到非线性可分的簇,这是KMeans和高斯混合无法做到的。当集群足够密集并被低密度区域分隔时,它会很好地工作。DBSCAN工作原理的高级概述。该算法将集群定义为高密度的连续区域。该算法非常简单:对于每个实例,它计算有多少实例在距离它很小的距离ε(ε)之内。该区域称为实例的ε社区。如果一个实例在其ε邻域中有超过min_samples个实例,则该实例被认为是核心实例。这意味着实例位于高密度区域(内部有许多实例的区域)。核心实例的ε-邻域内的所有实例都分配给同一个集群。这可能包括其他核心实例,因此一长串相邻的核心实例形成了一个集群。任何不是核心实例或不在任何核心实例的ε邻域中的实例都是异常值。DBSCAN的实际应用借助Scikit-Learn的直观API,DBSCAN算法非常易于使用。让我们看一个实际算法的例子:fromsklearn.clusterimportDBSCANfromsklearn.datasetsimportmake_moonsX,y=make_moons(n_samples=1000,noise=0.05)dbscan=DBSCAN(eps=0.2,min_samples=5)dbscan.fit(X)这里我们将实例化一个ε邻域长度为0.05的DBSCAN,并设置5作为一个实例被认为是核心实例所需的最小样本数。请记住,我们不传递标签,因为它是一种无监督算法。我们可以使用以下命令查看算法生成的标签:dbscan.labels_OUT:array([0,2,-1,-1,1,0,0,0,...,3,2,3,3,4,2,6,3])注意一些标签的值如何等于-1:这些是离群值。DBSCAN没有predict方法,只有fit_predict方法,这意味着它不能聚类新实例。相反,我们可以使用其他分类器进行训练和预测。在这个例子中,我们使用KNN:fromsklearn.neighborsimportKNeighborsClassifierknn=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数据没有任何异常值。这是有问题的,因为它会强制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])这里我们讨论并实现了用于异常检测的DBSCAN。DBSCAN很棒,因为它速度快,只有两个超参数并且对异常值具有鲁棒性。解决方案2:IsolationForest>图片由作者提供IsolationForest是一种集成学习异常检测算法,特别适用于检测高维数据集中的异常值。该算法基本上执行以下操作:它创建一个随机森林,其中决策树随机生长:在每个节点,随机选择特征,并选择一个随机阈值将数据集一分为二。它继续削减数据集,直到所有实例最终彼此隔离。异常通常远离其他实例,因此,平均而言(在所有决策树中),异常被隔离的步骤比正常实例少。行动中的森林同样,借助Scikit-Learn的直观API,我们可以轻松实现IsolationForest类。让我们看一个实际算法的例子:fromsklearn.ensembleimportIsolationForestfromsklearn.metricsimportmean_absolute_errorimportpandasaspd我们还将导入mean_absolute_error来衡量我们的错误。对于数据,我们将使用JasonBrownlee的GitHub上提供的数据集:url='https://raw.githubusercontent.com/jbrownlee/Datasets/master/housing.csv'df=pd.read_csv(url,header=None)data=df.values#splitintoinputandoutputelementsX,y=data[:,:-1],data[:,-1]在拟合隔离森林之前,让我们尝试在数据上拟合普通线性回归模型并获得MAE:fromsklearn.linear_modelimportLinearRegressionlr=LinearRegression()lr.fit(X,y)mean_absolute_error(lr.predict(X),y)OUT:3.2708628109003177分数更好。现在,让我们看看隔离森林是否可以通过消除异常来提高分数!首先,我们将实例化一个IsolationForest:iso=IsolationForest(contamination='auto',random_state=42)该算法中最重要的超参数可能是污染参数,它用于帮助估计数据集中的异常值。这是一个介于0.0和0.5之间的值,默认设置为0.1。但它本质上是一个随机森林,所以算法中也可以使用随机森林的所有超参数。接下来,我们将数据拟合到算法中:y_pred=iso.fit_predict(X,y)mask=y_pred!=-1注意我们如何也过滤掉预测值=-1,就像在DBSCAN中一样,这些被认为是离群值。现在,我们将使用异常值过滤数据重新分配X和Y:X,y=X[mask,:],y[mask]现在,让我们尝试将线性回归模型拟合到数据并测量MAE:lr。fit(X,y)mean_absolute_error(lr.predict(X),y)OUT:2.643367450077622哇,成本大大降低了。这清楚地展示了隔离林的强大功能。解决方案3:箱线图+Tuckey方法虽然箱线图是识别异常值的常用方法,但我确实发现后者可能是最被低估的识别异常值的方法。但在我们进入“Tuckey方法”之前,让我们先谈谈箱线图:箱线图>维基百科照片箱线图本质上提供了一种按分位数显示数值数据的图形方式,这是一种非常简单但有效的异常值可视化方式。上部和下部晶须显示分布的边界,高于或低于此的任何值都被视为异常值。在上图中,任何高于~80和低于~62的值都被视为异常值。箱线图如何工作从本质上讲,箱线图通过将您的数据集分为5个部分来工作:>来自StackOverflow的照片最小值:分布中的最低数据点,不包括任何异常值。最大值:分布中的最高数据点,不包括任何异常值。中位数(Q2/50%):数据集的中间值。第一个四分位数(Q1/第25个百分位数):是数据集后半部分的中位数。第三四分位数(Q3/第75个百分位数):是数据集上半部分的中位数。四分位数间距(IQR)很重要,因为它定义了异常值。本质上,它如下:IQR=Q3-Q1Q3:第三个四分位数Q1:第一个四分位数在箱线图中,测量距离为1.5*IQR并且包含数据集的较高观察点。类似地,在数据集的较低观察点上测量的距离是1.5*IQR。这些距离之外的任何值都是异常值。此外:如果观察值低于(Q1-1.5*IQR)或箱线图的下须线,则该观察值被视为异常值。同样,如果观察值高于(Q3+1.5*IQR)或箱线图上的须线,则它们被视为异常值。>图片来自维基百科BoxplotsinAction让我们看看如何在Python中使用Boxplots检测异常值!importmatplotlib.pyplotaspltimportseabornassnssimportnumpyasnpX=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])让我们绘制数据的箱线图:sns.boxplot(X)plt.show()>PhotoByAuthor所以,根据箱线图,我们看到我们的数据的中位数为50和3个离群值。让我们去掉点:X=X[(X<150)&(X>-50)]sns.boxplot(X)plt.show()>PhotoByAuthor这里我基本上设置了一个阈值,这样所有的点都更少大于-50和大于150被排除在外。结果均匀分布!Tukey方法异常值检测Hockey方法异常值检测实际上是一种非可视化的箱线图方法;除了没有可视化之外,方法是相同的。我有时更喜欢这种方法而不是箱线图的原因是,有时查看可视化并大致了解阈值应设置为多少并不真正有效。相反,我们可以编写一个算法,该算法实际上返回它定义为异常值的实例。这个实现的代码如下:importnumpyasnpfromcollectionsimportCounterdefdetect_outliers(df,n,features):#listtostoreoutlierindicesoutlier_indices=[]#iterateoverfeatures(columns)forcolinfeatures:#Getthe1stquartile(25%)Q1=np.percentile(df[col],25)#获取第三个四分位数(7%)Q3=np.percentile(df[col],75)#GettheInterquartilerange(IQR)IQR=Q3-Q1#Defineouroutlierstepoutlier_step=1.5*IQR#Determinelistofindicesofoutliersoutlier_list_col=df[(df[col]
