AUC代码实现

·3595·8 分钟·
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