不平衡数据集的单类分类算法
异常值或异常是与其他数据不符的罕见例子。
识别数据中的离群值称为离群值或异常检测,机器学习中专注于此问题的子领域称为单类分类。这些是无监督学习算法,旨在对“正常”示例进行建模,以便将新示例分类为正常或异常(例如离群值)。
单类分类算法可用于类别分布严重偏斜的二分类任务。这些技术可以适用于训练数据集中多数类别的输入示例,然后在保留测试数据集上进行评估。
尽管一类分类算法并非为解决这些类型的问题而设计,但它可以有效地用于不平衡的分类数据集,其中少数类别的示例没有或很少,或者数据集中没有连贯的结构来分离可以通过监督算法学习的类别。
在本教程中,您将了解如何对类分布严重偏斜的数据集使用一类分类算法。
完成本教程后,您将了解:
- 一类分类是机器学习的一个领域,提供异常值和异常检测技术。
- 如何使一类分类算法适应具有严重倾斜的类别分布的不平衡分类。
- 如何拟合和评估一类分类算法,例如 SVM、孤立森林、椭圆包络和局部异常因子。
教程概述
本教程分为五个部分,分别是:
- 不平衡数据的一类分类
- 单类支持向量机
- 孤立森林
- 最小协方差行列式
- 局部异常因素
不平衡数据的一类分类
[异常值]既罕见又不常见。
稀有性表明它们相对于非异常数据(所谓的内部数据)的频率较低。不寻常表明它们不能很好地适应数据分布。
离群值的存在可能会引发问题。例如,单个变量可能存在远离大量示例的离群值,这可能会扭曲平均值和方差等汇总统计数据。
拟合机器学习模型可能需要识别和去除异常值作为数据准备技术。
识别数据集中的异常值的过程通常称为异常检测,其中异常值是“异常”,其余数据是“正常”。异常值检测或异常检测是一个具有挑战性的问题,包含一系列技术。
在机器学习中,解决异常检测问题的一种方法是一类分类。
一类分类(简称 OCC)涉及在“正常”数据上拟合模型并预测新数据是正常的还是异常值/异常。
一类分类器旨在捕捉训练实例的特征,以便能够区分它们和可能出现的异常值。
— 第 139 页,从不平衡数据集中学习,2018 年。
单类分类器适用于仅包含正常类别示例的训练数据集。准备好后,该模型用于将新示例分类为正常或非正常,即离群值或异常值。
一类分类技术可用于二元(两类)不平衡分类问题,其中负案例(类 0)被视为“正常”,而正案例(类 1)被视为异常值或异常。
- 负面情况:正常或内部值。
- 正面案例:异常或离群值。
鉴于该方法的性质,单类分类最适合那些正例在特征空间中没有一致模式或结构的任务,这使得其他分类算法很难学习类别边界。相反,将正例视为离群值,它允许单类分类器忽略判别任务,而是专注于偏离正常值或预期值的情况。
事实证明,当少数类缺乏任何结构、主要由小的分离项或噪声实例组成时,这种解决方案特别有用。
— 第 139 页,从不平衡数据集中学习,2018 年。
当训练集中的正例数量太少,不值得纳入模型(例如几十个或更少)时,这种方法可能也适用。或者在训练模型之前无法收集到正例示例的问题中。
需要明确的是,这种针对不平衡分类的单类分类算法的改编并不常见,但在某些问题上却很有效。这种方法的缺点是,我们在训练期间拥有的任何异常值(正例)示例都不会被单类分类器使用,而是会被丢弃。这表明也许可以并行尝试对问题进行逆向建模(例如,将正例建模为正常情况)。它还表明,单类分类器可以为一组算法提供输入,每个算法都以不同的方式使用训练数据集。
必须记住,单类分类器的优势是以丢弃有关多数类的所有可用信息为代价的。因此,应谨慎使用此解决方案,它可能不适合某些特定应用。
— 第 140 页,从不平衡数据集学习,2018 年。
scikit-learn 库提供了一些常见的一类分类算法,用于异常值或异常检测和变化检测,例如一类 SVM、孤立森林、椭圆包络和局部异常值因子。
在接下来的章节中,我们将逐一讨论。
在此之前,我们将设计一个二元分类数据集来演示算法。我们将使用 make_classification () scikit-learn 函数创建 10,000 个示例,其中少数类中有 10 个示例,多数类中有 9,990 个示例,即 0.1% vs. 99.9%,或大约 1:1000 的类别分布。
下面的示例创建并总结了该数据集。
# Generate and plot a synthetic imbalanced classification dataset
from collections import Counter
from sklearn.datasets import make_classification
from matplotlib import pyplot
from numpy import where
# define dataset
X, y = make_classification(n_samples=10000, n_features=2, n_redundant=0,n_clusters_per_class=1, weights=[0.999], flip_y=0, random_state=4)
# summarize class distribution
counter = Counter(y)
print(counter)
# scatter plot of examples by class label
for label, _ in counter.items():row_ix = where(y == label)[0]pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(label))
pyplot.legend()
pyplot.show()
首先运行示例会总结类别分布,确认不平衡是按预期产生的。
Counter({0: 9990, 1: 10})
接下来,创建一个散点图,并将示例绘制为按其类别标签着色的点,显示多数类别(蓝色)的较大质量和少数类别(橙色)的几个点。
由于正类中的例子非常少,并且正类中少数例子具有非结构化性质,因此这种严重的类别不平衡可能为使用单类分类方法奠定良好的基础。
具有 1 到 1000 个类别不平衡的二元分类问题的散点图
单类支持向量机
支持向量机(SVM)算法最初是为二元分类而开发的,可用于一类分类。
如果用于不平衡分类,最好在测试单类版本之前先在数据集上评估标准 SVM 和加权 SVM。
在对一个类进行建模时,该算法会捕获多数类的密度,并将密度函数极值上的示例归类为异常值。这种 SVM 的修改称为单类 SVM。
…一种计算二元函数的算法,该函数应该捕获概率密度所在的输入空间区域(其支持),也就是说,大多数数据都位于函数非零的区域中。
—估计高维分布的支持度,2001 年。
scikit-learn 库在OneClassSVM 类中提供了一类 SVM 的实现。
与标准 SVM 的主要区别在于,它以无监督的方式进行拟合,并且不提供用于调整边距(如*C )*的正常超参数。相反,它提供了一个超参数“ nu ”,用于控制支持向量的敏感度,并且应调整为数据中异常值的近似比率,例如 0.01%。
...
# define outlier detection model
model = OneClassSVM(gamma='scale', nu=0.01)
该模型可以适用于训练数据集中的所有示例,也可以仅适用于多数类中的示例。也许可以尝试两种方法来解决您的问题。
在这种情况下,我们将尝试仅对训练集中属于多数类别的示例进行拟合。
# fit on majority class
trainX = trainX[trainy==0]
model.fit(trainX)
一旦适合,该模型可用于识别新数据中的异常值。
当在模型上调用predict()函数时,它将为正常示例(即所谓的内部值)输出 +1,为异常值输出 -1。
- 内部预测:+1
- 异常值预测:-1
...
# detect outliers in the test set
yhat = model.predict(testX)
如果我们想要评估模型作为二元分类器的性能,我们必须将测试数据集中的标签从多数类和少数类的 0 和 1 分别更改为 +1 和 -1。
...
# mark inliers 1, outliers -1
testy[testy == 1] = -1
testy[testy == 0] = 1
然后,我们可以将模型的预测与预期目标值进行比较,并计算得分。鉴于我们有明确的类别标签,我们可能会使用诸如准确率、召回率之类的得分,或者两者的组合,例如 F 度量(F1 得分)。
在本例中,我们将使用 F 度量分数,即准确率和召回率的调和平均值。我们可以使用 f1_score ()函数计算 F 度量,并通过“ pos_label ”参数将少数类的标签指定为 -1 。
...
# calculate score
score = f1_score(testy, yhat, pos_label=-1)
print('F1 Score: %.3f' % score)
结合这些,我们可以在合成数据集上评估一类 SVM 算法。我们将数据集一分为二,一半用于以无监督方式训练模型,另一半用于评估模型。
完整的示例如下。
# one-class svm for imbalanced binary classification
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
from sklearn.svm import OneClassSVM
# generate dataset
X, y = make_classification(n_samples=10000, n_features=2, n_redundant=0,n_clusters_per_class=1, weights=[0.999], flip_y=0, random_state=4)
# split into train/test sets
trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.5, random_state=2, stratify=y)
# define outlier detection model
model = OneClassSVM(gamma='scale', nu=0.01)
# fit on majority class
trainX = trainX[trainy==0]
model.fit(trainX)
# detect outliers in the test set
yhat = model.predict(testX)
# mark inliers 1, outliers -1
testy[testy == 1] = -1
testy[testy == 0] = 1
# calculate score
score = f1_score(testy, yhat, pos_label=-1)
print('F1 Score: %.3f' % score)
运行示例将模型与训练集中多数类的输入示例进行拟合。然后,该模型用于将测试集中的示例分类为正常值和异常值。
注意:由于算法或评估程序的随机性,或数值精度的差异,您的结果可能会有所不同。考虑运行示例几次并比较平均结果。
在这种情况下,F1 分数达到 0.123。
F1 Score: 0.123
孤立森林
孤立森林(Isolation Forest,简称 iForest)是一种基于树的异常检测算法。
…孤立森林 (iForest) 纯粹基于隔离概念来检测异常,而不采用任何距离或密度测量
—基于隔离的异常检测,2012 年。
它基于对正常数据的建模,以隔离特征空间中数量少且不同的异常。
…我们提出的方法利用了两个异常的定量特性:i)它们是由较少实例组成的少数;ii)它们的属性值与正常实例的属性值有很大不同。
—孤立森林,2008 年。
创建树结构是为了隔离异常。结果是,孤立的示例在树中的深度相对较短,而正常数据的孤立性较低,在树中的深度较大。
… 可以有效地构建一个树形结构来隔离每个实例。由于异常容易被隔离,因此它们被隔离在更靠近树根的位置;而正常点则被隔离在树的较深一端。
—孤立森林,2008 年。
scikit-learn 库在IsolationForest 类中提供了孤立森林的实现。
该模型最重要的超参数可能是“ n_estimators ”参数,它设置要创建的树的数量,以及“ contamination ”参数,它用于帮助定义数据集中的异常值的数量。
我们知道阳性病例与阴性病例之间的污染约为 0.01%,因此我们可以将“污染”参数设置为 0.01。
...
# define outlier detection model
model = IsolationForest(contamination=0.01, behaviour='new')
该模型可能最适合在排除异常值的示例上进行训练。在这种情况下,我们仅将模型拟合到多数类的示例的输入特征上。
...
# fit on majority class
trainX = trainX[trainy==0]
model.fit(trainX)
与一类 SVM 类似,该模型将预测一个标签为 +1 的内点和一个标签为 -1 的异常值,因此,在评估预测之前必须更改测试集的标签。
将这些结合在一起,完整的示例如下所示。
# isolation forest for imbalanced classification
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
from sklearn.ensemble import IsolationForest
# generate dataset
X, y = make_classification(n_samples=10000, n_features=2, n_redundant=0,n_clusters_per_class=1, weights=[0.999], flip_y=0, random_state=4)
# split into train/test sets
trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.5, random_state=2, stratify=y)
# define outlier detection model
model = IsolationForest(contamination=0.01, behaviour='new')
# fit on majority class
trainX = trainX[trainy==0]
model.fit(trainX)
# detect outliers in the test set
yhat = model.predict(testX)
# mark inliers 1, outliers -1
testy[testy == 1] = -1
testy[testy == 0] = 1
# calculate score
score = f1_score(testy, yhat, pos_label=-1)
print('F1 Score: %.3f' % score)
运行示例以无监督的方式在训练数据集上拟合隔离森林模型,然后将测试集中的示例分类为内值和异常值并对结果进行评分。
注意:由于算法或评估程序的随机性,或数值精度的差异,您的结果可能会有所不同。考虑运行示例几次并比较平均结果。
在这种情况下,F1 分数达到 0.154。
F1 Score: 0.154
注意:污染相当低,可能会导致许多运行的 F1 分数为 0.0。
为了提高该方法在该数据集上的稳定性,请尝试将污染增加到 0.05 甚至 0.1,然后重新运行该示例。
最小协方差行列式
如果输入变量具有高斯分布,则可以使用简单的统计方法来检测异常值。
例如,如果数据集有两个输入变量,并且都是高斯分布,那么特征空间就会形成一个多维高斯分布,并且可以利用该分布的知识来识别远离分布的值。
这种方法可以通过定义一个覆盖正常数据的超球面(椭圆体)来推广,超出此形状的数据将被视为异常值。对于多变量数据,此技术的有效实现称为最小协方差行列式,简称 MCD。
拥有如此表现良好的数据并不常见,但如果您的数据集就是这种情况,或者您可以使用幂变换使变量服从高斯分布,那么这种方法可能是合适的。
最小协方差行列式 (MCD) 方法是多元位置和散度的高度稳健估计,并有快速算法可用。[…] 它还是一种方便有效的异常值检测工具。
—最小协方差行列式及其扩展,2017 年。
scikit-learn 库通过EllipticEnvelope 类提供对此方法的访问。
它提供了“污染”参数,该参数定义了实际观察到的异常值的预期比率。我们知道这在我们的合成数据集中是 0.01%,因此我们可以相应地设置它。
...
# define outlier detection model
model = EllipticEnvelope(contamination=0.01)
该模型仅适合于来自多数类的输入数据,以便以无监督的方式估计“正常”数据的分布。
...
# fit on majority class
trainX = trainX[trainy==0]
model.fit(trainX)
然后,该模型将用于将新示例分类为正常(+1)或异常值(-1)。
...
# detect outliers in the test set
yhat = model.predict(testX)
综合以上几点,下面列出了在我们的合成二元分类数据集上使用椭圆包络异常值检测模型进行不平衡分类的完整示例。
# elliptic envelope for imbalanced classification
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
from sklearn.covariance import EllipticEnvelope
# generate dataset
X, y = make_classification(n_samples=10000, n_features=2, n_redundant=0,n_clusters_per_class=1, weights=[0.999], flip_y=0, random_state=4)
# split into train/test sets
trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.5, random_state=2, stratify=y)
# define outlier detection model
model = EllipticEnvelope(contamination=0.01)
# fit on majority class
trainX = trainX[trainy==0]
model.fit(trainX)
# detect outliers in the test set
yhat = model.predict(testX)
# mark inliers 1, outliers -1
testy[testy == 1] = -1
testy[testy == 0] = 1
# calculate score
score = f1_score(testy, yhat, pos_label=-1)
print('F1 Score: %.3f' % score)
运行该示例以无监督的方式在训练数据集上拟合椭圆包络模型,然后将测试集中的示例分类为正常值和异常值并对结果进行评分。
注意:由于算法或评估程序的随机性,或数值精度的差异,您的[结果可能会有所不同]。考虑运行示例几次并比较平均结果。
在这种情况下,F1 分数达到 0.157。
F1 Score: 0.157
局部异常因素
识别异常值的一个简单方法是找到那些远离特征空间中其他示例的示例。
这对于低维数(少量特征)的特征空间非常有效,但随着特征数量的增加,它的可靠性会降低,这被称为维数灾难。
局部离群因子(简称 LOF)是一种尝试利用最近邻居的思想进行离群值检测的技术。根据每个示例的局部邻域大小,为其分配一个分数,以表明其孤立程度或离群值的可能性。得分最高的示例更有可能是离群值。
我们为数据集中的每个对象引入一个局部异常值(LOF),表示其异常程度。
— LOF:识别基于密度的局部异常值,2000 年。
scikit-learn 库在LocalOutlierFactor 类中提供了这种方法的实现。
该模型可以定义,并要求指示数据集中异常值的预期百分比,例如在我们的合成数据集的情况下为 0.01%。
...
# define outlier detection model
model = LocalOutlierFactor(contamination=0.01)
该模型不合适。相反,通过调用*fit_predict() ,使用“*正常”数据集作为识别新数据中异常值的基础。
要使用该模型识别测试数据集中的异常值,我们必须首先准备训练数据集,使其仅包含来自多数类的输入示例。
...
# get examples for just the majority class
trainX = trainX[trainy==0]
接下来,我们可以将这些示例与来自测试数据集的输入示例连接起来。
...
# create one large dataset
composite = vstack((trainX, testX))
*然后,我们可以通过调用fit_predict()*进行预测,并仅检索测试集中示例的标签。
...
# make prediction on composite dataset
yhat = model.fit_predict(composite)
# get just the predictions on the test set
yhat yhat[len(trainX):]
为了使事情变得简单,我们可以将其包装到一个名为*lof_predict()*的新函数中,如下所示。
# make a prediction with a lof model
def lof_predict(model, trainX, testX):# create one large datasetcomposite = vstack((trainX, testX))# make prediction on composite datasetyhat = model.fit_predict(composite)# return just the predictions on the test setreturn yhat[len(trainX):]
预测标签为 +1(表示正常)和 -1(表示异常值),与 scikit-learn 中的其他异常值检测算法一样。
综合起来,下面列出了使用 LOF 异常值检测算法对具有偏斜类分布的分类的完整示例。
# local outlier factor for imbalanced classification
from numpy import vstack
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
from sklearn.neighbors import LocalOutlierFactor# make a prediction with a lof model
def lof_predict(model, trainX, testX):# create one large datasetcomposite = vstack((trainX, testX))# make prediction on composite datasetyhat = model.fit_predict(composite)# return just the predictions on the test setreturn yhat[len(trainX):]# generate dataset
X, y = make_classification(n_samples=10000, n_features=2, n_redundant=0,n_clusters_per_class=1, weights=[0.999], flip_y=0, random_state=4)
# split into train/test sets
trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.5, random_state=2, stratify=y)
# define outlier detection model
model = LocalOutlierFactor(contamination=0.01)
# get examples for just the majority class
trainX = trainX[trainy==0]
# detect outliers in the test set
yhat = lof_predict(model, trainX, testX)
# mark inliers 1, outliers -1
testy[testy == 1] = -1
testy[testy == 0] = 1
# calculate score
score = f1_score(testy, yhat, pos_label=-1)
print('F1 Score: %.3f' % score)
运行示例以无监督的方式使用局部异常值因子模型和训练数据集将测试集中的示例分类为正常值和异常值,然后对结果进行评分。
注意:由于算法或评估程序的随机性,或数值精度的差异,您的[结果可能会有所不同]。考虑运行示例几次并比较平均结果。
在这种情况下,F1 分数达到 0.138。
F1 Score: 0.138