本文分享自华为云社区《技术干货 | 解决面试中80%问题,基于MindSpore实现AUC/ROC》,原文作者:李嘉琪。

ROC/AUC作为机器学习的评估指标非常重要,也是面试中经常出现的问题(80%都会问到)。其实,理解它并不是非常难,但是好多朋友都遇到了一个相同的问题,那就是:每次看书的时候都很明白,但回过头就忘了,经常容易将概念弄混。还有的朋友面试之前背下来了,但是一紧张大脑一片空白全忘了,导致回答的很差。

我在之前的面试过程中也遇到过类似的问题,我的面试经验是:一般笔试题遇到选择题基本都会考这个率,那个率,或者给一个场景让你选用哪个。面试过程中也被问过很多次,比如什么是AUC/ROC?横轴纵轴都代表什么?有什么优点?为什么要使用它?

我记得在我第一次回答的时候,我将准确率,精准率,召回率等概念混淆了,最后一团乱。回去以后我从头到尾梳理了一遍所有相关概念,后面的面试基本都回答地很好。现在想将自己的一些理解分享给大家,希望读完本篇可以彻底记住ROC/AUC的概念。

ROC的全名叫做Receiver Operating Characteristic,其主要分析工具是一个画在二维平面上的曲线——ROC 曲线。平面的横坐标是false positive rate(FPR),纵坐标是true positive rate(TPR)。对某个分类器而言,我们可以根据其在测试样本上的表现得到一个TPR和FPR点对。这样,此分类器就可以映射成ROC平面上的一个点。调整这个分类器分类时候使用的阈值,我们就可以得到一个经过(0, 0),(1, 1)的曲线,这就是此分类器的ROC曲线。一般情况下,这个曲线都应该处于(0, 0)和(1, 1)连线的上方。因为(0, 0)和(1, 1)连线形成的ROC曲线实际上代表的是一个随机分类器。如果很不幸,你得到一个位于此直线下方的分类器的话,一个直观的补救办法就是把所有的预测结果反向,即:分类器输出结果为正类,则最终分类的结果为负类,反之,则为正类。虽然,用ROC 曲线来表示分类器的性能很直观好用。

可是,人们总是希望能有一个数值来标志分类器的好坏。于是Area Under roc Curve(AUC)就出现了。顾名思义,AUC的值就是处于ROC 曲线下方的那部分面积的大小。通常,AUC的值介于0.5到1.0之间,较大的AUC代表了较好的性能。AUC(Area Under roc Curve)是一种用来度量分类模型好坏的一个标准。

ROC示例曲线(二分类问题):

解读ROC图的一些概念定义:

  • 真正(True Positive , TP)被模型预测为正的正样本;
  • 假负(False Negative , FN)被模型预测为负的正样本;
  • 假正(False Positive , FP)被模型预测为正的负样本;
  • 真负(True Negative , TN)被模型预测为负的负样本。

灵敏度,特异度,真正率,假正率

在正式介绍ROC/AUC之前,我们需要介绍两个指标,这两个指标的选择也正是ROC和AUC可以无视样本不平衡的原因。这两个指标分别是:灵敏度和(1-特异度),也叫做真正率(TPR)和假正率(FPR)。

灵敏度(Sensitivity) = TP/(TP+FN)

特异度(Specificity) = TN/(FP+TN)

其实我们可以发现灵敏度和召回率是一模一样的,只是名字换了而已。

由于我们比较关心正样本,所以需要查看有多少负样本被错误地预测为正样本,所以使用(1-特异度),而不是特异度。

真正率(TPR) = 灵敏度 = TP/(TP+FN)

假正率(FPR) = 1- 特异度 = FP/(FP+TN)

下面是真正率和假正率的示意,我们发现TPR和FPR分别是基于实际表现1和0出发的,也就是说它们分别在实际的正样本和负样本中来观察相关概率问题。

正因为如此,所以无论样本是否平衡,都不会被影响。比如总样本中,90%是正样本,10%是负样本。我们知道用准确率是有水分的,但是用TPR和FPR不一样。这里,TPR只关注90%正样本中有多少是被真正覆盖的,而与那10%毫无关系,同理,FPR只关注10%负样本中有多少是被错误覆盖的,也与那90%毫无关系,所以可以看出:

如果我们从实际表现的各个结果角度出发,就可以避免样本不平衡的问题了,这也是为什么选用TPR和FPR作为ROC/AUC的指标的原因。

或者我们也可以从另一个角度考虑:条件概率。我们假设X为预测值,Y为真实值。那么就可以将这些指标按条件概率表示:

  • 精准率 = P(Y=1 | X=1)
  • 召回率 = 灵敏度 = P(X=1 | Y=1)
  • 特异度 = P(X=0 | Y=0)

从上面三个公式看到:如果我们先以实际结果为条件(召回率,特异度),那么就只需考虑一种样本,而先以预测值为条件(精准率),那么我们需要同时考虑正样本和负样本。所以先以实际结果为条件的指标都不受样本不平衡的影响,相反以预测结果为条件的就会受到影响。

ROC(接受者操作特征曲线)

ROC(Receiver Operating Characteristic)曲线,又称接受者操作特征曲线。该曲线最早应用于雷达信号检测领域,用于区分信号与噪声。后来人们将其用于评价模型的预测能力,ROC曲线是基于混淆矩阵得出的。

ROC曲线中的主要两个指标就是真正率和假正率,上面也解释了这么选择的好处所在。其中横坐标为假正率(FPR),纵坐标为真正率(TPR),下面就是一个标准的ROC曲线图。

  • ROC曲线的阈值问题

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

  • 如何判断ROC曲线的好坏

改变阈值只是不断地改变预测的正负样本数,即TPR和FPR,但是曲线本身是不会变的。那么如何判断一个模型的ROC曲线是好的呢?这个还是要回归到我们的目的:FPR表示模型虚报的响应程度,而TPR表示模型预测响应的覆盖程度。我们所希望的当然是:虚报的越少越好,覆盖的越多越好。所以总结一下就是TPR越高,同时FPR越低(即ROC曲线越陡),那么模型的性能就越好。参考如下动态图进行理解。

  • ROC曲线无视样本不平衡

前面已经对ROC曲线为什么可以无视样本不平衡做了解释,下面我们用动态图的形式再次展示一下它是如何工作的。我们发现:无论红蓝色样本比例如何改变,ROC曲线都没有影响。

AUC(曲线下的面积)

为了计算 ROC 曲线上的点,我们可以使用不同的分类阈值多次评估逻辑回归模型,但这样做效率非常低。幸运的是,有一种基于排序的高效算法可以为我们提供此类信息,这种算法称为曲线下面积(Area Under Curve)。

比较有意思的是,如果我们连接对角线,它的面积正好是0.5。对角线的实际含义是:随机判断响应与不响应,正负样本覆盖率应该都是50%,表示随机效果。ROC曲线越陡越好,所以理想值就是1,一个正方形,而最差的随机判断都有0.5,所以一般AUC的值是介于0.5到1之间的。

  • AUC的一般判断标准

0.5 - 0.7:效果较低,但用于预测股票已经很不错了0.7 - 0.85:效果一般0.85 - 0.95:效果很好0.95 - 1:效果非常好,但一般不太可能

  • AUC的物理意义

曲线下面积对所有可能的分类阈值的效果进行综合衡量。曲线下面积的一种解读方式是看作模型将某个随机正类别样本排列在某个随机负类别样本之上的概率。以下面的样本为例,逻辑回归预测从左到右以升序排列:

好了,原理已经讲完,上MindSpore框架的代码。

MindSpore代码实现(ROC)

"""ROC"""
import numpy as np
from mindspore._checkparam import Validator as validator
from .metric import Metric
class ROC(Metric):def __init__(self, class_num=None, pos_label=None):super().__init__()# 分类数为一个整数self.class_num = class_num if class_num is None else validator.check_value_type("class_num", class_num, [int])# 确定正类的整数,对于二分类问题,它被转换为1。对于多分类问题,不应设置此参数,因为它在[0,num_classes-1]范围内迭代更改。self.pos_label = pos_label if pos_label is None else validator.check_value_type("pos_label", pos_label, [int])self.clear()def clear(self):"""清除历史数据"""self.y_pred = 0self.y = 0self.sample_weights = Noneself._is_update = Falsedef _precision_recall_curve_update(self, y_pred, y, class_num, pos_label):"""更新曲线"""if not (len(y_pred.shape) == len(y.shape) or len(y_pred.shape) == len(y.shape) + 1):raise ValueError("y_pred and y must have the same number of dimensions, or one additional dimension for"" y_pred.")# 二分类验证if len(y_pred.shape) == len(y.shape):if class_num is not None and class_num != 1:raise ValueError('y_pred and y should have the same shape, but number of classes is different from 1.')class_num = 1if pos_label is None:pos_label = 1y_pred = y_pred.flatten()y = y.flatten()# 多分类验证elif len(y_pred.shape) == len(y.shape) + 1:if pos_label is not None:raise ValueError('Argument `pos_label` should be `None` when running multiclass precision recall ''curve, but got {}.'.format(pos_label))if class_num != y_pred.shape[1]:raise ValueError('Argument `class_num` was set to {}, but detected {} number of classes from ''predictions.'.format(class_num, y_pred.shape[1]))y_pred = y_pred.transpose(0, 1).reshape(class_num, -1).transpose(0, 1)y = y.flatten()return y_pred, y, class_num, pos_labeldef update(self, *inputs):"""更新预测值和真实值。"""# 输入数量的校验if len(inputs) != 2:raise ValueError('ROC need 2 inputs (y_pred, y), but got {}'.format(len(inputs)))# 将输入转为numpyy_pred = self._convert_data(inputs[0])y = self._convert_data(inputs[1])# 更新曲线y_pred, y, class_num, pos_label = self._precision_recall_curve_update(y_pred, y, self.class_num, self.pos_label)self.y_pred = y_predself.y = yself.class_num = class_numself.pos_label = pos_labelself._is_update = Truedef _roc_(self, y_pred, y, class_num, pos_label, sample_weights=None):if class_num == 1:fps, tps, thresholds = self._binary_clf_curve(y_pred, y, sample_weights=sample_weights,pos_label=pos_label)tps = np.squeeze(np.hstack([np.zeros(1, dtype=tps.dtype), tps]))fps = np.squeeze(np.hstack([np.zeros(1, dtype=fps.dtype), fps]))thresholds = np.hstack([thresholds[0][None] + 1, thresholds])if fps[-1] <= 0:raise ValueError("No negative samples in y, false positive value should be meaningless.")fpr = fps / fps[-1]if tps[-1] <= 0:raise ValueError("No positive samples in y, true positive value should be meaningless.")tpr = tps / tps[-1]return fpr, tpr, thresholds# 定义三个列表fpr, tpr, thresholds = [], [], []for c in range(class_num):preds_c = y_pred[:, c]res = self.roc(preds_c, y, class_num=1, pos_label=c, sample_weights=sample_weights)fpr.append(res[0])tpr.append(res[1])thresholds.append(res[2])return fpr, tpr, thresholdsdef roc(self, y_pred, y, class_num=None, pos_label=None, sample_weights=None):"""roc"""y_pred, y, class_num, pos_label = self._precision_recall_curve_update(y_pred, y, class_num, pos_label)return self._roc_(y_pred, y, class_num, pos_label, sample_weights)def (self):"""计算ROC曲线。返回的是一个元组,由`fpr`、 `tpr`和 `thresholds`组成的元组。"""if self._is_update is False:raise RuntimeError('Call the update method before calling .')y_pred = np.squeeze(np.vstack(self.y_pred))y = np.squeeze(np.vstack(self.y))return self._roc_(y_pred, y, self.class_num, self.pos_label) 

使用方法如下:

  • 二分类的例子
import numpy as np
from mindspore import Tensor
from mindspore.nn.metrics import ROC
# binary classification example
x = Tensor(np.array([3, 1, 4, 2]))
y = Tensor(np.array([0, 1, 2, 3]))
metric = ROC(pos_label=2)
metric.clear()
metric.update(x, y)
fpr, tpr, thresholds = metric.()
print(fpr, tpr, thresholds)
[0., 0., 0.33333333, 0.6666667, 1.]
[0., 1, 1., 1., 1.]
[5, 4, 3, 2, 1] 
  • 多分类的例子
import numpy as np
from mindspore import Tensor
from mindspore.nn.metrics import ROC
# multiclass classification example
x = Tensor(np.array([[0.28, 0.55, 0.15, 0.05], [0.10, 0.20, 0.05, 0.05], [0.20, 0.05, 0.15, 0.05],0.05, 0.05, 0.05, 0.75]]))
y = Tensor(np.array([0, 1, 2, 3]))
metric = ROC(class_num=4)
metric.clear()
metric.update(x, y)
fpr, tpr, thresholds = metric.()
print(fpr, tpr, thresholds)
[array([0., 0., 0.33333333, 0.66666667, 1.]), array([0., 0.33333333, 0.33333333, 1.]), array([0., 0.33333333, 1.]), array([0., 0., 1.])]
[array([0., 1., 1., 1., 1.]), array([0., 0., 1., 1.]), array([0., 1., 1.]), array([0., 1., 1.])]
[array([1.28, 0.28, 0.2, 0.1, 0.05]), array([1.55, 0.55, 0.2, 0.05]), array([1.15, 0.15, 0.05]), array([1.75, 0.75, 0.05])] 

MindSpore代码实现(AUC)

"""auc"""
import numpy as np
def auc(x, y, reorder=False):"""使用梯形法则计算曲线下面积(AUC)。这是一个一般函数,给定曲线上的点。计算ROC曲线下的面积。"""# 输入x是由ROC曲线得到的fpr值或者一个假阳性numpy数组。如果是多类的,这是一个这样的list numpy,每组代表一类。# 输入y是由ROC曲线得到的tpr值或者一个真阳性numpy数组。如果是多类的,这是一个这样的list numpy,每组代表一类。if not isinstance(x, np.ndarray) or not isinstance(y, np.ndarray):raise TypeError('The inputs must be np.ndarray, but got {}, {}'.format(type(x), type(y)))# 检查所有数组的第一个维度是否一致。检查数组中的所有对象是否具有相同的形状或长度。_check_consistent_length(x, y)# 展开列或1d numpy数组。x = _column_or_1d(x)y = _column_or_1d(y)# 进行校验if x.shape[0] < 2:raise ValueError('At least 2 points are needed to compute the AUC, but x.shape = {}.'.format(x.shape))direction = 1if reorder:order = np.lexsort((y, x))x, y = x[order], y[order]else:dx = np.diff(x)if np.any(dx < 0):if np.all(dx  1:raise ValueError("Found input variables with inconsistent numbers of samples: {}.".format([int(length) for length in lengths])) 

使用方法如下:

  • 利用ROC的fpr, tpr值求auc
import numpy as np
from mindspore.nn.metrics import auc
x = Tensor(np.array([[3, 0, 1], [1, 3, 0], [1, 0, 2]]))
y = Tensor(np.array([[0, 2, 1], [1, 2, 1], [0, 0, 1]]))
metric = ROC(pos_label=1)
metric.clear()
metric.update(x, y)
fpr, tpr, thre = metric.eval()
# 利用ROC的fpr, tpr值求auc
output = auc(fpr, tpr)
print(output)
0.45 

点击关注,第一时间了解华为云新鲜技术~

AUC/ROC:面试中80%都会问的知识点相关推荐

  1. 这些面试中经常被问到的线程池问题,你都能回答的上来吗?

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:Real_man juejin.im/post/5e435ac3f265da5753 ...

  2. 面试中,被问到“哑口无言”的瞬间怎么办?

    在面试过程中,可能会遇到各种意想不到的问题,有些问题会让你感到非常自信,而有些问题却会让你感到犯愁,甚至哑口无言.这种情况发生时,如何应对是很关键的.面试中,被问到"哑口无言"的瞬 ...

  3. 二面面试官一般都会问什么?

    在找工作的过程中,面试是不可避免的一环.对于面试者来说,准备充分是必须的,但往往也会面对各种各样的问题,其中就包括"二面面试官一般都会问什么?"这个问题.下面,小编将为大家介绍二面 ...

  4. Python培训常识:Python面试中常被问到的几种设计模式要知道

    学习Python技术大家都是为了日后能够找到适合自己的工作岗位,那么除了要学习好Python技术外,对于面试环节的问题也要有所了解,本期小编为大家介绍的Python培训教程就算关于Python面试中常 ...

  5. 面试中经常会问的智力题,来看看你会做几道

    转载自   面试中经常会问的智力题,来看看你会做几道 下面是大部分题目来自滴滴出行2017秋招题.开始头脑风暴吧~~~ 问题 question one 有50家人家,每家一条狗.有一天警察通知,50条 ...

  6. e - 数据结构实验之查找五:平方之哈希表_面试中常被问到的Hash表,你了解吗

    #新人扶持计划# Hash表在面试中经常被问到,今天我们来了解下. Hash表也称散列表,也有直接译作哈希表,Hash表是一种特殊的数据结构,它同数组.链表以及二叉排序树等相比较有很明显的区别,它能够 ...

  7. (免费领取Java面试题)Java面试中经常被问到的问题

    (免费领取Java面试题)Java面试中经常被问到的问题 免费领取Java面试题 -------------https://blog.csdn.net/kaikeba/article/details/ ...

  8. 面试中常被问到(11)虚函数/纯虚函数

    虚函数 如何定义一个虚函数?在基类成员函数前加入virtual关键字,但并不代表此函数不被实现,只是说明允许基类指针调用派生类重写的此函数 一个类只要声明有虚函数或者从基类继承了虚函数,在编译过程中就 ...

  9. 淘宝十年资深程序员在面试中最爱问的秒杀的面试题

    淘宝十年资深程序员在面试中最爱问的秒杀的面试题 文章目录 淘宝十年资深程序员在面试中最爱问的秒杀的面试题 1.什么是秒杀?秒杀是什么场景? 2.秒杀场景下有哪些挑战?如何应对这些挑战? 3.如何防止商 ...

最新文章

  1. setTimeOut()
  2. 快速提升UI设计感的7个版式小妙招
  3. Redis持久化:AOF和RDB
  4. SOHU BLOG的缺憾,及开启留言功能的必要
  5. C#委托、事件学习之(三)——热水器烧水案例
  6. H.264/AVC技术进展及其务实发展策略思考
  7. Codewars--8kyu解题集
  8. 如何利用Pre.im分发iOS测试包
  9. Javascript找出所有没有重复的字符(优化)
  10. # 设置当前标注样式_CAD图纸不会标注?模型空间如何标注,标注样式设置规范解析...
  11. Linux下查看网卡实时流量工具
  12. springboot springmvc mybatis_深圳嘉华学校之springboot实战教程
  13. JAVA中,目录C:\\TEMP等效于C:\TEMP
  14. 【matlab深度学习工具箱】classificationLayer参数详解
  15. 公司账号密码、通信录泄露屡见不鲜,肆意流淌的敏感信息:WEB安全基础入门—信息泄露漏洞
  16. mysql mango_mangodb与mysql的区别及部署
  17. 服务器密码机的标准和产品应用要点
  18. 计算机控制键有哪些,常用的电脑快捷键大全有哪些
  19. 单身程序员上班路上二三事(一)
  20. 自动化操作桌面之根据图片移动鼠标

热门文章

  1. Linux(ubuntu)安装JLink 驱动
  2. 【Java数据结构】泛型详解+图文,通配符上界、下界
  3. CAS TGT 校验不成功:No principal was found in the response from the CAS server.WHO: audit:unknown
  4. file上传代码 ios_iOS-实现文件上传下载
  5. go语言csv包_golang导出csv
  6. 朗润国际期货:去中心化稳定币谁能笑到最后?
  7. 解决Spring Spring Data JPA 错误: Page 1 of 1 containing UNKNOWN instances
  8. arduino入门套件学习过程-安装配置
  9. Android jetpack 篮球计分板的实现
  10. 基于目标检测的狗品种识别及图像检索