Scikit-learn 中的 class _ weight 参数是如何工作的?

我很难理解 scikit-learn Logit模型中的 class_weight参数是如何运作的。

情况

我想用 Logit模型对一个非常不平衡的数据集进行二进制分类。这些类别被标记为0(阴性)和1(阳性) ,观察数据的比例约为19:1,大多数样本有阴性结果。

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

为了训练和测试,我把数据分割成不相交的集合(大约80/20)。然后,我随机抽取手工训练数据,得到不同比例的训练数据比19:1; 从2:1-> 16:1。

然后,我就这些不同的训练数据子集对 Logit模型进行训练,并将回忆(= TP/(TP + FN))作为不同训练比例的函数绘制出来。当然,召回是根据观察到的比例为19:1的不相交的 TEST 样品计算出来的。注意,虽然我在不同的训练数据上训练了不同的模型,但是我在相同的(不相交的)测试数据上计算了所有模型的回忆。

结果正如预期的那样: 在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

这一次的结果完全出乎意料。除了 auto之外,我所有的回忆对于 class_weight的每一个值来说都是微不足道的(< 0.05)。所以我只能假设我对如何设置 class_weight字典的理解是错误的。有趣的是,在网格搜索中,“ auto”的 class_weight值对于 C的所有值都在59% 左右,我猜它的比例是1:1?

我的问题

  1. 如何正确使用 class_weight来实现训练数据与实际数据的不同平衡?具体来说,我应该传递给 class_weight什么样的字典来使用 n: m 比例的负: 正训练样本?

  2. 如果你把各种各样的 class_weight字典传给 GridSearchCV,在交叉验证期间,它是否会根据字典重新平衡训练折叠数据,而是使用给定的真实样本比例来计算我在测试折叠上的得分函数?这是至关重要的,因为任何度量对我来说都是有用的,如果它来自观测比例的数据。

  3. 就比例而言,class_weightauto值有什么作用?我阅读了文档,我假设“平衡数据与其频率成反比”,这意味着它是1:1。是这样吗?如果没有,有人能澄清一下吗?

166445 次浏览

首先,单靠召回可能不太好。你可以简单地通过把所有东西归类为正类来实现100% 的召回。 我通常建议使用 AUC 选择参数,然后找到您感兴趣的操作点的阈值(比如给定的精度级别)。

关于 class_weight的工作原理: 它用 class_weight[i]而不是1惩罚 class[i]样本中的错误。所以更高的类权重意味着你要更加强调一个类。从你所说的来看,0类似乎是1类的19倍。因此,应该将类1的 class_weight相对于类0增加,比如{0: .1,1: .9}。 如果 class_weight不和为1,它基本上会改变正则化参数。

至于 class_weight="auto"是如何工作的,你可以看看 这个讨论。 在 dev 版本中,您可以使用 class_weight="balanced",这更容易理解: 它基本上意味着复制较小的类,直到您拥有与较大的类一样多的样本,但是是以隐式的方式。

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

使用不平衡-学习

对于不平衡的数据,不平衡-学习中的方法比使用类权重参数产生更好的结果,特别是在样本中。

总结

  • 对于没有噪声的中度不平衡数据,应用类权重没有太大区别
  • 对于中度不平衡且带噪声且强不平衡的数据,最好应用类权重
  • Param class_weight="balanced"在你不想手动优化的情况下工作得当
  • 使用 class_weight="balanced"可以捕获更多的真实事件(更高的 TRUE 回忆) ,但也更容易得到错误警报(更低的 TRUE 精度)
    • 因此,由于所有的假阳性,总的% TRUE 可能高于实际值
    • 如果错误警报是个问题,AUC 可能会误导你
  • 不需要将决策阈值改为不平衡% ,即使是对于强烈的不平衡,也可以保持0.5(或者根据需要保持在0.5左右)

注意

使用 RF 或 GBM 时,结果可能有所不同。 GBM 时 斯克伦没有 class_weight="balanced",而 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