题目重点:用正负配对概率推导分组统计,解释阈值与不平衡局限,避免同分顺序影响分数。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练二分类,score越大越像正类;AUC无样本权重,标签0/1,必须同时有正负。
- score有限,同分算0.5;单类返回错误而非编造0或1。
解题路线
- ROC点由阈值产生TPR=TP/(TP+FN)和FPR=FP/(FP+TN),AUC可解释为随机正样本分数高于随机负样本的概率,加同分一半。它主要评价排序,不直接选业务阈值。
- 暴力枚举P×N正负配对作为明确基线。优化先按score升序分组,维护此前负样本数below;本组p个正、n个负贡献p·below+0.5pn。
- 整组一次更新below,避免同分项的排序先后决定胜负。最后除总P·N;反转score方向通常让AUC变为1−AUC,同分贡献保持。
- AUC对严格单调score变换不变,但不说明概率校准正确;两个模型相同排序可有非常不同概率值。低FPR区域、precision或PR曲线对稀少正例业务可能更相关。
- 只有一种标签时没有正负对,AUC未定义;空输入、NaN和标签错误需明确。跨batch局部AUC平均也不一定等于全量AUC,配对会跨batch。
- 测试全同分、完美/反向排序、重复分数、单类和小整数穷举,对照全配对不用另一个排序算法。线上按群组计算时还要报告样本数量和单类缺值。
手动走一遍
正score=0.8、负=0.2,AUC1;同为0.5则0.5;正低负高为0。labels=[0,1]同分不能因排序稳定性变成0或1。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
from math import isfinite
def roc_auc(labels, scores):
if not labels or len(labels) != len(scores) or any(y not in (0, 1) for y in labels):
raise ValueError('matching binary labels required')
if any(not isfinite(s) for s in scores):
raise ValueError('finite scores required')
positive = sum(labels)
negative = len(labels) - positive
if not positive or not negative:
raise ValueError('AUC undefined without both classes')
ordered = sorted(zip(scores, labels))
wins, below, i = 0.0, 0, 0
while i < len(ordered):
j = i
p = n = 0
while j < len(ordered) and ordered[j][0] == ordered[i][0]:
if ordered[j][1]:
p += 1
else:
n += 1
j += 1
wins += p * below + 0.5 * p * n
below += n
i = j
return wins / (positive * negative)
复杂度
n样本排序O(n log n),分组O(n),排序存储O(n);暴力oracle O(PN)。输出1个数但需全排序或其他统计结构。
易错点与追问
容易失分的边界
- 同分按0或1取决于顺序。
- 单类随便返回0.5。
- AUC高便概率校准好。
- 平均batch AUC当全量。
继续深挖
- 加样本权重如何计配对?
- 分布式怎么合并全局排序信息?
- 业务为何看低FPR区间?
