问题:scikit-learn中的class_weight参数如何工作?
我在理解class_weight
scikit-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
并且我还包括None
和auto
。
这次的结果是完全错误的。class_weight
除了的每个值,我所有的召回都很小(<0.05)auto
。因此,我只能假设我对如何设置class_weight
字典的理解是错误的。有趣的是,class_weight
对于的所有值,网格搜索中“自动” 的值约为59%C
,我猜想它与1:1平衡吗?
我的问题
您如何正确使用
class_weight
训练数据与实际提供的数据取得不同的平衡?具体来说,我传递给哪个字典class_weight
来使用n:m比例的负数:正数训练样本?如果您将各种
class_weight
字典传递给GridSearchCV,则在交叉验证期间,它将根据字典重新平衡训练折叠数据,但使用真实给定的样本比例来计算我在测试折叠上的得分函数吗?这很关键,因为任何度量标准仅对来自观察到的比例的数据有用。就比例而言,
auto
价值是class_weight
什么?我阅读了文档,并假设“与数据频率成反比地平衡数据”只是意味着将其设为1:1。这样对吗?如果没有,有人可以澄清吗?
回答 0
首先,仅靠召回可能并不好。通过将所有内容都归为肯定类,您可以简单地实现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
第一个答案有助于理解其工作原理。但是我想了解我应该如何在实践中使用它。
摘要
- 对于没有噪声的中度不平衡数据,应用类权重没有太大差异
- 对于带有噪声且严重失衡的中等失衡数据,最好应用类权重
class_weight="balanced"
在您不想手动优化的情况下,param的效果不错- 与
class_weight="balanced"
您捕捉更真实事件(高TRUE召回),而且你更有可能得到虚假警报(降低TRUE精度)- 结果,由于所有误报,总的TRUE百分比可能高于实际值
- 如果误报是个问题,AUC可能会误导您
- 无需将决策阈值更改为不平衡百分比,即使是严重的不平衡,也可以保持0.5(或取决于您所需的值)
NB
使用RF或GBM时,结果可能会有所不同。sklearn没有 class_weight="balanced"
GBM,但是lightgbm有LGBMClassifier(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