- TP (真正) 实际正
- FP (假正) 实际负
- TN (真负) 实际负
- FN (假负) 实际正
以及下面两个比率
- TPR(真正率) = TP➗(TP+FN) = 被预测正确的正例➗所有实际正例
- FPR(假正率) = FP➗(FP+TN) = 被预测错误的负例➗所有实际负例
ROC曲线就是根据通过取不同的阈值,得到不同的(FPR, TPR)点画出来的曲线。 FPR为x轴,TPR为y轴
可以想象如果阈值为0,那么>=0的都认为是正例,所有的正例都被预测正确则FPR=1, 所有的负例都被预测错误TPR=1 最右侧点 如果阈值为1,那么所有的正例都认为是负例则FPR=0,没有负例被预测错误则TPR=0 最左侧点 AUC就是这条曲线下的面积,面积越大说明这个分类器的效果越好。
在推荐或者搜索里面一般是要评价最后这个结果的排序能力,那么我们也可以通过排序的好坏来计算AUC。AUC还有一个有趣的性质就是测试任意给定一个正样本和负样本,这个正样本有多大的概率大于负样本,下面解释一下这个公式。
怎么理解上面的公式呢,设正样本数量为M,负样本数量为N,那么正负两两组合就是M*N种,AUC=这些组合中正样本大于负样本的数量除以M*N。 计算方法如下: 按score从大到小倒序排序,score最大的rank为n(n=M+N)以此类推。score最大的正例rank为i的话,说明和该正例组合的样本一共有i种,还要减掉M个和正样本组合的情况, 排第二的正例要减去 M-1个正样本,以此类推,最后我们要减去的就是一个等比数列的和。这样就得到了上面的公式。就很容易写代码实现了。
如下计算AUC的scala代码:
<pre name="code" class="java">def abs(n: Double): Double = {
n match {
case i if i < 0 => -n
case _ => n
}
}
val datas = Array((0.7, 1.0), (0.8, 0.0), (0.9, 1.0), (0.5, 0.0))
val predicts = datas.sortBy(_._1)
val M = predicts.filter{case (score, label) => abs(label - 1.0) <= 1e-6}.length.toDouble
val N = predicts.filter{case (score, label) => abs(label - 0.0) <= 1e-6}.length.toDouble
val sumRank = predicts.zipWithIndex.map {
case ((score, label), index) => (label, index+1)
}.filter {
case (label, index) => abs(label - 1.0) <= 1e-6
}.map {
case (label, index) => index
}.sum.toDouble
val auc = M*N match {
case i if (i > 0) => (sumRank - M*(1+M)/2)/(M*N)
case _ => 0.0
}
println(auc)
输出结果:0.75