首页 > 其他分享 >小白也能看懂的 ROC 曲线详解

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

时间:2023-07-21 10:00:58浏览次数:48  
标签:曲线 ROC 详解 TPR FPR fpr tpr 能看懂

作者:PrimiHub-Kevin

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

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

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

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

一、什么是 ROC 曲线

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

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

TPR: True positive rate; FPR: False positive rate

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

  • 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}\)
1 1 1
2 1 0
3 0 0
4 1 1
5 0 1

正样本数 #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 真实标签 预测分数
1 1 .9 11 1 .4
2 1 .8 12 0 .39
3 0 .7 13 1 .38
4 1 .6 14 0 .37
5 1 .55 15 0 .36
6 1 .54 16 0 .35
7 0 .53 17 1 .34
8 0 .52 18 0 .33
9 1 .51 19 1 .30
10 0 .505 20 0 .1

当设定阈值为 0.9 时,只有第一个点预测为 1,其余都为 0,故 #FP=0、#TP=1,计算出 FPR=0/10=0,TPR=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.1,TPR=2/10=0.2,画出点 (0.1,0.2)。

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

因此,在画 ROC 曲线前,需要将预测分数从大到小排序,然后将预测分数依次设定为阈值,分别计算 FPR 和 TPR。而对于基准线,假设随机预测为正样本的概率为 \(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》中的算法 1(伪代码)。值得一提的是,知名机器学习库 scikit-learn 的 roc_curve 函数 也参考了这个算法。

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

# 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_examples

    tp, fp = 0, 0
    tpr, fpr, thresholds = [], [], []
    score = max(y_score) + 1
    
    # 根据排序后的预测分数分别计算fpr和tpr
    for 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 += 1
        else:
            fp += 1

    fpr.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 np

y_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 plt

plt.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])
        # 计算平均的tpr
        tpravg.append(tprsum / nrocs)

    return fpr, tpravg

# 计算对应fpr的tpr
def tpr_for_fpr(fpr_sample, fpr, tpr):
    i = 0
    while i < len(fpr) - 1 and fpr[i + 1] <= fpr_sample:
        i += 1

    if 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 = 0
        tprsum = 0
        # 将所有计算的fpr和tpr累加
        for i in range(nrocs):
            fprp, tprp = roc_point_at_threshold(FPR[i], TPR[i], THRESHOLDS[i], T[tidx])
            fprsum += fprp
            tprsum += tprp
        # 计算平均的fpr和tpr
        fpravg.append(fprsum / nrocs)
        tpravg.append(tprsum / nrocs)

    return fpravg, tpravg

# 计算对应threshold的fpr和tpr
def roc_point_at_threshold(fpr, tpr, thresholds, thresh):
    i = 0
    while i < len(fpr) - 1 and thresholds[i] > thresh:
        i += 1
    return fpr[i], tpr[i]

在我们的 PrimiHub 联邦学习模块中,就实现了上述 ROC 平均方法。

五、最后

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

标签:曲线,ROC,详解,TPR,FPR,fpr,tpr,能看懂
From: https://www.cnblogs.com/primihub/p/17570490.html

相关文章

  • 如何理解小程序插件?微信及支付宝官方详解
    一、小程序插件功能介绍1、如何理解插件插件,英文名可称作“Plug-in、Plugin、add-in、addin、add-on、addon或extension”,是一个依附于主程序的辅助程序,透过和主程序的互动,用来代替主程序需要增加一些所需的特定功能。更通俗的来讲,就类似机器的零件,可以“插入”的形式添加到程......
  • nproc
    nproc打印可用的处理器单元数量。概要nproc[OPTION]...主要用途打印可用的处理器单元数量。选项--all打印已安装处理器的数量。--ignore=N如果可以的情况下,排除N个处理单元。--help显示帮助信息并退出。--version显示版本信息并退出。......
  • Linux的USB协议栈详解
    USB协议栈是指在USB通信协议中,将不同层次的协议分开处理,实现模块化封装,从而提高软件开发效率和系统灵活性的技术。USB协议栈中包含了7个不同的层级,如下:应用层:应用程序通过操作系统提供的API与USB设备进行通信;传输层:负责管理传输控制和可靠性;网络层:负责处理逻辑地址、路由和寻址等问......
  • ABAQUS-循环对称条件的详解
    概括anlysisofmodelthatexhibitcyclicsymmetry循环对称分析技术用于Standard求解器。makesitpossibletoanalyzethebehaviorofa360°structurewith cyclicsymmetrybasedonamodelofarepetitivesector;可以在静态、准静态、热传递分析中确定循环......
  • 【Java编程教程】详解Java 中的对象和类
    在本页中,我们将了解Java对象和类。在面向对象的编程技术中,我们使用对象和类来设计程序。Java中的对象既是物理实体又是逻辑实体,而Java中的类只是逻辑实体。什么是Java中的对象具有状态和行为的实体称为对象,例如椅子、自行车、记号笔、笔、桌子、汽车等。它可以是物理的或逻辑......
  • Array方法: indexOf、filter、forEach、map、reduce详解
    [array方法:indexof、filter、foreach、map、reduce详解|FEblog](https://007sair.github.io/2015/08/17/js-Extras/#map)ECMAScript5标准新增了几个数组操作的方法,让我们来看看都是什么:Array.prototype.indexOfArray.prototype.lastIndexOfArray.prototype.everyArray.......
  • c语言学习详解
    C语言一.C语言概述C语言是一种用于和计算机交流的高级语言,它既具有高级语言的特点,又具有汇编语言的特点非常接近自然语言程序的执行效率非常高C语言是所有编程语言中的经典,很多高级语言都是从C语言中衍生出来的,例如:C++、C#、Object-C、Java、Go等等C语言是所有编程语言......
  • 语言模型的预训练[6]:思维链(Chain-of-thought,CoT)定义原理详解、Zero-shot CoT、Few-s
    大语言模型的预训练[6]:思维链(Chain-of-thought,CoT)定义原理详解、Zero-shotCoT、Few-shotCoT以及在LLM上应用1.思维链定义背景在2017-2019年之间,随着Transformer模型的提出,计算资源与大规模语料库不断出现,自然语言处理领域发生了翻天覆地的变化,传统的全监督学习的范......
  • Java详解ThreadLocal
    threadlocal1基础现象:threadlocal作为一个全局变量,在不同的线程去get的时候能够获取不同的值。应用场景:SimpleDateFormat线程不安全,每个线程都要用,new太多,放到threadlocal中线程池可反复使用。一个请求链路很长,经过数个服务,每次都要放到参数带着。改为直接放到threadlocal作为上下......
  • ORA-01555错误详解
    ORA-01555错误详解 ORA-01555(快照过旧)问题让很多人感到十分头痛。最近我们的生产系统上也报出了ORA-01555错误。就结合这次案例将ORA-1555问题作个案例分析,并浅析产生原因和各种解决办法。如果要了解1555错误产生的原因,就需要知道ORACLE的两个特性:一致性读(ConsistentGet)和延......