共计 8548 个字符,预计需要花费 22 分钟才能阅读完成。
摘要:ROC/AUC 作为机器学习的评估指标十分重要,也是面试中经常出现的问题(80% 都会问到)
本文分享自华为云社区《技术干货 | 解决面试中 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 = 0
self.y = 0
self.sample_weights = None
self._is_update = False
def _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 = 1
if pos_label is None:
pos_label = 1
y_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_label
def update(self, *inputs):
"""更新预测值和实在值。"""
# 输出数量的校验
if len(inputs) != 2:
raise ValueError('ROC need 2 inputs (y_pred, y), but got {}'.format(len(inputs)))
# 将输出转为 numpy
y_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_pred
self.y = y
self.class_num = class_num
self.pos_label = pos_label
self._is_update = True
def _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, thresholds
def 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 = 1
if 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
点击关注,第一工夫理解华为云陈腐技术~