小白也能看懂的 AUC 曲线详解

简介

上篇文章 小白也能看懂的 ROC 曲线详解 介绍了 ROC 曲线。本文介绍 AUC。AUC 的全名为Area Under the ROC Curve,即 ROC 曲线下的面积,最大为 1。

根据 ROC 和 AUC 的关系,我们可以得到如下结论

  • ROC 曲线接近左上角 ---> AUC 接近 1:模型预测准确率很高
  • ROC 曲线略高于基准线 ---> AUC 略大于 0.5:模型预测准确率一般
  • ROC 低于基准线 ---> AUC 小于 0.5:模型未达到最低标准,无法使用

二分类 AUC

由 AUC 名称可知,可以先计算 ROC 曲线,得到 TPR 和 FPR 的坐标后再分段计算面积即可得到 AUC

下面是对应的 Python 代码

def auc_from_roc(fpr, tpr):
"""
计算ROC面积
fpr: 从小到大排序的fpr坐标
tpr: 从小到大排序的tpr坐标
"""
area = 0
for i in range(len(fpr) - 1):
area += trapezoid_area(fpr[i], fpr[i + 1], tpr[i], tpr[i + 1])
return area def trapezoid_area(x1, x2, y1, y2):
"""
计算梯形面积
x1, x2: 横坐标 (x1 <= x2)
y1, y2: 纵坐标 (y1 <= y2)
"""
base = x2 - x1
height_avg = (y1 + y2) / 2
return base * height_avg

也可以直接从真实标签和模型预测分数中计算 ROC,算法的时间复杂度为\(O(n\log n)\),参考文献 1 中的算法 2

# import numpy as np
def auc_binary(y_true, y_score, pos_label):
"""
y_true:真实标签
y_score:模型预测分数
pos_label:正样本标签,如“1”
"""
num_positive_examples = (y_true == pos_label).sum()
num_negtive_examples = len(y_true) - num_positive_examples tp, fp, tp_prev, fp_prev, area = 0, 0, 0, 0, 0
score = -np.inf for i in np.flip(np.argsort(y_score)):
if y_score[i] != score:
area += trapezoid_area(fp_prev, fp, tp_prev, tp)
score = y_score[i]
fp_prev = fp
tp_prev = tp if y_true[i] == pos_label:
tp += 1
else:
fp += 1 area += trapezoid_area(fp_prev, fp, tp_prev, tp)
area /= num_positive_examples * num_negtive_examples return area

多分类 AUC

现在考虑多分类的情况,假设类别数为\(C\)。

一种想法是将某一类别设为正样本类别,其余类别设为负样本类别,然后计算二分类下的 AUC。这种方法叫做一对多,即 One-Vs-Rest (OVR)。可以得到\(C\)个二分类的 AUC,然后计算平均数得到多分类的 AUC。

另一种想法是将某一类别设为正样本类别,另外一个类别(非自身)设为负样本类别计算二分类的 AUC。这种方法叫做一对一,即 One-Vs-One (OVO)。可以得到\(C(C-1)\)个二分类的 AUC,然后计算平均数。

当计算平均数时,可以考虑算数平均数(称为 macro),或者加权平均数(称为 weighted)。其中,加权为各类别的样本所占比例。因此,两两组合可以的得到四种计算多分类 AUC 的方法。值得一提的是,知名机器学习库 scikit-learn 的 roc_auc_score 函数 包含了上述四种方法。

  1. 一对多 + 算数平均数(OVR + macro)
  2. 一对多 + 加权平均数(OVR + weighted)
  3. 一对一 + 算数平均数(OVO + macro)
  4. 一对一 + 加权平均数(OVO + weighted)

一对多 + 算数平均数

多分类 AUC 的计算公式为

\[\text{AUC}_\text{total}=\frac{1}{C}\sum_{c_i\in C}\text{AUC}(c_i)
\]

其中\(\text{AUC}(c_i)\)是将类别\(c_i\)作为正样本类别(剩余作为负样本类别),计算的二分类 AUC。

# sklearn.metrics.roc_auc_score(y_true, y_score, average='macro', multi_class='ovr')
def auc_ovr_macro(y_true, y_score):
auc = 0
C = max(y_true) + 1 for i in range(C):
auc += auc_binary(y_true, y_score[:, i], pos_label=i) return auc / C

一对多 + 加权平均数

多分类 AUC 的计算公式为

\[\text{AUC}_\text{total}=\sum_{c_i\in C}\text{AUC}(c_i)p(c_i)
\]

其中,权重\(p(c_i)=\frac{\sum\mathbb{I}\{y=c_i\}}{n}\),即标签为\(c_i\)的样本所占比例,权重之和为 1。

# sklearn.metrics.roc_auc_score(y_true, y_score, average='weighted', multi_class='ovr')
def auc_ovr_weighted(y_true, y_score):
auc = 0
C = max(y_true) + 1
n = len(y_true) for i in range(C):
p = sum(y_true == i) / n
auc += auc_binary(y_true, y_score[:, i], pos_label=i) * p return auc

一对一 + 算数平均数

多分类 AUC 的计算公式为

\[\text{AUC}_\text{total}=\frac{2}{C(C-1)}\sum_{\{c_i,c_j\}\in C,\ c_i<c_j}\text{AUC}(c_i,c_j)
\]

其中,\(\text{AUC}(c_i,c_j)=\frac{\text{AUC}(c_i|c_j)+\text{AUC}(c_j|c_i
)}{2}\)。即将\(c_i\)作为正样本类别、\(c_j\)作为负样本类别计算二分类\(\text{AUC}(c_i|c_j)\);然后将\(c_j\)作为正样本类别、\(c_i\)作为负样本类别计算二分类\(\text{AUC}(c_j|c_i)\)。\(\text{AUC}(c_i,c_j)\)为其计算的算数平均值。由于将\(c_i\)和\(c_j\)组合计算,共得到\(C(C-1)/2\) 个二分类 AUC。

# sklearn.metrics.roc_auc_score(y_true, y_score, average='macro', multi_class='ovo')
def auc_ovo_macro(y_true, y_score):
auc = 0
C = max(y_true) + 1 for i in range(C - 1):
i_index = np.where(y_true == i)[0]
for j in range(i + 1, C):
j_index = np.where(y_true == j)[0]
index = np.concatenate((i_index, j_index)) auc_i_j = auc_binary(y_true[index], y_score[index, i], pos_label=i)
auc_j_i = auc_binary(y_true[index], y_score[index, j], pos_label=j)
auc += (auc_i_j + auc_j_i) / 2 return auc * 2 / (C * (C - 1))

一对一 + 加权平均数

多分类 AUC 的计算公式为

\[\text{AUC}_\text{total}=\sum_{\{c_i,c_j\}\in C,\ c_i<c_j}\text{AUC}(c_i,c_j)p(c_i,c_j)
\]

其中,权重\(p(c_i,c_j)=\frac{\sum\mathbb{I}\{y=c_i\}+\sum\mathbb{I}\{y=c_j\}}{(C-1)n}\),即标签为\(c_i\)和\(c_j\)的样本所占比例,分母中的系数\(C-1\)使得权重之和为 1。

# sklearn.metrics.roc_auc_score(y_true, y_score, average='weighted', multi_class='ovo')
def auc_ovo_weighted(y_true, y_score):
auc = 0
C = max(y_true) + 1
n = len(y_true) for i in range(C - 1):
i_index = np.where(y_true == i)[0]
for j in range(i + 1, C):
j_index = np.where(y_true == j)[0]
index = np.concatenate((i_index, j_index)) p = len(index) / n / (C - 1)
auc_i_j = auc_binary(y_true[index], y_score[index, i], pos_label=i)
auc_j_i = auc_binary(y_true[index], y_score[index, j], pos_label=j)
auc += (auc_i_j + auc_j_i) / 2 * p return auc

参考文献

  1. Fawcett, Tom. "An introduction to ROC analysis." Pattern recognition letters 27, no. 8 (2006): 861-874. https://www.researchgate.net/profile/Tom-Fawcett/publication/222511520_Introduction_to_ROC_analysis/links/5ac7844ca6fdcc8bfc7fa47e/Introduction-to-ROC-analysis.pdf
  2. Hand, David J., and Robert J. Till. "A simple generalisation of the area under the ROC curve for multiple class classification problems." Machine learning 45 (2001): 171-186. https://link.springer.com/content/pdf/10.1023/A:1010920819831.pdf

作者:PrimiHub-Kevin

小白也能看懂的 AUC 曲线详解的更多相关文章

  1. 小白也能看懂的插件化DroidPlugin原理(二)-- 反射机制和Hook入门

    前言:在上一篇博文<小白也能看懂的插件化DroidPlugin原理(一)-- 动态代理>中详细介绍了 DroidPlugin 原理中涉及到的动态代理模式,看完上篇博文后你就会发现原来动态代 ...

  2. 小白也能看懂的插件化DroidPlugin原理(三)-- 如何拦截startActivity方法

    前言:在前两篇文章中分别介绍了动态代理.反射机制和Hook机制,如果对这些还不太了解的童鞋建议先去参考一下前两篇文章.经过了前面两篇文章的铺垫,终于可以玩点真刀实弹的了,本篇将会通过 Hook 掉 s ...

  3. 小白也能看懂的Redis教学基础篇——朋友面试被Skiplist跳跃表拦住了

    各位看官大大们,双节快乐 !!! 这是本系列博客的第二篇,主要讲的是Redis基础数据结构中ZSet(有序集合)底层实现之一的Skiplist跳跃表. 不知道那些是Redis基础数据结构的看官们,可以 ...

  4. 【vscode高级玩家】Visual Studio Code❤️安装教程(最新版🎉教程小白也能看懂!)

    目录 如果您在浏览过程中发现文章内容有误,请点此链接查看该文章的完整纯净版 下载 Linux Mac OS 安装 运行安装程序 同意使用协议 选择附加任务 准备安装 开始安装 安装完成 如果您在浏览过 ...

  5. 小白也能看懂的Redis教学基础篇——做一个时间窗限流就是这么简单

    不知道ZSet(有序集合)的看官们,可以翻阅我的上一篇文章: 小白也能看懂的REDIS教学基础篇--朋友面试被SKIPLIST跳跃表拦住了 书接上回,话说我朋友小A童鞋,终于面世通过加入了一家公司.这 ...

  6. 搭建分布式事务组件 seata 的Server 端和Client 端详解(小白都能看懂)

    一,server 端的存储模式为:Server 端 存 储 模 式 (store-mode) 支 持 三 种 : file: ( 默 认 ) 单 机 模 式 , 全 局 事 务 会 话 信 息 内 存 ...

  7. 小白进阶之Scrapy第六篇Scrapy-Redis详解(转)

    Scrapy-Redis 详解 通常我们在一个站站点进行采集的时候,如果是小站的话 我们使用scrapy本身就可以满足. 但是如果在面对一些比较大型的站点的时候,单个scrapy就显得力不从心了. 要 ...

  8. 小白也能看懂插件化DroidPlugin原理(一)-- 动态代理

    前言:插件化在Android开发中的优点不言而喻,也有很多文章介绍插件化的优势,所以在此不再赘述.前一阵子在项目中用到 DroidPlugin 插件框架 ,近期准备投入生产环境时出现了一些小问题,所以 ...

  9. 小白也能看懂的插件化DroidPlugin原理(一)-- 动态代理

    前言:插件化在Android开发中的优点不言而喻,也有很多文章介绍插件化的优势,所以在此不再赘述.前一阵子在项目中用到 DroidPlugin 插件框架 ,近期准备投入生产环境时出现了一些小问题,所以 ...

  10. 小白都能看懂的tcp三次握手

    众所周知,TCP在建立连接时需要经过三次握手.许多初学者经常对这个过程感到混乱:SYN是干什么的,怎么一会儿是1一会儿是0?怎么既有大写的ACK又有小写的ack?为什么ACK在第二次握手才开始出现?初 ...

随机推荐

  1. 跟运维学 Linux - 01

    跟运维学 Linux - 01 运维的诞生 运维工程师有很多叫法:系统运维.Linux 工程师.系统管理员... 网管可以说是运维工程师最早的雏形.在个人电脑未普及时,大家去网吧玩游戏. 玩家:&qu ...

  2. 国产化之x64平台安装银河麒麟操作系统

    背景 某个项目需要实现基础软件全部国产化,其中操作系统指定银河麒麟v4,CPU使用飞腾处理器.飞腾处理器是ARMv8架构的,在之前的文章中介绍了使用QEMU模拟ARMv8架构安装银河麒麟操作系统的方式 ...

  3. java发送http请求(jquery发送http请求,前后端看这一篇文章够了,很完整)

    为什么写这篇博客? 1.目前很多系统使用了微服务架构,那么各个微服务之间进行内部通信一般采用http协议的方式,springcloud中提供了ribbon,feign,openFeign等组件. 但是 ...

  4. 一步一图带你构建 Linux 页表体系 —— 详解虚拟内存如何与物理内存进行映射

    笔者之前在自己的专栏<聊聊 Linux 内核> 里通过大量的篇幅写了一个系列关于内存管理相关的文章,在这个系列文章中,笔者分别通过虚拟内存管理和物理内存管理两个角度算是把 Linux 内存 ...

  5. Ubutnu 20.04 安装和使用单机版hadoop 3.2 [转载]

    按照此文档操作,可以一次部署成功:Ubutnu 20.04 安装和使用单机版hadoop 3.2 部署之后,提交测试任务报资源问题.原因是yarn还需要配置,如下: $ cat yarn-site.x ...

  6. Python 潮流周刊第 13 期(2023-07-29)

    查看全文: https://pythoncat.top/posts/2023-07-29-weekly 文章&教程 1.Jupyter Notebook 7 隆重发布 (英) 2.Python ...

  7. javascript创建数组

    javascript数组:var array=[ ]等于创建一个数组 array[0]代表给数组的第一个位置上赋值,值为32 array[5]代表给数组的第六位置上赋值.值为3 在位置0,1,2,5位 ...

  8. C# 中关于 T 泛型【C# 基础】

    〇.前言 C# 里面的泛型不仅可以使用泛型函数.泛型接口,也可以使用泛型类.泛型委托等等.在使用泛型的时候,它们会自行检测你传入参数的类型,因此它可以为我们省去大量的时间,不用一个个编写方法的重载.与 ...

  9. C++火车头优化

    代码如下(加在头文件前): 1 #pragma GCC optimize(3) 2 #pragma GCC target("avx") 3 #pragma GCC optimize ...

  10. 微服务集成seata完成分布式事务,解决数据不一致问题

    细心的盆友可能已经发现了,我们的跨行转账并没有保证数据一致性,比如小明扣除了100,但是因为各种问题小红在添加100金额的时候遇到了异常,这个时候数据就出现不一致性 我们可以选择seata来进行分布式 ...