3.3. 调整用于类别预测的决策阈值#

分类最好分为两部分:

  • 统计问题:学习一个模型来预测(理想情况下)类别概率;

  • 决策问题:根据这些概率预测采取具体行动。

让我们以一个与天气预报相关的简单例子来说明:第一点是回答“明天有雨的几率有多大?”,而第二点是回答“明天我应该带伞吗?”。

在 scikit-learn API 中,第一点通过使用 predict_probadecision_function 提供分数来解决。前者返回每个类别的条件概率估计值 \(P(y|X)\),而后者返回每个类别的决策分数。

与标签对应的决策是通过 predict 获得的。在二元分类中,决策规则或行动是通过对分数设置阈值来定义的,从而为每个样本预测一个类别标签。对于 scikit-learn 中的二元分类,类别标签预测是通过硬编码的截止规则获得的:当条件概率 \(P(y|X)\) 大于 0.5 时(通过 predict_proba 获得),或者决策分数大于 0 时(通过 decision_function 获得),预测为正类。

在这里,我们展示了一个例子,说明条件概率估计值 \(P(y|X)\) 与类别标签之间的关系

>>> from sklearn.datasets import make_classification
>>> from sklearn.tree import DecisionTreeClassifier
>>> X, y = make_classification(random_state=0)
>>> classifier = DecisionTreeClassifier(max_depth=2, random_state=0).fit(X, y)
>>> classifier.predict_proba(X[:4])
array([[0.94     , 0.06     ],
       [0.94     , 0.06     ],
       [0.0416, 0.9583],
       [0.0416, 0.9583]])
>>> classifier.predict(X[:4])
array([0, 0, 1, 1])

虽然这些硬编码规则作为默认行为可能看起来合理,但对于大多数用例来说,它们肯定不是理想的。让我们用一个例子来说明。

考虑一个场景,部署一个预测模型来协助医生检测肿瘤。在这种情况下,医生最感兴趣的可能是识别所有患有癌症的患者,不漏掉任何一个患癌者,以便为他们提供正确的治疗。换句话说,医生优先考虑实现高召回率。当然,这种对召回率的强调伴随着可能会有更多假阳性预测的权衡,从而降低了模型的精确率。这是医生愿意承担的风险,因为漏诊癌症的成本远高于进一步诊断测试的成本。因此,当决定是否将患者归类为患有癌症时,当条件概率估计值远低于 0.5 时,将其归类为癌症阳性可能更有益。

3.3.1. 决策阈值的后处理调整#

解决引言中提出的问题的一种解决方案是在模型训练完成后调整分类器的决策阈值。TunedThresholdClassifierCV 使用内部交叉验证来调整此阈值。选择最佳阈值以最大化给定的指标。

下图展示了对梯度提升分类器进行决策阈值调整的过程。虽然原始分类器和调整后的分类器提供相同的 predict_proba 输出,因此具有相同的接收者操作特征 (ROC) 曲线和精确率-召回率曲线,但类别标签预测有所不同,因为决策阈值经过了调整。原始分类器在条件概率大于 0.5 时预测感兴趣的类别,而调整后的分类器在非常低的概率(约 0.02)时预测感兴趣的类别。这个决策阈值优化了业务(在这种情况下是一家保险公司)定义的效用指标。

../_images/sphx_glr_plot_cost_sensitive_learning_002.png

3.3.1.1. 调整决策阈值的选项#

可以通过由参数 scoring 控制的不同策略来调整决策阈值。

调整阈值的一种方法是最大化预定义的 scikit-learn 指标。可以通过调用函数 get_scorer_names 来找到这些指标。默认情况下,使用的是平衡准确率,但请注意,应该为您的用例选择一个有意义的指标。

注意

重要的是要注意这些指标带有默认参数,特别是感兴趣类别的标签(即 pos_label)。因此,如果这个标签不适合您的应用程序,您需要定义一个评分器并使用 make_scorer 传递正确的 pos_label(以及附加参数)。请参阅 可调用评分器 以获取有关定义自己的评分函数的信息。例如,我们展示了如何在最大化 f1_score 时向评分器传递感兴趣标签为 0 的信息

>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.model_selection import TunedThresholdClassifierCV
>>> from sklearn.metrics import make_scorer, f1_score
>>> X, y = make_classification(
...   n_samples=1_000, weights=[0.1, 0.9], random_state=0)
>>> pos_label = 0
>>> scorer = make_scorer(f1_score, pos_label=pos_label)
>>> base_model = LogisticRegression()
>>> model = TunedThresholdClassifierCV(base_model, scoring=scorer)
>>> scorer(model.fit(X, y), X, y)
0.88
>>> # compare it with the internal score found by cross-validation
>>> model.best_score_
np.float64(0.86)

3.3.1.2. 关于内部交叉验证的重要说明#

默认情况下,TunedThresholdClassifierCV 使用 5 折分层交叉验证来调整决策阈值。参数 cv 允许控制交叉验证策略。可以通过设置 cv="prefit" 并提供一个已拟合的分类器来绕过交叉验证。在这种情况下,决策阈值是在提供给 fit 方法的数据上进行调整的。

但是,在使用此选项时应极其小心。由于存在过拟合的风险,绝不能使用相同的数据来训练分类器和调整决策阈值。有关更多详细信息,请参阅以下示例部分(参见 关于模型重新拟合和交叉验证的考虑因素)。如果资源有限,请考虑为 cv 使用浮点数以限制为内部的单个训练-测试拆分。

选项 cv="prefit" 仅应用于提供的分类器已经训练过,而您只想使用新的验证集来找到最佳决策阈值的情况。

3.3.1.3. 手动设置决策阈值#

前面的部分讨论了寻找最佳决策阈值的策略。也可以使用 FixedThresholdClassifier 类手动设置决策阈值。如果您在调用 fit 时不想重新拟合模型,请使用 FrozenEstimator 包装您的子估计器,并执行 FixedThresholdClassifier(FrozenEstimator(estimator), ...)

3.3.1.4. 示例#