使用cross_val_score评估不平衡数据集时Brier Skill Score返回NaN的问题排查与解决
看起来你在处理高度不平衡的欺诈检测数据集时,遇到了所有模型(包括基线)的Brier Skill Score(BSS)在交叉验证中全返回NaN的问题,我帮你一步步排查原因并给出解决办法:
一、最可能的核心问题:概率数组维度不匹配
在二分类任务中,sklearn模型的predict_proba()方法默认返回二维数组(形状为(n_samples, 2)),其中第一列是负类概率,第二列是正类概率。你的brier_skill_score函数直接将这个二维数组传入brier_score_loss,虽然sklearn会尝试自动处理,但在某些边缘场景(比如某fold中测试集只有单一类别)可能会引发计算异常,最终导致NaN。
解决方法是显式提取正类的概率(一维数组)传入brier_score_loss,修改你的brier_skill_score函数:
def brier_skill_score(y_true, y_prob): """ Compute the Brier Skill Score (BSS). - 0.0 = no skill (same as baseline) - >0.0 = better than baseline - <0.0 = worse than baseline """ # 处理二维概率数组,提取正类概率(假设y_true中1是正类) if y_prob.ndim == 2: y_prob = y_prob[:, 1] pos_prob = np.count_nonzero(y_true) / len(y_true) ref_probs = [pos_prob for _ in range(len(y_true))] # baseline (predict always prior prob) bs_ref = brier_score_loss(y_true, ref_probs) bs_model = brier_score_loss(y_true, y_prob) if bs_ref == 0: # 避免除以0(测试集只有单一类别时) return 0.0 return 1.0 - (bs_model / bs_ref)
二、次要排查点:分层交叉验证的边缘情况
你的数据集很小(仅2133行,66个正例),使用RepeatedStratifiedKFold时,有可能出现某一个fold的测试集中只有单一类别(全0或全1)。虽然你的代码已经处理了bs_ref == 0的情况,但可以额外添加日志排查:
在brier_skill_score函数中临时加入打印语句,查看每个fold的测试集分布和计算值:
from collections import Counter def brier_skill_score(y_true, y_prob): # 临时添加:打印当前fold测试集分布 print(f"当前fold测试集分布: {Counter(y_true)}") # 处理二维概率数组 if y_prob.ndim == 2: y_prob = y_prob[:, 1] pos_prob = np.count_nonzero(y_true) / len(y_true) ref_probs = [pos_prob for _ in range(len(y_true))] bs_ref = brier_score_loss(y_true, ref_probs) bs_model = brier_score_loss(y_true, y_prob) print(f"bs_ref: {bs_ref}, bs_model: {bs_model}") if bs_ref == 0: print("测试集只有单一类别,返回BSS=0.0") return 0.0 bss = 1.0 - (bs_model / bs_ref) print(f"当前fold BSS: {bss}") return bss
运行后如果看到某fold的测试集只有单一类别,这是数据集过小导致的正常现象,不会影响整体结果(因为其他fold会返回有效数值);如果所有fold都出现这种情况,建议调整交叉验证参数(比如减小n_splits)。
三、其他潜在问题排查
- ImbPipeline的使用:你使用了
imblearn.pipeline.Pipeline,如果没有添加任何采样器(如SMOTE),其实和sklearn的普通Pipeline功能一致,不会引发问题;如果后续要添加采样器,建议将采样器放在预处理步骤之后、模型之前。 - 模型的概率输出有效性:确保所有模型的
predict_proba返回的概率值都在[0,1]区间内,没有NaN值(sklearn模型默认不会输出NaN,除非数据有缺失且未处理,但你的预处理已经包含了Imputer,所以这一点大概率没问题)。
验证修改效果
修改后重新运行代码,基线模型的BSS应该接近0.0(因为基线模型和参考基准的性能一致),而其他模型(如逻辑回归、随机森林)应该返回有效BSS值(大于0表示比基线好)。
内容来源于stack exchange

