AUC代码实现
·约 3365 字·7 分钟
AI摘要: 本文详细介绍了AUC(Area Under Curve)的两种计算方法,包括时间复杂度为O(N^2)的方法和更高效的O(log N)的方法。文章通过公式推导和Python代码示例,解释了如何根据正负样本的预测概率来计算AUC值。
ROC曲线以FPR作为横轴,TPR作为纵轴。AUC值越大,模型越可能将其分类为正样本
- FPR:假阳率
- TPR:真阳率
AUC适合正负样本不均衡的数据集评估,取值范围
AUC在希望提高Recall的基础上,还希望降低犯错误的概率,尽量不误报,是偏保守的
计算方法1:
根据AUC的统计学定义:随机从正负样本中抽取一对正负样本,正样本的预测概率大于负样本预测概率的概率就是AUC。有点拗口,但就是概率的概率。
用代码来实现就是,在m个正样本,n个负样本的数据集中,对m * n个正样本里,统计正样本预测概率大于负样本的数目,然后除以总数目
def calcAUC_byProb(labels, probs):
N = 0 # 正样本数量
P = 0 # 负样本数量
neg_prob = [] # 负样本的预测值
pos_prob = [] # 正样本的预测值
for index, label in enumerate(labels):
if label == 1: # 正样本数++
P += 1
pos_prob.append(probs[index])
else:
N += 1 # 负样本数++
neg_prob.append(probs[index])
number = 0
for pos in pos_prob: # 遍历正负样本间的两两组合
for neg in neg_prob:
if (pos > neg): # 如果正样本预测值>负样本预测值,正序对数+1
number += 1
elif (pos == neg): # 如果正样本预测值==负样本预测值,算0.5个正序对
number += 0.5
return number / (N * P)
计算方法2:
还有个更好的方法,时间复杂度为 ,这也是比上面方法更重要的方法。
def get_auc(labels, preds):
# 这段代码基本上是沿着公式计算的:
# 1. 先求正样本的rank和
# 2. 再减去(m*(m+1)/2)
# 3. 最后除以组合个数
# 但是要特别注意,需要对预测值pred相等的情况进行了一些处理。
# 对于这些预测值相等的样本,它们对应的rank是要取平均的
# 先将data按照pred进行排序
sorted_data = sorted(list(zip(labels, preds)), key=lambda item: item[1])
pos = 0.0 # 正样本个数
neg = 0.0 # 负样本个数
auc = 0.0
# 注意这里的一个边界值,在初始时我们将last_pre记为第一个数,那么遍历到第一个数时只会count++
# 而不会立刻向结果中累加(因为此时count==0,根本没有东西可以累加)
last_pre = sorted_data[0][1]
count = 0.0
pre_sum = 0.0 # 当前位置之前的预测值相等的rank之和,rank是从1开始的,所以在下面的代码中就是i+1
pos_count = 0.0 # 记录预测值相等的样本中标签是正的样本的个数
# 为了处理这些预测值相等的样本,我们这里采用了一种lazy计算的策略:
# 当预测值相等时仅仅累加count,直到下次遇到一个不相等的值时,再将他们一起计入结果
for i, (label, pred) in enumerate(sorted_data):
# 注意:rank就是i+1
if label > 0:
pos += 1
else:
neg += 1
if last_pre != pred: # 当前的预测概率值与前一个值不相同
# lazy累加策略被触发,求平均并计入结果,各个累积状态置为初始态
auc += pos_count * pre_sum / count # 注意这里只有正样本的部分才会被累积进结果
count = 1
pre_sum = i + 1 # 累积rank被清空,更新为当前数rank
last_pre = pred
if label > 0:
pos_count = 1 # 如果当前样本是正样本 ,则置为1
else:
pos_count = 0 # 反之置为0
# 如果预测值是与前一个数相同的,进入累积状态
else:
pre_sum += i + 1 # rank被逐渐累积
count += 1 # 计数器也被累计
if label > 0: # 这里要另外记录正样本数,因为负样本在计算平均
pos_count += 1 # rank的时候会被计入,但不会被计入rank和的结果
# 注意这里退出循环后我们要额外累加一下。
# 这是因为我们上面lazy的累加策略,导致最后一组数据没有累加
auc += pos_count * pre_sum / count
auc -= pos * (pos + 1) / 2 # 减去正样本在正样本之前的情况即公式里的(m+1)m/2
auc = auc / (pos * neg) # 除以总的组合数即公式里的m*n
return auc