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

时间:2015-06-22 04:12:00

标签: python scikit-learn

我很难理解scikit-learn的Logistic回归中class_weight参数的运作方式。

情况

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

首次尝试:手动准备培训数据

我将我拥有的数据拆分为不相交的集合进行培训和测试(约80/20)。然后我手动随机抽取训练数据,得到不同比例的训练数据,比例为19:1;从2:1 - > 16:1

然后,我在这些不同的训练数据子集上训练逻辑回归,并绘制召回(= 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

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

我的问题

1)您如何正确使用class_weight来实现培训数据的不同平衡?具体来说,我将哪些词典传递给class_weight使用n:m比例的负面:正面训练样本?

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

3)auto的{​​{1}}值与比例有什么关系?我阅读了文档,我假设“平衡数据与它们的频率成反比”只是意味着它以1:1的比例。它是否正确?如果没有,有人可以澄清吗?

非常感谢,非常感谢任何澄清!

2 个答案:

答案 0 :(得分:93)

首先,单独召回可能不太好。通过将所有内容归类为积极的类,您可以简单地实现100%的召回。 我通常建议使用AUC选择参数,然后找到您感兴趣的工作点(比如给定的精度等级)的阈值。

class_weight如何运作:它会对class[i]的样本中的错误进行惩罚,而不是class_weight[i]。因此,更高的类权重意味着您希望更多地强调某个类。从你说的来看,似乎0级比1级频率高19倍。所以你应该相对于0级增加1级的class_weight,比如{0:.1,1:。9}。 如果class_weight没有求和为1,它将基本上改变正则化参数。

有关class_weight="auto"的工作原理,您可以查看you should not use traits like you're doing。 在开发版本中,您可以使用class_weight="balanced",这更容易理解:它基本上意味着复制较小的类,直到您拥有与较大类一样多的样本,但是以隐式方式。

答案 1 :(得分:5)

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

摘要

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

NB

使用RF或GBM时结果可能有所不同。 sklearn does not have 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