Machine Learning Mastery 不平衡数据教程(二)
用于不平衡分类的成本敏感 SVM
最后更新于 2020 年 8 月 21 日
支持向量机算法对于平衡分类是有效的,尽管它在不平衡数据集上表现不佳。
SVM 算法找到了一个超平面决策边界,可以最好地将示例分为两类。通过使用允许某些点被错误分类的边距,分割变得柔和。默认情况下,该余量有利于不平衡数据集上的多数类,尽管它可以更新以考虑每个类的重要性,并显著提高算法在具有偏斜类分布的数据集上的表现。
SVM 的这种将边际与阶级重要性成正比的修正通常被称为加权 SVM,或成本敏感型 SVM。
在本教程中,您将发现用于不平衡分类的加权支持向量机。
完成本教程后,您将知道:
- 标准支持向量机算法对不平衡分类的限制。
- 如何修改支持向量机算法以在训练过程中根据类的重要性来加权边际惩罚。
- 如何为 SVM 配置类别权重,以及如何网格搜索不同的类别权重配置。
用我的新书Python 不平衡分类启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
如何实现加权支持向量机不平衡分类 图片由 Bas Leenders 提供,保留部分权利。
教程概述
本教程分为四个部分;它们是:
- 不平衡类别数据集
- 不平衡分类 SVM
- 用 Scikit 学习加权 SVM
- 网格搜索加权 SVM
不平衡类别数据集
在我们深入研究 SVM 对不平衡分类的修改之前,让我们首先定义一个不平衡类别数据集。
我们可以使用 make_classification()函数定义一个合成的不平衡两类类别数据集。我们将生成 10,000 个少数与多数类比例大约为 1:100 的示例。
...
# define dataset
X, y = make_classification(n_samples=10000, n_features=2, n_redundant=0,
n_clusters_per_class=1, weights=[0.99], 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()
将这些联系在一起,下面列出了生成合成数据集和绘制示例的完整示例。
# 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.99], 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()
运行该示例首先创建数据集并总结类分布。
我们可以看到,数据集具有大约 1:100 的类分布,多数类中的示例不到 10,000 个,少数类中的示例不到 100 个。
Counter({0: 9900, 1: 100})
接下来,创建数据集的散点图,显示多数类的大量示例(蓝色)和少数类的少量示例(橙色),并有一些适度的类重叠。
1-100 类不平衡的二进制类别数据集散点图
接下来,我们可以在数据集上拟合标准 SVM 模型。
可以使用 Sklearn 库中的 SVC 类来定义 SVM。
...
# define model
model = SVC(gamma='scale')
我们将使用重复交叉验证来评估模型,重复三次 10 倍交叉验证。模式表现将使用重复和所有折叠的平均曲线下面积(ROC AUC) 来报告。
...
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# evaluate model
scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1)
# summarize performance
print('Mean ROC AUC: %.3f' % mean(scores))
将这些联系在一起,下面列出了定义和评估关于不平衡分类问题的标准 SVM 模型的完整示例。
支持向量机是二进制分类任务的有效模型,尽管默认情况下,它们在不平衡分类方面无效。
# fit a svm on an imbalanced classification dataset
from numpy import mean
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.svm import SVC
# generate dataset
X, y = make_classification(n_samples=10000, n_features=2, n_redundant=0,
n_clusters_per_class=1, weights=[0.99], flip_y=0, random_state=4)
# define model
model = SVC(gamma='scale')
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# evaluate model
scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1)
# summarize performance
print('Mean ROC AUC: %.3f' % mean(scores))
运行该示例在不平衡数据集上评估标准 SVM 模型,并报告平均 ROC AUC。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
我们可以看到模型有技巧,实现了 0.5 以上的 ROC AUC,在这种情况下实现了 0.804 的平均得分。
Mean ROC AUC: 0.804
这为对标准 SVM 算法进行的任何修改提供了比较基准。
不平衡分类 SVM
支持向量机,简称 SVMs,是一种有效的非线性机器学习算法。
SVM 训练算法寻找一条线或超平面来最好地划分类别。超平面由最大化决策边界和来自两个类中的每一个的最接近的例子之间的距离的边界来定义。
粗略地说,余量是分类边界和最近的训练集点之间的距离。
—第 343-344 页,应用预测建模,2013 年。
可以使用核来变换数据,以允许定义线性超平面来分离变换特征空间中的类,该变换特征空间对应于原始特征空间中的非线性类边界。常见的核变换包括线性、多项式和径向基函数变换。这种数据转换被称为“内核技巧”
通常,类是不可分离的,即使有数据转换。因此,余量被软化,以允许一些点出现在决策边界的错误侧。保证金的这种软化由被称为软保证金参数的正则化超参数λ或大写字母-C(“C”)控制。
……其中 C 代表正则化参数,该参数控制最大化类间分离裕度和最小化错误分类实例数量之间的权衡。
—第 125 页,从不平衡数据集中学习,2018。
C 值表示硬边界,不允许违反边界。小正值允许一些违规,而大的整数值,如 1、10 和 100,则允许更软的余量。
… [C]决定了我们所能容忍的违反边界(和超平面)的数量和严重程度。我们可以把 C 看作是 n 个观测值可能违反的差额的预算。
—第 347 页,统计学习导论:在 R 中的应用,2013。
尽管有效,但当类分布严重倾斜时,支持向量机的表现很差。因此,该算法有许多扩展,以使其在不平衡数据集上更有效。
尽管支持向量机经常为平衡数据集产生有效的解决方案,但它们对数据集的不平衡很敏感,并产生次优模型。
—第 86 页,不平衡学习:基础、算法和应用,2013。
C 参数在模型拟合期间用作惩罚,特别是寻找决策边界。默认情况下,每个类都有相同的权重,这意味着边距的柔和度是对称的。
考虑到多数群体比少数群体有更多的例子,这意味着软边际和决策边界将有利于多数群体。
…[该]学习算法将有利于多数类,因为专注于它将导致分类误差和利润最大化之间更好的权衡。这将以少数类为代价,尤其是当不平衡率较高时,因为忽略少数类会导致更好的优化结果。
—第 126 页,从不平衡数据集中学习,2018。
对于不平衡分类,SVM 最简单和最常见的扩展可能是根据每个类的重要性按比例加权 C 值。
为了适应支持向量机中的这些因素,提出了实例级加权修改。[……]权重值可以根据类之间的不平衡比率或单个实例的复杂性因素给出。
—第 130 页,从不平衡数据集中学习,2018。
具体而言,训练数据集中的每个示例都有其自己的惩罚项( C 值),用于拟合 SVM 模型时计算余量。示例的 C 值可以作为全局 C- 值的权重来计算,其中权重定义为与类别分布成比例。
- 重量=重量*重量
少数类可以使用较大的权重,使边距更软,而多数类可以使用较小的权重,使边距更硬,并防止错误分类的示例。
- 小权重:较小的 C 值,对误分类的例子处罚较大。
- 较大的权重:较大的 C 值,对于错误分类的例子惩罚较小。
这样做的效果是鼓励差额以较小的灵活性包含多数类,但允许少数类具有灵活性,如果需要,可以将多数类示例错误分类到少数类一侧。
也就是说,改进的 SVM 算法不会倾向于将分离超平面向少数类示例倾斜以减少总的误分类,因为少数类示例现在被分配了更高的误分类成本。
—第 89 页,不平衡学习:基础、算法和应用,2013。
SVM 的这种修改可以称为加权支持向量机(SVM),或者更一般地称为类加权 SVM、实例加权 SVM 或成本敏感 SVM。
基本思想是给不同的数据点分配不同的权重,使得 WSVM 训练算法根据数据点在训练数据集中的相对重要性来学习决策面。
——一种用于数据分类的加权支持向量机,2007。
用 Scikit 学习加权 SVM
Sklearn Python 机器学习库提供了支持类加权的 SVM 算法的实现。
线性支持向量机和支持向量机类提供了可指定为模型超参数的类权重参数。类权重是一个字典,它定义了每个类标签(例如 0 和 1)以及在计算软余量时应用于 C 值的权重。
例如,每个类别 0 和 1 的 1 比 1 权重可以定义如下:
...
# define model
weights = {0:1.0, 1:1.0}
model = SVC(gamma='scale', class_weight=weights)
类别称重可以多种方式定义;例如:
- 领域专长,通过与主题专家交谈确定。
- 调谐,由超参数搜索如网格搜索确定。
- 启发式,使用一般最佳实践指定。
使用类别权重的最佳实践是使用训练数据集中类别分布的倒数。
例如,测试数据集的类分布是少数类与多数类的比例为 1:100。该比率的倒数可以用于多数类的 1 和少数类的 100;例如:
...
# define model
weights = {0:1.0, 1:100.0}
model = SVC(gamma='scale', class_weight=weights)
我们也可以使用分数来定义相同的比率,并获得相同的结果;例如:
...
# define model
weights = {0:0.01, 1:1.0}
model = SVC(gamma='scale', class_weight=weights)
通过将类权重设置为“平衡,可以直接使用该启发式算法。例如:
...
# define model
model = SVC(gamma='scale', class_weight='balanced')
我们可以使用上一节中定义的相同评估过程,使用类权重来评估 SVM 算法。
我们希望等级加权版的 SVM 比没有任何等级加权的标准版的 SVM 表现得更好。
下面列出了完整的示例。
# svm with class weight on an imbalanced classification dataset
from numpy import mean
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.svm import SVC
# generate dataset
X, y = make_classification(n_samples=10000, n_features=2, n_redundant=0,
n_clusters_per_class=1, weights=[0.99], flip_y=0, random_state=4)
# define model
model = SVC(gamma='scale', class_weight='balanced')
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# evaluate model
scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1)
# summarize performance
print('Mean ROC AUC: %.3f' % mean(scores))
运行该示例准备合成不平衡类别数据集,然后使用重复交叉验证评估 SVM 算法的类加权版本。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,报告的平均 ROC AUC 分数比 SVM 算法的未加权版本显示更好的分数,0.964 比 0.804。
Mean ROC AUC: 0.964
网格搜索加权 SVM
使用与训练数据成反比的类权重只是一种启发。
使用不同的类权重可以获得更好的表现,这也将取决于用于评估模型的表现度量的选择。
在这一部分,我们将网格搜索一系列不同的类别权重的加权 SVM,并发现哪一个导致最好的 ROC AUC 分数。
我们将对类别 0 和 1 尝试以下权重:
- 0 级:100,1 级:1
- 0: 10 班,1: 1 班
- 0: 1 级,1: 1 级
- 0: 1 班,1: 10 班
- 0: 1 班,1: 100 班
这些可以定义为 GridSearchCV 类的网格搜索参数,如下所示:
...
# define grid
balance = [{0:100,1:1}, {0:10,1:1}, {0:1,1:1}, {0:1,1:10}, {0:1,1:100}]
param_grid = dict(class_weight=balance)
我们可以使用重复交叉验证对这些参数执行网格搜索,并使用 ROC AUC 估计模型表现:
...
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# define grid search
grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=cv, scoring='roc_auc')
一旦执行,我们可以将最佳配置以及所有结果总结如下:
...
# report the best configuration
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
# report all configurations
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
将这些联系在一起,下面的示例在不平衡的数据集上为 SVM 算法搜索五个不同的类权重。
我们可能会认为启发式类加权是表现最好的配置。
# grid search class weights with svm for imbalance classification
from numpy import mean
from sklearn.datasets import make_classification
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.svm import SVC
# generate dataset
X, y = make_classification(n_samples=10000, n_features=2, n_redundant=0,
n_clusters_per_class=1, weights=[0.99], flip_y=0, random_state=4)
# define model
model = SVC(gamma='scale')
# define grid
balance = [{0:100,1:1}, {0:10,1:1}, {0:1,1:1}, {0:1,1:10}, {0:1,1:100}]
param_grid = dict(class_weight=balance)
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# define grid search
grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=cv, scoring='roc_auc')
# execute the grid search
grid_result = grid.fit(X, y)
# report the best configuration
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
# report all configurations
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
运行该示例使用重复的 k 倍交叉验证评估每个类别权重,并报告最佳配置和相关的平均 ROC AUC 分数。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到 1:100 的多数对少数类权重获得了最佳的平均 ROC 分数。这与一般启发式算法的配置相匹配。
探索更严格的类别权重,看看它们对平均 ROC AUC 评分的影响,可能会很有趣。
Best: 0.966189 using {'class_weight': {0: 1, 1: 100}}
0.745249 (0.129002) with: {'class_weight': {0: 100, 1: 1}}
0.748407 (0.128049) with: {'class_weight': {0: 10, 1: 1}}
0.803727 (0.103536) with: {'class_weight': {0: 1, 1: 1}}
0.932620 (0.059869) with: {'class_weight': {0: 1, 1: 10}}
0.966189 (0.036310) with: {'class_weight': {0: 1, 1: 100}}
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
报纸
- 控制支持向量机的灵敏度,1999。
- 数据分类的加权支持向量机,2005。
- 一种用于数据分类的加权支持向量机,2007。
- 成本敏感支持向量机,2012。
书
- 统计学习导论:在 R 中的应用,2013。
- 应用预测建模,2013。
- 从不平衡数据集中学习,2018。
- 不平衡学习:基础、算法和应用,2013。
蜜蜂
- sklearn . utils . class _ weight . compute _ class _ weight API。
- 硬化. svm.SVC API 。
- 硬化. svm.LinearSVC API 。
- sklearn.model_selection。GridSearchCV API 。
文章
摘要
在本教程中,您发现了用于不平衡分类的加权支持向量机。
具体来说,您了解到:
- 标准支持向量机算法对不平衡分类的限制。
- 如何修改支持向量机算法以在训练过程中根据类的重要性来加权边际惩罚。
- 如何为 SVM 配置类别权重,以及如何网格搜索不同的类别权重配置。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
如何为不平衡分类修复 K 折交叉验证
最后更新于 2020 年 7 月 31 日
模型评估包括使用可用的数据集来拟合模型,并在对未知示例进行预测时估计其表现。
这是一个具有挑战性的问题,因为用于拟合模型的训练数据集和用于评估模型的测试集都必须足够大,并且能够代表潜在的问题,以便对模型表现的最终估计不会过于乐观或悲观。
模型评估最常用的两种方法是训练/测试分割和 k-fold 交叉验证程序。总的来说,这两种方法都非常有效,尽管它们可能导致误导性结果,并且当用于具有严重类别不平衡的分类问题时,可能会失败。取而代之的是,这些技术必须被修改,以通过类别标签对采样进行分层,这被称为分层的列车测试分割或分层的 k 倍交叉验证。
在本教程中,您将发现如何在不平衡的数据集上评估分类器模型。
完成本教程后,您将知道:
- 使用训练/测试分割和交叉验证在数据集上评估分类器的挑战。
- 当在不平衡的数据集上评估分类器时,k-fold 交叉验证和训练-测试分割的简单应用将如何失败。
- 如何使用修改后的 k-fold 交叉验证和训练测试分割来保留数据集中的类分布。
用我的新书Python 不平衡分类启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
如何使用 k-Fold 交叉验证进行不平衡分类 图片由 Bonnie Moreland 提供,保留部分权利。
教程概述
本教程分为三个部分;它们是:
- 评估分类广告的挑战
- k 折叠交叉验证失败
- 修复不平衡分类的交叉验证
评估分类广告的挑战
评估一个分类模型是具有挑战性的,因为在使用之前我们不会知道一个模型有多好。
相反,我们必须使用已经有目标或结果的可用数据来评估模型的表现。
模型评估不仅仅是评估一个模型;它包括测试不同的数据准备方案、不同的学习算法,以及针对表现良好的学习算法的不同超参数。
- 模型=数据准备+学习算法+超参数
理想情况下,可以选择和使用具有最佳分数(使用您选择的度量)的模型构建过程(数据准备、学习算法和超参数)。
最简单的模型评估过程是将数据集分成两部分,一部分用于训练模型,另一部分用于测试模型。因此,数据集的各个部分分别以它们的函数、训练集和测试集来命名。
如果您收集的数据集非常大并且代表问题,这将非常有效。所需示例的数量因问题而异,但可能需要数千、数十万或数百万个示例才足够。
训练和测试的 50/50 分割将是理想的,尽管更偏斜的分割是常见的,例如训练和测试集的 67/33 或 80/20。
我们很少有足够的数据来使用模型的训练/测试分割评估来获得表现的无偏估计。相反,我们的数据集通常比首选数据集小得多,因此必须在该数据集上使用重采样策略。
分类器最常用的模型评估方案是 10 重交叉验证程序。
k 折叠交叉验证程序包括将训练数据集分割成 k 折叠。第一个 k-1 折叠用于训练模型,保持的第 k 折叠用作测试集。重复此过程,每个折叠都有机会用作保持测试集。对总共 k 个模型进行拟合和评估,并将模型的表现计算为这些运行的平均值。
事实证明,与单一训练/测试分割相比,该过程对小训练数据集上的模型表现给出了不太乐观的估计。 k=10 的值已被证明对各种数据集大小和模型类型都有效。
k 折叠交叉验证失败
可悲的是,k-fold 交叉验证不适合评估不平衡的分类器。
10 倍交叉验证,特别是机器学习中最常用的误差估计方法,可以很容易地在类不平衡的情况下分解,即使偏斜没有之前考虑的那么极端。
—第 188 页,不平衡学习:基础、算法和应用,2013。
原因是数据被拆分成k-折叠,概率分布均匀。
这对于具有平衡的类分布的数据可能工作得很好,但当分布严重倾斜时,一个或多个折叠可能很少或没有来自少数类的示例。这意味着一些或可能许多模型评估将具有误导性,因为模型只需要正确预测多数类。
我们可以用一个例子来具体说明。
首先,我们可以定义一个少数与多数类分布比为 1:100 的数据集。
这可以通过使用 make_classification()函数创建合成数据集来实现,指定示例数(1,000)、类数(2)和每个类的权重(99%和 1%)。
# generate 2 class dataset
X, y = make_classification(n_samples=1000, n_classes=2, weights=[0.99, 0.01], flip_y=0, random_state=1)
下面的示例生成了合成二进制类别数据集,并总结了类别分布。
# create a binary classification dataset
from numpy import unique
from sklearn.datasets import make_classification
# generate 2 class dataset
X, y = make_classification(n_samples=1000, n_classes=2, weights=[0.99, 0.01], flip_y=0, random_state=1)
# summarize dataset
classes = unique(y)
total = len(y)
for c in classes:
n_examples = len(y[y==c])
percent = n_examples / total * 100
print('> Class=%d : %d/%d (%.1f%%)' % (c, n_examples, total, percent))
运行该示例会创建数据集,并总结每个类中的示例数量。
通过设置 random_state 参数,它确保我们每次运行代码时都能得到相同的随机生成的例子。
> Class=0 : 990/1000 (99.0%)
> Class=1 : 10/1000 (1.0%)
总共 10 个例子在少数民族阶层并不多。如果我们使用 10 倍,在理想情况下,我们会在每个折叠中得到一个例子,这不足以训练一个模型。为了演示,我们将使用 5 倍。
在理想情况下,我们将在每个折叠中有 10/5 或两个示例,这意味着在训练数据集中有 42 (8)个折叠的示例,在给定的测试数据集中有 12 个折叠(2)。
首先,我们将使用 KFold 类将数据集随机拆分为 5 倍,并检查每个训练和测试集的组成。下面列出了完整的示例。
# example of k-fold cross-validation with an imbalanced dataset
from sklearn.datasets import make_classification
from sklearn.model_selection import KFold
# generate 2 class dataset
X, y = make_classification(n_samples=1000, n_classes=2, weights=[0.99, 0.01], flip_y=0, random_state=1)
kfold = KFold(n_splits=5, shuffle=True, random_state=1)
# enumerate the splits and summarize the distributions
for train_ix, test_ix in kfold.split(X):
# select rows
train_X, test_X = X[train_ix], X[test_ix]
train_y, test_y = y[train_ix], y[test_ix]
# summarize train and test composition
train_0, train_1 = len(train_y[train_y==0]), len(train_y[train_y==1])
test_0, test_1 = len(test_y[test_y==0]), len(test_y[test_y==1])
print('>Train: 0=%d, 1=%d, Test: 0=%d, 1=%d' % (train_0, train_1, test_0, test_1))
运行该示例会创建相同的数据集,并枚举数据的每个分割,显示训练集和测试集的类分布。
我们可以看到,在这种情况下,有一些分裂对训练集和测试集有预期的 8/2 分裂,还有一些分裂要差得多,比如 6/4(乐观)和 10/0(悲观)。
在这些数据分割的基础上评估一个模型不会给出一个可靠的表现估计。
>Train: 0=791, 1=9, Test: 0=199, 1=1
>Train: 0=793, 1=7, Test: 0=197, 1=3
>Train: 0=794, 1=6, Test: 0=196, 1=4
>Train: 0=790, 1=10, Test: 0=200, 1=0
>Train: 0=792, 1=8, Test: 0=198, 1=2
如果我们使用简单的数据集训练/测试分割,我们可以证明存在类似的问题,尽管问题没有那么严重。
我们可以使用 train_test_split()函数来创建数据集的 50/50 分割,平均来说,如果我们多次执行这种分割,每个数据集中会出现少数民族类的五个示例。
下面列出了完整的示例。
# example of train/test split with an imbalanced dataset
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
# generate 2 class dataset
X, y = make_classification(n_samples=1000, n_classes=2, weights=[0.99, 0.01], flip_y=0, random_state=1)
# split into train/test sets with same class ratio
trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.5, random_state=2)
# summarize
train_0, train_1 = len(trainy[trainy==0]), len(trainy[trainy==1])
test_0, test_1 = len(testy[testy==0]), len(testy[testy==1])
print('>Train: 0=%d, 1=%d, Test: 0=%d, 1=%d' % (train_0, train_1, test_0, test_1))
运行该示例会创建与之前相同的数据集,并将其拆分为随机训练和测试拆分。
在这种情况下,我们可以看到训练集中只有三个少数民族类的例子,测试集中有七个。
以这种方式评估模型不会给他们提供足够多的可以学习的例子,太多的可以评估的例子,并且很可能会给出很差的表现。你可以想象,如果随机吐槽更严重,情况会变得更糟。
>Train: 0=497, 1=3, Test: 0=493, 1=7
修复不平衡分类的交叉验证
解决方案是在使用 k 倍交叉验证或训练测试分割时,不要随机分割数据。
具体来说,我们可以随机分割数据集,尽管在每个子集中保持相同的类分布。这被称为分层或分层采样,目标变量( y )类用于控制采样过程。
例如,我们可以使用 k-fold 交叉验证的一个版本,它保留了每个 fold 中不平衡的类分布。它被称为分层 K 折交叉验证,并将在数据的每个分割中强制类别分布,以匹配完整训练数据集中的分布。
……通常情况下,特别是在阶级不平衡的情况下,使用分层的 10 倍交叉验证,这确保在所有折叠中尊重原始分布中发现的阳性与阴性样本的比例。
—第 205 页,不平衡学习:基础、算法和应用,2013。
我们可以用一个例子来具体说明。
我们可以使用StrateFiedkfold 类对拆分进行分层,正如其名称所示,该类支持分层的 k 倍交叉验证。
下面是相同的数据集和相同的交叉验证分层版本的示例。
# example of stratified k-fold cross-validation with an imbalanced dataset
from sklearn.datasets import make_classification
from sklearn.model_selection import StratifiedKFold
# generate 2 class dataset
X, y = make_classification(n_samples=1000, n_classes=2, weights=[0.99, 0.01], flip_y=0, random_state=1)
kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=1)
# enumerate the splits and summarize the distributions
for train_ix, test_ix in kfold.split(X, y):
# select rows
train_X, test_X = X[train_ix], X[test_ix]
train_y, test_y = y[train_ix], y[test_ix]
# summarize train and test composition
train_0, train_1 = len(train_y[train_y==0]), len(train_y[train_y==1])
test_0, test_1 = len(test_y[test_y==0]), len(test_y[test_y==1])
print('>Train: 0=%d, 1=%d, Test: 0=%d, 1=%d' % (train_0, train_1, test_0, test_1))
运行该示例会像以前一样生成数据集,并总结每次拆分的训练集和测试集的类分布。
在这种情况下,我们可以看到每个分割都与我们在理想情况下的预期相匹配。
少数类中的每个示例都有一次机会在测试集中使用,并且每个数据分割的每个训练和测试集都有相同的类分布。
>Train: 0=792, 1=8, Test: 0=198, 1=2
>Train: 0=792, 1=8, Test: 0=198, 1=2
>Train: 0=792, 1=8, Test: 0=198, 1=2
>Train: 0=792, 1=8, Test: 0=198, 1=2
>Train: 0=792, 1=8, Test: 0=198, 1=2
这个例子强调了需要首先为 k 选择一个值k-折叠交叉验证,以确保在训练和测试集中有足够数量的例子来拟合和评估模型(测试集中少数类的两个例子对于测试集来说可能太少)。
它还强调了使用分层 k 的要求——使用不平衡数据集进行多重交叉验证,以便为给定模型的每次评估保留训练和测试集中的类分布。
我们也可以使用分层版本的列车/测试分割。
这可以通过在调用 train_test_split() 时设置“分层参数,并将其设置为包含数据集目标变量的“ y ”变量来实现。由此,该函数将确定所需的类别分布,并确保列车和测试集都具有该分布。
我们可以用下面列出的一个工作示例来演示这一点。
# example of stratified train/test split with an imbalanced dataset
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
# generate 2 class dataset
X, y = make_classification(n_samples=1000, n_classes=2, weights=[0.99, 0.01], flip_y=0, random_state=1)
# split into train/test sets with same class ratio
trainX, testX, trainy, testy = train_test_split(X, y, test_size=0.5, random_state=2, stratify=y)
# summarize
train_0, train_1 = len(trainy[trainy==0]), len(trainy[trainy==1])
test_0, test_1 = len(testy[testy==0]), len(testy[testy==1])
print('>Train: 0=%d, 1=%d, Test: 0=%d, 1=%d' % (train_0, train_1, test_0, test_1))
运行该示例会将数据集随机拆分为训练集和测试集,从而确保类分布得到保留,在这种情况下,每个数据集中会留下五个示例。
>Train: 0=495, 1=5, Test: 0=495, 1=5
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
教程
书
- 不平衡学习:基础、算法和应用,2013。
应用程序接口
- sklearn.model_selection。KFold API 。
- sklearn.model_selection。stratifedkfold API。
- sklearn . model _ selection . train _ test _ split API。
摘要
在本教程中,您发现了如何在不平衡的数据集上评估分类器模型。
具体来说,您了解到:
- 使用训练/测试分割和交叉验证在数据集上评估分类器的挑战。
- 当在不平衡的数据集上评估分类器时,k-fold 交叉验证和训练-测试分割的简单应用将如何失败。
- 如何使用修改后的 k-fold 交叉验证和训练测试分割来保留数据集中的类分布。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
不平衡类别的数据采样方法之旅
机器学习技术在具有不平衡类分布的类别数据集上经常失败或给出令人误解的乐观表现。
原因是,许多机器学习算法被设计成对类别数据进行操作,每个类具有相等数量的观察值。当情况并非如此时,算法可以了解到,为了获得良好的表现,极少数示例并不重要,可以忽略。
数据采样提供了一组转换训练数据集的技术,以便平衡或更好地平衡类分布。一旦平衡,标准的机器学习算法可以直接在转换后的数据集上训练,而无需任何修改。这允许用数据准备方法解决不平衡分类的挑战,即使是严重不平衡的类分布。
有许多不同类型的数据采样方法可以使用,并且没有单一的最佳方法可以用于所有分类问题和所有分类模型。像选择预测模型一样,需要仔细的实验来发现什么最适合你的项目。
在本教程中,您将发现一套数据采样技术,可用于平衡不平衡的类别数据集。
完成本教程后,您将知道:
- 不平衡类别数据集下机器学习的挑战。
- 使用数据采样技术平衡偏斜的类分布。
- 过采样、欠采样和方法组合的数据采样方法。
用我的新书Python 不平衡分类启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
不平衡分类的数据重采样方法之旅。新西兰,保留部分权利。
教程概述
本教程分为三个部分;它们是:
- 一个不平衡的类分布问题
- 用数据采样平衡类分布
- 流行数据采样方法巡礼
- 过采样技术
- 欠采样技术
- 技术的结合
一个不平衡的类分布问题
不平衡分类涉及类别分布不相等的数据集。
这意味着训练数据集中属于每一类的例子的数量是不同的,通常很大。在类分布中有严重的偏差并不罕见,例如少数类中的例子与多数类中的例子之比为 1:10、1:1000 甚至 1:1000。
……我们将不平衡学习定义为具有严重数据分布偏差的数据表示和信息提取的学习过程,以开发有效的决策边界来支持决策过程。
—第 1 页,不平衡学习:基础、算法和应用,2013。
虽然经常用两类分类问题来描述,但类不平衡也会影响那些有两个以上类的数据集,这些数据集可能有多个少数类或多个多数类。
不平衡类别数据集的一个主要问题是标准机器学习算法在这些数据集上表现不佳。许多机器学习算法依赖于训练数据集中的类分布来衡量当模型将被用于进行预测时观察每个类中的例子的可能性。
因此,许多机器学习算法,如决策树、k 近邻和神经网络,将因此了解到少数类不如多数类重要,并将更多注意力放在多数类上,并在多数类上表现更好。
不平衡数据集的问题在于,标准的分类学习算法通常偏向多数类(称为“负类”),因此在少数类实例(称为“正”类)中有较高的错误分类率。
—第 79 页,从不平衡数据集中学习,2018。
这是一个问题,因为少数类正是不平衡分类问题中我们最关心的类。
这是因为多数类通常反映正常情况,而少数类代表诊断、错误、欺诈或其他类型异常情况的阳性情况。
用数据采样平衡类分布
不平衡分类问题最流行的解决方案是改变训练数据集的组成。
当我们对现有数据样本进行采样时,设计用于改变训练数据集中的类分布的技术通常被称为采样方法或重采样方法。
采样方法似乎是社区中占主导地位的方法,因为它们以直接的方式处理不平衡的学习。
—第 3 页,不平衡学习:基础、算法和应用,2013。
采样方法如此常见的原因是因为它们易于理解和实现,并且因为一旦应用于转换训练数据集,就可以直接使用一套标准的机器学习算法。
这意味着,为平衡(或大部分平衡)分类而开发的数十或数百种机器学习算法中的任何一种都可以适合训练数据集,而无需对它们进行任何修改来适应观察中的不平衡。
基本上,我们可以尝试平衡班级频率,而不是让模型处理不平衡。采用这种方法消除了困扰模型训练的根本不平衡问题。
—第 427 页,应用预测建模,2013 年。
像朴素贝叶斯分类器这样的机器学习算法从训练数据集中学习观察每个类的例子的可能性。通过将这些模型拟合到一个样本训练数据集中,使之具有一个人为的更均匀的类分布,它允许他们学习一个更少偏差的先验概率,而不是关注每个输入变量的细节(或证据)来区分类。
一些模型使用先验概率,例如朴素贝叶斯和判别分析分类器。除非手动指定,否则这些模型通常从训练数据中导出先验值。使用更平衡的先验或平衡的训练集可能有助于处理班级不平衡。
—第 426 页,应用预测建模,2013 年。
采样仅在训练数据集上执行,该数据集由算法用来学习模型。它不会在保持测试或验证数据集上执行。原因是这样做的目的不是为了消除模型拟合中的类偏差,而是为了继续在真实的和代表目标问题领域的数据上评估结果模型。
因此,我们可以将数据采样方法视为解决训练数据集中相对类不平衡的问题,而忽略问题域中不平衡的根本原因。少数群体中所谓的相对罕见和绝对罕见之间的区别。
采样方法是处理不平衡数据的一种非常流行的方法。这些方法主要用于解决相对罕见的问题,但不能解决绝对罕见的问题。
—第 29 页,不平衡学习:基础、算法和应用,2013。
在经过转换的数据集上评估模型,并删除或合成示例,可能会对表现提供一个误导性的、或许是乐观的估计。
训练数据集中使用的数据采样有两种主要类型:过采样和欠采样。在下一节中,我们将浏览每种类型的流行方法,以及结合多种方法的方法。
流行数据采样方法巡礼
有数十种(如果不是数百种)数据采样方法可供选择,以便调整训练数据集的类分布。
没有最好的数据采样方法,就像没有最好的机器学习算法一样。这些方法根据学习算法的选择以及训练数据集的密度和组成而有所不同。
……在许多情况下,采样可以减轻不平衡造成的问题,但在各种方法中没有明显的赢家。此外,许多建模技术对采样的反应不同,这进一步复杂化了使用哪个过程的简单指南的想法
—第 429 页,应用预测建模,2013 年。
因此,仔细设计实验来测试和评估一套不同的方法和一些方法的不同配置是很重要的,以便发现什么最适合您的特定项目。
虽然有许多技术可供选择,但平均来说,可能有十几种更受欢迎,也可能更成功。在本节中,我们将对这些方法进行介绍,这些方法分为过采样、欠采样和组合方法的粗略分类。
该领域的代表性工作包括随机过采样、随机欠采样、带有数据生成的合成采样、基于聚类的采样方法以及采样和增强的集成。
—第 3 页,不平衡学习:基础、算法和应用,2013。
以下部分回顾了一些更流行的方法,在二进制(两类)分类问题的上下文中描述,这是一种常见的做法,尽管大多数可以直接使用或适用于两类以上的不平衡分类。
这里的列表主要基于 Sklearn 友好库中可用的方法,称为不平衡学习。有关数据采样方法的详细列表,请参见 2018 年出版的《从不平衡数据集中学习一书中的第五章数据级预处理方法》
你最喜欢的数据采样技术是什么? 我是不是错过了好方法? 在下面的评论里告诉我。
过采样技术
过采样方法复制少数类的例子,或者从少数类的例子中合成新的例子。
一些更广泛使用和实现的过采样方法包括:
- 随机过采样
- 合成少数过采样技术
- 边界线-SMOTE
- SVM 边界过采样
- 自适应合成采样
让我们仔细看看这些方法。
最简单的过采样方法包括从训练数据集中的少数类中随机复制例子,称为随机过采样。
最流行也可能是最成功的过采样方法是SMOTE;这是合成少数过采样技术的缩写。
SMOTE 的工作方式是选择特征空间中靠近的示例,在特征空间中的示例之间绘制一条线,并沿着该线绘制一个新样本作为点。
SMOTE 方法有许多扩展,旨在对合成的多数类中的示例类型更具选择性。
边界线-SMOTE 包括选择那些被错误分类的少数民族类的实例,例如使用k-最近邻分类模型,并且仅生成难以分类的合成样本*。*
*边界过采样是 SMOTE 的扩展,它将 SVM 拟合到数据集,并使用由支持向量定义的决策边界作为生成合成示例的基础,同样基于决策边界是需要更多少数示例的区域的思想。
自适应合成采样 (ADASYN)是 SMOTE 的另一个扩展,它生成的合成样本与少数类中示例的密度成反比。它被设计成在特征空间中少数示例密度低的区域中创建合成示例,而在密度高的区域中创建较少或没有合成示例。
欠采样技术
欠采样方法从多数类中删除或选择示例的子集。
一些更广泛使用和实现的欠采样方法包括:
- 随机欠采样
- 压缩最近邻规则
- 差点采样不足
- Tomek 链接欠采样
- 编辑最近邻居规则(ENN)
- 单边选择
- 邻里清洁规则(NCR)
让我们仔细看看这些方法。
最简单的欠采样方法包括从训练数据集中的多数类中随机删除示例,称为随机欠采样。
一组技术包括在多数类中选择一个健壮且有代表性的例子子集。
压缩最近邻规则,简称 CNN,是为了减少 k 最近邻算法所需的内存而设计的。它的工作原理是枚举数据集中的示例,并且仅当无法通过存储的当前内容对示例进行正确分类时,才将其添加到存储中,并且在少数类中的所有示例都添加到存储后,可以应用它来减少多数类中的示例数量。
未遂事件指的是使用 KNN 从多数类中选择例子的一系列方法。NearMiss-1 从多数类中选择与少数类中最接近的三个示例具有最小平均距离的示例。NearMiss-2 从多数类中选择与少数类最远的三个示例之间平均距离最小的示例。NearMiss-3 包括为少数类中最接近的每个示例选择给定数量的多数类示例。
另一组技术包括从多数类中选择要删除的示例。这些方法通常涉及识别那些难以分类的例子,因此增加了决策边界的模糊性。
也许最广为人知的删除欠采样方法被称为Tomek link,最初是作为压缩最近邻规则扩展的一部分开发的。Tomek Link 指的是训练数据集中的一对示例,这两个示例都是最近的邻居(在特征空间中具有最小距离),并且属于不同的类。Tomek Links 通常是沿着类边界发现的错误分类的示例,大多数类中的示例被删除。
编辑最近邻规则,简称 ENN,是另一种选择删除示例的方法。该规则包括使用 k=3 个最近邻来定位数据集中那些分类错误的示例,并将其删除。
ENN 过程可以在同一数据集上重复多次,从而更好地优化多数类中的示例选择。这种扩展最初被称为“无限制编辑”,尽管它通常被称为“重复编辑最近邻”。
停留在“选择保留”vs“选择删除”系列欠采样方法,也有结合这两种方法的欠采样方法。
单侧选择,简称 OSS,是一种结合了 Tomek Links 和压缩最近邻(CNN)规则的欠采样技术。Tomek Links 方法用于移除类边界上有噪声的示例,而 CNN 用于移除多数类密度内部的冗余示例。
邻域清理规则,简称 NCR,是另一种组合欠采样技术,它结合了压缩最近邻(CNN)规则来移除冗余示例,以及编辑最近邻(ENN)规则来移除有噪声或不明确的示例。
技术的结合
尽管在训练数据集上单独使用过采样或欠采样方法可能是有效的,但是实验表明,将这两种类型的技术一起应用通常可以在所得的变换数据集上获得模型拟合的更好的整体表现。
一些更广泛使用和实现的数据采样方法组合包括:
- SMOTE 和随机欠采样
- SMOTE 和 Tomek 链接
- 移动和编辑最近邻规则
让我们仔细看看这些方法。
SMOTE 可能是最流行和最广泛使用的过采样技术。因此,它通常与一系列不同的欠采样方法配对。
最简单的配对包括将 SMOTE 与随机欠采样相结合,在提出该方法的论文中,该方法的表现优于单独使用 SMOTE。
通常将 SMOTE 与从数据集中选择要删除的示例的欠采样方法配对,该过程在 SMOTE 之后应用于数据集,从而允许编辑步骤同时应用于少数和多数类。其目的是从两个类中去除沿着类边界的噪声点,这似乎具有更好的分类器表现适合变换数据集的效果。
两个流行的例子包括使用 SMOTE 后删除 Tomek 链接,以及 SMOTE 后删除那些通过 KNN 模型错误分类的例子,即所谓的编辑最近邻规则。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
报纸
- SMOTE:合成少数过采样技术,2011。
- 平衡机器学习训练数据的几种方法的行为研究,2004。
书
- 应用预测建模,2013。
- 从不平衡数据集中学习,2018。
- 不平衡学习:基础、算法和应用,2013。
文章
摘要
在本教程中,您发现了一套数据采样技术,可用于平衡不平衡的类别数据集。
具体来说,您了解到:
- 不平衡类别数据集下机器学习的挑战。
- 使用数据采样技术平衡偏斜的类分布。
- 过采样、欠采样和多种方法组合的流行数据采样方法。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。*
不平衡类别分布的分类准确率故障
最后更新于 2021 年 1 月 22 日
分类准确率是一种度量,它将分类模型的表现总结为正确预测的数量除以预测的总数。
它易于计算和直观理解,使其成为评估分类器模型最常用的指标。当实例在类中的分布严重偏斜时,这种直觉就会崩溃。
从业者在平衡数据集上开发的直觉,例如 99%代表熟练的模型,在不平衡分类预测建模问题上可能是不正确的和危险的误导。
在本教程中,您将发现不平衡分类问题的分类准确率故障。
完成本教程后,您将知道:
- 准确性和错误率是总结分类模型表现的事实上的标准度量。
- 由于从业者在具有相等类别分布的数据集上开发的直觉,分类准确率在具有偏斜类别分布的分类问题上失败。
- 用一个实例说明偏斜类分布的准确率故障的直觉。
用我的新书Python 不平衡分类启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
- 2020 年 1 月更新:针对 Sklearn v0.22 API 的变化进行了更新。
分类准确率对倾斜的类别分布有误导性 图片由Esqui-Ando con tnho提供,保留部分权利。
教程概述
本教程分为三个部分;它们是:
- 什么是分类准确率?
- 不平衡分类的准确性失败
- 不平衡分类的准确度示例
什么是分类准确率?
分类预测建模包括预测问题领域中给定示例的类别标签。
用于评估分类预测模型表现的最常见指标是分类准确率。通常,预测模型的准确性很好(高于 90%的准确性),因此,根据模型的错误率来总结模型的表现也是非常常见的。
准确性及其补充错误率是在分类问题中评估学习系统表现最常用的指标。
——不平衡分布下的预测建模综述,2015 年。
分类准确率首先使用分类模型对测试数据集中的每个示例进行预测。然后,将预测与测试集中那些示例的已知标签进行比较。准确度随后被计算为测试集中正确预测的示例的比例除以测试集中做出的所有预测。
- 准确性=正确预测/总预测
相反,错误率可以计算为在测试集上做出的不正确预测的总数除以在测试集上做出的所有预测。
- 错误率=错误预测/总预测
准确性和错误率是相辅相成的,这意味着我们总是可以从一个计算另一个。例如:
- 准确率= 1–误差率
- 误差率= 1–准确率
思考准确性的另一个有价值的方式是根据混淆矩阵。
混淆矩阵是分类模型所做预测的汇总,按类别组织成表格。表中的每一行都表示实际的类,每一列都表示预测的类。单元格中的值是对一个类所做的实际上是给定类的预测数的计数。对角线上的单元格表示正确的预测,其中预测的类和预期的类对齐。
评估分类器表现的最直接的方法是基于混淆矩阵分析。[……]从这样的矩阵中,可以提取出许多广泛使用的度量标准来衡量学习系统的表现,例如错误率[……]和准确性……
——平衡机器学习训练数据的几种方法的行为研究,2004。
混淆矩阵不仅可以更深入地了解预测模型的准确性,还可以了解哪些类别被正确预测,哪些被错误预测,以及出现了什么类型的错误。
最简单的混淆矩阵是针对两类分类问题,有负(0 类)和正(1 类)类。
在这种类型的混淆矩阵中,表中的每个单元格都有一个具体且易于理解的名称,总结如下:
| Positive Prediction | Negative Prediction
Positive Class | True Positive (TP) | False Negative (FN)
Negative Class | False Positive (FP) | True Negative (TN)
分类准确率可以从这个混淆矩阵中计算出来,即表格中正确单元格的总和(真阳性和真阴性)除以表格中的所有单元格。
- 准确率= (TP + TN) / (TP + FN + FP + TN)
同样,错误率也可以从混淆矩阵中计算出来,即表格中错误单元格的总和(假阳性和假阴性)除以表格中的所有单元格。
- 错误率= (FP + FN) / (TP + FN + FP + TN)
既然我们已经熟悉了分类准确率和它的补码错误率,让我们来发现为什么用它们来解决不平衡的分类问题可能是个坏主意。
不平衡分类的准确性失败
分类准确率是评估分类模型最常用的指标。
它被广泛使用的原因是因为它易于计算,易于解释,并且是一个单一的数字来概括模型的能力。
因此,在不平衡的分类问题上使用它是很自然的,在不平衡的分类问题上,训练数据集中的例子在类之间的分布是不相等的。
这是初学者对不平衡分类最常犯的错误。
当类别分布稍有偏差时,准确性仍然是一个有用的指标。当类分布中的偏差严重时,准确率可能成为模型表现的不可靠度量。
这种不可靠性的原因集中在一般的机器学习从业者和分类准确性的直觉上。
典型地,分类预测建模是用类分布相等或非常接近相等的小数据集来实践的。因此,大多数从业者会产生一种直觉,即大的准确度分数(或者相反,小的错误率分数)是好的,90%以上的值是好的。
达到 90%的分类准确率,甚至 99%的分类准确率,对于不平衡的分类问题来说可能是微不足道的。
这意味着,基于平衡类分布开发的分类准确率直觉将被应用,并且将是错误的,误导从业者认为模型具有良好甚至优秀的表现,而事实上,它没有。
准确性悖论
考虑 1:100 类不平衡的不平衡数据集的情况。
在这个问题中,少数类(类 1)的每个例子将有对应的多数类(类 0)的 100 个例子。
在这类问题中,多数类代表“正常”,少数类代表“异常”,如故障、诊断或欺诈。少数族裔的优秀表现将优先于两个阶层的优秀表现。
考虑到用户对少数(正)类示例的偏好,准确性是不合适的,因为与多数类相比,表示最少但更重要的示例的影响会降低。
——不平衡分布下的预测建模综述,2015 年。
在这个问题上,预测测试集中所有示例的多数类(类 0)的模型将具有 99%的分类准确率,反映了测试集中预期的主要和次要示例的平均分布。
许多机器学习模型是围绕平衡类分布的假设设计的,并且经常学习简单的规则(显式的或其他的),比如总是预测多数类,导致它们达到 99%的准确率,尽管在实践中表现不比不熟练的多数类分类器好。
初学者会看到一个复杂模型在这种类型的不平衡数据集上达到 99%的表现,并相信他们的工作已经完成,而事实上,他们已经被误导了。
这种情况如此普遍,以至于它有了一个名字,被称为“准确度悖论”
……在不平衡数据集的框架内,准确性不再是一个恰当的衡量标准,因为它不能区分不同类别的正确分类的例子的数量。因此,它可能导致错误的结论…
——阶级不平衡问题的集成综述:基于装袋、提升和混合的方法,2011。
严格来说,准确性确实报告了一个正确的结果;只有练习者对高准确度分数的直觉才是失败的点。通常使用替代度量来总结不平衡分类问题的模型表现,而不是纠正错误的直觉。
既然我们已经熟悉了分类可能会产生误导的观点,让我们来看一个成功的例子。
不平衡分类的准确度示例
虽然已经给出了为什么准确性对于不平衡分类是一个坏主意的解释,但它仍然是一个抽象的想法。
我们可以用一个工作示例来具体说明准确性的失败,并尝试反驳您可能已经开发的平衡类分布的任何准确性直觉,或者更有可能劝阻对不平衡数据集使用准确性。
首先,我们可以定义一个 1:100 类分布的合成数据集。
make_blobs() Sklearn 函数将始终创建类分布相等的合成数据集。
然而,我们可以使用这个函数来创建具有任意类分布的合成类别数据集,只需要几行额外的代码。类别分布可以定义为字典,其中关键字是类别值(例如 0 或 1),值是要包含在数据集中的随机生成的示例数。
下面这个名为 get_dataset() 的函数将采用一个类分布,并返回一个具有该类分布的合成数据集。
# create a dataset with a given class distribution
def get_dataset(proportions):
# determine the number of classes
n_classes = len(proportions)
# determine the number of examples to generate for each class
largest = max([v for k,v in proportions.items()])
n_samples = largest * n_classes
# create dataset
X, y = make_blobs(n_samples=n_samples, centers=n_classes, n_features=2, random_state=1, cluster_std=3)
# collect the examples
X_list, y_list = list(), list()
for k,v in proportions.items():
row_ix = where(y == k)[0]
selected = row_ix[:v]
X_list.append(X[selected, :])
y_list.append(y[selected])
return vstack(X_list), hstack(y_list)
该函数可以接受任意数量的类,尽管我们将把它用于简单的二进制分类问题。
接下来,我们可以使用上一节中的代码为已创建的数据集创建散点图,并将其放入助手函数中。下面是 plot_dataset() 函数,该函数将绘制数据集并显示一个图例,以指示颜色到类标签的映射。
# scatter plot of dataset, different color for each class
def plot_dataset(X, y):
# create scatter plot for samples from each class
n_classes = len(unique(y))
for class_value in range(n_classes):
# get row indexes for samples with this class
row_ix = where(y == class_value)[0]
# create scatter of these samples
pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(class_value))
# show a legend
pyplot.legend()
# show the plot
pyplot.show()
最后,我们可以测试这些新功能。
我们将定义一个 1:100 比率的数据集,少数类有 1000 个示例,多数类有 10000 个示例,并绘制结果。
下面列出了完整的示例。
# define an imbalanced dataset with a 1:100 class ratio
from numpy import unique
from numpy import hstack
from numpy import vstack
from numpy import where
from matplotlib import pyplot
from sklearn.datasets import make_blobs
# create a dataset with a given class distribution
def get_dataset(proportions):
# determine the number of classes
n_classes = len(proportions)
# determine the number of examples to generate for each class
largest = max([v for k,v in proportions.items()])
n_samples = largest * n_classes
# create dataset
X, y = make_blobs(n_samples=n_samples, centers=n_classes, n_features=2, random_state=1, cluster_std=3)
# collect the examples
X_list, y_list = list(), list()
for k,v in proportions.items():
row_ix = where(y == k)[0]
selected = row_ix[:v]
X_list.append(X[selected, :])
y_list.append(y[selected])
return vstack(X_list), hstack(y_list)
# scatter plot of dataset, different color for each class
def plot_dataset(X, y):
# create scatter plot for samples from each class
n_classes = len(unique(y))
for class_value in range(n_classes):
# get row indexes for samples with this class
row_ix = where(y == class_value)[0]
# create scatter of these samples
pyplot.scatter(X[row_ix, 0], X[row_ix, 1], label=str(class_value))
# show a legend
pyplot.legend()
# show the plot
pyplot.show()
# define the class distribution 1:100
proportions = {0:10000, 1:1000}
# generate dataset
X, y = get_dataset(proportions)
# summarize class distribution:
major = (len(where(y == 0)[0]) / len(X)) * 100
minor = (len(where(y == 1)[0]) / len(X)) * 100
print('Class 0: %.3f%%, Class 1: %.3f%%' % (major, minor))
# plot dataset
plot_dataset(X, y)
运行该示例首先创建数据集并打印类分布。
我们可以看到,数据集中超过 99%的示例属于多数类,不到 1%的示例属于少数类。
Class 0: 99.010%, Class 1: 0.990%
创建了一个数据集图,我们可以看到每个类都有更多的示例,还有一个有用的图例来指示图颜色到类标签的映射。
1 到 100 类分布的二元类别数据集的散点图
接下来,我们可以拟合一个总是预测多数类的朴素分类器模型。
我们可以使用 scikit 中的 DummyClassifier 来实现这一点——学习并使用“最频繁策略,该策略将始终预测在训练数据集中观察到最多的类标签。
...
# define model
model = DummyClassifier(strategy='most_frequent')
然后,我们可以使用重复的 k 倍交叉验证在训练数据集上评估该模型。重要的是,我们使用分层交叉验证来确保数据集的每个分割都具有与训练数据集相同的类分布。这可以通过使用repeated stratifiedfold 类来实现。
下面的 evaluate_model() 函数实现了这一点,并返回模型每次评估的分数列表。
# evaluate a model using repeated k-fold cross-validation
def evaluate_model(X, y, metric):
# define model
model = DummyClassifier(strategy='most_frequent')
# evaluate a model with repeated stratified k fold cv
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
return scores
然后,我们可以评估模型,并计算每个评估得分的平均值。
我们预计朴素分类器将达到大约 99%的分类准确率,这是我们知道的,因为这是训练数据集中大多数类的分布。
...
# evaluate model
scores = evaluate_model(X, y, 'accuracy')
# report score
print('Accuracy: %.3f%%' % (mean(scores) * 100))
将所有这些联系在一起,下面列出了在具有 1:100 类分布的合成数据集上评估朴素分类器的完整示例。
# evaluate a majority class classifier on an 1:100 imbalanced dataset
from numpy import mean
from numpy import hstack
from numpy import vstack
from numpy import where
from sklearn.datasets import make_blobs
from sklearn.dummy import DummyClassifier
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
# create a dataset with a given class distribution
def get_dataset(proportions):
# determine the number of classes
n_classes = len(proportions)
# determine the number of examples to generate for each class
largest = max([v for k,v in proportions.items()])
n_samples = largest * n_classes
# create dataset
X, y = make_blobs(n_samples=n_samples, centers=n_classes, n_features=2, random_state=1, cluster_std=3)
# collect the examples
X_list, y_list = list(), list()
for k,v in proportions.items():
row_ix = where(y == k)[0]
selected = row_ix[:v]
X_list.append(X[selected, :])
y_list.append(y[selected])
return vstack(X_list), hstack(y_list)
# evaluate a model using repeated k-fold cross-validation
def evaluate_model(X, y, metric):
# define model
model = DummyClassifier(strategy='most_frequent')
# evaluate a model with repeated stratified k fold cv
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
return scores
# define the class distribution 1:100
proportions = {0:10000, 1:1000}
# generate dataset
X, y = get_dataset(proportions)
# summarize class distribution:
major = (len(where(y == 0)[0]) / len(X)) * 100
minor = (len(where(y == 1)[0]) / len(X)) * 100
print('Class 0: %.3f%%, Class 1: %.3f%%' % (major, minor))
# evaluate model
scores = evaluate_model(X, y, 'accuracy')
# report score
print('Accuracy: %.3f%%' % (mean(scores) * 100))
运行该示例首先再次报告训练数据集的类分布。
然后对模型进行评估,并报告平均准确率。我们可以看到,正如预期的那样,朴素分类器的表现与类分布完全匹配。
通常,达到 99%的分类准确率是值得庆祝的。虽然,正如我们所看到的,因为类分布不平衡,99%实际上是这个数据集可接受的最低准确率,也是更复杂的模型必须改进的起点。
Class 0: 99.010%, Class 1: 0.990%
Accuracy: 99.010%
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
教程
报纸
- 不平衡分布下的预测建模综述,2015。
- 班级不平衡问题的集合研究综述:基于装袋、提升和混合的方法,2011。
书
- 不平衡学习:基础、算法和应用,2013。
- 从不平衡数据集中学习,2018。
蜜蜂
- sklearn . dataset . make _ blobs API。
- 硬化. dummy . dummy class ification API。
- sklearn.model_selection。重复的策略应用编程接口。
文章
摘要
在本教程中,您发现了不平衡分类问题的分类准确率故障。
具体来说,您了解到:
- 准确性和错误率是总结分类模型表现的事实上的标准度量。
- 由于从业者在具有相等类别分布的数据集上开发的直觉,分类准确率在具有偏斜类别分布的分类问题上失败。
- 用一个实例说明偏斜类分布的准确率故障的直觉。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
机器学习的 Fbeta 测量的温和介绍
Fbeta-measure 是一个可配置的单一评分指标,用于基于对正类的预测来评估二进制分类模型。
Fbeta 度量是使用准确率和召回率计算的。
准确率是计算正类正确预测百分比的指标。回忆计算所有可能做出的积极预测中积极类的正确预测的百分比。最大化准确率将最小化假阳性错误,而最大化召回将最小化假阴性错误。
F-measure 被计算为精确度和召回率的调和平均值,给予每个相同的权重。它允许使用单个分数同时考虑精确度和召回率来评估模型,这在描述模型的表现和比较模型时很有帮助。
Fbeta 测度是 F 测度的推广,增加了一个配置参数β。默认β值为 1.0,与 F-测度相同。较小的β值(如 0.5)在计算分数时更重视准确率,而较少考虑召回,而较大的β值(如 2.0)在计算分数时更重视准确率,而更多考虑召回。
当精确度和召回率都很重要,但其中一个需要稍微多注意时,例如当假阴性比假阳性更重要时,或者相反,这是一个有用的度量标准。
在本教程中,您将发现用于评估机器学习分类算法的 Fbeta-measure。
完成本教程后,您将知道:
- 在二分类问题中,准确率和召回率提供了两种方法来总结正类的错误。
- F-measure 提供了一个总结精确度和召回率的单一分数。
- Fbeta-measure 提供了一个可配置的 F-measure 版本,在计算单个分数时或多或少会关注精确度和召回率。
用我的新书Python 不平衡分类启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
机器学习的 Fbeta-Measure 的温和介绍 照片由马尔科·韦奇拍摄,保留部分权利。
教程概述
本教程分为三个部分;它们是:
- 精确度和召回率
- 混淆矩阵
- 精确
- 回忆
- 衡量
- 最坏情况
- 最佳案例
- 50%准确率,完美召回
- 测量
- F1-测量
- F0.5 测量
- F2 测量
精确度和召回率
在我们深入研究 Fbeta-measure 之前,我们必须回顾用于评估分类模型所做预测的准确率和召回度量的基础。
混淆矩阵
一个混淆矩阵总结了一个模型对每个类所做的预测的数量,以及这些预测实际属于的类。它有助于理解模型产生的预测误差的类型。
最简单的混淆矩阵是针对两类分类问题,有负(0 类)和正(1 类)类。
在这种类型的混淆矩阵中,表中的每个单元格都有一个具体且易于理解的名称,总结如下:
| Positive Prediction | Negative Prediction
Positive Class | True Positive (TP) | False Negative (FN)
Negative Class | False Positive (FP) | True Negative (TN)
准确率和召回度量是根据混淆矩阵中的单元格来定义的,特别是像真阳性和假阴性这样的术语。
精确
准确率是一个度量标准,它量化了正确的积极预测的数量。
它的计算方法是正确预测的正例数除以预测的正例总数。
- 准确率=真阳性/(真阳性+假阳性)
结果是一个介于 0.0(无准确率)和 1.0(完全或完美准确率)之间的值。
准确率的直觉是它不关心假阴性,它最小化假阳性。我们可以用下面的一个小例子来演示这一点。
# intuition for precision
from sklearn.metrics import precision_score
# no precision
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
score = precision_score(y_true, y_pred)
print('No Precision: %.3f' % score)
# some false positives
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
score = precision_score(y_true, y_pred)
print('Some False Positives: %.3f' % score)
# some false negatives
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 0, 0, 0, 0, 0, 0, 1, 1, 1]
score = precision_score(y_true, y_pred)
print('Some False Negatives: %.3f' % score)
# perfect precision
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
score = precision_score(y_true, y_pred)
print('Perfect Precision: %.3f' % score)
运行该示例演示计算所有不正确和所有正确的预测类标签的准确率,分别显示无准确率和完美准确率。
一个预测一些假阳性的例子显示了精确度的下降,强调了该度量与最小化假阳性有关。
一个预测一些假阴性的例子显示了完美的准确率,强调了该措施与假阴性无关。
No Precision: 0.000
Some False Positives: 0.714
Some False Negatives: 1.000
Perfect Precision: 1.000
回忆
回忆是一个度量标准,它量化了从所有可能做出的积极预测中做出的正确积极预测的数量。
计算方法是正确预测的正例数除以可预测的正例总数。
- 回忆=真阳性/(真阳性+假阴性)
结果是 0.0(无召回)到 1.0(完全或完美召回)之间的值。
回忆的直觉是它不关心假阳性,它最小化假阴性。我们可以用下面的一个小例子来演示这一点。
# intuition for recall
from sklearn.metrics import recall_score
# no recall
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
score = recall_score(y_true, y_pred)
print('No Recall: %.3f' % score)
# some false positives
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
score = recall_score(y_true, y_pred)
print('Some False Positives: %.3f' % score)
# some false negatives
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 0, 0, 0, 0, 0, 0, 1, 1, 1]
score = recall_score(y_true, y_pred)
print('Some False Negatives: %.3f' % score)
# perfect recall
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
score = recall_score(y_true, y_pred)
print('Perfect Recall: %.3f' % score)
运行该示例演示了计算所有不正确和所有正确的预测类标签的召回,分别显示无召回和完美召回。
一个预测一些假阳性的例子显示了完美的回忆,强调了该措施与假阳性无关。
一个预测一些假阴性的例子显示了召回率的下降,强调了该措施与最小化假阴性有关。
No Recall: 0.000
Some False Positives: 1.000
Some False Negatives: 0.600
Perfect Recall: 1.000
现在我们已经熟悉了准确率和召回率,让我们回顾一下 F-measure。
衡量
精确度和召回率衡量正类可能出现的两种错误。
最大化准确率可以最小化误报,最大化召回可以最小化误报。
F-Measure 或 F-Score 提供了一种将准确率和召回率结合到一个度量中的方法,该度量可以捕获这两个属性。
- F-Measure = (2 准确率召回)/(准确率+召回)
这是两个分数的调和平均值。
结果是一个介于 0.0 和 1.0 之间的值。
F-measure 的直觉是,这两个度量在重要性上是平衡的,只有好的准确率和好的召回率才能产生好的 F-measure。
最坏情况
第一,如果所有的例子都被完美地错误地预测,我们将具有零准确率和零召回,导致零 F 测度;例如:
# worst case f-measure
from sklearn.metrics import f1_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
# no precision or recall
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
p = precision_score(y_true, y_pred)
r = recall_score(y_true, y_pred)
f = f1_score(y_true, y_pred)
print('No Precision or Recall: p=%.3f, r=%.3f, f=%.3f' % (p, r, f))
运行该示例,我们可以看到没有准确率或召回率导致最坏情况下的 F 度量。
No Precision or Recall: p=0.000, r=0.000, f=0.000
假设准确率和召回率只与正类相关,我们可以通过预测所有示例的负类来实现相同的最坏情况准确率、召回率和 F-measure:
# another worst case f-measure
from sklearn.metrics import f1_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
# no precision and recall
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
p = precision_score(y_true, y_pred)
r = recall_score(y_true, y_pred)
f = f1_score(y_true, y_pred)
print('No Precision or Recall: p=%.3f, r=%.3f, f=%.3f' % (p, r, f))
鉴于没有预测到阳性病例,我们必须输出零准确率和召回率,进而输出 f 测度。
No Precision or Recall: p=0.000, r=0.000, f=0.000
最佳案例
相反,完美的预测将产生完美的精确度和召回率,进而产生完美的 F 值,例如:
# best case f-measure
from sklearn.metrics import f1_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
# perfect precision and recall
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
p = precision_score(y_true, y_pred)
r = recall_score(y_true, y_pred)
f = f1_score(y_true, y_pred)
print('Perfect Precision and Recall: p=%.3f, r=%.3f, f=%.3f' % (p, r, f))
运行该示例,我们可以看到完美的准确率和召回率导致了完美的 F 度量。
Perfect Precision and Recall: p=1.000, r=1.000, f=1.000
50%准确率,完美召回
不可能有完美的精确和不召回,或者没有精确和完美的召回。精确度和召回率都需要预测真正的阳性。
考虑我们预测所有情况的正类的情况。
这将给我们 50%的准确率,因为一半的预测是假阳性。它会给我们完美的回忆,因为我们不会有假阴性。
对于我们在示例中使用的平衡数据集,一半的预测是真阳性,一半是假阳性;因此,精确率将为 0.5%或 50%。将 50%的感知准确率和完美召回率结合起来,将得到一个惩罚的 F-测度,具体来说就是 50%到 100%之间的调和平均值。
下面的例子演示了这一点。
# perfect precision f-measure
from sklearn.metrics import f1_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
# perfect precision, 50% recall
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
p = precision_score(y_true, y_pred)
r = recall_score(y_true, y_pred)
f = f1_score(y_true, y_pred)
print('Result: p=%.3f, r=%.3f, f=%.3f' % (p, r, f))
运行该示例证实,我们确实具有 50 的感知准确率和完美的召回率,并且 F 分数的结果值约为 0.667。
Result: p=0.500, r=1.000, f=0.667
测量
f 检验平衡了精确度和召回率。
在某些问题上,我们可能会对更注重准确率的 F-测度感兴趣,例如当误报对于最小化更重要,但漏报仍然重要时。
在其他问题上,我们可能会对一个更注重回忆的 F-测度感兴趣,比如什么时候假阴性对最小化更重要,但假阳性仍然重要。
解决办法是 Fbeta-措施。
Fbeta 测度是 F 测度的抽象,其中谐波均值计算中的准确率和召回率的平衡由一个称为β的系数控制。
- Fbeta = ((1 + beta²) 准确率召回)/ (beta² *准确率+召回)
β参数的选择将用于 Fbeta 测量的名称中。
例如,β值 2 被称为 f 2 度量或 F2 分数。β值 1 被称为 f 1 度量或 F1 分数。
beta 参数的三个常见值如下:
- f 0.5-测量(β= 0.5):准确率权重更大,召回权重更小。
- F1-测量(β= 1.0):在准确率和召回率上平衡权重。
- F2-测量(β= 2.0):准确率权重较小,召回权重较大
起初,不同β值对计算的影响并不直观。
让我们仔细看看这些案例。
F1-测量
上一节中讨论的 F-测度是一个β值为 1 的 Fbeta 测度的例子。
具体来说,F-measure 和 F1-measure 计算的是同一件事;例如:
- F-Measure = ((1 + 1²) 准确率召回)/ (1² *准确率+召回)
- F-Measure = (2 准确率召回)/(准确率+召回)
考虑我们有 50 感知准确率和完美回忆的情况。对于这种情况,我们可以手动计算 F1 度量,如下所示:
- F-Measure = (2 准确率召回)/(准确率+召回)
- f-测量= (2 * 0.5 * 1.0) / (0.5 + 1.0)
- f-测量= 1.0 / 1.5
- f-测量= 0.666
我们可以使用 Sklearn 中的 fbeta_score()函数来确认这个计算,将“ beta 参数设置为 1.0。
下面列出了完整的示例。
# calculate the f1-measure
from sklearn.metrics import fbeta_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
# perfect precision, 50% recall
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
p = precision_score(y_true, y_pred)
r = recall_score(y_true, y_pred)
f = fbeta_score(y_true, y_pred, beta=1.0)
print('Result: p=%.3f, r=%.3f, f=%.3f' % (p, r, f))
运行该示例证实了完美的准确率和 50%的召回率以及 0.667 的 F1 度量,证实了我们的计算(带舍入)。
0.667 的 F1 度量值与上一节中为相同场景计算的 F 度量值相匹配。
Result: p=0.500, r=1.000, f=0.667
f 0.5-测量
F0.5 测量值是 Fbeta 测量值的一个示例,其β值为 0.5。
它具有提高精确度的重要性和降低召回率的重要性的效果。
如果最大化准确率可以最小化假阳性,最大化召回可以最小化假阴性,那么f 0.5-度量将更多的注意力放在最小化假阳性上而不是最小化假阴性上。
f 0.5-测量值计算如下:
- f 0.5-测量= ((1 + 0.5²) 准确率召回)/ (0.5² *准确率+召回)
- f 0.5-测量= (1.25 准确率召回)/ (0.25 *准确率+召回)
假设我们有 50%的准确率和完美的召回率。对于这种情况,我们可以手动计算 F0.5 测量值,如下所示:
- f 0.5-测量= (1.25 准确率召回)/ (0.25 *准确率+召回)
- f 0.5-测量= (1.25 * 0.5 * 1.0) / (0.25 * 0.5 + 1.0)
- f 0.5-测量值= 0.625 / 1.125
- f 0.5-测量值= 0.555
我们预计β值为 0.5 会导致该场景的得分较低,因为准确率得分较低,召回率很高。
这正是我们所看到的,在 F1 分数计算为 0.667 的相同情况下,F0.5 的测量值为 0.555。准确率在计算中发挥了更大的作用。
我们可以证实这个计算;下面列出了完整的示例。
# calculate the f0.5-measure
from sklearn.metrics import fbeta_score
from sklearn.metrics import f1_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
# perfect precision, 50% recall
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
p = precision_score(y_true, y_pred)
r = recall_score(y_true, y_pred)
f = fbeta_score(y_true, y_pred, beta=0.5)
print('Result: p=%.3f, r=%.3f, f=%.3f' % (p, r, f))
运行该示例确认准确率和召回值,然后报告 0.556(带舍入)的 F0.5 度量,与我们手动计算的值相同。
Result: p=0.500, r=1.000, f=0.556
F2-测量
F2 测量值是 Fbeta 测量值的一个示例,其β值为 2.0。
它具有降低精确度的重要性和增加召回率的重要性的效果。
如果最大化准确率可以最小化假阳性,最大化召回可以最小化假阴性,那么 F2-measure 比最小化假阳性更注重最小化假阴性。
F2 测量值计算如下:
- F2-Measure = ((1 + 2²) 准确率召回)/ (2² *准确率+召回)
- F2-测量= (5 准确率召回)/ (4 *准确率+召回)
假设我们有 50%的准确率和完美的召回率。
对于这种情况,我们可以手动计算 F2 度量,如下所示:
- F2-测量= (5 准确率召回)/ (4 *准确率+召回)
- F2-测量= (5 * 0.5 * 1.0) / (4 * 0.5 + 1.0)
- F2-测量= 2.5 / 3.0
- F2-测量值= 0.833
我们预计 2.0 的 beta 值将导致该场景的更高得分,因为召回具有满分,这将比准确率表现差的情况得到提升。
这正是我们所看到的,对于 F1 分数计算为 0.667 的相同场景,F2 测量值为 0.833。回忆在计算中起了更大的作用。
我们可以证实这个计算;下面列出了完整的示例。
# calculate the f2-measure
from sklearn.metrics import fbeta_score
from sklearn.metrics import f1_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
# perfect precision, 50% recall
y_true = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
y_pred = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
p = precision_score(y_true, y_pred)
r = recall_score(y_true, y_pred)
f = fbeta_score(y_true, y_pred, beta=2.0)
print('Result: p=%.3f, r=%.3f, f=%.3f' % (p, r, f))
运行该示例确认准确率和召回值,然后报告 0.883 的 F2 度量值,与我们手动计算的值相同(带舍入)。
Result: p=0.500, r=1.000, f=0.833
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
教程
报纸
- F-measure 的真相,2007。
蜜蜂
- 硬化. metrics.f1_score API 。
- 硬化. metrics.fbeta_score API 。
文章
- f1 得分,维基百科。
- 调和的意思,维基百科。
摘要
在本教程中,您发现了用于评估机器学习分类算法的 Fbeta 度量。
具体来说,您了解到:
- 在二分类问题中,准确率和召回率提供了两种方法来总结正类的错误。
- F-measure 提供了一个总结精确度和召回率的单一分数。
- Fbeta-measure 提供了一个可配置的 F-measure 版本,在计算单个分数时或多或少会关注精确度和召回率。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。
不平衡分类项目的分步框架
最后更新于 2020 年 3 月 19 日
分类预测建模问题包括预测给定输入集的类别标签。
总的来说,这是一个具有挑战性的问题,尤其是如果对数据集知之甚少的话,因为有几十个(如果不是几百个)机器学习算法可供选择。如果示例在类之间的分布不平衡,那么这个问题就会变得更加困难。这需要使用专门的方法来改变数据集或者改变学习算法来处理倾斜的类分布。
处理一个新分类项目的压倒性优势的一个常见方法是使用一个喜欢的机器学习算法,如随机森林或 SMOTE。另一种常见的方法是在研究文献中寻找模糊相似问题的描述,并尝试重新实现所描述的算法和配置。
这些方法可能是有效的,尽管它们分别是命中或未命中和耗时的。相反,在新的分类任务中获得好结果的最短路径是系统地评估一套机器学习算法,以便发现哪些算法运行良好,然后加倍。这种方法也可以用于不平衡的分类问题,为数据采样、成本敏感和单类分类算法的范围量身定制,可供选择。
在本教程中,您将发现一个处理不平衡类别数据集的系统框架。
完成本教程后,您将知道:
- 选择不平衡分类算法的挑战。
- 通过不平衡分类项目系统工作的高级框架。
- 在不平衡分类项目的每一步尝试的具体算法建议。
用我的新书Python 不平衡分类启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
不平衡分类项目分步框架 图片由 ~jar{} 提供,保留部分权利。
教程概述
本教程分为三个部分;它们是:
- 使用什么算法?
- 使用系统框架
- 不平衡分类的详细框架
- 选择一个指标
- 抽查算法
- 抽查不平衡算法
- 超参数调谐
1.使用什么算法?
你得到了一个不平衡的类别数据集。现在怎么办?
有这么多机器学习算法可供选择,更不用说专门为不平衡分类设计的技术了。
你用哪些算法? 你怎么选?
这是每个新的不平衡分类项目开始时面临的挑战。正是这一挑战使得应用机器学习既令人兴奋又令人恐惧。
或许有两种常见的方法来解决这个问题:
- 使用最喜欢的算法。
- 使用以前有效的方法。
一种方法可能是选择一个喜欢的算法,并开始调整超参数。这是一种快速的解决方案,但只有当您最喜欢的算法恰好是特定数据集的最佳解决方案时,这种方法才有效。
另一种方法可能是回顾文献,看看像您这样的数据集使用了什么技术。如果许多人在类似的数据集上研究并报告了结果,这可能是有效的。
实际上,这种情况很少,研究出版物倾向于展示 pet 算法的前景,而不是对方法进行诚实的比较。充其量,文学可以作为尝试技术的思路。
相反,如果对这个问题知之甚少,那么获得“好的”结果的最短路径是在你的数据集上系统地测试一套不同的算法。
2.使用系统框架
考虑一个平衡的分类任务。
您面临着选择使用哪些算法来处理数据集的同样挑战。
这个问题有很多解决方案,但最稳健的可能是系统地测试一套算法,并使用经验结果进行选择。
像“我最喜欢的算法”或“过去有效的算法”这样的偏见可以为研究提供思路,但如果依赖它们,可能会把你引入歧途。相反,您需要让系统经验实验的结果来告诉您什么算法对您的不平衡类别数据集是好的或最好的。
一旦有了数据集,这个过程包括三个步骤:(1)选择评估候选模型的度量标准,(2)测试一套算法,以及(3)调整表现最好的模型。这可能不是唯一的方法;只是最简单可靠的过程让你从我有新数据集到我很快就有好结果。
这个过程可以总结如下:
- 选择一个指标
- 抽查算法
- 超参数调谐
抽查算法稍微复杂一些,因为许多算法需要专门的数据准备,如缩放、去除异常值等。此外,评估候选算法需要仔细设计测试工具,通常包括使用 k 倍交叉验证来估计给定模型在未知数据上的表现。
我们可以用这个简单的过程进行不平衡分类。
抽查不平衡分类的标准机器学习算法仍然很重要。当类分布不均衡时,标准算法通常表现不佳。然而,首先测试它们提供了一个表现基线,通过这个基线可以比较更专业的模型,并且必须表现出色。
调整表现良好的算法的超参数也很重要。这包括专门为不平衡分类设计的模型的超参数。
因此,我们可以使用相同的三步过程,并插入一个额外的步骤来评估不平衡的分类算法。
我们可以将这个过程总结如下:
- 选择一个指标
- 抽查算法
- 抽查不平衡算法
- 超参数调谐
这为解决不平衡分类问题提供了一个高层次的系统框架。
然而,有许多不平衡的算法可供选择,更不用说许多不同的标准机器学习算法可供选择。
我们要求每个步骤都有一个类似的低级系统框架。
3.不平衡分类的详细框架
我们可以开发一个类似的低级框架,系统地完成不平衡分类项目的每一步。
从选择度量到超参数调整。
3.1.选择一个指标
选择度量标准可能是项目中最重要的一步。
公制是用来评估和比较所有模型的标尺。选择错误的度量可能意味着选择错误的算法。也就是说,一个模型解决了一个与你实际想要解决的问题不同的问题。
度量必须捕捉关于模型或其预测的那些细节,这些细节对项目或项目涉众来说是最重要的。
这很难,因为有很多度量标准可供选择,而且项目涉众通常不确定他们想要什么。也可能有多种方法来描述这个问题,探索一些不同的框架可能是有益的,反过来,探索不同的度量标准,看看什么对利益相关者是有意义的。
首先,你必须决定你是想要预测概率还是简单的类标签。回想一下,对于二元不平衡分类任务,多数类是正常的,称为“负类,少数类是例外的,称为“正类”。
概率捕捉预测的不确定性,而清晰的类标签可以立即使用。
- 概率:预测每个例子的类成员概率。
- 类标签:为每个示例预测一个清晰的类标签。
3.1.1.预测概率
如果打算直接使用概率,那么一个好的度量标准可能是简单分数和简单技能分数。
或者,您可能希望预测概率,并允许用户通过用户选择的阈值将概率映射到清晰的类标签本身。在这种情况下,可以选择一个度量来总结模型在可能阈值范围内的表现。
如果正类最重要,那么可以使用准确率-召回曲线和曲线下面积(PR AUC)。这将优化所有阈值的精确度和召回率。
或者,如果两个类别同等重要,可以使用 ROC 曲线和曲线下面积(ROC AUC)。这将最大化真阳性率,最小化假阳性率。
3.1.2.预测类别标签
如果需要类别标签,并且两个类别同等重要,那么一个好的默认度量是分类准确率。这只有在多数阶层偏离数据不到 80%的情况下才有意义。偏斜度大于 80%或 90%的多数类将淹没准确性度量,并且将失去其对比较算法的意义。
如果类别分布严重偏斜,那么可以使用 G 均值度量来优化灵敏度和特异性度量。
如果正类更重要,那么可以使用变异的 F-Measure 来优化准确率和召回率。如果假阳性和假阴性同样重要,那么可以使用 F1。如果假阴性成本更高,则可以使用 F2-Measure,否则,如果假阳性成本更高,则可以使用 F0.5-Measure。
3.1.3.选择指标的框架
这些只是试探法,但如果您觉得为不平衡的分类任务选择一个度量标准有所损失,则提供了一个有用的起点。
我们可以将这些试探法总结成如下框架:
- 你在预测概率吗?
- 需要类标签吗?
- 正课更重要吗?
- 使用准确率-召回 AUC
- 两个班都重要吗?
- 使用 ROC AUC
- 正课更重要吗?
- 你需要概率吗?
- 使用简短分数和简短技能分数
- 需要类标签吗?
- 你在预测类标签吗?
- 正课更重要吗?
- 假阴性和假阳性的成本一样高吗?
- 使用 F1-测量
- 假阴性成本更高吗?
- 使用 F2-测量
- 误报成本更高吗?
- 使用 f 0.5-测量
- 假阴性和假阳性的成本一样高吗?
- 两个班都重要吗?
- 多数班有< 80%-90%的例子吗?
- 使用精确度
- 多数班有> 80%-90%的例子吗?
- 使用 G 均值
- 多数班有< 80%-90%的例子吗?
- 正课更重要吗?
我们还可以将这些决策转换成决策树,如下所示。
如何选择不平衡分类的度量
一旦选择了度量标准,您就可以开始评估机器学习算法了。
3.2.抽查算法
抽查机器学习算法意味着用最小的超参数调整来评估一套不同类型的算法。
具体来说,这意味着给每个算法一个了解问题的好机会,包括执行算法预期的任何所需数据准备,以及使用最佳实践配置选项或默认值。
目标是快速测试一系列标准机器学习算法,并提供一个表现基线,专门用于不平衡分类的技术必须与该基线进行比较并超越该基线才能被认为是熟练的。这里的想法是,如果他们不能执行所谓的不平衡算法,那么使用花哨的不平衡算法就没有什么意义。
必须定义一个健壮的测试线束。这通常涉及 k 倍交叉验证,通常以 k-10 作为合理的默认值。通常需要分层交叉验证来确保每个折叠与原始数据集具有相同的类分布。并且交叉验证过程经常重复多次,例如 3 次、10 次或 30 次,以便有效地捕获数据集上模型表现的样本,用分数的平均值和标准偏差进行总结。
可能有四个级别的算法需要抽查;它们是:
- 朴素算法
- 线性算法
- 非线性算法
- 集成算法
3.2.1.朴素算法
首先,必须评估一个简单的分类。
这提供了一个最低的表现基线,任何算法都必须克服这个基线才能掌握数据集。
天真意味着算法除了 if 语句或预测一个常量值之外没有其他逻辑。朴素算法的选择是基于表现度量的选择。
例如,一个合适的用于分类准确率的朴素算法是预测所有情况下的多数类。在评估概率时,一个适用于 Brier Score 的简单算法是预测训练数据集中每个类的先验概率。
建议将表现指标映射到简单算法如下:
- 准确度:预测多数类(0 类)。
- G 均值:预测一个一致随机类。
- F-Measure :预测少数类(1 类)。
- ROC AUC :预测一个分层随机类。
- PR ROC :预测一个分层的随机类。
- Brier 评分:预测多数类优先。
如果您不确定度量标准的“最佳”“简单”算法,或许可以测试一些,并发现哪种算法能带来更好的表现,您可以将其用作最低基准。
*一些选项包括:
- 预测所有情况下的多数类。
- 预测所有情况下的少数阶级。
- 预测一个均匀随机选择的班级。
- 用每个类别的先验概率预测随机选择的类别。
- 预测类先验概率。
3.2.2.线性算法
线性算法是那些经常从统计领域中提取出来的算法,并且对问题的函数形式做出强有力的假设。
我们可以称它们为线性的,因为输出是输入的线性组合,或者是加权输入,尽管这个定义有些牵强。您也可以将这些算法称为概率算法,因为它们通常适用于概率框架。
他们通常训练速度很快,表现也很好。您应该考虑尝试的线性算法示例包括:
- 逻辑回归
- 线性判别分析
- 朴素贝叶斯
3.2.3.非线性算法
非线性算法来源于机器学习领域,对问题的函数形式几乎不做假设。
我们可以称之为非线性,因为输出通常是输入到输出的非线性映射。
它们通常比线性算法需要更多的数据,并且训练速度更慢。您应该考虑尝试的非线性算法示例包括:
- 决策图表
- k-最近邻
- 人工神经网络
- 支持向量机
3.2.4.集成算法
集成算法也是从机器学习领域中提取出来的,并且结合了来自两个或更多模型的预测。
有许多集成算法可供选择,但在抽查算法时,最好将重点放在决策树算法的集成上,因为众所周知,它们在实践中对各种问题都有很好的表现。
您应该考虑尝试的决策树算法集成示例包括:
- 袋装决策树
- 随机森林
- 额外树
- 随机梯度升压
3.2.5.抽查机器学习算法框架
我们可以将这些建议总结成一个在数据集上测试机器学习算法的框架。
- 朴素算法
- 多数阶级
- 少数民族阶层
- 阶级优先
- 线性算法
- 逻辑回归
- 线性判别分析
- 朴素贝叶斯
- 非线性算法
- 决策图表
- k-最近邻
- 人工神经网络
- 支持向量机
- 集成算法
- 袋装决策树
- 随机森林
- 额外树
- 随机梯度升压
步骤的顺序可能不灵活。把算法的顺序看作是复杂性的增加,进而是能力的增加。
每一步中算法的顺序是灵活的,算法的列表是不完整的,并且可能永远无法给出大量可用的算法。将测试的算法限制在最常见或最广泛使用的子集是一个良好的开端。使用数据准备建议和超参数默认值也是一个好的开始。
下图总结了框架的这一步。
如何抽查机器学习算法
3.3.抽查不平衡算法
抽查不平衡算法很像抽查机器学习算法。
目标是快速测试大量的技术,以便发现哪些技术有希望,这样您就可以在稍后的超参数调优过程中更加关注它。
在前一节中执行的抽查提供了幼稚和适度熟练的模型,通过这些模型可以比较所有不平衡的技术。这使您能够专注于这些真正显示问题前景的方法,而不是对仅与其他不平衡分类技术相比看起来有效的结果感到兴奋(这是一个容易陷入的陷阱)。
可能有四种类型的不平衡分类技术需要抽查:
- 数据采样算法
- 成本敏感算法
- 一类算法
- 概率调整算法
3.3.1.数据采样算法
数据采样算法改变训练数据集的组成,以提高标准机器学习算法在不平衡分类问题上的表现。
数据采样技术可能有三种主要类型;它们是:
- 数据过放大。
- 数据欠采样。
- 组合过采样和欠采样。
数据过采样包括复制少数类的例子,或者从现有例子中合成少数类的新例子。也许最受欢迎的方法是 SMOTE 和变体,如边界线 SMOTE。也许要调整的最重要的超参数是要执行的过采样量。
数据过采样方法的示例包括:
- 随机过采样
- 重击
- 边界线 SMOTE
- SVM·斯摩尔特
- k-Means SMOTE
- 阿达林
欠采样包括从多数类中删除示例,例如随机删除或使用算法仔细选择要删除的示例。流行的编辑算法包括编辑的最近邻和 Tomek 链接。
数据欠采样方法的示例包括:
- 随机欠采样
- 凝聚最近邻
- 托梅克左边
- 编辑的最近邻居
- 邻域清理规则
- 片面选择
几乎任何过采样方法都可以与几乎任何欠采样技术相结合。因此,测试一套不同的过采样和欠采样技术组合可能是有益的。
过采样和欠采样的常见组合包括:
- SMOTE 和随机欠采样
- SMOTE 和 Tomek 链接
- 移动和编辑最近的邻居
根据机器学习算法的选择,数据采样算法的表现可能会有所不同。
因此,测试一套标准的机器学习算法可能是有益的,例如前面部分中抽查时使用的所有算法或这些算法的子集。
此外,大多数数据采样算法在内部使用 k 最近邻算法。该算法对输入变量的数据类型和规模非常敏感。因此,在测试方法之前,至少标准化具有不同标度的输入变量可能是重要的,并且如果一些输入变量是分类的而不是数字的,则可能使用专门的方法。
3.3.2.成本敏感算法
成本敏感算法是机器学习算法的改进版本,设计用于在训练数据集上拟合模型时考虑错误分类的不同成本。
当用于不平衡分类时,这些算法可以是有效的,其中错误分类的成本被配置成与训练数据集中的示例分布成反比。
有许多对成本敏感的算法可供选择,尽管测试一系列对成本敏感的线性、非线性和集成算法可能是实用的。
可以使用成本敏感训练配置的机器学习算法的一些例子包括:
- 逻辑回归
- 决策树
- 支持向量机
- 人工神经网络
- 袋装决策树
- 随机森林
- 随机梯度升压
3.3.3.一类算法
用于异常检测和异常检测的算法可以用于分类任务。
虽然不常见,但当以这种方式使用时,它们通常被称为单类分类算法。
在某些情况下,单类分类算法可能非常有效,例如当存在严重的类不平衡,并且正类的例子很少时。
可以尝试的单类分类算法包括:
- 一类支持向量机
- 隔离森林
- 最小协方差行列式
- 局部异常因子
3.3.4.概率调整算法
预测概率可以通过两种方式提高;它们是:
- 校准概率。
- 调整分类阈值。
校准概率
一些算法使用概率框架进行拟合,并依次校准概率。
这意味着,当预测 100 个示例具有 80%概率的正类标签时,算法将在 80%的时间内预测正确的类标签。
当需要概率作为输出或用于评估模型时(例如,ROC AUC 或 PR AUC),模型需要校准的概率才能被认为在二分类任务中是熟练的。
预测校准概率的机器学习算法的一些例子如下:
- 逻辑回归
- 线性判别分析
- 朴素贝叶斯
- 人工神经网络
大多数非线性算法不预测校准的概率,因此可以使用算法对预测的概率进行后处理以校准它们。
因此,当概率被直接要求或用于评估模型,并且使用非线性算法时,重要的是校准预测概率。
尝试概率校准算法的一些例子包括:
- 普拉特缩放
- 等渗回归
调整分类阈值
一些算法被设计成天真地预测概率,这些概率随后必须被映射到清晰的类标签。
如果需要类标签作为问题的输出,或者使用类标签评估模型,就会出现这种情况。
默认预测概率的概率机器学习算法的例子包括:
- 逻辑回归
- 线性判别分析
- 朴素贝叶斯
- 人工神经网络
使用阈值概率值将概率映射到类标签。所有低于阈值的概率被映射到类别 0,所有等于或高于阈值的概率被映射到类别 1。
默认阈值为 0.5,尽管可以使用不同的阈值,这将极大地影响类标签,进而影响本机预测概率的机器学习模型的表现。
因此,如果使用概率算法来自然预测概率,并且需要类别标签作为输出或用于评估模型,那么尝试调整分类阈值是一个好主意。
3.3.5.抽查不平衡算法框架
我们可以将这些建议总结成一个在数据集上测试不平衡机器学习算法的框架。
- 数据采样算法
- 数据过采样
- 随机过采样
- 重击
- 边界线 SMOTE
- SVM·斯摩尔特
- k-Means SMOTE
- 阿达林
- 数据欠采样
- 随机欠采样
- 凝聚最近邻
- 托梅克左边
- 编辑的最近邻居
- 邻域清理规则
- 片面选择
- 组合过采样和欠采样
- SMOTE 和随机欠采样
- SMOTE 和 Tomek 链接
- 移动和编辑最近的邻居
- 数据过采样
- 成本敏感算法
- 逻辑回归
- 决策树
- 支持向量机
- 人工神经网络
- 袋装决策树
- 随机森林
- 随机梯度升压
- 一类算法
- 一类支持向量机
- 隔离森林
- 最小协方差行列式
- 局部异常因子
- 概率调整算法
- 校准概率
- 普拉特缩放
- 等渗回归
- 调整分类阈值
- 校准概率
步骤的顺序是灵活的,每个步骤内算法的顺序也是灵活的,算法列表不完整。
该结构旨在让您系统地思考评估什么算法。
下图总结了该框架。
如何抽查不平衡机器学习算法
3.4.超参数调谐
在抽查机器学习算法和不平衡算法后,您将对特定数据集上哪些可行,哪些不可行有所了解。
超参数调整的最简单方法是选择表现良好的前五或前十个算法或算法组合,并为每个算法调整超参数。
有三种流行的超参数调整算法可供您选择:
- 随机搜索
- 网格搜索
- 贝叶斯优化
如果您知道要尝试什么样的超参数值,一个好的缺省值是网格搜索,否则,应该使用随机搜索。如果可能的话,应该使用贝叶斯优化,但是设置和运行可能更具挑战性。
调整最佳执行方法是一个好的开始,但不是唯一的方法。
可能有一些标准的机器学习算法表现良好,但与数据采样或概率校准一起使用时表现不佳。这些算法可以与它们的不平衡分类增强相协调,看看是否能获得更好的表现。
此外,可能存在不平衡分类算法,例如导致一个或多个算法表现显著提升的数据采样方法。这些算法本身可能会为进一步调整提供一个有趣的基础,以查看是否可以实现表现的额外提升。
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
教程
书
- 从不平衡数据集中学习,2018。
- 不平衡学习:基础、算法和应用,2013。
摘要
在本教程中,您发现了一个处理不平衡类别数据集的系统框架。
具体来说,您了解到:
- 选择不平衡分类算法的挑战。
- 通过不平衡分类项目系统工作的高级框架。
- 在不平衡分类项目的每一步尝试的具体算法建议。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。*
如何为乳腺癌患者存活建立概率模型
最后更新于 2020 年 8 月 21 日
一般来说,开发一个概率模型是具有挑战性的,尽管当案例的分布存在偏差时,这种挑战性会更大,这种偏差被称为不平衡数据集。
哈贝曼数据集描述了 20 世纪 50 年代和 60 年代乳腺癌患者 5 年或更长时间的存活率,并且主要包含存活的患者。这个标准的机器学习数据集可以用作开发概率模型的基础,该概率模型可以预测给定一些患者病例细节的患者的存活概率。
给定数据集中病例的偏斜分布,必须仔细注意预测模型的选择,以确保预测校准的概率,并注意模型评估的选择,以确保模型的选择基于其预测概率的技巧,而不是简单的存活与非存活类别标签。
在本教程中,您将发现如何开发一个模型来预测不平衡数据集上患者的存活概率。
完成本教程后,您将知道:
- 如何加载和探索数据集,并为数据准备和模型选择产生想法。
- 如何评估一套概率模型,并通过适当的数据准备提高它们的表现。
- 如何拟合最终模型并使用它来预测特定情况下的概率。
用我的新书Python 不平衡分类启动你的项目,包括分步教程和所有示例的 Python 源代码文件。
我们开始吧。
如何开发乳腺癌患者存活的概率模型 图片由 Tanja-Milfoil 提供,保留部分权利。
教程概述
本教程分为五个部分;它们是:
- 哈贝曼乳腺癌存活数据集
- 浏览数据集
- 模型测试和基线结果
- 评估概率模型
- 概率算法评估
- 具有缩放输入的模型评估
- 用幂变换进行模型评估
- 对新数据进行预测
哈贝曼乳腺癌存活数据集
在这个项目中,我们将使用一个小型乳腺癌存活数据集,通常称为“哈贝曼数据集”
数据集描述了乳腺癌患者数据,结果是患者存活率。具体来说,患者是否存活了 5 年或更长时间,或者患者是否没有存活。
这是一个用于不平衡分类研究的标准数据集。根据数据集描述,手术于 1958 年至 1970 年在芝加哥大学比林斯医院进行。
数据集中有 306 个例子,有 3 个输入变量;它们是:
- 手术时患者的年龄。
- 运营的两位数年份。
- 检测到的“阳性腋窝淋巴结的数量,这是癌症已经扩散的一种度量。
因此,除了数据集中可用的情况之外,我们无法控制组成数据集的情况或在这些情况下使用的要素的选择。
尽管数据集描述了乳腺癌患者的存活率,但鉴于数据集规模较小,并且数据基于几十年前的乳腺癌诊断和手术,因此任何基于该数据集构建的模型都不可一概而论。
说得再清楚不过,我们不是“解决乳腺癌”我们正在探索一个标准的不平衡类别数据集。
您可以在此了解有关数据集的更多信息:
我们将选择将此数据集作为患者存活概率的预测框架。
那就是:
给定患者乳腺癌手术细节,患者存活至五年或五年以上的概率是多少?
这将为探索可以预测概率而不是类标签的概率算法以及评估预测概率而不是类标签的模型的度量提供基础。
接下来,让我们仔细看看数据。
浏览数据集
首先,下载数据集并保存在您当前的工作目录中,名称为“ haberman.csv ”。
查看文件的内容。
文件的前几行应该如下所示:
30,64,1,1
30,62,3,1
30,65,0,1
31,59,2,1
31,65,4,1
33,58,10,1
33,60,0,1
34,59,0,2
34,66,9,2
34,58,30,1
...
我们可以看到患者的年龄像 30 岁或 31 岁(第 1 栏),手术分别发生在 1964 年和 1962 年的 64 年和 62 年(第 2 栏),“腋窝淋巴结的值像 1 和 0。
所有值都是数字;具体来说,它们是整数。没有标有“”的缺失值?“性格。
我们还可以看到,类别标签(第 3 列)的值为 1 表示患者存活,2 表示患者非存活。
首先,我们可以加载 CSV 数据集,并使用五个数字的摘要来总结每一列。可以使用 read_csv()熊猫函数将数据集加载为数据框,指定列的位置和名称,因为没有标题行。
...
# define the dataset location
filename = 'haberman.csv'
# define the dataset column names
columns = ['age', 'year', 'nodes', 'class']
# load the csv file as a data frame
dataframe = read_csv(filename, header=None, names=columns)
然后我们可以调用description()函数创建一个每列五个数汇总的报表,并打印报表内容。
一列的五个数汇总包括有用的细节,如最小值和最大值,如果变量具有高斯分布,则平均值和标准偏差有用,如果变量不具有高斯分布,则第 25、50 和 75 四分位数有用。
...
# summarize each column
report = dataframe.describe()
print(report)
将这些联系在一起,下面列出了加载和汇总数据集列的完整示例。
# load and summarize the dataset
from pandas import read_csv
# define the dataset location
filename = 'haberman.csv'
# define the dataset column names
columns = ['age', 'year', 'nodes', 'class']
# load the csv file as a data frame
dataframe = read_csv(filename, header=None, names=columns)
# summarize each column
report = dataframe.describe()
print(report)
运行该示例将加载数据集,并为三个输入变量和输出变量报告五个数字的摘要。
看年龄,可以看出最小的患者 30 岁,最大的 83 岁;这是相当大的差距。患者平均年龄约为 52 岁。如果癌症的发生有点随机,我们可能会认为这种分布是高斯分布。
我们可以看到所有的手术都是在 1958 年到 1969 年之间进行的。如果乳腺癌患者的数量随着时间的推移有些固定,我们可能会期望这个变量有一个均匀的分布。
我们可以看到节点的值在 0 到 52 之间。这可能是与淋巴结相关的癌症诊断。
age year nodes class
count 306.000000 306.000000 306.000000 306.000000
mean 52.457516 62.852941 4.026144 1.264706
std 10.803452 3.249405 7.189654 0.441899
min 30.000000 58.000000 0.000000 1.000000
25% 44.000000 60.000000 0.000000 1.000000
50% 52.000000 63.000000 1.000000 1.000000
75% 60.750000 65.750000 4.000000 2.000000
max 83.000000 69.000000 52.000000 2.000000
所有变量都是整数。因此,将每个变量视为直方图来了解变量分布可能会有所帮助。
如果我们以后选择对数据分布或数据规模敏感的模型,这可能会有所帮助,在这种情况下,我们可能需要转换或重新缩放数据。
我们可以通过调用 hist()函数来创建数据框中每个变量的直方图。
下面列出了完整的示例。
# create histograms of each variable
from pandas import read_csv
from matplotlib import pyplot
# define the dataset location
filename = 'haberman.csv'
# define the dataset column names
columns = ['age', 'year', 'nodes', 'class']
# load the csv file as a data frame
dataframe = read_csv(filename, header=None, names=columns)
# create a histogram plot of each variable
dataframe.hist()
pyplot.show()
运行该示例会为每个变量创建一个直方图。
我们可以看到,年龄似乎有高斯分布,正如我们可能预期的那样。我们还可以看到,那一年有一个均匀的分布,大多数情况下,第一年的异常值显示操作数量几乎是第一年的两倍。
我们可以看到节点具有指数型分布,可能大多数示例显示 0 个节点,其后是一长串值。将这个分布转换为非聚集分布可能对以后的一些模型有所帮助。
最后,我们可以看到具有不平等的阶级分布的两个阶级的价值,显示出可能比非存活情况多 2-3 倍的存活。
哈贝曼乳腺癌存活数据集中各变量的直方图
了解数据集实际上有多不平衡可能会有所帮助。
我们可以使用 Counter 对象来统计每个类中的示例数量,然后使用这些计数来总结分布。
下面列出了完整的示例。
# summarize the class ratio
from pandas import read_csv
from collections import Counter
# define the dataset location
filename = 'haberman.csv'
# define the dataset column names
columns = ['age', 'year', 'nodes', 'class']
# load the csv file as a data frame
dataframe = read_csv(filename, header=None, names=columns)
# summarize the class distribution
target = dataframe['class'].values
counter = Counter(target)
for k,v in counter.items():
per = v / len(target) * 100
print('Class=%d, Count=%d, Percentage=%.3f%%' % (k, v, per))
运行该示例总结了数据集的类分布。
我们可以看到存活类 1 在 225 处有最多的例子,大约占数据集的 74%。我们可以看到非存活类 2 的例子更少,只有 81 个,约占数据集的 26%。
阶级分布是倾斜的,但并不严重不平衡。
Class=1, Count=225, Percentage=73.529%
Class=2, Count=81, Percentage=26.471%
现在我们已经回顾了数据集,让我们看看开发一个测试工具来评估候选模型。
模型测试和基线结果
我们将使用重复的分层 k 折叠交叉验证来评估候选模型。
k 倍交叉验证程序提供了一个良好的模型表现的总体估计,至少与单个列车测试分割相比,不太乐观。我们将使用 k=10,这意味着每个折叠将包含 306/10 或大约 30 个示例。
分层意味着每一个折叠将包含相同的样本混合类,即大约 74%到 26%的存活率和非存活率。
重复意味着评估过程将执行多次,以帮助避免侥幸结果,并更好地捕捉所选模型的方差。我们将使用三次重复。
这意味着单个模型将被拟合和评估 10 * 3 或 30 次,并且将报告这些运行的平均值和标准偏差。
这可以通过使用repeated stratifiedfold Sklearn 类来实现。
鉴于我们对预测存活概率感兴趣,我们需要一个基于预测概率评估模型技能的表现指标。在这种情况下,我们将使用 Brier 评分,计算预测概率和预期概率之间的均方误差。
这可以使用brier _ score _ loss()sci kit-learn 功能来计算。这个分数被最小化,满分为 0.0 分。我们可以通过将预测分数与参考分数进行比较来将分数反转为最大化,显示模型与 0.0 到 1.0 之间的参考分数相比有多好。任何得分低于 0.0 的模型都表示技能低于参考模型。这被称为 Brier 技能评分,简称 BSS。
- BrierSkillScore = 1.0 –( modelberscore/ReferenceBrierScore)
习惯上,不平衡数据集会将少数民族类建模为正类。在这个数据集中,正类代表非存活。这意味着,我们将预测不存活的概率,并且需要计算预测概率的补数,以便获得存活的概率。
因此,我们可以将 1 类值(存活)映射到具有 0 类标签的负案例,将 2 类值(非存活)映射到具有 1 类标签的正案例。这可以使用标签编码器类来实现。
例如下面的 load_dataset() 函数将加载数据集,将变量列拆分为输入和输出,然后将目标变量编码为 0 和 1 值。
# load the dataset
def load_dataset(full_path):
# load the dataset as a numpy array
data = read_csv(full_path, header=None)
# retrieve numpy array
data = data.values
# split into input and output elements
X, y = data[:, :-1], data[:, -1]
# label encode the target variable to have the classes 0 and 1
y = LabelEncoder().fit_transform(y)
return X, y
接下来,我们可以计算模型的 Brier 技能分数。
首先,我们需要一个 Brier 分数作为参考预测。我们预测概率的问题的参考预测是数据集内正类标签的概率。
在这种情况下,正的类标签表示非存活,并且在数据集中出现大约 26%。因此,预测约 0.26471 代表此数据集上预测模型的最坏情况或基线表现。任何比这个布里埃得分高的模型都有一些技能,而任何比这个布里埃得分低的模型都没有技能。简明技能评分抓住了这一重要关系。我们可以在 K 折交叉验证过程中为每个训练集自动计算这个默认预测策略的 Brier 分数,然后将其用作给定模型的比较点。
...
# calculate reference brier score
ref_probs = [0.26471 for _ in range(len(y_true))]
bs_ref = brier_score_loss(y_true, ref_probs)
然后,可以为模型中的预测计算布瑞尔分数,并将其用于布瑞尔技能分数的计算。
下面的 brier_skill_score() 函数实现了这一点,并为同一测试集上给定的一组真实标签和预测计算 Brier Skill Score。任何达到 0.0 以上的 BSS 的模型都意味着它在这个数据集上显示了技能。
# calculate brier skill score (BSS)
def brier_skill_score(y_true, y_prob):
# calculate reference brier score
pos_prob = count_nonzero(y_true) / len(y_true)
ref_probs = [pos_prob for _ in range(len(y_true))]
bs_ref = brier_score_loss(y_true, ref_probs)
# calculate model brier score
bs_model = brier_score_loss(y_true, y_prob)
# calculate skill score
return 1.0 - (bs_model / bs_ref)
接下来,我们可以使用 brier_skill_score() 函数,使用重复的分层 K 折交叉验证来评估模型。
要使用我们的自定义表现指标,我们可以使用 make_scorer() Sklearn 函数,该函数采用我们的自定义函数的名称,并创建一个指标,我们可以使用 Sklearn API 来评估模型。我们将把 needs_proba 参数设置为 True,以确保被评估的模型使用 predict_proba() 函数进行预测,以确保它们给出概率而不是类标签。
...
# define the model evaluation the metric
metric = make_scorer(brier_skill_score, needs_proba=True)
下面的 evaluate_model() 函数使用我们的自定义评估指标定义评估过程,将整个训练数据集和模型作为输入,然后返回每个折叠和每个重复的分数样本。
# evaluate a model
def evaluate_model(X, y, model):
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# define the model evaluation the metric
metric = make_scorer(brier_skill_score, needs_proba=True)
# evaluate model
scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
return scores
最后,我们可以使用我们的三个函数来评估一个模型。
首先,我们可以加载数据集并总结输入和输出数组,以确认它们被正确加载。
...
# define the location of the dataset
full_path = 'haberman.csv'
# load the dataset
X, y = load_dataset(full_path)
# summarize the loaded dataset
print(X.shape, y.shape, Counter(y))
在这种情况下,我们将评估预测训练集中正例分布的基线策略,作为测试集中每个案例的概率。
这可以通过使用 DummyCollector 类并设置“策略”为“先验”来自动实现,该策略将预测训练数据集中每个类的先验概率,对于正类,我们知道该概率约为 0.26471。
...
# define the reference model
model = DummyClassifier(strategy='prior')
然后,我们可以通过调用我们的 evaluate_model() 函数来评估模型,并报告结果的平均值和标准差。
...
# evaluate the model
scores = evaluate_model(X, y, model)
# summarize performance
print('Mean BSS: %.3f (%.3f)' % (mean(scores), std(scores)))
将所有这些结合起来,下面列出了使用布瑞尔技能评分在哈贝曼乳腺癌存活数据集上评估基线模型的完整示例。
我们期望基线模型达到 0.0 的 BSS,例如与参考模型相同,因为它是参考模型。
# baseline model and test harness for the haberman dataset
from collections import Counter
from numpy import mean
from numpy import std
from pandas import read_csv
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.metrics import brier_score_loss
from sklearn.metrics import make_scorer
from sklearn.dummy import DummyClassifier
# load the dataset
def load_dataset(full_path):
# load the dataset as a numpy array
data = read_csv(full_path, header=None)
# retrieve numpy array
data = data.values
# split into input and output elements
X, y = data[:, :-1], data[:, -1]
# label encode the target variable to have the classes 0 and 1
y = LabelEncoder().fit_transform(y)
return X, y
# calculate brier skill score (BSS)
def brier_skill_score(y_true, y_prob):
# calculate reference brier score
ref_probs = [0.26471 for _ in range(len(y_true))]
bs_ref = brier_score_loss(y_true, ref_probs)
# calculate model brier score
bs_model = brier_score_loss(y_true, y_prob)
# calculate skill score
return 1.0 - (bs_model / bs_ref)
# evaluate a model
def evaluate_model(X, y, model):
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# define the model evaluation the metric
metric = make_scorer(brier_skill_score, needs_proba=True)
# evaluate model
scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
return scores
# define the location of the dataset
full_path = 'haberman.csv'
# load the dataset
X, y = load_dataset(full_path)
# summarize the loaded dataset
print(X.shape, y.shape, Counter(y))
# define the reference model
model = DummyClassifier(strategy='prior')
# evaluate the model
scores = evaluate_model(X, y, model)
# summarize performance
print('Mean BSS: %.3f (%.3f)' % (mean(scores), std(scores)))
运行该示例首先加载数据集,并按照我们的预期正确报告案例数为 306,以及负案例和正案例的类标签分布。
然后使用重复的分层 k 倍交叉验证来评估带有我们默认策略的 DummyClassifier ,Brier 技能评分的平均值和标准偏差报告为 0.0。这正如我们所料,因为我们正在使用测试工具来评估参考策略。
(306, 3) (306,) Counter({0: 225, 1: 81})
Mean BSS: -0.000 (0.000)
现在我们已经有了测试工具和表现基线,我们可以开始在这个数据集上评估一些模型了。
评估概率模型
在本节中,我们将使用上一节中开发的测试工具来评估一套算法,然后对这些算法进行改进,例如数据准备方案。
概率算法评估
我们将评估一套已知能有效预测概率的模型。
具体来说,这些模型适用于概率框架,并明确预测每个示例的校准概率。因此,这使得它们非常适合这个数据集,即使是在类不平衡的情况下。
我们将评估 Sklearn 库实现的以下六个概率模型:
我们有兴趣直接比较这些算法的结果。我们将根据平均分数以及分数分布来比较每种算法。
我们可以定义一个我们想要评估的模型列表,每个模型都有它们的默认配置或者被配置为不产生警告。
...
# define models
models = [LogisticRegression(solver='lbfgs'), LinearDiscriminantAnalysis(),
QuadraticDiscriminantAnalysis(), GaussianNB(), MultinomialNB(),
GaussianProcessClassifier()]
然后,我们可以枚举每个模型,记录模型的唯一名称,对其进行评估,并报告平均 BSS,并将结果存储到运行结束时。
...
names, values = list(), list()
# evaluate each model
for model in models:
# get a name for the model
name = type(model).__name__[:7]
# evaluate the model and store results
scores = evaluate_model(X, y, model)
# summarize and store
print('>%s %.3f (%.3f)' % (name, mean(scores), std(scores)))
names.append(name)
values.append(scores)
在运行结束时,我们可以创建一个方框和触须图,显示每个算法的结果分布,其中方框显示分数的第 25、50 和 75 个百分点,三角形显示平均结果。每幅图的触须都给出了每种分布的极端情况。
...
# plot the results
pyplot.boxplot(values, labels=names, showmeans=True)
pyplot.show()
将这些联系在一起,完整的示例如下所示。
# compare probabilistic model on the haberman dataset
from numpy import mean
from numpy import std
from pandas import read_csv
from matplotlib import pyplot
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.metrics import brier_score_loss
from sklearn.metrics import make_scorer
from sklearn.linear_model import LogisticRegression
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB
from sklearn.naive_bayes import MultinomialNB
from sklearn.gaussian_process import GaussianProcessClassifier
# load the dataset
def load_dataset(full_path):
# load the dataset as a numpy array
data = read_csv(full_path, header=None)
# retrieve numpy array
data = data.values
# split into input and output elements
X, y = data[:, :-1], data[:, -1]
# label encode the target variable to have the classes 0 and 1
y = LabelEncoder().fit_transform(y)
return X, y
# calculate brier skill score (BSS)
def brier_skill_score(y_true, y_prob):
# calculate reference brier score
ref_probs = [0.26471 for _ in range(len(y_true))]
bs_ref = brier_score_loss(y_true, ref_probs)
# calculate model brier score
bs_model = brier_score_loss(y_true, y_prob)
# calculate skill score
return 1.0 - (bs_model / bs_ref)
# evaluate a model
def evaluate_model(X, y, model):
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# define the model evaluation the metric
metric = make_scorer(brier_skill_score, needs_proba=True)
# evaluate model
scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
return scores
# define the location of the dataset
full_path = 'haberman.csv'
# load the dataset
X, y = load_dataset(full_path)
# define models
models = [LogisticRegression(solver='lbfgs'), LinearDiscriminantAnalysis(),
QuadraticDiscriminantAnalysis(), GaussianNB(), MultinomialNB(),
GaussianProcessClassifier()]
names, values = list(), list()
# evaluate each model
for model in models:
# get a name for the model
name = type(model).__name__[:7]
# evaluate the model and store results
scores = evaluate_model(X, y, model)
# summarize and store
print('>%s %.3f (%.3f)' % (name, mean(scores), std(scores)))
names.append(name)
values.append(scores)
# plot the results
pyplot.boxplot(values, labels=names, showmeans=True)
pyplot.show()
运行该示例首先总结了每个算法的平均和标准偏差(分数越大越好)。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,结果表明,只有两种算法不熟练,显示出负分数,也许逻辑推理(LR)和线性判别分析(LDA)算法表现最好。
>Logisti 0.064 (0.123)
>LinearD 0.067 (0.136)
>Quadrat 0.027 (0.212)
>Gaussia 0.011 (0.209)
>Multino -0.213 (0.380)
>Gaussia -0.141 (0.047)
创建了一个方框和触须图,总结了结果的分布。
有趣的是,即使不是所有算法,也有大部分算法显示出一种差异,表明它们在某些运行中可能是不稳定的。这两个表现最好的模型之间的分布似乎大致相当,所以选择一个基于平均表现的模型可能是一个好的开始。
哈贝曼乳腺癌存活数据集概率模型的盒须图
这是一个好的开始;让我们看看能否通过基础数据准备来改善结果。
具有缩放输入的模型评估
如果变量有不同的度量单位,那么为某些算法缩放数据可能是一个很好的做法,就像在这种情况下一样。
像 LR 和 LDA 这样的算法对数据的性质很敏感,并假设输入变量为高斯分布,这并不是所有情况下都有的。
然而,我们可以用标准化来测试算法,其中每个变量都被转移到零平均值和单位标准偏差。我们将放弃多项式算法,因为它不支持负输入值。
我们可以通过将每个模型包装在管道中来实现这一点,其中第一步是标准缩放器,它将正确地适合训练数据集,并应用于每个 k 倍交叉验证评估中的测试数据集,防止任何数据泄漏。
...
# create a pipeline
pip = Pipeline(steps=[('t', StandardScaler()),('m',model)])
# evaluate the model and store results
scores = evaluate_model(X, y, pip)
下面列出了使用标准化输入数据评估其余五种算法的完整示例。
# compare probabilistic models with standardized input on the haberman dataset
from numpy import mean
from numpy import std
from pandas import read_csv
from matplotlib import pyplot
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.metrics import brier_score_loss
from sklearn.metrics import make_scorer
from sklearn.linear_model import LogisticRegression
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB
from sklearn.gaussian_process import GaussianProcessClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
# load the dataset
def load_dataset(full_path):
# load the dataset as a numpy array
data = read_csv(full_path, header=None)
# retrieve numpy array
data = data.values
# split into input and output elements
X, y = data[:, :-1], data[:, -1]
# label encode the target variable to have the classes 0 and 1
y = LabelEncoder().fit_transform(y)
return X, y
# calculate brier skill score (BSS)
def brier_skill_score(y_true, y_prob):
# calculate reference brier score
ref_probs = [0.26471 for _ in range(len(y_true))]
bs_ref = brier_score_loss(y_true, ref_probs)
# calculate model brier score
bs_model = brier_score_loss(y_true, y_prob)
# calculate skill score
return 1.0 - (bs_model / bs_ref)
# evaluate a model
def evaluate_model(X, y, model):
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# define the model evaluation the metric
metric = make_scorer(brier_skill_score, needs_proba=True)
# evaluate model
scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
return scores
# define the location of the dataset
full_path = 'haberman.csv'
# load the dataset
X, y = load_dataset(full_path)
# define models
models = [LogisticRegression(solver='lbfgs'), LinearDiscriminantAnalysis(),
QuadraticDiscriminantAnalysis(), GaussianNB(), GaussianProcessClassifier()]
names, values = list(), list()
# evaluate each model
for model in models:
# get a name for the model
name = type(model).__name__[:7]
# create a pipeline
pip = Pipeline(steps=[('t', StandardScaler()),('m',model)])
# evaluate the model and store results
scores = evaluate_model(X, y, pip)
# summarize and store
print('>%s %.3f (%.3f)' % (name, mean(scores), std(scores)))
names.append(name)
values.append(scores)
# plot the results
pyplot.boxplot(values, labels=names, showmeans=True)
pyplot.show()
再次运行该示例总结了每个算法的平均和标准偏差。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到,除了高斯过程分类器(GPC)之外,标准化对算法没有太大影响。具有标准化的 GPC 的表现大幅提升,现在是表现最好的技术。这突出了准备数据以满足每个模型的期望的重要性。
>Logisti 0.065 (0.121)
>LinearD 0.067 (0.136)
>Quadrat 0.027 (0.212)
>Gaussia 0.011 (0.209)
>Gaussia 0.092 (0.106)
为每种算法的结果创建方框图和须图,显示平均表现的差异(绿色三角形)和三种表现最好的方法之间相似的分数分布。
这表明所有三种概率方法都在数据集中发现相同的输入到概率的一般映射。
哈贝曼乳腺癌存活数据集数据标准化概率模型的盒须图
有进一步的数据准备,使输入变量更高斯,如幂变换。
基于功率变换的模型评估
幂变换,如 Box-Cox 和 Yeo-Johnson 变换,旨在将分布改变为更具高斯性。
这将有助于我们的数据集中的“年龄”输入变量,并可能有助于“节点”变量和稍微解开分布。
我们可以使用power transformerSklearn 类来执行 Yeo-Johnson,并根据数据集自动确定要应用的最佳参数,例如如何最好地使每个变量更具高斯性。重要的是,作为转换的一部分,该转换器还将标准化数据集,确保我们保持上一节中看到的收益。
电力变换可以利用 log() 函数,该函数对零值不起作用。我们的数据集中有零值,因此我们将使用最小最大缩放器在幂变换之前缩放数据集。
同样,我们可以在管道中使用这种转换,以确保它适合训练数据集,并正确应用于训练和测试数据集,而不会出现数据泄漏。
...
# create a pipeline
pip = Pipeline(steps=[('t1', MinMaxScaler()), ('t2', PowerTransformer()),('m',model)])
# evaluate the model and store results
scores = evaluate_model(X, y, pip)
我们将关注三种表现最好的方法,在本例中是 LR、LDA 和 GPC。
下面列出了完整的示例。
# compare probabilistic models with power transforms on the haberman dataset
from numpy import mean
from numpy import std
from pandas import read_csv
from matplotlib import pyplot
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.metrics import brier_score_loss
from sklearn.metrics import make_scorer
from sklearn.linear_model import LogisticRegression
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis
from sklearn.naive_bayes import GaussianNB
from sklearn.gaussian_process import GaussianProcessClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PowerTransformer
from sklearn.preprocessing import MinMaxScaler
# load the dataset
def load_dataset(full_path):
# load the dataset as a numpy array
data = read_csv(full_path, header=None)
# retrieve numpy array
data = data.values
# split into input and output elements
X, y = data[:, :-1], data[:, -1]
# label encode the target variable to have the classes 0 and 1
y = LabelEncoder().fit_transform(y)
return X, y
# calculate brier skill score (BSS)
def brier_skill_score(y_true, y_prob):
# calculate reference brier score
ref_probs = [0.26471 for _ in range(len(y_true))]
bs_ref = brier_score_loss(y_true, ref_probs)
# calculate model brier score
bs_model = brier_score_loss(y_true, y_prob)
# calculate skill score
return 1.0 - (bs_model / bs_ref)
# evaluate a model
def evaluate_model(X, y, model):
# define evaluation procedure
cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# define the model evaluation the metric
metric = make_scorer(brier_skill_score, needs_proba=True)
# evaluate model
scores = cross_val_score(model, X, y, scoring=metric, cv=cv, n_jobs=-1)
return scores
# define the location of the dataset
full_path = 'haberman.csv'
# load the dataset
X, y = load_dataset(full_path)
# define models
models = [LogisticRegression(solver='lbfgs'), LinearDiscriminantAnalysis(), GaussianProcessClassifier()]
names, values = list(), list()
# evaluate each model
for model in models:
# get a name for the model
name = type(model).__name__[:7]
# create a pipeline
pip = Pipeline(steps=[('t1', MinMaxScaler()), ('t2', PowerTransformer()),('m',model)])
# evaluate the model and store results
scores = evaluate_model(X, y, pip)
# summarize and store
print('>%s %.3f (%.3f)' % (name, mean(scores), std(scores)))
names.append(name)
values.append(scores)
# plot the results
pyplot.boxplot(values, labels=names, showmeans=True)
pyplot.show()
再次运行该示例总结了每个算法的平均和标准偏差。
注:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。
在这种情况下,我们可以看到被评估的三个模型的模型技能进一步提升。我们可以看到 LR 似乎已经超过了其他两种方法。
>Logisti 0.111 (0.123)
>LinearD 0.106 (0.147)
>Gaussia 0.103 (0.096)
为每种算法的结果创建方框图和触须图,这表明与第二好的方法 LDA 相比,LR 的扩散可能更小、更集中。
所有方法仍然平均显示技能,但是分数分布显示在某些情况下低于 0.0(无技能)。
哈贝曼乳腺癌存活数据集上带幂变换的概率模型的盒须图
对新数据进行预测
我们将选择对输入数据进行幂变换的逻辑回归模型作为最终模型。
我们可以在整个训练数据集上定义和拟合这个模型。
...
# fit the model
model = Pipeline(steps=[('t1', MinMaxScaler()), ('t2', PowerTransformer()),('m',LogisticRegression(solver='lbfgs'))])
model.fit(X, y)
一旦拟合,我们可以通过调用 predict_proba() 函数来使用它对新数据进行预测。这将为每个预测返回两个概率,第一个概率用于存活,第二个概率用于非存活,例如它的补码。
例如:
...
row = [31,59,2]
yhat = model.predict_proba([row])
# get percentage of survival
p_survive = yhat[0, 0] * 100
为了证明这一点,我们可以使用拟合模型对一些我们知道有存活的情况和一些我们知道没有存活的情况进行概率预测。
下面列出了完整的示例。
# fit a model and make predictions for the on the haberman dataset
from pandas import read_csv
from sklearn.preprocessing import LabelEncoder
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PowerTransformer
from sklearn.preprocessing import MinMaxScaler
# load the dataset
def load_dataset(full_path):
# load the dataset as a numpy array
data = read_csv(full_path, header=None)
# retrieve numpy array
data = data.values
# split into input and output elements
X, y = data[:, :-1], data[:, -1]
# label encode the target variable to have the classes 0 and 1
y = LabelEncoder().fit_transform(y)
return X, y
# define the location of the dataset
full_path = 'haberman.csv'
# load the dataset
X, y = load_dataset(full_path)
# fit the model
model = Pipeline(steps=[('t1', MinMaxScaler()), ('t2', PowerTransformer()),('m',LogisticRegression(solver='lbfgs'))])
model.fit(X, y)
# some survival cases
print('Survival Cases:')
data = [[31,59,2], [31,65,4], [34,60,1]]
for row in data:
# make prediction
yhat = model.predict_proba([row])
# get percentage of survival
p_survive = yhat[0, 0] * 100
# summarize
print('>data=%s, Survival=%.3f%%' % (row, p_survive))
# some non-survival cases
print('Non-Survival Cases:')
data = [[44,64,6], [34,66,9], [38,69,21]]
for row in data:
# make prediction
yhat = model.predict_proba([row])
# get percentage of survival
p_survive = yhat[0, 0] * 100
# summarize
print('data=%s, Survival=%.3f%%' % (row, p_survive))
运行该示例首先在整个训练数据集上拟合模型。
然后,拟合模型用于预测从数据集文件中选择的我们知道患者存活的病例的存活概率。我们可以看到,对于选择的存活病例,存活的概率很高,在 77%到 86%之间。
然后将一些非存活的情况作为模型的输入,并预测存活的概率。正如我们可能希望的那样,存活的概率不大,在 53%到 63%之间徘徊。
Survival Cases:
>data=[31, 59, 2], Survival=83.597%
>data=[31, 65, 4], Survival=77.264%
>data=[34, 60, 1], Survival=86.776%
Non-Survival Cases:
data=[44, 64, 6], Survival=63.092%
data=[34, 66, 9], Survival=63.452%
data=[38, 69, 21], Survival=53.389%
进一步阅读
如果您想更深入地了解这个主题,本节将提供更多资源。
蜜蜂
- 熊猫. read_csv API
- 熊猫。描述应用编程接口
- 熊猫. DataFrame.hist API
- sklearn.model_selection。重复的策略应用编程接口。
- 硬化。预处理。标签编码 API 。
- 硬化。预处理。动力变压器 API
资料组
摘要
在本教程中,您发现了如何开发一个模型来预测不平衡数据集中患者的存活概率。
具体来说,您了解到:
- 如何加载和探索数据集,并为数据准备和模型选择产生想法。
- 如何评估一套概率模型,并通过适当的数据准备提高它们的表现。
- 如何拟合最终模型并使用它来预测特定情况下的概率。
你有什么问题吗? 在下面的评论中提问,我会尽力回答。