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. 示例#