You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

验证GridSearchCV中自定义AUPRC评分器的实现正确性

问题分析与修正方案

一、自定义AUPRC评分器的核心错误

你的评分器代码存在关键问题:average_precision_score需要模型输出的正类概率(或置信度),而非硬分类的0/1标签。原代码中将y_pred转换为0/1的操作,完全丢失了模型的置信度信息,导致计算出的AUPRC毫无意义——这相当于只计算了单一阈值下的精确率,而非整个PR曲线的面积。

同时,原代码中函数名重复覆盖(custom_auprc_scorer既作为函数名,又作为make_scorer的结果变量),虽然不报错,但会降低代码可读性。

二、修正后的AUPRC评分器代码

from sklearn.metrics import average_precision_score, make_scorer
import numpy as np

def custom_auprc_score_func(y_true, y_pred_proba):
    # 将标签映射为0/1
    pos_label = 'positive'
    y_true_mapped = np.where(y_true == pos_label, 1, 0)
    # y_pred_proba是模型输出的正类概率,直接传入即可
    return average_precision_score(y_true_mapped, y_pred_proba)

# 创建评分器,指定需要模型输出概率
custom_auprc_scorer = make_scorer(
    custom_auprc_score_func, 
    greater_is_better=True,
    needs_proba=True  # 关键:告诉GridSearchCV传入概率而非分类标签
)

scorer = {'AUPRC': custom_auprc_scorer, 'ROC_AUC': 'roc_auc'}

关键修正点:

  • 添加needs_proba=True参数:让GridSearchCV在调用评分器时,传入模型的predict_proba()输出(正类概率),而非predict()的分类结果。
  • 移除对y_pred的0/1转换:直接使用模型输出的概率值计算AUPRC。
  • 重命名函数避免变量覆盖:将原函数名改为custom_auprc_score_func,避免与最终的评分器变量冲突。

三、AUPRC作为超参数选择指标的有效性

对于你使用SMOTE处理的场景(说明数据集存在类别不平衡),AUPRC是比ROC AUC更合适的超参数优化指标:

  • ROC AUC对不平衡数据不敏感,即使模型对少数类预测很差,也可能得到较高的ROC AUC分数。
  • AUPRC聚焦于正类(少数类)的精确率与召回率 trade-off,能更准确反映模型在目标类别上的性能,非常适合不平衡分类任务的超参数选择。因此你选择refit='AUPRC'是完全合理的。

四、evaluatealgorithm函数的补充优化

原函数整体逻辑没问题,但可以增加两点鲁棒性:

  1. 确保y_train中仅包含pos_label和另一类标签,避免映射错误。
  2. 必须使用imblearn的Pipeline,避免交叉验证中的数据泄露。

修正后的完整evaluatealgorithm函数:

from sklearn.ensemble import GradientBoostingClassifier
from imblearn.pipeline import Pipeline  # 注意:使用imblearn的Pipeline而非sklearn的
from imblearn.over_sampling import SMOTE
from sklearn.model_selection import GridSearchCV

def evaluatealgorithm(x_train, y_train, kfold, scorer):
    # 验证标签是否符合预期
    unique_labels = np.unique(y_train)
    assert len(unique_labels) == 2, "数据集必须是二分类任务"
    assert 'positive' in unique_labels, "标签中必须包含'positive'类别"

    gbm = GradientBoostingClassifier(loss='log_loss')
    smote = SMOTE(sampling_strategy='minority')

    # 使用imblearn的Pipeline,确保SMOTE在每个交叉验证 fold 中仅拟合训练数据
    pipeline = Pipeline(steps=[['smote', smote],
                               ['gbm', gbm]])

    parameters = {
        'smote__k_neighbors': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        'gbm__learning_rate': [0.001, 0.005, 0.01, 0.05, 0.1, 0.2, 0.5, 1],
        'gbm__subsample': [0.4, 0.5, 0.6, 0.8, 1],
        'gbm__n_estimators': [50, 100, 200, 300, 400, 600],
        'gbm__max_depth': [3, 4, 5, 6, 7, 8, 9, 10],
        'gbm__min_samples_split': [2, 3, 4, 5],
        'gbm__min_samples_leaf': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
    }

    grid_gbm = GridSearchCV(estimator=pipeline, param_grid=parameters, cv=kfold,
                            verbose=1, n_jobs=-1, refit='AUPRC', scoring=scorer)

    grid_gbm.fit(x_train, y_train)

    auprc_score = grid_gbm.cv_results_['mean_test_AUPRC'][grid_gbm.best_index_]
    roc_score = grid_gbm.cv_results_['mean_test_ROC_AUC'][grid_gbm.best_index_]

    model_gbm = grid_gbm.best_estimator_
    smote_params = model_gbm.named_steps['smote'].get_params()
    gbm_params = model_gbm.named_steps['gbm'].get_params()

    return smote_params, gbm_params, model_gbm, roc_score, auprc_score

额外注意:

  • 必须使用imblearn.pipeline.Pipeline:sklearn原生Pipeline会在整个数据集上拟合SMOTE后再做交叉验证,导致数据泄露;而imblearn的Pipeline会在每个交叉验证的训练fold中单独拟合SMOTE,符合交叉验证的严谨性要求。

内容的提问来源于stack exchange,提问作者Lucas F. T. Leonardo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 14:26:18