AUC 面试手撕原理与代码【三种手撕代码】

AUC 的原理

衡量分类模型,不能仅考虑分对的情况,还要考虑分错的情况

  • 真阳:正样本中被正确分为正样本的比例, T P R = T P T P + F N TPR=\frac{TP}{TP+FN} TPR=TP+FNTP
  • 假阳:负样本中被错误分为正样本的比例, F P R = F P F P + T N FPR=\frac{FP}{FP+TN} FPR=FP+TNFP
  • 划分标准:神经网络只是输出一个预测概率 p p p,需要根据人为设定的阈值去判断是否预测为正样本。因此,取不同的阈值,会得到不同的 T P R TPR TPR F P R FPR FPR 取值。分别作为纵坐标、横坐标并连成线,即可获得 ROC 曲线。
  • AUC:ROC 曲线下的面积,就是 AUC。

AUC 计算的三种方法

AUC 计算有两条思路:

  • 一种是积分思想,用矩形近似计算 ROC 曲线下的面积;
  • 一种是AUC作为概率,具体有两种实现方法。

1. 矩形近似法

根据 AUC 的定义,取不同的阈值获得不同的 TPR / FPR,根据定义计算。
(但此方法比较繁琐,而且只是近似计算,并不是很推荐)
在这里插入图片描述
原文:https://blog.csdn.net/weixin_44398263/article/details/123639531#:~:text=AUC%E7%9A%84%E5%85%A8%E5%90%8D%E6%98%AFA

### 计算AUC
## 对于正样本N和负样本M, 枚举正负样本对(Ni, Mj),如果正样本分值大于负样本,则+1,相等则+0.5,小于则不加分,再用总分除以N*M
import numpy as np
import matplotlib.pyplot as plt

def AUC(pre, label):
    label = label == 1
    print(label)
    pos = pre[label]
    neg = pre[~label]
    pos = np.sort(pos)[::-1]
    neg = np.sort(neg)[::-1]
    print(pos, neg)
    pre_sort = np.sort(pre)[::-1]
    label_sort = label[np.argsort(pre)[::-1]]
    possum, negsum = pos.shape[0], neg.shape[0]
    TPR =[]
    FPR = []
    # 按照定义,逐个取每个预测结果作为阈值,计算 TPR FPR
    for i in range(pre_sort.shape[0]):
        TP = np.sum(label_sort[:i+1])
        TPR.append(TP/possum)
        FP = i+1 - TP
        FPR.append(FP/negsum)
    
    
    i, j, nextj = 0, 0, 0
    auc = 0
    while i < pos.shape[0] and j < neg.shape[0]:
        if nextj:
            j = nextj
            nexj = 0
        while i < pos.shape[0] and j < neg.shape[0] and neg[j] > pos[i]:
            j += 1
        while i < pos.shape[0] and j < neg.shape[0] and pos[i] >= neg[j]:
            if pos[i] > neg[j]:
                auc += 1 * (neg.shape[0]-j)
            else:
                nextj = j + 1
                ## 如果相等,找到第一个绝对大于的为nextj
                while nextj < neg.shape[0] and neg[nextj]==neg[j]:
                    nextj += 1
                auc += 0.5 * (nextj-j) + 1 * (neg.shape[0]-nextj)
            i += 1
    auc /= pos.shape[0] * neg.shape[0]
    print(auc)
    plt.plot(FPR, TPR)
    plt.show()

pre = np.asarray([0.9, 0.8, 0.3, 0.1, 0.4, 0.9, 0.66, 0.7])
label = np.asarray([1,0,0,0,1,0,1,0])
auc = AUC(pre, label)
print(auc)

2. AUC 作为概率

原理

AUC等于,随机选取一正一负样本,正样本预测值 p p o s p_{pos} ppos大于负样本预测值 p n e g p_{neg} pneg的概率。

证明

原理证明参考:AUC公式的证明,本文直接将其作为定理。

参考:A Complete Guide to Area Under Curve (AUC)
根据定理,随机选取一正一负样本,计算正样本预测值 大于负样本预测值 的概率。直接统计 正样本预测值大于负样本预测值概率 的总数,除以正负样本组合数,即可获得概率。对于 正样本预测值等于负样本预测值概率 的情况,按照 0.5 权重处理。
(此方法易于理解,实现简单,不考虑复杂度的情况下比较推荐)

import numpy as np
import pandas as pd
from sklearn.metrics import roc_auc_score

def AUC(preds,labels):
    df = pd.DataFrame({'pred':preds,'label':labels})
    # 从 label 取出正样本、负样本
    ones = df[df['label']==1]
    zeros = df[df['label']==0]
    # 总的组合数
    totoal_pairs = len(ones) * len(zeros)
    # 正样本预测值大于负样本预测值的方案数
    cnt1 = sum(ones['pred'].apply(lambda x : (x > zeros['pred'])).sum())
    # 正样本预测值小于负样本预测值的方案数
    cnt2 = sum(ones['pred'].apply(lambda x : (x < zeros['pred'])).sum())
    # 正样本预测值大于负样本预测值的概率
    p1 = cnt1 / totoal_pairs
    # 正样本预测值小于负样本预测值的概率
    p2 = cnt2 / totoal_pairs
    # 正样本预测值等于负样本预测值的概率
    p3 = 1 - p1 - p2
    # 相等的情况占 0.5 权重
    auc = p1 + 0.5 * p3
    return auc

if __name__=='__main__':
    labels = np.array([1, 0, 0, 0, 1, 0, 1, 0, ])
    preds = np.array([0.9, 0.8, 0.3, 0.1, 0.4, 0.9, 0.66, 0.7])
    print("AUC by hand:",AUC(preds,labels))
    print("AUC by sklearn",roc_auc_score(labels,preds))

运行结果:
在这里插入图片描述

甚至,可以直接 O ( M × N ) O(M\times N) O(M×N) 枚举样本对,计算概率:
原文:https://zhuanlan.zhihu.com/p/84035782

def AUC(label, pre):
  """
  适用于python3.0以上版本
   """
  #计算正样本和负样本的索引,以便索引出之后的概率值
    pos = [i for i in range(len(label)) if label[i] == 1]
    neg = [i for i in range(len(label)) if label[i] == 0]
 
    auc = 0
    for i in pos:
        for j in neg:
            if pre[i] > pre[j]:
                auc += 1
            elif pre[i] == pre[j]:
                auc += 0.5
 
    return auc / (len(pos)*len(neg))
 
 
if __name__ == '__main__':
    label = [1,0,0,0,1,0,1,0]
    pre = [0.9, 0.8, 0.3, 0.1, 0.4, 0.9, 0.66, 0.7]
    print(AUC(label, pre))
 
    from sklearn.metrics import roc_curve, auc
    fpr, tpr, th = roc_curve(label, pre , pos_label=1)
    print('sklearn', auc(fpr, tpr))

3. AUC 作为概率的计算公式

公式

按照上述定理,可得出 AUC 计算公式如下:
A U C = ∑ i ∈ 正样本 r a n k ( i ) − M × ( 1 + M ) 2 M × N AUC=\frac{\sum_{i\in正样本}rank(i)-\frac{M\times(1+M)}{2}}{M\times N} AUC=M×Ni正样本rank(i)2M×(1+M)

解释

设:正样本个数为 M M M 个,负样本个数为 N N N 个,则总样本数为 M + N M+N M+N

  1. 按照求概率的方法,确定 总方案数目标方案数 ,再求比值;
  2. 总方案数:随机选取一正一负的方案数= M × N M\times N M×N;
  3. 目标方案数:选取的正样本预测概率 > 选取的负样本预测概率 的方案数。根据定义,正样本有 M M M 个,负样本有 N N N 个,这里拿正样本进行分类讨论:
  • 首先,将样本按照模型预测的概率大小,增序排序,序列长度为 M + N M+N M+N;
  • 对于概率最大的正样本,设下标为 r a n k 1 rank_1 rank1,则表明其前方(含自身)共有 r a n k 1 rank_1 rank1 个样本。同时根据定义,其又是概率最大的正样本,表明前方(含自身)共有 M M M 个正样本(因为其是预测概率最大的正样本,则其他所有的正样本概率都比它小,都在其前方)。现在要满足负样本的概率比其小,则负样本的下标 i i i 必须满足 i < r a n k 1 i<rank_1 i<rank1,这样的负样本个数有 r a n k 1 − M rank_1-M rank1M 个,从中选一个,共有 r a n k 1 − M rank_1-M rank1M 种方案;
  • 对于概率次大的正样本,设下标为 r a n k 2 rank_2 rank2,则表明其前方(含自身)共有 r a n k 2 rank_2 rank2 个样本。同时根据定义,其又是概率次大的正样本,表明前方(含自身)共有 M − 1 M-1 M1 个正样本。现在要满足负样本的概率比其小,则负样本的下标 i i i 必须满足 i < r a n k 2 i<rank_2 i<rank2,这样的负样本个数有 r a n k 2 − ( M − 1 ) rank_2-(M-1) rank2(M1) 个,从中选一个,共有 r a n k 2 − ( M − 1 ) rank_2-(M-1) rank2(M1) 种方案;
  • 同理,对于概率最小的正样本,设下标为 r a n k M rank_M rankM,则表明其前方(含自身)共有 r a n k M rank_M rankM 个样本, 同时其又是概率最小的正样本,表明前方(含自身)共有 1 个正样本,负样本个数则为 r a n k M − 1 rank_M-1 rankM1,从中选一个,共有 r a n k M − 1 rank_M-1 rankM1 种方案;
  • 将上述 M M M 中情况求和,可得目标方案数 为 : r a n k 1 + r a n k 2 + . . . + r a n k M − ( 1 + 2 + . . . + M ) = ∑ r a n k i − M × ( 1 + M ) M × N rank_1+rank_2+...+rank_M-(1+2+...+M)=\sum rank_i-\frac{M\times (1+M)}{M\times N} rank1+rank2+...+rankM(1+2+...+M)=rankiM×NM×(1+M) .
  • 求概率:
    A U C = ∑ i ∈ 正样本 r a n k ( i ) − M × ( 1 + M ) 2 M × N AUC=\frac{\sum_{i\in正样本}rank(i)-\frac{M\times(1+M)}{2}}{M\times N} AUC=M×Ni正样本rank(i)2M×(1+M)

细节:对于预测概率相同的样本,将其 rank 求平均。
原文:看完这篇AUC文章,搞定任何有关AUC的面试不成问题 - 知乎 (zhihu.com)

import numpy as np
import pandas as pd
y_pred = list(np.random.uniform(0.4,0.6, 2000))+ list(np.random.uniform(0.5, 0.7, 8000))
y_true =[0]*2000+[1]*8000

def calc_auc(y_true, y_pred):
	pair = list(zip(y_true,y_pred))
	pair = sorted(pair,key=lambda x:x[1]) 
	df = pd.DataFrame([[x[0],x[1], i+1] for i,x in enumerate(pair)],columns=['y_true','y_pred','rank'])
#下面需要将预测值为一样的进行重新编号
	for k,y in df.y pred.value_counts().items():
		if v==1: # 说明该预测值k只出现了一次,没有重复,不考虑
			continue
		rank_mean = df[df.y pred == k]['rank'].mean( )
		df.loc[df.y_pred ==k,"rank']= rank_mean
	pos_df = df[df.y true ==1]
	m = pos_df.shape[0]	# 正样本数
	n = df.shape[0]-m # 负样本数
	return (pos_df['rank'].sum()-m*(m+1)/2)/(m *n)
	
print(calc_auc(y_true,y_pred))
from sklearn,metrics import roc_auc_score
print(roc_auc_score(y_true,y_pred))

在这里插入图片描述

总结

AUC 的实现方法分为 积分近似基于概率 两种思路。
积分近似 严格按照 AUC 的定义,取不同阈值绘制 ROC 曲线,划分 bins 计算,但此方法实现较为繁琐,面试中一般不推荐此写法。
基于概率 可以直接根据定理统计概率,也可以根据计算公式去实现,面试中比较推荐实现这两种写法。

面试八股

原文
优点

  1. 全局评估能力,其他指标比如precision,recall,F1,根据区分正负样本阈值的变化会有不同的结果,而AUC不需要手动设定阈值,是一种整体上的衡量方法。
  2. 因此对正负样本均衡不敏感,在样本不均衡的情况下,也可以做出合理的评估;
  3. AUC衡量的是一种排序能力,因此特别适合排序类业务;

缺点

  1. 过于笼统,无法反映召回率、精确率等在实际业务中经常关心的指标;它没有给出模型误差的空间分布信息,AUC只关注正负样本之间的排序,并不关心正样本内部,或者负样本内部的排序,这样我们也无法衡量样本对于好坏客户的好坏程度的刻画能力;
  2. 忽略了预测的概率值和模型的拟合程度;

延伸

三种方法的时间复杂度对比
多分类AUC

参考文献

A Complete Guide to Area Under Curve (AUC)
AUC公式的证明
看完这篇AUC文章,搞定任何有关AUC的面试不成问题 - 知乎 (zhihu.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值