首页 > IT职业 > IT职场 > AUC 的两种等价定义:从排序概率到 ROC
假设我们面对的是一个二分类 / 排序问题:
定义:
AUC 的概率定义是:
从正样本集合中随机抽取一个样本,从负样本集合中随机抽取一个样本,
正样本的预测分数大于负样本预测分数的概率。
其数学形式为:
其中:
这也是为什么在推荐系统中,即便最终没有明确的“正负分类决策”,AUC 依然是最核心的离线评估指标之一。
给定一组预测分数 ��,我们引入一个阈值 �:
在每一个阈值 � 下,可以计算:
当阈值
� 从
+∞ 连续下降到
−∞ 时,
点对
(FPR(�),TPR(�)) 在平面上形成一条曲线,即
ROC 曲线。
AUC 定义为 ROC 曲线下方的面积:
这是一个 几何意义上的定义。
一个统计学习中的经典结论是:
其中 �+ 表示正样本分数, �− 表示负样本分数。
直观解释如下:
因此:
ROC 曲线下面积,等价于所有正负样本对中,排序正确的比例。
ROC 只是将“排序关系”用几何方式进行了表达。
| 样本 | label | score |
|---|---|---|
| A | 1 | 0.90 |
| B | 1 | 0.60 |
| C | 0 | 0.70 |
| D | 0 | 0.40 |
| E | 0 | 0.20 |
| 正样本 | 负样本 | 是否排序正确 |
|---|---|---|
| A(0.90) | C(0.70) | ✓ |
| A(0.90) | D(0.40) | ✓ |
| A(0.90) | E(0.20) | ✓ |
| B(0.60) | C(0.70) | ✗ |
| B(0.60) | D(0.40) | ✓ |
| B(0.60) | E(0.20) | ✓ |
下面给出两种 AUC 计算实现。
def auc_pairwise(labels, scores):"gov.zuqiu.zuqba.info"" 基于 AUC 的概率定义(Pairwise Comparison)进行计算。 输入: - labels: List[int] 或 1D array 样本真实标签,取值为 {0, 1} 1 表示正样本,0 表示负样本 - scores: List[float] 或 1D array 模型对每个样本给出的预测分数,分数越大表示越倾向正类 输出: - auc: float AUC 值,取值范围 [0, 1] 核心思想: 随机取一个正样本 p 和一个负样本 n, 统计 P(score_p > score_n) 的比例 """# 提取正样本 (label=1) 的预测分数pos_scores = [s for l, s in zip(labels, scores) if l == 1]# 提取负样本 (label=0) 的预测分数neg_scores = [s for l, s in zip(labels, scores) if l == 0]# 排序正确的正负样本对数量(允许 0.5 的打平贡献)correct = 0.0# 正负样本对的总数量 |P| * |N|total = len(pos_scores) * len(neg_scores)# 对所有正负样本对进行两两比较for sp in pos_scores: # sp: positive sample scorefor sn in neg_scores: # sn: negative sample scoreif sp > sn:# 正样本分数严格大于负样本分数,排序正确correct += 1.0elif sp == sn:# 分数相等,按约定计为 0.5(随机打平)correct += 0.5# sp < sn 时不加分,表示排序错误# AUC = 排序正确的比例return correct / total
复杂度分析:
适用场景:
import numpy as npdef auc_rank(labels, scores):""" 基于排序(Rank / Mann–Whitney U)的 AUC 计算方法。 输入: - labels: List[int] 或 1D numpy array 样本真实标签,取值为 {0, 1} - scores: List[float] 或 1D numpy array 模型预测分数,分数越大表示越可能为正样本 输出: - auc: float AUC 值,取值范围 [0, 1] 核心思想: 1. 按预测分数从小到大排序 2. 扫描排序后的样本序列 3. 每遇到一个正样本,统计其前面已有多少负样本 这些负样本都被该正样本“正确地排在后面” """# 转为 numpy array,便于排序和向量化操作labels = np.asarray(labels)scores = np.asarray(scores)# 获取按照 score 从小到大排序后的索引order = np.argsort(scores)# 按排序后的顺序重排标签labels_sorted = labels[order]# 正样本数量 |P|n_pos = np.sum(labels_sorted == 1)# 负样本数量 |N|n_neg = np.sum(labels_sorted == 0)# 已扫描到的负样本数量(前缀负样本计数)neg_count = 0# 排序正确的正负样本对数量correct = 0.0# 从低分到高分扫描for l in labels_sorted:if l == 1:# 当前是正样本:# 它前面的所有负样本都满足 score_neg < score_poscorrect += neg_countelse:# 当前是负样本,增加负样本计数neg_count += 1# AUC = 排序正确的正负样本对 / 总正负样本对return correct / (n_pos * n_neg)
复杂度分析:
工程说明:
def calc_auc(score_list, label_list):""" 使用分桶法计算AUC(ROC曲线下面积)。 将预测分数离散化到固定数量的桶中,统计每个桶的正负样本数, 然后按分数从高到低遍历,使用梯形法则计算ROC曲线下面积。 Args: score_list: 模型预测分数列表,取值范围应在[0, 1]之间 label_list: 真实标签列表,正样本>0,负样本<=0 Returns: tuple: (正样本数, 负样本数, AUC值) - 当正样本或负样本数为0时,返回(0, 0, 0.5) - AUC值范围为[0, 1],0.5表示随机猜测水平 """scoredict = dict()numbin = 10000maxval = 1.0minval = 0.0step = (maxval-minval)/numbininv_step = 1.0 / steptotal_pos = 0total_neg = 0for (score, label) in zip(score_list, label_list):c = 0v = 0if label > 0:c = 1total_pos += 1else:v = 1total_neg += 1score_bin = int((float(score) - minval) * inv_step)if scoredict.has_key(score_bin):scoredict[score_bin][0] += vscoredict[score_bin][1] += celse:scoredict[score_bin] = [v, c]if total_neg == 0:return 0, 0, 0.5if total_pos == 0:return 0, 0, 0.5sorted_list = sorted(scoredict.items(), key=lambda x:x[0], reverse=True)sum_pos = 0sum_neg = 0pretp = 0.0prefp = 0.0roc_auc = 0.0roc_curve = dict()fpidx = 0for (binidx, value) in sorted_list:sum_neg += (float(value[0])/total_neg)sum_pos += (float(value[1])/total_pos)tp = float(sum_pos)fp = float(sum_neg)delta =(fp-prefp)*(pretp+tp)*0.5roc_auc += deltaroc_curve[fpidx] = (fp, tp)fpidx += 1pretp = tpprefp = fpreturn total_pos, total_neg, roc_auc
把连续 score 离散成很多桶(bin)
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70048957/viewspace-3107268/ ,如需转载,请注明出处,否则将追究法律责任。