CatBoost类别不平衡场景下训练与预测Precision不一致问题排查
我之前也碰到过一模一样的问题,核心原因其实是分类阈值的不一致——CatBoost训练时显示的验证集Precision,和你用model.predict()+sklearn计算的Precision,用的不是同一个判断阈值!
为什么会出现这种差异?
CatBoost的eval_metric计算逻辑:
当你设置eval_metric='F1'时,CatBoost在训练过程中会自动寻找能最大化F1值的分类阈值,然后用这个最优阈值去计算对应的Precision、Recall等指标。你在model.get_best_score()里看到的0.964的Precision,正是基于这个最优阈值的结果。model.predict()的默认阈值:
CatBoost的predict()方法默认使用0.5作为分类阈值,但你设置了scale_pos_weight=56.88(应该是负样本数/正样本数的比值吧?),模型为了关注少数类,会把正类的预测概率整体拉高。这时候用0.5的阈值,会把大量负样本误判为正样本(假阳性FP剧增),直接导致Precision暴跌,而Recall保持很高——这正好和你得到的结果(Precision0.29,Recall0.91)完全吻合。
怎么解决这个问题?
你需要找到CatBoost训练时用的那个最优阈值,或者自己搜索能匹配目标指标的阈值,再用这个阈值来生成预测结果。这里给你两种具体的实现方法:
方法一:用Precision-Recall曲线找最优阈值
通过predict_proba获取正类概率,再结合sklearn的工具找到最大化F1的阈值:
import numpy as np from sklearn.metrics import precision_recall_curve, f1_score, precision_score, recall_score, balanced_accuracy_score # 1. 获取测试集的正类预测概率 y_test_proba = model.predict_proba(X_test)[:, 1] # 2. 生成Precision-Recall曲线,找到最大化F1的阈值 precision, recall, thresholds = precision_recall_curve(y_test, y_test_proba) f1_scores = 2 * (precision * recall) / (precision + recall) best_idx = np.argmax(f1_scores) best_threshold = thresholds[best_idx] # 3. 用最优阈值生成预测结果 y_test_pred_opt = (y_test_proba >= best_threshold).astype(int) # 4. 重新计算指标 print('Balanced accuracy: {:.2f}'.format(balanced_accuracy_score(y_test, y_test_pred_opt))) print('Optimal Precision: {:.2f}'.format(precision_score(y_test, y_test_pred_opt))) print('Optimal Recall: {:.2f}'.format(recall_score(y_test, y_test_pred_opt))) print('Optimal F1: {:.2f}'.format(f1_score(y_test, y_test_pred_opt)))
方法二:直接指定CatBoost predict的阈值
如果你想复用模型训练时的最优阈值,可以直接在predict方法中传入threshold参数:
# 使用方法一中找到的最优阈值 y_test_pred_opt = model.predict(X_test, threshold=best_threshold) # 重新计算指标 print('Optimal Precision: {:.2f}'.format(precision_score(y_test, y_test_pred_opt)))
额外提醒
scale_pos_weight只是调整损失函数中正负样本的权重,让模型更关注少数类,但它不会自动调整分类阈值。在处理不平衡数据时,永远不要默认使用0.5阈值,一定要根据模型的输出概率和目标指标选择合适的阈值,这样才能得到和训练时一致的评估结果。
内容的提问来源于stack exchange,提问作者Alexey Nikolaev

