ROC 曲线详解

前言

ROC 曲线是一种坐标图式的分析工具,是由二战中的电子和雷达工程师发明的,发明之初是用来侦测敌军飞机、船舰,后来被应用于医学、生物学、犯罪心理学。

如今,ROC 曲线已经被广泛应用于机器学习领域的模型评估,说到这里就不得不提到 Tom Fawcett 大佬,他一直在致力于推广 ROC 在机器学习领域的应用,他发布的论文《An introduction to ROC analysis》[1]更是被奉为 ROC 的经典之作(引用 2.2w 次),知名机器学习库 scikit-learn 中的 ROC 算法就是参考此论文实现,可见其影响力!

不知道大多数人是否和我一样,对于 ROC 曲线的理解只停留在调用 scikit-learn 库的函数,对于它的背后原理和公式所知甚少。

前几天我重读了《An introduction to ROC analysis》终于将 ROC 曲线彻底搞清楚了,独乐乐不如众乐乐!如果你也对 ROC 的算法及实现感兴趣,不妨花些时间看完全文,相信你一定会有所收获!

图片

一、什么是 ROC 曲线

下图中的蓝色曲线就是 ROC 曲线,它常被用来评价二值分类器的优劣,即评估模型预测的准确度。

二值分类器,就是字面意思它会将数据分成两个类别(正/负样本)。例如:预测银行用户是否会违约、内容分为违规和不违规,以及广告过滤、图片分类等场景。篇幅关系这里不做多分类 ROC 的讲解。

图片

坐标系中纵轴为 TPR(真阳率/命中率/召回率)最大值为 1,横轴为 FPR(假阳率/误判率)最大值为 1,虚线为基准线(最低标准),蓝色的曲线就是 ROC 曲线。其中 ROC 曲线距离基准线越远,则说明该模型的预测效果越好。(TPR: True positive rate; FPR: False positive rate)

  • ROC 曲线接近左上角:模型预测准确率很高

  • ROC 曲线略高于基准线:模型预测准确率一般

  • ROC 低于基准线:模型未达到最低标准,无法使用

二、背景知识

考虑一个二分类模型, 负样本(Negative) 为 0,正样本(Positive) 为 1。即:

  • 标签 y 的取值为 0 或 1。

  • 模型预测的标签为 \hat{y},取值也是 0 或 1。

因此,将 y\hat{y} 两两组合就会得到 4 种可能性,分别称为:

图片

2.1 公式

ROC 曲线的横坐标为 FPR(False Positive Rate),纵坐标为 TPR(True Positive Rate)。FPR 统计了所有负样本中 预测错误(FP) 的比例,TPR 统计了所有正样本中 预测正确(TP) 的比例,其计算公式如下,其中 # 表示统计个数,例如 #N 表示负样本的个数,#P 表示正样本的个数

\text{FPR}=\frac{\#\text{FP}}{\#\text{N}} $$ $$\text{TPR}=\frac{\#\text{TP}}{\#\text{P}}

2.2 计算方法

下面举一个实际例子作为讲解,以下表 5 个样本为例,讲解如何计算 FPR 和 TPR

id真实标签  y预测标签 \hat{y}
111
210
300
411
501

正样本数 \#P=3,负样本数\#N=2

其中 y=0\hat{y}=1的样本有 1 个,即 \#FP=1,所以 FPR=1/2=0.5

其中 y=1\hat{y}=1 的样本有 2 个,即 \#TP=2,所以 FPR=2/3

FPR 和 TPR 的取值范围均是 0 到 1 之间。对于 FPR,我们希望其越小越好。而对于 TPR,我们希望其越大越好。

至此,我们已经介绍完如何计算 FPR 和 TPR 的值,下面将会讲解如何绘制 ROC 曲线。

三、绘制 ROC 曲线

讲到这里,可能有的同学会问:ROC 不是一条曲线吗?讲了这么多它到底应该怎么画呢?下面将分为两部分讲解如何绘制 ROC 曲线,直接打通你的“任督二脉”彻底拿下 ROC 曲线:

  • 第一部分:通过手绘的方式讲解原理

  • 第二部分:Python 代码实现,代码清爽易读

3.1 手绘 ROC 曲线

一般在二分类模型里(标签取值为 0 或 1),会默认设定一个阈值 (threshold)。当预测分数大于这个阈值时,输出 1,反之输出 0。我们可以通过调节这个阈值,改变模型预测的输出,进而画出 ROC 曲线。

以下面表格中的 20 个点为例,介绍如何人工画出 ROC 曲线,其中正样本和负样本都是 10 个,即 \#P = \#N = 10

id真实标签预测分数id真实标签预测分数
11.9111.4
21.8120.39
30.7131.38
41.6140.37
51.55150.36
61.54160.35
70.53171.34
80.52180.33
91.51191.30
100.505200.1

当设定阈值为 0.9 时,只有第一个点预测为 1,其余都为 0,故 \#FP=0\#TP=1,计算出 FPR=0/10=0TPR=1/10=0.1,画出点 (0,0.1)

当设定阈值为 0.8 时,只有前两个点预测为 1,其余都为 0,故 $\#FP=0、\#TP=2$,计算出 FPR=0/10=0 、TPR=2/10=0.2,画出点 (0,0.2)

当设定阈值为 0.7 时,只有前三个点预测为 1,其余都为 0,故 \#FP=1\#TP=2,计算出 FPR=1/10=0.1TPR=2/10=0.2,画出点 (0.1,0.2)。

以此类推,画出的 ROC 曲线如下:

图片

因此,在画 ROC 曲线前,需要将预测分数从大到小排序,然后将预测分数依次设定为阈值,分别计算 FPRTPR。而对于基准线,假设随机预测为正样本的概率为 x,即 \Pr(\hat{y}=1)=x 由于 FPR 计算的是负样本中,预测为正样本的概率,因此 FPR= x(同理,TPR= x)。所以,基准线为从点 (0, 0) 到 (1, 1) 的斜线

3.2 Python 代码

接下来,我们将结合代码讲解如何在 Python 中绘制 ROC 曲线。

下面的代码参考了《An Introduction to ROC Analysis》[2]中的算法 1(伪代码)。值得一提的是,知名机器学习库 scikit-learn 的 roc_curve 函数[3] 也参考了这个算法。

图片

下面我自己实现的 roc 函数可以理解为是简化版的 roc_curve,这里的代码逻辑更加简洁易懂,算法的时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)。

完整的代码如下:

# import numpy as np
def roc(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_examplestp, fp = 0, 0tpr, fpr, thresholds = [], [], []score = max(y_score) + 1# 根据排序后的预测分数分别计算fpr和tprfor i in np.flip(np.argsort(y_score)):# 处理样本预测分数相同的情况if y_score[i] != score:fpr.append(fp / num_negtive_examples)tpr.append(tp / num_positive_examples)thresholds.append(score)score = y_score[i]if y_true[i] == pos_label:tp += 1else:fp += 1fpr.append(fp / num_negtive_examples)tpr.append(tp / num_positive_examples)thresholds.append(score)return fpr, tpr, thresholds

导入上面 3.1 表格中的数据,通过上面实现的 roc 方法,计算 ROC 曲线的坐标值。

import numpy as npy_true = np.array([1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0]
)
y_score = np.array([.9, .8, .7, .6, .55, .54, .53, .52, .51, .505,.4, .39, .38, .37, .36, .35, .34, .33, .3, .1
])fpr, tpr, thresholds = roc(y_true, y_score, pos_label=1)

最后,通过 Matplotlib 将计算出的 ROC 曲线坐标绘制成图。

import matplotlib.pyplot as pltplt.plot(fpr, tpr)
plt.axis("square")
plt.xlabel("False positive rate")
plt.ylabel("True positive rate")
plt.title("ROC curve")
plt.show()

图片

至此,ROC 的基础知识部分就全部讲完了,如果还想深入了解的同学可以继续往下看。

四、联邦学习中的 ROC 平均

图片

顾名思义,ROC 平均就是将多条 ROC 曲线“平均化”。那么,什么场景需要做 ROC 平均呢?例如:横向联邦学习中,由于样本都在用户本地,服务器可以采用 ROC 平均的方式,计算近似的全局 ROC 曲线

ROC 的平均有两种方法:垂直平均、阈值平均,下面将逐一进行讲解,并给出 Python 代码实现。

4.1 垂直平均

图片

垂直平均(Vertical averaging)的思想是,选取一些 FPR 的点,计算其平均的 TPR 值。下面是论文中的算法描述的伪代码,看不懂可直接略过看 Python 代码实现部分。

图片

下面是 Python 的代码实现:

# import numpy as np
def roc_vertical_avg(samples, FPR, TPR):"""samples:选取FPR点的个数FPR:包含所有FPR的列表TPR:包含所有TPR的列表"""nrocs = len(FPR)tpravg = []fpr = [i / samples for i in range(samples + 1)]for fpr_sample in fpr:tprsum = 0# 将所有计算的tpr累加for i in range(nrocs):tprsum += tpr_for_fpr(fpr_sample, FPR[i], TPR[i])# 计算平均的tprtpravg.append(tprsum / nrocs)return fpr, tpravg# 计算对应fpr的tpr
def tpr_for_fpr(fpr_sample, fpr, tpr):i = 0while i < len(fpr) - 1 and fpr[i + 1] <= fpr_sample:i += 1if fpr[i] == fpr_sample:return tpr[i]else:return interpolate(fpr[i], tpr[i], fpr[i + 1], tpr[i + 1], fpr_sample)# 插值
def interpolate(fprp1, tprp1, fprp2, tprp2, x):slope = (tprp2 - tprp1) / (fprp2 - fprp1)return tprp1 + slope * (x - fprp1)

4.2 阈值平均

图片

阈值平均(Threshold averaging)的思想是,选取一些阈值的点,计算其平均的 FPR 和 TPR。

图片

下面是 Python 的代码实现:

# import numpy as np
def roc_threshold_avg(samples, FPR, TPR, THRESHOLDS):"""samples:选取FPR点的个数FPR:包含所有FPR的列表TPR:包含所有TPR的列表THRESHOLDS:包含所有THRESHOLDS的列表"""nrocs = len(FPR)T = []fpravg = []tpravg = []for thresholds in THRESHOLDS:for t in thresholds:T.append(t)T.sort(reverse=True)for tidx in range(0, len(T), int(len(T) / samples)):fprsum = 0tprsum = 0# 将所有计算的fpr和tpr累加for i in range(nrocs):fprp, tprp = roc_point_at_threshold(FPR[i], TPR[i], THRESHOLDS[i], T[tidx])fprsum += fprptprsum += tprp# 计算平均的fpr和tprfpravg.append(fprsum / nrocs)tpravg.append(tprsum / nrocs)return fpravg, tpravg# 计算对应threshold的fpr和tpr
def roc_point_at_threshold(fpr, tpr, thresholds, thresh):i = 0while i < len(fpr) - 1 and thresholds[i] > thresh:i += 1return fpr[i], tpr[i]

五、最后

本文由浅入深地详细介绍了 ROC 曲线算法,包含算法原理、公式、计算、源码实现和讲解,希望能够帮助读者一口气搞懂 ROC。

虽然 ROC 是个不起眼的知识点,但能网上能彻底讲清楚 ROC 的文章并不多。所以我又花时间重温了一遍 Tom Fawcett 的经典论文《An introduction to ROC analysis》[4],并将论文的内容抽丝剥茧、配上通俗易懂的 Python 代码,最终写出了这篇文章。再次致敬🫡 Tom Fawcett,感谢他在机器学习领域的贡献!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/169096.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法导论6:摊还分析,显式与隐式

P258 摊还分析概念 聚合分析&#xff0c;利用它&#xff0c;我们证明对于n&#xff0c;一个n个操作的序列最坏情况下的花费的总时间为T(n)&#xff0c;因此&#xff0c;在最坏情况下&#xff0c;每个操作的平均代价&#xff08;摊还代价&#xff09;为T(n)/n 举了例子来形容这…

Clickhouse学习笔记(9)—— 语法优化

ClickHouse 的 SQL 优化规则是基于 RBO(Rule Based Optimization&#xff09;实现的 官方数据集的使用 为了方便测试CK的语法优化规则&#xff0c;尝试使用官方提供的数据集&#xff1b; 需要使用的数据集是visits_v1和hints_v1&#xff1a; Anonymized Web Analytics Data …

【LeetCode刷题-二分查找】--162.寻找峰值

162.寻找峰值 方法一&#xff1a;寻找最大值 题目保证了nums[i]≠nums[i1]&#xff0c;所以数组nums中最大值两侧的元素一定严格小于最大值本身&#xff0c;因此最大值所在的位置就是一个可行的峰值位置 class Solution {public int findPeakElement(int[] nums) {int idx 0…

ubuntu18-recvfrom接收不到广播报文异常分析

目录 前言 一、UDP广播接收程序 二、异常原因分析 总结 前言 在ubuntu18.04系统中&#xff0c;编写udp接收程序发现接收不到广播报文&#xff0c;使用抓包工具tcpdump可以抓取到广播报文&#xff0c;在此对该现象分析解析如下文所示。 一、UDP广播接收程序 UDP广播接收程序如…

win下安卓打包指南

win下安卓打包指南 0、缘起 换了台电脑竟然忘了怎么打包&#xff0c;还好有笔记&#xff0c;用软件打包也挺好&#xff0c;但是我感觉用 命令行 更有操作感&#xff0c;分享下。 1、下载并配置apktool&#xff08;放在C://Windows无需配置环境变量&#xff0c;需要java环境&…

【Python大数据笔记_day06_Hive】

hive内外表操作 建表语法 create [external] table [if not exists] 表名(字段名 字段类型 , 字段名 字段类型 , ... ) [partitioned by (分区字段名 分区字段类型)] # 分区表固定格式 [clustered by (分桶字段名) into 桶个数 buckets] # 分桶表固定格式 注意: 可以排序[so…

免费3D骨架工具

免费3D骨架工具 : https://posemy.art/ ControlNet 1.1http://www.coloradmin.cn/o/839105.html?actiononClick https://pan.baidu.com/s/1rh39DI9xVbguLO5l7O4pjA yqqe  网盘里的 预处理器/downloads文件夹&#xff08;包含所有预处理器&#xff09;直接放在 extensions/sd…

智安网络|探索人机交互的未来:自然语言处理的前沿技术

自然语言处理是人工智能领域中研究人类语言和计算机之间交互的一门学科。它涉及了语言的理解、生成、翻译、分类和摘要等多个方面。随着人们对自然语言处理的重视和需求不断增长&#xff0c;成为了热门的研究方向。 首先&#xff0c;我们需要了解自然语言处理的基本概念。自然…

使用MVS-GaN HEMT紧凑模型促进基于GaN的射频和高电压电路设计

标题&#xff1a;Facilitation of GaN-Based RF- and HV-Circuit Designs Using MVS-GaN HEMT Compact Model 来源&#xff1a;IEEE TRANSACTIONS ON ELECTRON DEVICES&#xff08;19年&#xff09; 摘要—本文阐述了基于物理的紧凑器件模型在研究器件行为细微差异对电路和系统…

SpringBoot集成easyexcel实现动态模板导出

添加依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-o…

Vue23组件自定义事件 和 解绑事件

Vue2&3组件自定义事件 和 解绑事件 Vue2组件自定义事件 功能&#xff1a;父组件绑定数据&#xff0c;子组件触发事件。&#xff08;父绑子触发&#xff09; 实现步骤&#xff08;前三步在父组件实现&#xff0c;第四步在子组件实现&#xff09;&#xff1a; 第一步&#…

scss 实用教程

变量 $ 定义变量 $link-color: blue;变量名可以与css中的属性名和选择器名称相同 使用变量 a {color: $link_color; }$highlight-border: 1px solid $link_color;中划线和下划线相互兼容&#xff0c;即中划线声明的变量可以使用下划线的方式引用&#xff0c;反之亦然。 $li…