zoukankan      html  css  js  c++  java
  • 理解ROC和AUC

    分类器各种各样,如何评价这些分类器的性能呢?(这里只考虑二元分类器,分类器的输出为概率值)

    方法一:概率定义法

    从正样本中随机选取元素记为x,从负样本中随机选取元素记为y,x的置信度大于y的概率

    计算方法可以描述为

    s=0
    for x in 正例:
      s+=1/正例总数×置信度小于x的负例所占比例
    return s
    

    概率是用来定义问题的利器,如基尼系数。

    方法二:正样本排名法

    对全部样本按照置信度从高到低进行排序,排名依次记做1,2,3......全部正例的排名之和记为R,R越小表明分类器越准。

    方法三:复杂定义法

    我提出一种评判指标:点评测法。
    给定一个阈值,就可以将分类器的概率值确定化为0、1值。现在只考虑你认为值为1的样本,这些样本占“实际假”样本的比例越小越好,这些样本占“实际为真”的样本的比例越大越好,于是可以得到二元组(这些样本占“实际假”的比例,这些样本占“实际真”的比例)。把这个二元组画在平面直角坐标系中,得到一个点,这个点离(0,1)越近越好!
    上面说的是给定了一个阈值,大多数时候,阈值是不好确定的,那就采用“动态阈值”,让阈值从0到1变化就会得到一个点序列,也就是一条曲线。这条曲线就是传说中的ROC。

    ROC是一种折线,它是一种评价二值分类器的性能指标
    AUC是求折线下面的面积,折线不一定是ROC折线

    00:我说0,实际也为0
    01:我说0,实际为1
    10:我说1,实际为0
    11:我说1,实际为1

    那么,x坐标可以表示为10/(00+10),y坐标可以表示为11/(01+11)
    也就是说,x坐标可以表示为10/x0,y坐标表示为11/x1
    也就是说,只看“我说1”的那一部分,该部分在全部“实际0”的部分所占比例为x轴,该部分在“实际1”部分所占比例为y轴。

    描述起来多么复杂!但是上面三种方法得到的结果是一模一样的,虽然定义上差很多,但最终说的是一回事!

    AUC的直观认识

    • AUC对于置信度不敏感,它只关注名次而不关注具体置信度
    • 从AUC角度看,好的分类器就是一个好的排序器。分类器在某种程度上就是排序器。
    • AUC对于样本正负样本不均衡的现象完全不敏感,从方法一定义上可以看出来。
    import sklearn.metrics as metrics
    import numpy as np
    
    
    def roc(y_true, y_mine):
        a = [(y_mine[i], y_true[i]) for i in range(len(y_true))]
        a = sorted(a, key=lambda x: -x[0])
        thresh = sorted(list(set([0, 1] + [i[0] for i in a])), key=lambda x: -x)
        cnt = [np.count_nonzero(y_true == 0), np.count_nonzero(y_true == 1), 0, 0]
        x = []
        y = []
        j = 0
        for i in thresh:
            while j < len(a) and a[j][0] >= i:
                if a[j][1]:
                    cnt[0b11] += 1
                    cnt[0b01] -= 1
                else:
                    cnt[0b10] += 1
                    cnt[0b00] -= 1
                j += 1
            x.append(cnt[0b10] / (cnt[0b00] + cnt[0b10]))
            y.append(cnt[0b11] / (cnt[0b11] + cnt[0b01]))
        return x, y, thresh
    
    
    def auc(x, y):
        a = sorted([(x[i], y[i]) for i in range(len(x))], key=lambda x: x[0])
        area = 0
        for i in range(1, len(a)):
            area += (a[i - 1][1] + a[i][1]) * (a[i][0] - a[i - 1][0]) / 2
        return area
    
    
    def auc2(y_mine, y_true):
        """
        auc的物理意义在于:随机正样本比随机负样本得分高的概率
        基于这种直观的思路可以更快速的计算auc
        :param y_mine:
        :param y_true:
        :return:
        """
        a = sorted([(y_mine[i], y_true[i]) for i in range(len(y_mine))], key=lambda x: -x[0])
        s = 0
        true_count = len([1 for i in y_true if i])
        false_count = len(y_true) - true_count
        total_false = false_count
        for i in a:
            if i[1]:
                s += 1 / true_count * false_count / total_false
            else:
                false_count -= 1
        return s
    
    
    y_mine = np.random.rand(10)
    y_true = np.random.randint(0, 2, 10)
    x, y, t = roc(y_true, y_mine)
    print(auc(x, y))
    print(metrics.auc(x, y))
    print(auc2(y_mine, y_true))
    
    

    其实,auc还可以推导一下得到更简单一点的公式:

    def auc2(y_mine, y_true):
        """
        auc的物理意义在于:随机正样本比随机负样本得分高的概率
        基于这种直观的思路可以更快速的计算auc
        本函数只适用于计算得分各不相同的样本,如果存在并列情况,此函数结果错误
        :param y_mine:
        :param y_true:
        :return:
        """
        a = sorted(zip(y_mine, y_true), key=lambda x: -x[0])
        T = np.count_nonzero(y_true)
        F = len(y_true) - T
        R = sum(i + 1 for i in range(len(a)) if a[i][1] == 1)
        return 1 + (T + 1) / (2 * F) - R / (T * F)
    

    知乎上大神的答案,很高级。

    从AUC到真实类别(label)?
    最开始思考这个问题是做一个网上的比赛,二分类问题,每次提交一组预测系统会计算出一个AUC值,因为这个比赛只有5000样本,并且系统显示的AUC值有小数点后面7、8位,所以我想是否可以通过可能通过AUC值计算出样本的真实label来。也许并没有实际价值,但是问题本身是很有趣的,像是在破解密码。
    一个naive但是可行但是效率很低的办法, 就是每次生成一组预测值,里面只有一个样本预测为1,其余都是0,然后提交预测计算AUC,然后根据这个AUC来判断此样本的label,但是这样效率太低了,5000个样本,需要5000次提交。
    思考了很久,最后发现可以通过AUC的另一个计算公式入手。也就是第一部分说的U statistic。
    
    3.1 根据一个AUC值计算样本中0,1的数目
    根据AUC与U statistic的联系,我们可以用下面的代码计算AUC:
    auc=(sum(rank(c(p(label==1),p(label==0)))[1:n1])-n1*(n1+1)/2)/n0/n1
    上面label表示样本的真实类别,p表示预测为1的概率,rank是R的内置函数,n0表示真实数据中0的数目,n1表示1的数目,n0+n1=n表示数据所有样本数目,根据这个简单的一行代码,我们可以不用任何包来计算AUC。
    上面公式很有趣,n0,n1还有label都是固定的,p不同导致auc不一致,观察sum里面,可以发现这个sum本质是什么?就是计算pred里面对应的真实label为1的那些预测值的rank的和。
    继续使用第一部分的例子,5个样本的预测值的rank:
    rank(c(0.5,0.6,0.55,0.4,0.7))[1] 2 4 3 1 5
    其中真实为1的样本(1,2,5)的对应rank是2,4,5,这三个rank值的和2+4+5=11,n0=2,n1=3,于是根据上面的AUC公式:(11-3*(3+1)/2)/2/3=5/6=0.83333,这个结果与我们在第一部分用AUC定义算的值完全一样。
    所以说,真正影响auc的,就是预测值里面对本来是1的样本的预测的rank的和。
    要破解真实label,第一部要做的是找到样本里面0和1的数目,也就是n0和n1等于多少。这个问题不复杂。要知道相同预测值的rank也一致,就是说如果所有样本的预测只取0或者1,那对应的rank只有两个unique数值。
    再观察AUC公式里面的sum:
    sum(rank(c(pred(label==1),pred(label==0)))[1:n1])
    这个sum是n1个数值的和,这n1个数值,当我们的pred只有两个不同取值的时候,仅包括两个unique的数值。继续用上面例子,一共有5个样本,我们生成第一组预测p如下:
    > p=c(1,1,1,0,0)> rank(p)[1] 4.0 4.0 4.0 1.5 1.5
    
    可见p的rank只有两个不同取值,1.5和4,这是因为预测概率p也只有两个不同取值。
    然后我们还知道sum是n1个数的sum,我们不知道的是这n1个数,里面有几个1.5,几个4,假设其中有k1个1.5,k2个4,可以列出一个方程:
    k1+k2=n1
    k1*1.5+k2*4=sum(rank(c(p(label==1),p(label==0)))[1:n1])=auc*n0*n1+n1*(1+n1)/2
    所以最终得到下面的方程组:
    k1+k2=n1
    k1*1.5+k2*4=0.833333*n0*n1+n1*(1+n1)/2
    n0+n1=5
    其中k1,k2和n1未知,两个方程,3个未知数,属于不定方程组,但是要知道k1,k2,和n1都是整数,并且有取值范围,要借出来很简单,写一个for 循环,1秒中就可以找到一组满足3个方程多k1,k2以及n1。
    如果我们变更p,比如p=c(rep(1,m),rep(0,5-m)),通过一样的算法,可以计算出来前m个样本中1的数量。
    通过这个算法,我可以算出来这个贷款预测比赛测试数据中有509个1和4491个0。
    做到这里,差点就放弃了,但是后来突然又有了灵感,找到了下面的算法:
    3.2 根据AUC破解样本的真实label
    这里就省略思考过程了, 直接来破解算法:
    对于一组总数为n的测试样本,我们先来计算
    m=floor(log(n,base=2))+1
    这个m表示我们通过两次auc计算可以计算出至少多少个样本的真实label,比如n=5000,那么m=13
    也就是说通过我们两次提交,可以最少得到13个样本的label。这13个样本是可以自己随便指定的,比如我们对前13个样本感兴趣,那么具体做法如下:
    fix1=2^c(0:12)fix2=c(fix1[-1],fix1[1])unfixed=setdiff(1:5000,fix1)p1=c(fix1,unfixed)#第1组预测p2=c(fix2,unfixed)#第2组预测
    使用上面的两组预测p1和p2分别计算AUC,得到auc1和auc2,根据上面给出的auc算法:
    sum(rank(c(p1(label==1),p1(label==0)))[1:n1])-n1*(1+n1)/2=auc1*n0*n1sum(rank(c(p2(label==1),p2(label==0)))[1:n1])-n1*(1+n1)/2=auc2*n0*n1
    两个公式相减:
    sum(rank(c(p1(label==1),p1(label==0)))[1:n1])-sum(rank(c(p2(label==1),p2(label==0)))[1:n1])-n1*(1+n1)/2=(auc1-auc2)*n0*n1
    得到的这个等式里,我们已经通过上面的方法找到了n0和n1,auc1和auc2是已知,所以等式右面值可以算出,那么等式左面呢,因为我们两个预测结果p1和p2只有前三个点的预测之不一样,其余点的预测值一样,rank也一样,那么等式左面的两个sum的差,其实只由前13个样本的真实label决定,具体来说:
    sum1-sum2=y1*(fix1[1]-fix2[1])+y2*(fix1[2]-fix2[2])+...+y13*(fix1[13]-fix2[13])=y1*(-1)+y2*(-2)+...+y12*(-2048)+y13*(4095)
    
    上面的方程里面yi代表样本i的真实label,有且只有唯一解,以为这个方程本质上就是10进制数用2进制表达。所以通过两次auc计算,我们可以找到13个点的真实标签。比如对上面提到的贷款预测比赛,选定前13个label,auc1=0.50220853,auc2= 0.5017588,然后就可以算出来前13个test样本只有第三个样本是0,其余都是1。
    但是13并不是上限,我有一些更好的结果,比较复杂,在这就不展开说了。
    

    参考资料

    https://www.zhihu.com/question/39840928

  • 相关阅读:
    大熊君JavaScript插件化开发------(第二季)
    大熊君JavaScript插件化开发------(第一季)
    大熊君大话NodeJS之------基于Connect中间件的小应用(Bigbear记事本应用之第一篇)
    大熊君大话NodeJS之 ------ Connect中间件第二季(源码分析)
    大熊君大话NodeJS之------Connect中间件模块(第一季)
    大熊君大话NodeJS之------MongoDB模块(额外篇)
    大熊君大话NodeJS之------(Url,QueryString,Path)模块
    The SSL certificate used to load resources from xxx will be distrusted in M70.
    理解ios 11中webview的视口
    谨慎升级到HTTPS
  • 原文地址:https://www.cnblogs.com/weiyinfu/p/7659807.html
Copyright © 2011-2022 走看看