scikit-learn中的class_weight参数如何工作?


115

我在理解class_weightscikit-learn的Logistic回归中的参数如何运行时遇到很多麻烦。

情况

我想使用逻辑回归对非常不平衡的数据集进行二进制分类。这些类别分别标记为0(负)和1(正),观察到的数据比例约为19:1,大多数样本的结果均为负。

第一次尝试:手动准备训练数据

我将我拥有的数据分为不相交的数据集进行训练和测试(大约80/20)。然后,我手工对训练数据进行了随机采样,得到的训练数据比例与19:1不同。从2:1-> 16:1。

然后,我对这些不同的训练数据子集进行了逻辑回归训练,并根据不同的训练比例绘制了召回率(= TP /(TP + FN))。当然,召回率是根据不连续的TEST样本(观察到的比例为19:1)计算的。注意,尽管我在不同的训练数据上训练了不同的模型,但我在相同(不相交)的测试数据上计算了所有模型的召回率。

结果符合预期:以2:1的训练比例召回率约为60%,到16:1时召回率很快下降。比例为2:1-> 6:1,召回率在5%以上。

第二次尝试:网格搜索

接下来,我想测试不同的正则化参数,因此我使用了GridSearchCV并制作了一个包含C参数值和参数值的网格class_weight。要将我的n:m否定:肯定的训练样本比例转换成class_weight我的词典语言,我认为我只是指定了几个字典,如下所示:

{ 0:0.67, 1:0.33 } #expected 2:1
{ 0:0.75, 1:0.25 } #expected 3:1
{ 0:0.8, 1:0.2 }   #expected 4:1

并且我还包括Noneauto

这次的结果是完全错误的。class_weight除了的每个值,我所有的召回都很小(<0.05)auto。因此,我只能假设我对如何设置class_weight字典的理解是错误的。有趣的是,class_weight对于的所有值,网格搜索中“自动” 的值约为59%C,我猜想它与1:1平衡吗?

我的问题

  1. 您如何正确使用class_weight训练数据与实际提供的数据取得不同的平衡?具体来说,我传递给哪个字典class_weight来使用n:m比例的负数:正数训练样本?

  2. 如果您将各种class_weight字典传递给GridSearchCV,则在交叉验证期间,它将根据字典重新平衡训练折叠数据,但使用真实给定的样本比例来计算我在测试折叠上的得分函数吗?这很关键,因为任何度量标准仅对来自观察到的比例的数据有用。

  3. 就比例而言,auto价值是class_weight什么?我阅读了文档,并假设“与数据频率成反比地平衡数据”只是意味着将其设为1:1。这样对吗?如果没有,有人可以澄清吗?


当使用class_weight时,损失函数会被修改。例如,代替交叉熵,它变成了加权的交叉熵。towardsdatascience.com/...
普拉香特

Answers:


123

首先,仅靠召回可能并不好。通过将所有内容都归为肯定类,您可以简单地实现100%的召回率。我通常建议使用AUC选择参数,然后找到您感兴趣的工作点阈值(例如给定的精度水平)。

对于如何class_weight作品:它惩罚失误的样品class[i]class_weight[i]的,而不是1。所以高类的重量意味着要更多地强调的一类。从您看来,类0的频率比类1的频率高19倍。因此,应class_weight相对于类0 增加类1的频率,例如{0:.1,1:.9}。如果class_weight不等于1,则基本上会更改正则化参数。

对于class_weight="auto"工作原理,您可以看一下这个讨论。在开发版本中,您可以使用class_weight="balanced",它更容易理解:从本质上讲,它意味着复制较小的类,直到您拥有与较大类相同的样本为止,但是是以隐式方式进行的。


1
谢谢!快速问题:我为清楚起见提到召回,实际上我正在尝试确定使用哪个AUC作为衡量标准。我的理解是我应该最大化ROC曲线下的面积,或者最大化召回率与精确度曲线下的面积以找到参数。以这种方式选择参数后,我相信我可以通过沿曲线滑动来选择分类的阈值。这是你的意思吗?如果是这样,那么,如果我的目标是捕获尽可能多的TP,那两条曲线中的哪一条最有意义?另外,感谢您为scikit-learn所做的工作和贡献!!!
kilgoretrout

1
我认为使用ROC将是更标准的方法,但我认为不会有很大的不同。但是,您确实需要一些标准来选择曲线上的点。
Andreas Mueller

3
@MiNdFrEaK我认为安德鲁的意思是估计量是在少数类中复制样本,以便平衡不同类别的样本。它只是以隐式方式进行过采样。
肖恩·田

8
@MiNdFrEaK和肖恩·田(Shawn Tian):当您使用“平衡”时,基于SV的分类器不会产生更多较小类的样本。从字面上讲,它惩罚了较小类的错误。换句话说,这是一个错误并且会引起误解,尤其是在无法承受创建更多样本的大型数据集中。该答案必须进行编辑。
巴勃罗·里瓦斯

4
scikit-learn.org/dev/glossary.html#term-class-weight根据算法不同,将使用不同的类别权重:对于线性模型(例如线性SVM或逻辑回归),类别权重将通过以下方式改变损失函数:用类别权重对每个样本的损失进行加权。对于基于树的算法,将使用类权重对分割标准进行重新加权。但是请注意,这种重新平衡并未考虑每个类别中样本的权重。
prashanth

2

第一个答案有助于理解其工作原理。但是我想了解我应该如何在实践中使用它。

摘要

  • 对于没有噪声的中度不平衡数据,应用类权重没有太大差异
  • 对于带有噪声且严重失衡的中等失衡数据,最好应用类权重
  • class_weight="balanced"在您不想手动优化的情况下,param的效果不错
  • class_weight="balanced"您捕捉更真实事件(高TRUE召回),而且你更有可能得到虚假警报(降低TRUE精度)
    • 结果,由于所有误报,总的TRUE百分比可能高于实际值
    • 如果误报是个问题,AUC可能会误导您
  • 无需将决策阈值更改为不平衡百分比,即使是严重的不平衡,也可以保持0.5(或取决于您所需的值)

NB

使用RF或GBM时,结果可能会有所不同。sklearn没有 class_weight="balanced" GBM,但是lightgbmLGBMClassifier(is_unbalance=False)

# scikit-learn==0.21.3
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score, classification_report
import numpy as np
import pandas as pd

# case: moderate imbalance
X, y = datasets.make_classification(n_samples=50*15, n_features=5, n_informative=2, n_redundant=0, random_state=1, weights=[0.8]) #,flip_y=0.1,class_sep=0.5)
np.mean(y) # 0.2

LogisticRegression(C=1e9).fit(X,y).predict(X).mean() # 0.184
(LogisticRegression(C=1e9).fit(X,y).predict_proba(X)[:,1]>0.5).mean() # 0.184 => same as first
LogisticRegression(C=1e9,class_weight={0:0.5,1:0.5}).fit(X,y).predict(X).mean() # 0.184 => same as first
LogisticRegression(C=1e9,class_weight={0:2,1:8}).fit(X,y).predict(X).mean() # 0.296 => seems to make things worse?
LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X).mean() # 0.292 => seems to make things worse?

roc_auc_score(y,LogisticRegression(C=1e9).fit(X,y).predict(X)) # 0.83
roc_auc_score(y,LogisticRegression(C=1e9,class_weight={0:2,1:8}).fit(X,y).predict(X)) # 0.86 => about the same
roc_auc_score(y,LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X)) # 0.86 => about the same

# case: strong imbalance
X, y = datasets.make_classification(n_samples=50*15, n_features=5, n_informative=2, n_redundant=0, random_state=1, weights=[0.95])
np.mean(y) # 0.06

LogisticRegression(C=1e9).fit(X,y).predict(X).mean() # 0.02
(LogisticRegression(C=1e9).fit(X,y).predict_proba(X)[:,1]>0.5).mean() # 0.02 => same as first
LogisticRegression(C=1e9,class_weight={0:0.5,1:0.5}).fit(X,y).predict(X).mean() # 0.02 => same as first
LogisticRegression(C=1e9,class_weight={0:1,1:20}).fit(X,y).predict(X).mean() # 0.25 => huh??
LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X).mean() # 0.22 => huh??
(LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict_proba(X)[:,1]>0.5).mean() # same as last

roc_auc_score(y,LogisticRegression(C=1e9).fit(X,y).predict(X)) # 0.64
roc_auc_score(y,LogisticRegression(C=1e9,class_weight={0:1,1:20}).fit(X,y).predict(X)) # 0.84 => much better
roc_auc_score(y,LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X)) # 0.85 => similar to manual
roc_auc_score(y,(LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict_proba(X)[:,1]>0.5).astype(int)) # same as last

print(classification_report(y,LogisticRegression(C=1e9).fit(X,y).predict(X)))
pd.crosstab(y,LogisticRegression(C=1e9).fit(X,y).predict(X),margins=True)
pd.crosstab(y,LogisticRegression(C=1e9).fit(X,y).predict(X),margins=True,normalize='index') # few prediced TRUE with only 28% TRUE recall and 86% TRUE precision so 6%*28%~=2%

print(classification_report(y,LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X)))
pd.crosstab(y,LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X),margins=True)
pd.crosstab(y,LogisticRegression(C=1e9,class_weight="balanced").fit(X,y).predict(X),margins=True,normalize='index') # 88% TRUE recall but also lot of false positives with only 23% TRUE precision, making total predicted % TRUE > actual % TRUE
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.