从零实现深度学习框架——衡量算法的基本指标

发布于:2022-12-07 ⋅ 阅读:(800) ⋅ 点赞:(0)

引言

本着“凡我不能创造的,我就不能理解”的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导。

要深入理解深度学习,从零开始创建的经验非常重要,从自己可以理解的角度出发,尽量不使用外部完备的框架前提下,实现我们想要的模型。本系列文章的宗旨就是通过这样的过程,让大家切实掌握深度学习底层实现,而不是仅做一个调包侠。

本文介绍了常用机器学习算法的衡量指标:准确率、查全率、查准率和ROC曲线。

混淆矩阵

要理解以上指标,我们首先要知道混淆矩阵(confusion matrix)的概念。名字看起来挺唬人,其实也没有很复杂。

其矩阵的每一列代表了预测类别,每一列的总数表示预测为该类的数据的数量;

而每一行代表了数据的真实类别,每一行的总数表示该类别的数据的数量;

每一列中的数值表示真实数据被预测为该类的数量;

它的命名由来在于可以通过这个矩阵方便地看出模型是否将两个不同的类别混淆(把一个类错分为另一个)了。

我们通过一个实例来理解,示例来自于维基百科

以癌症诊断为例,假设12个人中,其中有8个患有癌症,4个未患癌症。我们定义患有癌症的人(样本)属于类别1(正类,positive),未患癌症的人属于类别0(负类,negative),实际数据如下:

image-20220908150614460

假设我们有一个分类器来进行癌症诊断。该分类器判断对了9个,错了3个:其中2个患有癌症被错误地预测为未患癌症(样本1和2),1个未患癌症诶错误地预测为患有癌症(样本9):

image-20220908150734996

这里,如果我们比较实际的分类集和预测的分类集,有4种不同的输出可以产生4种不同的结果。

①如果实际类别为正类(positive)且预测类别为正类(1,1),这被称为真阳性(True Positive,TP)

②如果实际类别为正类(positive)且预测类别为负类(1,0),这被称为假阴性(False Negative,FN)

③如果实际类别为负类(negative)且预测类别为正类(0,1),这被称为假阳性(False Positive,FP)

④如果实际类别为负类(negative)且预测类别为负类(0,0),这被称为真阴性(True Negative,TN)

然后我们可以对真实和预测类别数量进行比较,并增加这些信息到下表中,将正确结果底色记为绿色:

image-20220908152527583

任何二类别混淆矩阵的模板都可以使用上面讨论的四种结果(TP,FN,FP,TN)以及正类和负类。这四个结果可以在2×2的混淆矩阵中体现如下:

image-20220908160502537

现在,我们可以简单地求和每种结果,替换到模板中,然后创建一个混淆矩阵,简单地总结测试分类器的结果:

image-20220908160713508

在该混淆矩阵中,8个样本患有癌症,而分类器认为其中2个没有癌症;而4个样本没有癌症,分类器认为其中有1个患有癌症。

所有正确的预测位于矩阵的对角线上(上图绿色背景),因此很容易从视觉上检查矩阵中的预测错误,因为它们位于对角线之外。

通过计算每行的总数,可以知道原始数据中正类§的总数和负类(N)的总数。比如 P = T P + F N N = F P + T N P=TP + FN \quad N = FP +TN P=TP+FNN=FP+TN

有了混淆矩阵,我们就可以计算上面提到的三个基本指标。首先来看准确率。

准确率

准确率(accuracy)可以说是一个最常用的分类性能指标了,表示预测正确的样本(TP和TN)在所有样本(TP+TN+FP+FN)中占的比例:
accuracy = T P + T N T P + T N + F P + F N (1) \text{accuracy} = \frac{TP+TN}{TP +TN + FP +FN} \tag 1 accuracy=TP+TN+FP+FNTP+TN(1)
它可以表示模型的精度,模型识别正确的样本数越多,模型的精度就越高。

但是准确率并不能反应分类器的真实性能,因为如果数据集严重不平衡(即不同类别间的数据量相差较大),它会产生误导性结果。举个例子,以网贷违约率为例,相对好用户(及时还贷),我们更关心坏用户(不及时甚至拒还)。假设在10000个用户中,有10个是坏用户。你的模型把其中1个坏用户判断为坏用户(TP=1),剩下的9999个用户判断为好用户。根据上面的知识我们可以作出混淆矩阵:

image-20220909142312186

那么准确率为对角线上元素值相加除以总数:(9990+1)/10000超过99%,红框中的总数除以蓝框中的总数。

image-20220909143354303

此时应该选择我们下面介绍的查全率。

查全率

查全率(recall)又称为召回率,表示正确识别出为正类的样本(TP)在所有正类样本(TP+FN)中所占的比例:
recall = T P T P + F N (2) \text{recall} = \frac{TP}{TP + FN} \tag 2 recall=TP+FNTP(2)
或者说在真实为正类的样本中,模型成功预测出来的样本所占的比例。召回率只和真实为正类的样本相关,与真实为负类的样本无关。

为啥叫召回率呢,可以解释为正类样本有多少被找出来(召回)了。

召回率强调不能漏检,宁错杀,不放过。

image-20220909143555276

所以应用到上面网贷违约的例子中,召回率为红框中的总数除以蓝框中的总数。

image-20220909142312186

召回率只有 1 10 = 10 % \frac{1}{10}=10\% 101=10%

查准率

查准率(precision)又称为精确率,表示正确识别为正类的样本中(TP)在所有预测为正类样本(TP + FP)中所占的比例:
precision = T P T P + F P (3) \text{precision} = \frac{TP}{TP +FP} \tag 3 precision=TP+FPTP(3)
查准率代表对正样本结果中的预测准确程度,而准确率则代表整体的预测准确程度。查准率的目的是让模型现有预测结果尽可能不出错。

查准率强调不准出错,宁愿漏检,宁漏掉,不错杀。

image-20220909142312186

应用到上面网贷违约的例子中,查找率为红框中的总数除以蓝框中的总数。

image-20220909143751038

查准率为 1 1 = 100 % \frac{1}{1}=100\% 11=100%。判断为正类的样本只有1个,恰好它是真正类,所以对正样本的预测准确程度为100%。

最后用一张图总结下这几个指标的计算方法。

image-20220909144017408

查准率和查全率互相影响,理想状态下肯定追求两个都高,但是实际情况是两者相互“制约”:追求查准率高,则查全率就低;追求查全率高,则通常会影响查准率

F-measure

查准率和查全率这两个评估指标提高了在算法之间进行优劣比较的难度,假设你的算法表现如下:

image-20220909145036043

若根据上方表格中的数值对两个分类器进行比较,显然两者都没有较为明显的优势,因此也无法指导你立即做出选择。

如果你认为查准率和查全率指标很关键,那么可以将这两个值合并为一个值来表示。例如取两者的平均值,或者还可以计算F值,这是一种经过修正的平均值计算方法,比直接取平均值的效果会好一些。

F值的计算公式为:
F β = ( β 2 + 1 ) P R β 2 P + R (4) F_\beta = \frac{(\beta^2+1)PR}{\beta^2P+R} \tag 4 Fβ=β2P+R(β2+1)PR(4)
其中 P P P代表查准率; R R R代表查全率; β \beta β定义这两个指标的重要性,取决于应用的不同。若 β > 1 \beta > 1 β>1则倾向于 R R R;若 β < 1 \beta<1 β<1倾向于 P P P;若 β = 1 \beta=1 β=1,把它们同等对待。

最常用的一个指标是 F β = 1 F_{\beta=1} Fβ=1,或称为 F 1 F_1 F1(F1 score):
F 1 = 2 P R P + R (5) F_1=\frac{2PR}{P+R} \tag 5 F1=P+R2PR(5)
F-measure来自查准率和查全率的加权调和平均(harmonic mean)。调和平均是将所有数值取倒数并求其算术平均数后,再将此结果取倒数而得:
HarmonicMean ( a 1 , a 2 , a 3 , ⋯   , a n ) = n 1 a 1 + 1 a 2 + 1 a 3 + ⋯ + 1 a n (6) \text{HarmonicMean}(a_1,a_2,a_3,\cdots,a_n) = \frac{n}{\frac{1}{a_1} + \frac{1}{a_2} + \frac{1}{a_3} + \cdots + \frac{1}{a_n} } \tag 6 HarmonicMean(a1,a2,a3,,an)=a11+a21+a31++an1n(6)
因此F-measure为:
F = 1 α 1 P + ( 1 − α ) 1 R (7) F = \frac{1}{\alpha \frac{1}{P} + (1-\alpha)\frac{1}{R}} \tag 7 F=αP1+(1α)R11(7)

F = ( β 2 + 1 ) P R β 2 P + R ( with   β 2 = 1 − α α ) (8) F=\frac{(\beta^2+1)PR}{\beta^2P+R} \quad \left( \text{with} \, \beta^2= \frac{1 -\alpha}{\alpha}\right) \tag 8 F=β2P+R(β2+1)PR(withβ2=α1α)(8)

为什么 ( 7 ) (7) (7)式中的分子为 1 1 1,而不是指标个数 2 2 2呢?

可以理解为由权重 α \alpha α吸收了,比如在F1 score中, β 2 = 1 → α = 1 2 \beta^2=1 \rightarrow \alpha=\frac{1}{2} β2=1α=21。那么也变成了
F 1 = 1 1 2 ⋅ 1 P + 1 2 ⋅ 1 R = 2 1 P + 1 R F_1=\frac{1}{\frac{1}{2}\cdot \frac{1}{P} + \frac{1}{2} \cdot\frac{1}{R}} = \frac{2}{\frac{1}{P} + \frac{1}{R} } F1=21P1+21R11=P1+R12

使用调和平均值是因为它是一个保守的度量;两个值的调和平均值比算术平均值更接近两个值的最小值。因此,它强调了两个数字中的较低者。

那么在本小节开始的例子中,分别计算它们的F1 score,就对于如何选择有了评判标准:

image-20220909160422216

ROC曲线与AUC

ROC曲线

ROC曲线(receiver operating characteristic curve,接收者操作特征曲线),又称为感受性曲线(sensitivity curve),其显示的是分类器的TPR(真正率,真阳性率)和FPR(假正率,假阴性率)之间的关系,能帮助我们可视化分类器的表示。

那么什么是TPR和FRP呢?

TPR: 在所有实际为正类( T P + F N TP+FN TP+FN)的样本中,被正确地判断为正类( T P TP TP)的比率:
TPR = T P T P + F N \text{TPR} = \frac{TP}{TP +FN} TPR=TP+FNTP
它的计算公式和召回率一样,也称为灵敏度(Sensitivity )。

FPR: 在所有实际为负类( F P + T N FP+TN FP+TN)的样本中,被错误地判断为正类( F P FP FP)的比率:
FPR = F P F P + T N \text{FPR} = \frac{FP}{FP +TN} FPR=FP+TNFP
FPR也可以通过1-特异度(Specificity,TNR)来计算, specificity = T N T N + F P \text{specificity} = \frac{TN}{TN+FP} specificity=TN+FPTN

那么这个所谓的ROC曲线是如何画的呢?

在给定的阈值下,以FPR为横坐标,TPR为纵坐标就能得到一个点;给定一系列阈值得到的坐标点都画出来,就能得到特定模型的ROC曲线。

这里的阈值是什么意思呢?以逻辑回归分类器为例,它给出了每个样本为正类的概率,那么我们可以设定一个阈值,比如0.6,如果概率大于等于该阈值则认为样本为正类,否则为负类。这样就得到了一个坐标。

假设已经得出一系列样本被划分为正类§的概率,下表中共有10个样本,我们知道它们的实际类别。

样本编号 实际类别 正类概率
1 N 0.1
2 N 0.2
3 N 0.3
4 P 0.4
5 N 0.5
6 P 0.6
7 N 0.7
8 P 0.8
9 P 0.9
10 P 1.0

我们也可以对应画出下面的图片,坐标代表概率,从左到右由0到1。坐标上的样本代表被判断为正类的概率,样本的颜色代表真实属于的类别。

image-20220922175037894

这样,给定阈值我们可以画一条垂直坐标的竖线,竖线右边的样本判断为正类,左边的判断为负类。从而可以快速计算出TPR和FPR。

比如,假设阈值为0.8,我们得到:

image-20220923100745925

那么它把最右边的3个样本判断为正,左边的7个样本判断为负。

image-20220923101401859

我们可以很快标记出每个样本所属的类别,计算出混淆矩阵:

P N
P TP(3) FP(0)
N FN(2) TN(5)

我们就可以分别计算 T P R = T P T P + F N = 3 3 + 2 = 0.6 TPR=\frac{TP}{TP+FN}=\frac{3}{3+2}=0.6 TPR=TP+FNTP=3+23=0.6; F P R = F P F P + T N = 0 0 + 5 = 0 FPR=\frac{FP}{FP+TN}=\frac{0}{0+5}=0 FPR=FP+TNFP=0+50=0

我们把阈值调低一点,假设为0.7,同理可以计算出混淆矩阵:

image-20220923103402089

P N
P TP(3) FP(1)
N FN(2) TN(4)

分别计算 T P R = T P T P + F N = 3 3 + 2 = 0.6 TPR=\frac{TP}{TP+FN}=\frac{3}{3+2}=0.6 TPR=TP+FNTP=3+23=0.6; F P R = F P F P + T N = 1 1 + 4 = 0.2 FPR=\frac{FP}{FP+TN}=\frac{1}{1+4}=0.2 FPR=FP+TNFP=1+41=0.2

再调低一点,假设为0.6:

image-20220923103425706

P N
P TP(4) FP(1)
N FN(1) TN(4)

分别计算 T P R = T P T P + F N = 4 4 + 1 = 0.8 TPR=\frac{TP}{TP+FN}=\frac{4}{4+1}=0.8 TPR=TP+FNTP=4+14=0.8; F P R = F P F P + T N = 1 1 + 4 = 0.2 FPR=\frac{FP}{FP+TN}=\frac{1}{1+4}=0.2 FPR=FP+TNFP=1+41=0.2

image-20220923112057682

就这样,我们可以计算出一系列阈值对应的TPR和FPR值,从而得到一系列坐标点,将它们连接起来就得到了ROC曲线。

同时我们可以发现,TN和FN随着阈值调低而减少(或持平);TP和FP随着阈值的调低而增加(或持平)。随着阈值调低,ROC点 往右上(或右/或上)移动,或不动;但绝不会往左下(或左/或下)移动

  • 当阈值为最高时,所有样本被预测为负类,没有样本被预测为正类,此时 F P R = F P / ( F P + T N ) FPR = FP / (FP+TN) FPR=FP/(FP+TN)中的 F P = 0 FP=0 FP=0,所以 T P R = 0 % TPR = 0\% TPR=0%;同时在TPR中, T P R = T P / ( T P + F N ) TPR=TP/(TP+FN) TPR=TP/(TP+FN)中的 T P = 0 TP=0 TP=0,所以 T P R = 0 % TPR=0\% TPR=0%。即当阈值设置为最高时,必得出ROC坐标系左下角点 ( 0 , 0 ) (0,0) (0,0)
  • 当阈值设置为最低时,所有样本被预测为正类,没有样本被预测为负类,此时 F P R = F P / ( F P + T N ) FPR=FP/(FP+TN) FPR=FP/(FP+TN)中的 T N = 0 TN=0 TN=0,所以 F P R = 100 % FPR=100\% FPR=100%。同时在TPR中, T P R = T P / ( T P + F N ) TPR=TP/(TP+FN) TPR=TP/(TP+FN)中的 F N = 0 FN=0 FN=0,所以 T P R = 100 % TPR=100\% TPR=100%。即当阈值设置为最低时,必得出ROC坐标系右上角的点 ( 1 , 1 ) (1,1) (1,1)

绘制ROC曲线

上面说了,我们有了一系列TPR和FPR值,可以得到一系列坐标点,将它们连接起来就得到了ROC曲线。本小节我们就来绘制ROC曲线,首先需要计算混淆矩阵:

def TN(y_true,y_predict):
  # 真实是0,预测也是0
  return np.sum((y_true == 0) & (y_predict == 0))

def FP(y_true,y_predict):
  # 真实是0,预测是1
  return np.sum((y_true == 0) & (y_predict == 1))

def FN(y_true,y_predict):
  # 真实是1,预测是0
  return np.sum((y_true == 1) & (y_predict == 0))

def TP(y_true,y_predict):
  # 真实是1,预测也是1
  return np.sum((y_true == 1) & (y_predict == 1))

# 混淆矩阵
def confusion_matrix(y_true,y_predict):
  return np.array([
      [TN(y_true,y_predict),FP(y_true,y_predict)],
      [FN(y_true,y_predict),TP(y_true,y_predict)],
  ])

然后定义上面例子中10个样本的概率和所属类别(1表示正类):

probabilities = np.arange(0.1,1.1,0.1)
y_true = np.array([0,0,0,1,0,1,0,1,1,1])
probabilities
array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. ])

套公式写出TPR和FPR的代码:

def TPR(y_true, y_predict):
  tp = TP(y_true,y_predict)
  fn = FN(y_true,y_predict)
  try:
    return tp/(tp + fn)
  except:
    return 0.0
    
def FPR(y_true, y_predict):
  fp = FP(y_true,y_predict)
  tn = TN(y_true,y_predict)
  try:
      return fp/(fp + tn)
  except:
      return 0.0

下面分别以0.1为幅度设置10个阈值,计算对应的FPR和TPR:

fprs = []
tprs = []

for threshold in probabilities:
  y_predict = np.array(probabilities >= threshold,dtype='int')
  print(f"y_predict:{y_predict}, threshold:{threshold:.1f}, TPR:{TPR(y_true, y_predict)},  FPR:{FPR(y_true, y_predict)}")
  tprs.append(TPR(y_true,y_predict))
  fprs.append(FPR(y_true,y_predict))

把它们画出来,以FPR为横坐标,TPR为纵坐标:

import matplotlib.pyplot as plt

plt.plot(fprs,tprs)
plt.show()

image-20220923133405721

这里的ROC曲线看起来很尖锐,因为这是我们刻意设计的例子。

实际上ROC曲线可能是这样的:

image-20220923134931373

AUC

很多时候ROC曲线并不能清晰的说明哪个分类器的效果更好,我们希望能有一个数值化的指标进行比较,那么可以用ROC曲线下的面积来表示。AUC**(Area under the ROC Curve)** 可以直观地评价分类器的好坏,值越大越好。

AUC值是一个概率值,当你随机挑选一个正样本以及负样本,分类器正确判断正样本的值高于负样本的概率就是AUC。

从AUC判断分类器(预测模型)优劣的标准:

  • AUC = 1,是完美分类器,采用这个预测模型时,存在至少一个阈值能得出完美预测。绝大多数预测的场合,不存在完美分类器。
  • 0.5 < AUC < 1,优于随机猜测。这个分类器(模型)妥善设置阈值的话,能有预测价值。
  • AUC = 0.5,跟随机猜测一样(例:丢铜板),模型没有预测价值。
  • AUC < 0.5,比随机猜测还差;但只要总是反预测而行,就优于随机猜测。

那如何计算曲线下面的面积呢?一种方法是梯形法:简单地将每个相邻的点以直线连接,计算连线下方的总面积。因为每一线段下方都是一个梯形,所以叫梯形法

1_FSeD4LAajYiO8ht4bG3nOQ

📚References

  • Speech and Language Processing

  • 《机器学习训练秘籍 》

  • 维基百科

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到