AUC代码实现

·3365·7 分钟
AI摘要: 本文详细介绍了AUC(Area Under Curve)的两种计算方法,包括时间复杂度为O(N^2)的方法和更高效的O(log N)的方法。文章通过公式推导和Python代码示例,解释了如何根据正负样本的预测概率来计算AUC值。

ROC曲线以FPR作为横轴,TPR作为纵轴。AUC值越大,模型越可能将其分类为正样本

  • FPR:假阳率
  • TPR:真阳率

AUC适合正负样本不均衡的数据集评估,取值范围[0.5,1][0.5, 1]

AUC在希望提高Recall的基础上,还希望降低犯错误的概率,尽量不误报,是偏保守的

计算方法1:O(N2)O(N^2)

根据AUC的统计学定义:随机从正负样本中抽取一对正负样本,正样本的预测概率大于负样本预测概率的概率就是AUC。有点拗口,但就是概率的概率。

AUC=(predpos>predneg)mn\begin{align} AUC = \frac{\sum (pred_{pos} > pred_{neg})}{m *n} \end{align}

用代码来实现就是,在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:O(logN)O(log N)

还有个更好的方法,时间复杂度为O(logN)O(logN) ,这也是比上面方法更重要的方法。

AUC=(predpos>predneg)mn=posneg(predpos>predneg)mn=(posrankpos)m(1+m)2mn\begin{align} AUC & = \frac{\sum (pred_{pos} > pred_{neg})}{m *n} \\ & = \frac{\sum_{pos} \sum_{neg} (pred_{pos} > pred_{neg})}{m * n} \\ & = \frac{(\sum_{pos} rank_{pos}) - \frac{m * (1 + m)}{2}}{m * n} \\ \end{align}
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
Kaggle学习赛初探