逻辑回归不直接支持不平衡分类。
相反,用于拟合逻辑回归模型的训练算法必须进行修改,以考虑倾斜的分布。这可以通过指定一个类权重配置来实现,该配置用于影响训练期间逻辑回归系数的更新量。
权重可以对多数类示例上的错误给予较少的惩罚,而对少数类示例上的错误给予更多的惩罚。结果是逻辑回归的一个版本,它在不平衡分类任务上表现更好,通常被称为成本敏感或加权逻辑回归。
在本教程中,您将了解用于不平衡分类的成本敏感逻辑回归。
完成本教程后,您将了解:
- 标准逻辑回归如何不支持不平衡分类。
- 如何修改逻辑回归以在拟合系数时根据类权重对模型误差进行加权。
- 如何为逻辑回归配置类权重以及如何网格搜索不同的类权重配置。
通过我的新书《使用 Python 进行不平衡分类》启动您的项目,其中包含分步教程和所有示例的 Python 源代码文件。
让我们开始吧。
- 2020 年 2 月更新:修复了权重计算中的一个错字。
- 2020 年 10 月更新:修复了平衡比描述中的一个错字。

不平衡分类的成本敏感逻辑回归
图片由 Naval S 拍摄,保留部分权利。
教程概述
本教程分为五个部分;它们是:
- 不平衡分类数据集
- 用于不平衡分类的逻辑回归
- 使用 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=2) |
生成后,我们可以总结类别分布,以确认数据集的创建符合预期。
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=2) # 总结类别分布 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类别不平衡的二元分类数据集散点图
接下来,我们可以使用标准逻辑回归模型对数据集进行拟合。
我们将使用重复交叉验证来评估模型,其中包含三次 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('平均 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 从 sklearn.线性模型 导入 LogisticRegression # 生成数据集 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=2) # 定义模型 model = LogisticRegression(solver='lbfgs') # 定义评估过程 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('平均 ROC AUC: %.3f' % mean(scores)) |
运行该示例,评估不平衡数据集上的标准逻辑回归模型,并报告平均 ROC AUC。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
我们可以看到该模型具有技能,ROC AUC 高于 0.5,在这种情况下,平均分数为 0.985。
1 |
平均 ROC AUC: 0.985 |
这为对标准逻辑回归算法执行的任何修改提供了比较基准。
想要开始学习不平衡分类吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
用于不平衡分类的逻辑回归
逻辑回归是二分类任务的有效模型,但默认情况下,它在不平衡分类中无效。
逻辑回归可以进行修改以更好地适用于逻辑回归。
逻辑回归算法的系数使用优化算法进行拟合,该算法最小化训练数据集上模型的负对数似然(损失)。
- 最小化 sum i to n -(log(yhat_i) * y_i + log(1 – yhat_i) * (1 – y_i))
这涉及重复使用模型进行预测,然后根据减少模型损失的方向调整系数。
可以修改给定系数集的损失计算,以考虑类别平衡。
默认情况下,每个类别的错误可能被认为是具有相同的权重,例如 1.0。这些权重可以根据每个类别的重要性进行调整。
- 最小化 sum i to n -(w0 * log(yhat_i) * y_i + w1 * log(1 – yhat_i) * (1 – y_i))
权重应用于损失,因此较小的权重值会导致较小的误差值,进而减少模型系数的更新。较大的权重值会导致较大的误差计算,进而增加模型系数的更新。
- 小权重:重要性较低,模型系数更新较少。
- 大权重:重要性较高,模型系数更新较多。
因此,修改后的逻辑回归版本被称为加权逻辑回归、类加权逻辑回归或成本敏感逻辑回归。
这些权重有时被称为重要性权重。
尽管加权逻辑回归实现起来很简单,但挑战在于为每个类别选择要使用的权重。
使用 Scikit-Learn 的加权逻辑回归
scikit-learn Python 机器学习库提供了支持类权重的逻辑回归实现。
LogisticRegression 类提供了 class_weight 参数,可以将其指定为模型超参数。class_weight 是一个字典,定义了每个类别标签(例如 0 和 1)以及在拟合模型时计算负对数似然时要应用的权重。
例如,类别 0 和 1 的 1 比 1 权重可以定义如下:
1 2 3 4 |
... # 定义模型 weights = {0:1.0, 1:1.0} model = LogisticRegression(solver='lbfgs', class_weight=weights) |
类别加权可以通过多种方式定义;例如:
- 领域专业知识,通过与主题专家交谈确定。
- 调优,通过超参数搜索(如网格搜索)确定。
- 启发式方法,使用一般的最佳实践指定。
使用类别加权的最佳实践是使用训练数据集中类别分布的倒数。
例如,训练数据集的类别分布是少数类与多数类的比例为 1:100。这种比例的倒数可以用于多数类为 1,少数类为 100;例如
1 2 3 4 |
... # 定义模型 weights = {0:1.0, 1:100.0} model = LogisticRegression(solver='lbfgs', class_weight=weights) |
我们也可以使用分数定义相同的比例,并达到相同的结果;例如
1 2 3 4 |
... # 定义模型 weights = {0:0.01, 1:1.0} model = LogisticRegression(solver='lbfgs', class_weight=weights) |
我们可以使用上一节中定义的相同评估程序来评估具有类权重的逻辑回归算法。
我们预计加权类别的逻辑回归版本将比不带任何类别权重的标准逻辑回归版本表现更好。
完整的示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 对不平衡分类数据集的加权逻辑回归模型 from numpy import mean from sklearn.datasets import make_classification from sklearn.model_selection import cross_val_score from sklearn.model_selection import RepeatedStratifiedKFold 从 sklearn.线性模型 导入 LogisticRegression # 生成数据集 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=2) # 定义模型 weights = {0:0.01, 1:1.0} model = LogisticRegression(solver='lbfgs', class_weight=weights) # 定义评估过程 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('平均 ROC AUC: %.3f' % mean(scores)) |
运行示例会准备合成的不平衡分类数据集,然后使用重复交叉验证评估加权类别的逻辑回归版本。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
报告了平均 ROC AUC 分数,在这种情况下,显示的分数优于未加权版本的逻辑回归,为 0.989,而未加权版本为 0.985。
1 |
平均 ROC AUC: 0.989 |
scikit-learn 库提供了类别权重最佳实践启发式的实现。
它通过 compute_class_weight() 函数实现,计算方式为
- n_samples / (n_classes * n_samples_with_class)
我们可以手动测试我们数据集上的这个计算。例如,我们数据集中有 10,000 个示例,其中 9900 个在类别 0 中,100 个在类别 1 中。
类别 0 的权重计算如下
- 权重 = 样本数 / (类别数 * 该类别样本数)
- 权重 = 10000 / (2 * 9900)
- 权重 = 10000 / 19800
- 权重 = 0.05
类别 1 的权重计算如下
- 权重 = 样本数 / (类别数 * 该类别样本数)
- 权重 = 10000 / (2 * 100)
- 权重 = 10000 / 200
- 权重 = 50
我们可以通过调用 compute_class_weight() 函数并指定 class_weight 为“balanced”来确认这些计算。例如
1 2 3 4 5 6 7 8 9 |
# 计算启发式类别权重 from sklearn.utils.class_weight import compute_class_weight from sklearn.datasets import make_classification # 生成 2 类数据集 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=2) # 计算类别权重 weighting = compute_class_weight('balanced', [0,1], y) print(weighting) |
运行示例,我们可以看到类别 0 的权重约为 0.5,类别 1 的权重约为 50。
这些值与我们手动计算的结果相符。
1 |
[ 0.50505051 50. ] |
这些值也与我们上面对训练数据集中类别分布比例倒数启发式计算的结果相符;例如
- 0.5:50 == 1:100
我们可以通过将 class_weight 参数设置为 'balanced',直接将默认的类平衡用于 LogisticRegression 类。例如
1 2 3 |
... # 定义模型 model = LogisticRegression(solver='lbfgs', 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 从 sklearn.线性模型 导入 LogisticRegression # 生成数据集 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=2) # 定义模型 model = LogisticRegression(solver='lbfgs', 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('平均 ROC AUC: %.3f' % mean(scores)) |
注意:由于算法或评估过程的随机性,或数值精度的差异,您的结果可能会有所不同。考虑多次运行示例并比较平均结果。
运行示例会得到与我们手动指定逆类比率相同的平均 ROC AUC。
1 |
平均 ROC AUC: 0.989 |
网格搜索加权逻辑回归
使用训练数据逆比例的类别权重只是一种启发式方法。
使用不同的类别权重可能会获得更好的性能,这也将取决于用于评估模型的性能指标的选择。
在本节中,我们将对加权逻辑回归的不同类别权重范围进行网格搜索,并发现哪个结果能获得最佳的 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("最佳: %f 使用 %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 从 sklearn.线性模型 导入 LogisticRegression # 生成数据集 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=2) # 定义模型 model = LogisticRegression(solver='lbfgs') # 定义网格 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("最佳: %f 使用 %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.989077 使用 {'class_weight': {0: 1, 1: 100}} 0.982498 (0.016722) 使用: {'class_weight': {0: 100, 1: 1}} 0.983623 (0.015760) 使用: {'class_weight': {0: 10, 1: 1}} 0.985387 (0.013890) 使用: {'class_weight': {0: 1, 1: 1}} 0.988044 (0.010384) 使用: {'class_weight': {0: 1, 1: 10}} 0.989077 (0.006865) 使用: {'class_weight': {0: 1, 1: 100}} |
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
论文
- 稀有事件数据中的逻辑回归, 2001.
- 基于选择样本的选择概率估计, 1977.
书籍
- 从不平衡数据集中学习 (Learning from Imbalanced Data Sets), 2018.
- 不平衡学习:基础、算法与应用 (Imbalanced Learning: Foundations, Algorithms, and Applications), 2013.
API
- sklearn.utils.class_weight.compute_class_weight API.
- sklearn.linear_model.LogisticRegression API.
- sklearn.model_selection.GridSearchCV API.
总结
在本教程中,您学习了用于不平衡分类的成本敏感逻辑回归。
具体来说,你学到了:
- 标准逻辑回归如何不支持不平衡分类。
- 如何修改逻辑回归以在拟合系数时根据类权重对模型误差进行加权。
- 如何为逻辑回归配置类权重以及如何网格搜索不同的类权重配置。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
杰森,书快读完了。
真是太棒了!
一个小小的建议:我希望看到更多关于算法的解释性信息图。
为你的进步喝彩!
很好的建议,谢谢。
我希望看到少数类的精确率和召回率有所提高。
谢谢,好建议。
你好 Jason,
关于 SVC 和 linearSVC 的问题。
有什么区别?
我正在尝试使用 1000 个观测值(750 个训练 + 250 个测试)进行情感分析。
对于分析,使用 SVC 还是 linearSVC 更好?
linearSVC 中 C 超参数的含义是什么(简单来说)?
我发现 C 参数在其他算法中也很常见。含义是否相同?
谢谢
SVC 可以通过线性核进行线性 SVC。linear SVC 只使用线性核,没有其他,并且针对此用例进行了优化——更快/更高效。
更多关于 C 的信息在这里
https://machinelearning.org.cn/support-vector-machines-for-machine-learning/
嗨
您的书是否涵盖了依赖于示例的成本敏感分类?
不,只基于类的成本。
感谢您的教程
不客气,希望它能帮助您的项目!
亲爱的 Jason Browniee,
早上好。
您什么时候会有 R 语言版本的书。
诚挚的问候,
Macilane
目前没有计划。鉴于 Python 是目前最流行的机器学习语言,我的重点是 Python。
“例如,我们的数据集中有 10,000 个示例,其中 9990 个在类别 0 中,100 个在类别 1 中。”
不是 9990-10 或 9900-100 吗?
好文章,解释清晰
谢谢,已修复!
您能提供“Counter”的代码吗?
NameError: 名称 'Counter' 未定义
谢谢你
您必须复制包含重要语句的完整代码示例。
感谢您的出色工作。这非常有帮助。我正在寻找如何同时使用分类和回归模型来解决特定问题?假设我想研究气温与土壤温度之间的关系,同时使用分类和回归模型,以获得最佳拟合和决策边界。有没有任何文章可以说明这两种方法的步骤会有何不同?
不客气。
抱歉,我没有这方面的例子。
你好 Jason,
感谢又一篇精彩文章。
我假设对数据集进行过采样和欠采样的替代方法(而不是使用类别权重)也适用于逻辑回归。
我说的对吗?
如果我没错,您会建议在面对不平衡数据集时同时采用这两种方法并比较结果吗?
谢谢你。
是的,您可以使用重采样数据而不是使用成本敏感分类器。
不,结合这两种方法并没有帮助,因为重采样后类别会平衡。尽管如此,还是可以尝试一下!也许您会发现一些不直观的东西?
你好,
好文!
您能否也添加关于如何在未见测试数据集上部署此模型的代码?
谢谢
您是指进行预测吗,如果是,请参阅此处
https://machinelearning.org.cn/make-predictions-scikit-learn/
嗨,Jason,
感谢您的教程。它们激励我购买了您的《使用 Python 进行不平衡分类》一书。
我正在尝试将本教程应用于多标签分类问题。我正在努力弄清楚如何在 cross_val_score 方法中正确设置评分指标。
任何提示都将不胜感激。
此致,
马里奥
谢谢!
您可以将要优化的指标指定给 cross_val_score 函数。
R 中可以使用逻辑回归、决策树和随机森林进行成本敏感学习吗?
也许吧,抱歉我不知道。
感谢这篇精彩的文章。它非常有帮助!
我有一个问题想请教您。
“sklearn.linear_model.LogisticRegression”这个类默认模型是否使用最大似然估计和 lbfgs 来估计参数?如果是,这是否意味着 sklearn 的类使用 lbfgs 是因为我们不能使用解析解来最小化负对数似然损失?
抱歉我的英语不好。
不客气。
是的,它使用 MLE 并使用二阶优化算法,因为没有稳定的解析解。
谢谢您的回复!
不客气。
您一开始提到
“逻辑回归不直接支持不平衡分类。”
您有支持这一说法的理论或理论论文吗?
想想您如何在逻辑回归中计算误差分数。然后您会发现不平衡分类会偏向多数类别。
感谢所有全面的教程!
我想知道是否可以使用 sklearn 对聚类数据进行具有稳健标准误差的逻辑回归?
我的网络搜索没有找到 sklearn 的任何结果(只找到了 statsmodel,但它有一些其他缺点)。
我有一个数据集,其中包含来自 15 个研究地点的观测值,并且假设一个研究地点内观测值之间的方差小于研究地点之间。因此,我们希望计算考虑聚类(研究地点)的稳健标准误差。
有人知道 sklearn 是否可以做到这一点吗?
嗨,丽兹……这里有一些有用的想法
https://stackoverflow.com/questions/75814525/statsmodels-clustered-logit-model-with-robust-standard-errors