不使用sklearn实现多分类AUC计算:代码结果与预期不符问题
多分类OvO AUC计算结果不符问题分析
我正在编写不依赖sklearn的多分类AUC计算代码,输入为包含类别和模型得分的列表,基于**OvO(一对一)**方法实现。二分类场景(示例1至4)运行正常,但从示例5开始,计算出的AUC与预期值不符——比如示例5预期AUC为0.538462,实际返回0.5083333333333333。
相关代码如下:
import itertools # example 1 # class_score = [ # [0,0], # [1,1] # ] # example 2 # class_score = [ # [0,1], # [1,0] # ] # example 3 # class_score = [ # [0.5, 0], # [0.5, 1], # [2, 0.5] # ] # example 4 # class_score = [ # [0,0], # [0,1], # [1,2], # [0,3], # [1,4], # [1,5], # [1,6] # ] # example 5 # class_score = [ # [0,4], # [3,0], # [1,2], # [2,4], # [1,0], # [2,1], # [4,1], # [2,1], # [4,4], # [0,0] # ] # example 6 class_score = [ [3,4], [3,4], [2,1], [3,2], [4,1], [0,2], [2,0], [1,2], [4,4], [3,3] ] unique_classes = set(i[0] for i in class_score) auc = 0 counter = 0 for cl0, cl1 in itertools.combinations(unique_classes, 2): battlefield = [i for i in class_score if i[0] in (cl0, cl1)] class_1 = [i for i in battlefield if i[0] == cl1] class_0 = [i for i in battlefield if i[0] == cl0] upper = 0 for pair1 in class_1: for pair2 in class_0: if pair1[1] > pair2[1]: upper += 1 if pair1[1] == pair2[1]: upper += 0.5 auc += upper/((len(class_1) * len(class_0))) counter += 1 print(auc / counter)
问题根源分析
你的代码逻辑是基于单一得分的OvO多分类AUC计算,核心是对每一对无序类别,将其中一类视为正类、另一类视为负类,计算二分类AUC后取平均。从示例5的手动计算结果来看,代码返回的0.508333是正确的,和代码运行结果完全一致。
你预期的0.538462可能来自以下两种误解:
- 混淆了OvO与其他多分类AUC计算方式:比如OvR(一对其余),或者对有序对(而非无序对)取平均,但这不符合标准OvO的定义。
- 输入格式误解:标准多分类OvO AUC要求每个样本包含对应所有类别的得分(如
[真实类别, 类别0得分, 类别1得分, ...]),而非单一得分。如果你的模型实际输出的是每个样本在各类别上的得分,那当前输入格式错误,需要调整代码逻辑。
修正方案(若输入应为多类别得分)
如果你的输入应该是每个样本对应所有类别的得分,比如格式为[真实类别, score_cl0, score_cl1, score_cl2, ...],则代码需要修改为:
import itertools # 示例输入格式:[真实类别, 类别0得分, 类别1得分, 类别2得分, 类别3得分, 类别4得分] class_score = [ [0, 0.8, 0.1, 0.05, 0.03, 0.02], [3, 0.05, 0.1, 0.2, 0.6, 0.05], # ... 其他样本 ] unique_classes = sorted(set(i[0] for i in class_score)) class_index_map = {cl: idx+1 for idx, cl in enumerate(unique_classes)} # 得分从索引1开始 auc = 0 counter = 0 for cl0, cl1 in itertools.combinations(unique_classes, 2): # 筛选真实类别为cl0或cl1的样本 samples = [s for s in class_score if s[0] in (cl0, cl1)] # 正类:cl1,负类:cl0,取对应类别的得分 pos_scores = [s[class_index_map[cl1]] for s in samples if s[0] == cl1] neg_scores = [s[class_index_map[cl0]] for s in samples if s[0] == cl0] upper = 0 for pos in pos_scores: for neg in neg_scores: if pos > neg: upper += 1 elif pos == neg: upper += 0.5 pair_auc = upper / (len(pos_scores) * len(neg_scores)) auc += pair_auc counter += 1 print(auc / counter)
内容的提问来源于stack exchange,提问作者Kirill Rubashevskiy
相关产品推荐
相关产品推荐

