决策树算法对于平衡分类是有效的,尽管它在不平衡数据集上的表现不佳。
树的分割点被选择以最佳地将样本分成两组,且混合程度最低。当两组都由一个类别的样本主导时,用于选择分割点的标准会显示出良好的分离,而实际上,少数类别的样本被忽略了。
这个问题可以通过修改用于评估分割点的标准来解决,使其考虑到每个类别的重要性,这通常被称为加权分割点或加权决策树。
在本教程中,您将学习用于不平衡分类的加权决策树。
完成本教程后,您将了解:
- 标准决策树算法如何不支持不平衡分类。
- 决策树算法如何通过在选择分割时按类别权重加权模型错误来进行修改。
- 如何为决策树算法配置类别权重,以及如何网格搜索不同的类别权重配置。
通过我的新书《使用Python进行不平衡分类》启动您的项目,其中包括逐步教程和所有示例的Python源代码文件。
让我们开始吧。

如何实现不平衡分类的加权决策树
图片由Bonnie Moreland提供,保留部分权利。
教程概述
本教程分为四个部分;它们是
- 不平衡分类数据集
- 不平衡分类的决策树
- 使用Scikit-Learn的加权决策树
- 网格搜索加权决策树
不平衡分类数据集
在我们深入探讨不平衡分类的决策修改之前,让我们首先定义一个不平衡分类数据集。
我们可以使用make_classification()函数来定义一个合成的不平衡两类分类数据集。我们将生成10,000个样本,其中少数类与多数类的比例约为1:100。
1 2 3 4 |
... # 定义数据集 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=3) |
生成后,我们可以总结类别分布,以确认数据集的创建符合预期。
1 2 3 4 |
... # 总结类别分布 counter = Counter(y) print(counter) |
最后,我们可以创建一个样本的散点图,并根据类别标签对其进行着色,以帮助理解对该数据集中的样本进行分类的挑战。
1 2 3 4 5 6 7 |
... # 按类别标签绘制样本散点图 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 生成并绘制一个合成的不平衡分类数据集 from collections import Counter from sklearn.datasets import make_classification from matplotlib import pyplot from numpy import where # 定义数据集 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=3) # 总结类别分布 counter = Counter(y) print(counter) # 按类别标签绘制样本散点图 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个样本。
1 |
Counter({0: 9900, 1: 100}) |
接下来,创建数据集的散点图,显示多数类(蓝色)的大量样本和少数类(橙色)的少量样本,并存在一些适度的类别重叠。

具有1比100类别不平衡的二元分类数据集散点图
接下来,我们可以在数据集上拟合一个标准决策树模型。
决策树可以使用 scikit-learn 库中的 DecisionTreeClassifier 类来定义。
1 2 3 |
... # 定义模型 model = DecisionTreeClassifier() |
我们将使用重复交叉验证来评估模型,进行三次重复的10折交叉验证。模型性能将使用平均ROC曲线下面积(ROC AUC)报告,该平均值在所有重复和所有折叠中进行。
1 2 3 4 5 6 7 |
... # 定义评估过程 cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # 评估模型 scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1) # 总结性能 print('Mean ROC AUC: %.3f' % mean(scores)) |
综合起来,下面列出了在不平衡分类问题上定义和评估标准决策树模型的完整示例。
决策树是二元分类任务的有效模型,但默认情况下,它们在不平衡分类方面效果不佳。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 在不平衡分类数据集上拟合决策树 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.tree import DecisionTreeClassifier # 生成数据集 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=3) # 定义模型 model = DecisionTreeClassifier() # 定义评估过程 cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # 评估模型 scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1) # 总结性能 print('Mean ROC AUC: %.3f' % mean(scores)) |
运行示例会评估不平衡数据集上的标准决策树模型,并报告平均ROC AUC。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的结果可能会有所不同。请尝试运行示例几次并比较平均结果。
我们可以看到模型具有一定的技能,ROC AUC高于0.5,在这种情况下达到了0.746的平均分数。
1 |
平均ROC AUC: 0.746 |
这为对标准决策树算法进行的任何修改提供了比较基线。
想要开始学习不平衡分类吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
不平衡分类的决策树
决策树算法也被称为分类和回归树(CART),它涉及构建一棵树来对训练数据集中的样本进行分类。
可以认为这棵树将训练数据集进行划分,样本沿着树的决策点向下移动,最终到达树的叶子节点并被分配一个类别标签。
这棵树是通过使用数据集中变量的值来分割训练数据集而构建的。在每个点上,以贪婪的方式选择数据中的分割,该分割会产生最纯净(混合最少)的样本组。
这里,纯度意味着将样本清晰地分离成组,其中由所有0或所有1类别组成的样本组是最纯净的,而50-50混合的两个类别是最不纯净的。纯度最常使用基尼不纯度(Gini impurity)计算,但也可以使用熵计算。
纯度度量的计算涉及计算给定类别的样本被分割错误分类的概率。计算这些概率涉及对每个组中每个类别中的样本数量进行求和。
可以更新分割准则,使其不仅考虑分割的纯度,而且还根据每个类别的重要性进行加权。
我们对成本敏感型树归纳的直觉是,根据错误分类实例所属类别的成本,按比例修改实例的权重……
——一种用于生成成本敏感型树的实例加权方法,2002年。
这可以通过将每个组中的样本计数替换为加权和来实现,其中系数用于对和进行加权。
更重要的类别被赋予更大的权重,而不那么重要的类别被赋予更小的权重。
- 小权重:重要性较低,对节点纯度的影响较小。
- 大权重:重要性较高,对节点纯度的影响较大。
可以将较小的权重分配给多数类,这样可以改善(降低)节点纯度得分,否则该节点可能看起来排序不佳。反过来,这可能允许将多数类中的更多样本分类为少数类,从而更好地适应少数类中的这些样本。
误分类成本较高的类别中的实例被赋予更高的权重。
——第71页,从不平衡数据集中学习,2018年。
因此,这种决策树算法的修改被称为加权决策树、类别加权决策树或成本敏感型决策树。
修改分割点计算是最常见的,尽管已经有很多研究针对决策树构建算法的其他一系列修改,以更好地适应类别不平衡。
使用 Scikit-Learn 的加权决策树
scikit-learn Python 机器学习库提供了一个支持类别加权的决策树算法实现。
DecisionTreeClassifier 类提供了 `class_weight` 参数,可以将其指定为模型超参数。`class_weight` 是一个字典,定义了每个类别标签(例如 0 和 1)以及在拟合模型时决策树中分割点组纯度计算中要应用的权重。
例如,类别 0 和 1 的 1 比 1 权重可以定义如下:
1 2 3 4 |
... # 定义模型 weights = {0:1.0, 1:1.0} model = DecisionTreeClassifier(class_weight=weights) |
类别加权可以通过多种方式定义;例如:
- 领域专业知识,通过与主题专家交谈确定。
- 调优,通过超参数搜索(如网格搜索)确定。
- 启发式方法,使用一般的最佳实践指定。
使用类别加权的最佳实践是使用训练数据集中类别分布的倒数。
例如,测试数据集的类别分布是少数类与多数类的比例为1:100。这个比例的倒数可以用作多数类的权重为1,少数类的权重为100。
例如:
1 2 3 4 |
... # 定义模型 weights = {0:1.0, 1:100.0} model = DecisionTreeClassifier(class_weight=weights) |
我们也可以用分数定义相同的比例,并达到相同的结果。
例如:
1 2 3 4 |
... # 定义模型 weights = {0:0.01, 1:1.0} model = DecisionTreeClassifier(class_weight=weights) |
通过将 `class_weight` 设置为 `'balanced'`,可以直接使用这种启发式方法。
例如:
1 2 3 |
... # 定义模型 model = DecisionTreeClassifier(class_weight='balanced') |
我们可以使用上一节中定义的相同评估程序来评估具有类别加权的决策树算法。
我们期望类别加权版本的决策树比没有任何类别加权的标准版本表现更好。
完整的示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 在不平衡分类数据集上使用类别权重的决策树 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.tree import DecisionTreeClassifier # 生成数据集 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=3) # 定义模型 model = DecisionTreeClassifier(class_weight='balanced') # 定义评估过程 cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # 评估模型 scores = cross_val_score(model, X, y, scoring='roc_auc', cv=cv, n_jobs=-1) # 总结性能 print('Mean ROC AUC: %.3f' % mean(scores)) |
运行示例会准备合成的不平衡分类数据集,然后使用重复交叉验证评估类别加权版本的决策树算法。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的结果可能会有所不同。请尝试运行示例几次并比较平均结果。
报告了平均 ROC AUC 分数,在本例中,它显示了比未加权版本的决策树算法更好的分数:0.759 对比 0.746。
1 |
平均 ROC AUC:0.759 |
网格搜索加权决策树
使用训练数据逆比例的类别权重只是一种启发式方法。
使用不同的类别权重可能会获得更好的性能,这也将取决于用于评估模型的性能指标的选择。
在本节中,我们将对加权决策树的一系列不同类别权重进行网格搜索,并找出哪个权重配置能产生最佳的 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类的网格搜索参数,如下所示:
1 2 3 4 |
... # 定义网格 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 评估模型性能。
1 2 3 4 5 |
... # 定义评估过程 cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # 定义网格搜索 grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=cv, scoring='roc_auc') |
执行后,我们可以总结最佳配置以及所有结果,如下所示:
1 2 3 4 5 6 7 8 9 |
... # 报告最佳配置 print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_)) # 报告所有配置 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)) |
综合起来,下面的示例对不平衡数据集上的决策树算法的五种不同类别权重进行网格搜索。
我们可能会期望启发式类别加权是表现最好的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# 使用决策树对不平衡分类进行网格搜索类别权重 from numpy import mean from sklearn.datasets import make_classification from sklearn.model_selection import GridSearchCV from sklearn.model_selection import RepeatedStratifiedKFold from sklearn.tree import DecisionTreeClassifier # 生成数据集 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=3) # 定义模型 model = DecisionTreeClassifier() # 定义网格 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) # 定义评估过程 cv = RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1) # 定义网格搜索 grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=cv, scoring='roc_auc') # 执行网格搜索 grid_result = grid.fit(X, y) # 报告最佳配置 print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_)) # 报告所有配置 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分数的影响可能会很有趣。
1 2 3 4 5 6 |
最佳:0.752643,使用 {'class_weight': {0: 1, 1: 100}} 0.737306 (0.080007) with: {'class_weight': {0: 100, 1: 1}} 0.747306 (0.075298) with: {'class_weight': {0: 10, 1: 1}} 0.740606 (0.074948) with: {'class_weight': {0: 1, 1: 1}} 0.747407 (0.068104) with: {'class_weight': {0: 1, 1: 10}} 0.752643 (0.073195) with: {'class_weight': {0: 1, 1: 100}} |
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
论文
- 一种用于生成成本敏感型树的实例加权方法, 2002.
书籍
- 从不平衡数据集中学习 (Learning from Imbalanced Data Sets), 2018.
- 不平衡学习:基础、算法与应用 (Imbalanced Learning: Foundations, Algorithms, and Applications), 2013.
API
- sklearn.utils.class_weight.compute_class_weight API.
- sklearn.tree.DecisionTreeClassifier API.
- sklearn.model_selection.GridSearchCV API.
总结
在本教程中,您学习了不平衡分类的加权决策树。
具体来说,你学到了:
- 标准决策树算法如何不支持不平衡分类。
- 决策树算法如何通过在选择分割时按类别权重加权模型错误来进行修改。
- 如何为决策树算法配置类别权重,以及如何网格搜索不同的类别权重配置。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
你好 Jason,
我有一个一般性问题。
我从去年五月开始学习M/L和D/L,我在想是否值得继续
学习机器学习(即Scikit-Learn)还是最好学习深度学习(Keras)。
或者最好两者都继续关注?
两者各有什么优缺点(简而言之)?
谢谢
两者兼顾。它们是解决不同问题的不同工具。
大多数用于分类和回归的表格数据最好使用 sklearn 解决。大多数其他数据,如图像、文本等,最好使用神经网络解决。
我正在想,调整类别权重是否足以确定最佳参数。原因是您没有校准决策边界(阈值)。如果没有这个,我想知道结果是否具有决定性?
不,调整权重通常是建模流程中的一步,但在类别不平衡时可以提供很大的帮助。
你好,
如何为不平衡多类别数据集创建加权决策树?
完全一样的方式。
指定数据集中类别的平衡。
你好,
我认为如果我们在 cross_validation_score() 中将 'roc_auc' 作为评分参数传入,则需要在评分中进行一些更改。
在 roc_auc_score() 中有一个名为 multi_class 的参数,其默认值为 'raise',如果将其用于多类问题,则会引发错误。如果遇到多类问题,我们需要将 'ovr' 或 'ovo' 传入 multi_class 参数。
如果我错了,请纠正我,因为当我在多类数据上运行相同的评估代码行时,我遇到了一个错误“ValueError: multiclass format is not supported”。
ROC AUC 不支持多类别分类。
对于不平衡多类问题,我应该为“scoring”参数使用什么参数?
谢谢!
只要你指定哪些类是少数类,哪些是多数类,大多数二元问题的度量指标都可以用于多类问题。
这将有助于选择
https://machinelearning.org.cn/tour-of-evaluation-metrics-for-imbalanced-classification/
你好,
我正在不平衡的多类数据上运行相同的标准模型,当我使用“cross_val_score”和参数“scoring”=‘roc_auc’评估它时,我遇到了一个错误。“ValueError: multiclass format is not supported”。
有什么办法可以解决吗?
请注意,我尝试了不同的方法,也尝试升级sklearn。
谢谢!
您不能将 ROC AUC 用于超过两个类别。
你好,
我可能遗漏了什么,但是如何在DT根节点中分配误分类成本呢,因为在第一个节点中我仍然不知道哪些是FP和FN案例?我在实现自己的成本敏感DT的初始步骤中遇到了困难。
谢谢!
您可以设置“class_weight”变量,并且该权重将用于每次分割的评估。
嗨,Jason,您能详细说明 class_weight 是如何修改熵方程的吗?sklearn 文档不清楚。一种可能性是关于所有类别的求和 wi*p(X=i)log(wi*p(X=i)),其中 wi 是每个类别的权重。谢谢。
嗨,Brian……以下内容可能对您有兴趣
https://machinelearning.org.cn/cross-entropy-for-machine-learning/
我会看看这个——谢谢!
Brian,继续努力!