前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >受试者工作特性曲线 (ROC) 的原理及绘制方式

受试者工作特性曲线 (ROC) 的原理及绘制方式

原创
作者头像
叶子Tenney
发布2024-05-28 11:10:08
4860
发布2024-05-28 11:10:08
举报

引言

受试者工作特性曲线 (Receiver Operating Characteristic, ROC) 曲线是生信分析中一种常用的性能评估方法,那么他背后的原理是什么呢?他为什么会被推荐作为二分类模型的优秀性能指标呢?

曲线下面积 (Area Under the Curve, AUC) 是什么?约登指数是什么?截断值是怎么来的?AUC 会随截断值变化吗?

效果展示

python sklearn roc_curve
python sklearn roc_curve
r pROC roc_curve
r pROC roc_curve

原理

案例背景

在下面的例子里,核酸是一种检测手段,可以是模型、实验方法、巫术,反正就是一种猜答案的方法。有病没病是一种真实存在的情况,可能是疾病、各种特征的有无。

核酸阳性,也确实有病:这个阳性是真阳性。

核酸阴性,也确实没病:这个阳性是真阴性。

核酸阳性,但其实没病:这个阳性是假阳性。

核酸阴性,但其实有病:这个阳性是假阴性。

之后,也就自然而然有了计算的方法。

这里为了便于说明直接取极限:20 个人,10 个有病,检测出来 0 个,没检测出来 20 个。

真实值\预测值

阳性

阴性

合计

病人

0

10

10

非病人

0

10

10

合计

0

20

20

真阳性:0; 预测阳性,同时是真实病人。

真阴性:10; 预测阴性,同时是真实非病人。

假阳性:0; 预测阳性,同时是真实非病人。

假阴性:10; 预测阴性,同时是真实病人。

有了各类样本量,就可以计算各种分类率了。

真阳性率 (真阳性/真实病人):0/10=0%

真阴性率 (真阴性/真实非病人):10/10==100%

假阳性率 (假阳性/真实非病人):0/10=0%

假阴性率 (假阴性/真实病人):10/10=100%

其实也很好理解,阳性一个没预测出来,那么真假阳性率就都是 0%, 因为真假阳性样本量就是 0 嘛,所以也无所谓真假阳性分子都会是 0。

这里出现了一个很关键的事情,那就是:真性和假性是之于预测结果来说的,但真率和假率是之于真实样本量来说的!!! 因此真阳性样本量 + 假阳性样本量等于预测样本量,但真阳性率 + 假阳性率不等于 1

同理,因为真率和假率是之于真实样本量的,所以真阳性率 + 假阴性率 = 1, 假阳性率 + 真阴性率 = 1。

实在搞不懂就死记住率的关系是两个字相反的加起来是 1, 量的关系是第一个字相反加起来等于某值 (不管是啥反正有个值)。

真阳性 + 假阴性 = 全部的阳性样本 = 10;

真阳性率 + 假阴性率 = 0% + 100% = 100%;

真阴性 + 假阳性 = 全部的阴性样本 = 10;

真阴性率 + 假阳性率 = 100% + 0% = 100%;

可以看出,在矩阵的表示真实值表示预测值时,这些率都是按行 (真实值量) 算的,当前行的预测正确的就是真率,预测错误就是假率。

不太严谨的总结矩阵:

阳性

阴性

合计

预测正确/真

真阳性率

假阴性率

1

预测错误/假

假阳性率

真阴性率

1

阳性

阴性

合计

预测正确/真

0%

100%

1

预测错误/假

0%

100%

1

合计

0%

200%

200%

在统计学里,我们又把真阳性率叫敏感度,真阴性率叫特异度。真阳性率越高,说明诊断效果越好,真阴性率越高,说明错误率越少。所以敏感度和特异度都是好东西,越多越好。

阳性

阴性

合计

预测正确/真

敏感度

1-敏感度

1

预测错误/假

1-特异度

特异度

1

下面是各指标的计算方法:

真实值\预测值

阳性

阴性

合计

病人

a

b

a+b

非病人

c

d

c+d

合计

a+c

b+d

a+b+c+d

$真阳性率=敏感度=\frac{a}{a+b}*100\%$

真阳性率(true positive rate,TPR)表示正样本中被预测为正样本的占比

$真阴性率=特异度=\frac{d}{c+d}*100\%$

真阴性率(true negative rate,TNR)表示负样本中被预测为负样本的占比

$假阳性率=1-特异度=\frac{c}{c+d}*100\%$

假阳性率(false positive rate,FPR)表示负样本中被错误地预测为正样本的占比

$假阴性率=1-敏感度=\frac{b}{a+b}*100\%$

假阴性率(false negative rate,FNR)表示正样本中被错误地预测为负样本的占比

混淆矩阵

仅仅使用 roc 的话,有以真实值为底的敏感度和特异度已经足够了,但是为了弄清楚为什么他们可以作为最佳指标以及背后的逻辑,我们需要了解一下混淆矩阵 (仅使用 roc 不想看可以跳过)。

混淆矩阵是机器学习中总结分类模型预测结果的情形分析表。以矩阵形式将数据集中的记录按照真实的类别与分类模型预测的类别判断两个标准进行汇总。

其中矩阵的表示真实值,矩阵的表示预测值,下面我们先以二分类为例,看下矩阵表现形式:

混淆矩阵表现形式
混淆矩阵表现形式

值得注意的是,混淆矩阵并不规定行和列是否由真实或预测值组成,因此计算时一定要注意矩阵的方向。

混淆矩阵除了敏感度和特异度值外,可以被用于计算准确率、召回率和 F1 分数。

这里有一篇文章写的非常好,可以直接看:一文看懂机器学习指标:准确率、精准率、召回率、F1、ROC 曲线、AUC 曲线 - 知乎

准确率(accuracy,ACC)

准确率

Accuracy

准确率是总体的,是所有算对的比例。

准确率 =(TP+TN)/(TP+TN+FP+FN)

精确率(percision)

精准率/查准率/精度/阳性预测值

Precision/Positive Predictive Value(PPV)

只计算预测为阳性样本中的正确率:

精准率 =TP/(TP+FP)

召回率(查全率)- Recall

召回率/查全率/真阳性率/敏感度

Sensitivity/Recall/Hit Rate/True Positive Rate(TPR)

只计算真阳性样本中的正确率:

召回率=TP/(TP+FN)

F1 分数

计算召回率和精确率时的分子都是 TP,不同在于分母。召回率的分母是 P,而精确率的分母是 P′。这也就是说,召回率是相对真实样本而言的,精确率是相对模型预测为正例的样本而言的。

显然,若要提高召回率,则模型会变得「贪婪」,于是犯错的可能性就会变大,也就是精确率下降;若要提高精确率,则模型会变得「保守」,此时模型能够覆盖的正例就少,于是召回率下降。考虑到召回率和精确率之间「跷跷板」的关系,人们发明了 F1 值这个指标,并将其定义为召回率和精确率的调和平均数,从而能够比较容易地在召回率和精确率方面取得平衡。6

F1 分数

F1-score

比较复杂,总之就是平衡精确率和召回率用的。

F1=(2×Precision×Recall)/(Precision+Recall)=2TP/(P+P')

约登指数

数学上召回率和精确率存在跷跷板一样的关系,这来源于精确率计算的分母是 P′而非 P。因为当模型发生变化的时候,P′就会发生变化。所以你无法在提升召回率的时候,保证精确率不变;反之亦然。

精确率 - 召回率曲线(precision-recall curve, PR-Curve)
精确率 - 召回率曲线(precision-recall curve, PR-Curve)

也就是说,如果不使用 P'作为底而使用 P 或者 N, 就可以解决跷跷板问题。于是统计学家发明了约登指数:

$J = 敏感度 + 特异度 -1 = TPR + TNR - 1%$

ROC 曲线

r pROC roc_curve
r pROC roc_curve

终于,基于前面的知识,我们得到了下面的定理或推论:

  1. 约登指数可以代表模型至少某个角度的最好评估能力
  2. 约登指数 = 灵敏度 + 特异度 -1 = 敏感度 - (1 - 特异度)

其中,预测方法下文以模型代指。

模型可以将样本分类为阴性阳性。在我们的预期中,最好的模型可以达到 100% 的预测率,而当模型不能达到 100% 时精确率和召回率是不可能同时有最大值的。

因此,我们可以尝试以约登指数的组成元素构建一个模型评价指标,也就有了 roc 曲线。

(1 - 特异度)为 x 轴,敏感度为 y 轴,一个 roc 空间就完成了。

遍历阈值绘制曲线
遍历阈值绘制曲线

注:橙色代表真实的值,紫色区域代表模型的预测值;

横轴代表测试值 (阈值), 纵轴代表概率但可以理解为无意义,只看面积即可。测试值右侧的面积是真值/阳性样本,左侧是假值/阴性样本。

ROC 曲线也是通过遍历所有阈值来绘制整条曲线的。如果我们不断的遍历所有阈值,预测的正样本和负样本是在不断变化的,相应的在 ROC 曲线图中也会沿着曲线滑动。

当测试值是最小值的时候,所以样本都是真值,预测全是阳性,所以真阳性率 (敏感度) 是 100%, 而没有假值,所以真阴性率 (特异度) 是 0%, 所以假阳性率 (1-真阴性率/1-特异度) 是 100%.

当测试值是最大值的时候,所以样本都是假值,预测全是阴性,所以真阳性率 (敏感度) 是 0%, 而没有真值,所以真阴性率 (特异度) 是 100%, 所以假阳性率 (1-真阴性率/1-特异度) 是 0%.

所以 ROC 曲线都是从左下角开始到右上角结束。

现在再回到一开始的示例图片,就可以解答什么是最近阈值,什么是约登指数了。

python sklearn roc_curve
python sklearn roc_curve

即:ROC 曲线的本质就是比大小,比如这个图就是原数据里面大于 0.205 的就认为是真,小于就是假,这么比大小比出来的。

很显然,Cut-off 指在该值时可得到最大的尤登指数。

这个点是按约登指数计算出来的具有最佳性能的阈值点,也就是尽可能增大灵敏度和特异度的点。

截断值是在模型生成过程中使用的数值,不是评价模型的数值。

绘制方式

r 代码

具体参观公众号"医学和生信笔记"的 "ROC 曲线最佳截点", 这个公众号有挺多干货的,而且免费。

代码语言:r
复制
## 使用pROC包的aSAH数据,其中outcome列是结果变量,1代表Good,2代表Poor
library(pROC)
data(aSAH)
dim(aSAH)
str(aSAH)
## 计算AUC及可信区间
res <- pROC::roc(aSAH$outcome,aSAH$s100b,ci=T,auc=T)
res
## 显示最佳截点,比如AUC最大的点
plot(res,
     legacy.axes = TRUE,
     thresholds="best", # AUC最大的点
     print.thres="best")

python 代码

auc 计算,来源于 python scikit-learn 包。

代码语言:python
复制
import numpy as np
import matplotlib.pyplot as plt
from sklearn import metrics
# pip install -U scikit-learn scipy matplotlib

y = np.array([1, 1, 2, 2])
scores = np.array([0.1, 0.4, 0.35, 0.8])
fpr, tpr, thresholds = metrics.roc_curve(y, scores, pos_label=2)
metrics.auc(fpr, tpr)
代码语言:python
复制
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import RocCurveDisplay
from sklearn.preprocessing import LabelBinarizer
from sklearn.linear_model import LogisticRegression

iris = load_iris()
target_names = iris.target_names
X, y = iris.data, iris.target
y = iris.target_names[y]

random_state = np.random.RandomState(0)
n_samples, n_features = X.shape
n_classes = len(np.unique(y))
X = np.concatenate([X, random_state.randn(n_samples, 200 * n_features)], axis=1)
(
    X_train,
    X_test,
    y_train,
    y_test,
) = train_test_split(X, y, test_size=0.5, stratify=y, random_state=0)

classifier = LogisticRegression()
y_score = classifier.fit(X_train, y_train).predict_proba(X_test)

label_binarizer = LabelBinarizer().fit(y_train)
y_onehot_test = label_binarizer.transform(y_test)
y_onehot_test.shape  # (n_samples, n_classes)
label_binarizer.transform(["virginica"])
class_of_interest = "virginica"
class_id = np.flatnonzero(label_binarizer.classes_ == class_of_interest)[0]
class_id
display = RocCurveDisplay.from_predictions(
    y_onehot_test[:, class_id],
    y_score[:, class_id],
    name=f"{class_of_interest} vs the rest",
    color="darkorange",
    plot_chance_level=True,
)
_ = display.ax_.set(
    xlabel="False Positive Rate",
    ylabel="True Positive Rate",
    title="One-vs-Rest ROC curves:\nVirginica vs (Setosa & Versicolor)",
)
display.figure_.savefig("roc_curve.png")

python sklearn 也可以绘制多分类 roc, 具体见Multiclass Receiver Operating Characteristic (ROC) - scikit-learn.

其他

ROC 与 PR-Curve 的比较6

样本比例变化时的 ROC 曲线性能变化情况
样本比例变化时的 ROC 曲线性能变化情况

由于 ROC 的横纵坐标分别表示 FPR 和 TPR,二者的分母完全隔开,从而使得 AUC of ROC 不受正负样本比例的影响(如上图所示)。这看起来是个好事,因为它在倾斜的数据集上依然保持了稳定的物理意义(类似准确率)。但是,另一方面,这说明在负例数量远大于正例数量的极度倾斜的数据集上,AUC of ROC 可能失真。在这种情况下,PR-Curve 能够更好地反映出模型的性能。

这里就要讨论到 roc 至于其他指标的一个优势了,他不受正负样本比例的影响,可以在比例极其不均的样本上得到优秀的表达效果。

点击率预估模型中的 AUC 与 gAUC(grouped AUC)6

所谓 grouped AUC 就是多组 roc, 那么 roc 作为一个二分类模型如何应用在多分类问题呢?

答案就是多次分组计算 AUC, 而后通过权重来计算 gAUC 值。

不过更多的时候我们不去计算 gAUC 值而是直接通过查看多组的 roc 曲线状态确认模型在多组中的表现情况,如效果展示 1 所示。

引用

  1. 一文详解 ROC 曲线和 AUC 值 - 知乎
  2. 混淆矩阵 Confusion Matrix - 知乎
  3. 一文看懂机器学习指标:准确率、精准率、召回率、F1、ROC 曲线、AUC 曲线 - 知乎
  4. 我想请教一下 ROC 曲线 cut-off 值如何确定?谢谢!!!? - 知乎
  5. 基于 R 语言的 ROC 曲线绘制及最佳阈值点 (Cutoff) 选择 - 知乎
  6. 二分类的评价指标 | 始终
  7. Multiclass Receiver Operating Characteristic (ROC) - scikit-learn
  8. ROC 曲线 - 医学和生信笔记

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 效果展示
  • 原理
    • 案例背景
      • 混淆矩阵
        • 准确率(accuracy,ACC)
        • 精确率(percision)
        • 召回率(查全率)- Recall
        • F1 分数
        • 约登指数
      • ROC 曲线
      • 绘制方式
        • r 代码
          • python 代码
          • 其他
            • ROC 与 PR-Curve 的比较6
              • 点击率预估模型中的 AUC 与 gAUC(grouped AUC)6
              • 引用
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档