AAAMLP-Chapter-3: Evaluation metrics

201 阅读21分钟

处理机器学习任务时,你会遇到不同类型的指标。有时人们会根据业务问题,创建特殊指标。

解释所有指标超出了本书的范围,但我们会一起动手实践最常用的一些指标。

本书第一章介绍了监督学习和无监督学习,那么我们接下来只关心监督学习相关指标,尽管在无监督学习中也可以使用一些指标。原因在于监督学习中有大量的比较运算,而无监督学习更为主观。

对于分类问题,最常用的指标如下:

  • Accuracy 准确率

  • Precision 精确率

  • Recall (R) 召回率

  • F1 score (F1) F1值

  • Area under the ROC curve or simply AUC

  • Log loss 对数损失值

  • Precision at k (P@k)

  • Average precision at k (AP@k)

  • Mean average precision at k (MAP@k)

对于回归问题,最常用的指标如下:

  • Mean absolute error (MAE) 平均绝对误差

  • Mean squared error (MSE) 均方误差

  • Root mean squared error (RMSE) 根均方误差

  • Root mean squared logarithmic error (RMSLE)

  • Mean precentage error (MPE)

  • Mean absolute percentage error (MAPE)

  • R2R^2

对我们来说,不仅需要理解上述指标的工作方式,还要理解合适使用哪种指标。

对指标的选择离不开对数据和目标值的理解,且目标值对指标的选择影响更大。


下边通过一个简单的例子,来认识这些指标。

假设有这样一个二分类问题,需要对胸部 X 光图像进行分类,类别包括无病与气胸。

因此,需要构建分类器,检测输入的 X 光图像的对应类别概率。

同时,假设已经有等量的气胸和非气胸图像各 100 张,即总共 200 张图片,100 个正例,100 个负例。

数据下载地址:

SIIM ACR Pneumothorax Segmentation Data | Kaggle

处理的第一步是将上述数据划分到两个相等的集合,即训练集和验证集。在每个集合中,都有 50 个正例和 50 个负例。

在一个二分类任务中,如果数据集中正例和负例的数量相等,那么模型的评估指标通常选择 Accuracy、Precision、Recall 或者 F1 。


Accuracy 准确率

机器学习中最直接的指标,测量模型输出结果的准确度。

对于上述问题,如果你的模型正确分类了 90 个图片,那么可以说该模型的 Accuracy 是 90% 。

代码计算 accuracy 相当简单。

def accuracy(y_true, y_pred):
    correct_counter = 0
    for y_t, y_p in zip(y_true, y_pred):
        if y_t == y_p:
            correct_counter += 1
    return correct_counter / len(y_true)

l1 = [0, 1, 1, 1, 0, 0, 0, 1]
l2 = [0, 1, 0, 1, 0, 1, 0, 0]
accuracy(l1, l2)

或者直接使用 scikit-learn 提供的方法

from sklearn import metrics

l1 = [0, 1, 1, 1, 0, 0, 0, 1]
l2 = [0, 1, 0, 1, 0, 1, 0, 0]
metrics.accuracy_score(l1, l2)

Precision 精确率

现在,让我们修改一下数据集,假如 200 个图片中有 180 个正例和20个负例,并按同样的比例划分出训练集和验证集。每个集合有 90 个正例和 10 个负例。

如果验证集里所有图片都被模型识别为正例,那么 accuracy 是多少?

100 个图片中有 90 个图片被正确分类,accuracy 为 90% 。

但是回过头来仔细思考一下,可能模型并没有 90% 的准确率,模型把所有输入都识别为正例,那这个模型可能根本没用。

数据集中的样本是不均衡的,一类数据的数量远多于另一类数据。在这种情况下,使用 accuracy 指标不能正确代表所有数据。即 accuray 值可能很高,但模型在处理真实世界的输入时,未必有如此效果。

这种情况下,最好使用另一种度量 Precision 。

学习 precision 之前,要了解以下几条术语:

假设胸部 X 光图像中,气胸图像为正例 (1),非气胸图像为负例 (0)。

  • True Positive (TP):给定一个图片,模型预测患者具有气胸,同时该图片的真实标签也是患者具有气胸,该现象被称为 true positive 。

  • True Negative (TN):给定一个图片,模型预测患者没有气胸,同时该图片的真实标签也是患者没有气胸,该现象被称为 true negative 。

简而言之,如果模型正确预测了正例,该现象为 true positive;如果模型正确预测了负例,该现象为 true negative。

  • False Positive (FP):给顶一个图片,模型预测患者具有气胸,同时该图片的真实标签是患者没有气胸,该现象被称为 false positive 。

  • False Negative (FN):给定一个图片,模型预测患者没有气胸,同时该图片的真实标签的患者具有气胸,该现象被称为 false negative 。

简而言之,如果模型错误预测了正例,该现象为 false positive;如果模型错误预测了负例,该现象为 false negative。

总之,对于 TP/TN/FP/FN ,P/N 都表示模型的预测值,T/F 表示预测值是否正确。

下边用代码简单实现这些指标。

def true_positive(y_true, y_pred):
    tp = 0
    for yt, yp in zip(y_true, y_pred):
        if yt == 1 and yp == 1:
            tp += 1
    return tp

def false_positive(y_true, y_pred):
    fp = 0
    for yt, yp in zip(y_true, y_pred):
        if yt == 0 and yp == 1:
            fp += 1
    return fp

def true_negative(y_true, y_pred):
    tn = 0
    for yt, yp in zip(y_true, y_pred):
        if yt == 0 and yp == 0:
            tn += 1
    return tn

def false_negative(y_true, y_pred):
    fn = 0
    for yt, yp in zip(y_true, y_pred):
        if yt == 1 and yp == 0:
            fn += 1
    return fn

l1 = [0, 1, 1, 1, 0, 0, 0, 1]
l2 = [0, 1, 0, 1, 0, 1, 0, 0]
print(true_positive(l1, l2))
print(false_positive(l1, l2))
print(true_negative(l1, l2))
print(false_negative(l1, l2))

有了这些术语,可以定义 accuracy :

AccuracyScore=(TP+TN)(TP+FP+TN+FN)AccuracyScore = \frac{(TP + TN)}{(TP + FP + TN + FN)}

下边使用 TP/FP/TN/FN 实现 accuracy 的计算。

def accuracy_v2(y_true, y_pred):
    tp = true_positive(y_true, y_pred)
    fp = false_positive(y_true, y_pred)
    tn = true_negative(y_true, y_pred)
    fn = false_negative(y_true, y_pred)
    accuracy_score = (tp + tn) / (tp + fp + tn + fn)
    return accuracy_score

l1 = [0, 1, 1, 1, 0, 0, 0, 1]
l2 = [0, 1, 0, 1, 0, 1, 0, 0]
accuracy_v2(l1, l2)

可以将该函数结果与之前实现的 accuracy 函数结果和 metrics.accuracy_score 函数结果相比较,它们输出相同的 0.625 。


了解过 TP/FP/TN/FN 之后,接下来继续介绍其他重要的指标。

Precision 的定义是:

Precision=TP/(TP+FP)Precision = TP / (TP + FP)

假设我们基于不平衡数据集训练出了一个模型,该模型在 90 个非气胸图像中正确识别了 80 个,在 10 个气胸图像中正确识别了 8 个,因此模型正确识别了 88 个图片,其 accuracy 是 88% 。

然而,100 个图片中,10 个非气胸图像被错误的识别为患有气胸,2 个气胸图像被错误的识别非气胸,所以有以下统计值:

  • TP - 8

  • FP - 10

  • TN - 80

  • FN - 2

据此,可计算 precision 为 8 / (8 + 10) = 0.444 。这表示模型对于正例样本的正确判断概率只有 44.4% 。

下边简单用代码实现 precision。

def precision(y_true, y_pred):
    tp = true_positive(y_true, y_pred)
    fp = false_positive(y_true, y_pred)
    precision = tp / (tp + fp)
    return precision

l1 = [0, 1, 1, 1, 0, 0, 0, 1]
l2 = [0, 1, 0, 1, 0, 1, 0, 0]
precision(l1, l2)

Recall 召回率

Recall 计算公式为

Recall=TP/(TP+FN)Recall = TP / (TP + FN)

上边例子中,模型的 recall 为 8 / (8 + 2) = 0.8 ,表示模型识别了 80% 的正例样本。

下边简单用代码实现 recall 。

def recall(y_true, y_pred):
    tp = true_positive(y_true, y_pred)
    fn = false_negative(y_true, y_pred)
    recall = tp / (tp + fn)
    return recall


l1 = [0, 1, 1, 1, 0, 0, 0, 1]
l2 = [0, 1, 0, 1, 0, 1, 0, 0]
recall(l1, l2)

一个好模型的 precision 和 recall 都应该足够高,而上边例子中的模型 recall 非常高,但是 precision 特别低,该模型输出了大量的 false positive 和极少量 false negative。

False negative 当然是越少越好,在上例中,你肯定不想把一个患有气胸的病人误诊为健康,这比把没病误诊为有病更危险。

但是大量的 false positive 表示模型识别的精确度并不高。

Precision-Recall Curve

大部分模型预测的是概率值,在判断 positive 或是 negative 时,通常选择一个阈值 threshold 来划分概率值。

这个选择的 threshold 对模型的 precision 和 recall 影响很大,对前者做轻微变动可能导致后者剧烈变动。

对于每一个选定的 threshold,可以收集对应的 precision 和 recall,然后将该序列绘制成图,该图中的曲线即为著名的 precision-recall 曲线。

下边通过代码演示该曲线的绘制。

y_true = [0, 0, 0, 1, 
          0, 0, 0, 0,
          0, 0, 1, 0,
          0, 0, 0, 0,
          0, 0, 1, 0]
y_pred = [0.02638412, 0.11114267, 0.31620708, 0.0490937, 
          0.0191491, 0.17554844, 0.15952202, 0.03819563, 
          0.11639273, 0.079377, 0.08584789, 0.39095342,
          0.27259048, 0.03447096, 0.04644807, 0.03543574,
          0.18521942, 0.05934905, 0.61977213, 0.33056815]
thresholds = [0.0490937, 0.05934905, 0.079377, 0.08584789,
              0.11114267, 0.11639273, 0.15952202, 0.17554844,
              0.18521942, 0.27259048, 0.31620708, 0.33056815,
              0.39095342, 0.61977213]
precisions = []
recalls = []

for i in thresholds:
    temp_prediction = [1 if x >= i else 0 for x in y_pred]
    p = precision(y_true, temp_prediction)
    r = recall(y_true, temp_prediction)
    precisions.append(p)
    recalls.append(r)

plt.figure(figsize=(7, 7))
plt.plot(recalls, precisions)
plt.xlabel('recall', fontsize=15)
plt.ylabel('precision', fontsize=15)
plt.show()

所得曲线如下图所示

该 precision-recall 曲线看上去非常奇怪,这主要是因为 20 个样本中只有 3 个是正例。绘制的曲线并没有出错。

接着你会发现,很难根据 precision 和 recall 数值对来选择某个 threshold。如果 threshold 值太高,会得到很少的 true positive 和大量的 false negative,这导致 recall 的降低,同时 precision 会非常高。如果 threshold 太低,会得到大量的 false positive,导致 precision 降低。

precision 和 recall 的取值范围都是 0-1,且越接近 1 表示模型效果越好。


F1

F1 值是一种结合了 precision 和 recall 的度量指标。它的定义为 precision 和 recall 的调和平均数,如果用 P 表示 precision,R 表示 recall,那么 F1 的定义公式为:

F1=2PR/(P+R)F1 = 2PR/(P + R)

将 P 和 R 定义公式代入 F1 的公式中,可得:

F1=2TP/(2TP+FP+FN)F1 = 2TP/(2TP + FP + FN)

即使用 TP、FP、FN 来定义 F1,这也说明 F1 与 TP、FP、FN 之间的关系。

下边使用代码实现 F1 值计算。

def f1(y_true, y_pred):
    p = precision(y_true, y_pred)
    r = recall(y_true, y_pred)
    f1 = 2 * p * r / (p + r)
    return f1

from sklearn import metrics
y_true = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 
          1, 0, 0, 0, 0, 0, 0, 0, 1, 0]
y_pred = [0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
          1, 0, 0, 0, 0, 0, 0, 0, 1, 0]
print(f1(y_true, y_pred))
print(metrics.f1_score(y_true, y_pred))

有了 F1 值后,不用再分别查看 precision 和 recall。F1 值的取值范围和 precision、recall、accuracy 一样,都是从 0 到 1 。

F1 值越高表示模型效果越好,最高为 1 。

基于不平衡数据集训练模型时,应当使用 F1 作为模型的评估指标,而不是 accuracy。


TPR/FPR

我们也要了解其他一些关键术语。

首先是 TPR,即 True Positive Rate,其定义与 Recall 一样。

TPR=TP/(TP+FN)TPR = TP / (TP + FN)

代码实现如下,定义该 tpr 函数是为了方便之后使用。

def tpr(y_true, y_pred):
    return recall(y_true, y_pred)

TPR 或者 Recall 也被称为敏感性 Sensitivity


FPR

FPR,即 False Positive Rate,定义如下:

FPR=FP/(FP+TN)FPR = FP / (FP + TN)

代码实现如下:

def fpr(y_true, y_pred):
    fp = false_positive(y_true, y_pred)
    tn = true_negative(y_true, y_pred)
    fpr = fp / (fp + tn)
    return fpr

然后,1-FPR 也被称为 TNRTrue Negative Rate 或者特异性 Specificity

这里说了许多术语,但其中最重要的只有 TPR 和 FPR 。


ROC 与 AUC

假设有 15 个样本,其各自的目标值都是二元取值:

Actual Target: [0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1]

基于样本训练随机森林,算得各样本目标值为 1 的概率为

Predicated probabilities for 1: [0.1, 0.3, 0.2, 0.6, 0.8, 0.05, 0.9, 0.5, 0.3, 0.66, 0.3, 0.2, 0.85, 0.15, 0.99]

假如 threshold 取经典的 0.5,那么我们可以根据上边的数值计算出 precision、recall/TPR、F1、FPR。

接着,对于从 0-1 中取出的各个 threshold 计算上边的指标。

假如只计算 TPR 和 FPR,下边用代码实现该过程。

tpr_list = []
fpr_list = []

y_true = [0, 0, 0, 0, 1,
          0, 1, 0, 0, 1, 
          0, 1, 0, 0, 1]
pred_prob = [0.1, 0.3, 0.2, 0.6, 0.8,
             0.05, 0.9, 0.5, 0.3, 0.66,
             0.3, 0.2, 0.85, 0.15, 0.99]
thresholds = [0, 0.1, 0.2, 0.3, 0.4,
              0.5, 0.6, 0.7, 0.8, 0.85, 
              0.9, 0.99, 1.0]

for t in thresholds:
    temp_pred = [1 if x >= t else 0 for x in pred_prob]
    temp_tpr = tpr(y_true, temp_pred)
    temp_fpr = fpr(y_true, temp_pred)
    tpr_list.append(temp_tpr)
    fpr_list.append(temp_fpr)

plt.figure(figsize=(7, 7))
plt.fill_between(fpr_list, tpr_list, alpha=0.4)
plt.plot(fpr_list, tpr_list, lw=3)
plt.xlim(0, 1.0)
plt.ylim(0, 1.0)
plt.xlabel('FPR', fontsize=15)
plt.ylabel('TPR', fontsize=15)
plt.show()

得到的曲线如下图所示。

该曲线便是著名的 ROCReceiver Operating Characteristic,接收器工作特性。

如果我们计算 ROC 曲线下的面积,将得到一个经常用在不平衡二分类目标数据集上的度量指标,Area Under ROC Curve 或者 AUC

有许多方式计算 AUC,这里仍然使用 scikit-learn 提供的方法。

from sklearn import metrics

y_true = [0, 0, 0, 0, 1,
          0, 1, 0, 0, 1, 
          0, 1, 0, 0, 1]
pred_prob = [0.1, 0.3, 0.2, 0.6, 0.8,
             0.05, 0.9, 0.5, 0.3, 0.66,
             0.3, 0.2, 0.85, 0.15, 0.99]
metrics.roc_auc_score(y_true, pred_prob)

AUC 的取值范围为 0-1,其中关键值的含义如下:

  • AUC = 1 表示你的模型非常完美。然而绝大多数情况下,它意味着你的验证步骤出了问题,你需要重新检查你的数据处理和验证过程。如果检查后没有问题,那么恭喜你,你获得了当前数据集上最好的模型。

  • AUC = 0 表示你的模型非常差,或者另一种意义上的非常好,只要反转概率对应的预测目标。与 AUC=1 的情况一样,这也可能表示你的数据处理或者验证过程中有问题。

  • AUC = 0.5 表示你在随机预测。对于任何二分类问题,如果我对每个样本按 0.5 的概率预测其为正,那么我会得到 AUC = 0.5 的结果。

AUC 取值在 0 到 0.5 之间,说明模型预测效果不如随机预测。大多数情况下,反转概率对应的预测目标,AUC 会大于 0.5。

AUC 越接近 1 越好。

那么 AUC 在上边的例子中有何含义?

假如你在预测气胸图片的问题上,训练出的模型的 AUC 为 0.85 。这说明随机从数据集中取一个气胸图像(正例)和一个非气胸图像(负例),那么气胸图像的排名将高于非气胸图像,概率为 0.85,即气胸图像的预测概率高于非气胸图像的预测概率的情况的概率为 0.85,P(P(1)>P(0))=0.85P(P(1) > P(0)) = 0.85

在计算训练集的输出概率和 AUC 之后,需要在测试集上做预测。

基于问题和用例,你可能需要输出概率值或者目标类型。如果你需要输出概率,那么模型原始输出就是概率值;如果你需要输出目标类型,你得选择一个 threshold 来划分概率值:

Prediction=Probability>=ThresholdPrediction = Probability >= Threshold

这意味着,当概率大于等于阈值时,预测目标为 1;概率小于阈值时,预测目标为 0。

现在,ROC 曲线可以帮你选择 threshold。ROC 曲线本身展示了 threshold 对与 FPR 和 TPR 的影响,即对 False Positive 和 True Positive 的影响。如果你不想要太多 False Positive,你可以选择较大的 threshold,然而这也会导致 False Negative 增多。

大多数情况下,ROC 曲线的左上角对应的 threshold 是权衡后最优的,既不会错过太多 True Positive ,也不会产生大量 False Positive。

在工业界,AUC 被广泛用于评估不平衡二分类任务。


Log Loss

在二分类任务中,另一个非常重要的度量是 Log Loss,其定义为:

LogLoss=1.0(targetlog(prediction)+(1target)log(1predction))LogLoss=-1.0 * (target * log(prediction) + (1-target) * log(1-predction))

其中 Target 取值为 0 或 1,Prediction 表示样本 Target = 1 的概率。

对于数据集中所有样本来说,LogLoss 是各个样本 LogLoss 的简单平均。

需要牢记 LogLoss 的特性:它惩罚了具有特别高概率的值,即惩罚了非常确定的样本,任何确定的预测都会产生极小的 LogLoss 值,即 Prediction 接近 1,Targe = 1;Prediction 接近 0,Targe = 0。

下边用代码演示 LogLoss。

import numpy as np
from sklearn import metrics

def log_loss(y_true, y_pred):
    epsilon = 1e-15
    loss = []
    for yt, yp in zip(y_true, y_pred):
        yp = np.clip(yp, epsilon, 1 - epsilon)
        temp_loss = -1.0 * (
            yt * np.log(yp) + (1 - yt) * np.log(1 - yp)
        )
        loss.append(temp_loss)
    return np.mean(loss)

y_true = [0, 0, 0, 0, 1,
          0, 1, 0, 0, 1, 
          0, 1, 0, 0, 1]
pred_prob = [0.1, 0.3, 0.2, 0.6, 0.8,
             0.05, 0.9, 0.5, 0.3, 0.66,
             0.3, 0.2, 0.85, 0.15, 0.99]
print(log_loss(y_true, pred_prob))
print(metrics.log_loss(y_true, pred_prob))

再次强调,LogLoss 惩罚了预测正确的样本,包括正确的正例和正确的负例,这导致模型预测的效果越好,Loss 值越小,而模型预测效果越差,Loss 值越大。


Multi-Class Metrics

以上讨论的大多数指标都可以转换成多分类版本,即目标值不止两个,而是有多个。

接下来以 Precision 和 Recall 为例,介绍如何在多目标分类问题下计算指标。

有三种不同的计算方式,并且经常会让人混淆。首先以 Precision 为例来介绍。

  • Macro average precision:分别计算每个类别的 Precision,然后取平均值。

  • Micro average precision:累计各个类别的 TP/FP,然后计算总的 Precision。

  • Weighted precision:和 Macro 类似,但取平均值时,按每个类型的样本数量对其加权。

下边演示 Macro average precision 。

import numpy as np
from sklearn import metrics

def macro_average_precision(y_true, y_pred):
    num_classes = len(np.unique(y_true))
    result = 0
    for class_ in range(num_classes):
        temp_true = [1 if x == class_ else 0 for x in y_true]
        temp_pred = [1 if x == class_ else 0 for x in y_pred]
        temp_precision = precision(temp_true, temp_pred)
        result += temp_precision
    return result / num_classes

y_true = [0, 1, 2, 0, 1, 2, 0, 2, 2]
y_pred = [0, 2, 1, 0, 2, 1, 0, 0, 2]
print(macro_average_precision(y_true, y_pred))
print(metrics.precision_score(y_true, y_pred, average='macro'))

类似地,实现 Micro average precision 。

import numpy as np
from sklearn import metrics

def micro_average_precision(y_true, y_pred):
    num_classes = len(np.unique(y_true))
    tp = 0
    fp = 0
    for class_ in range(num_classes):
        temp_true = [1 if x == class_ else 0 for x in y_true]
        temp_pred = [1 if x == class_ else 0 for x in y_pred]
        tp += true_positive(temp_true, temp_pred)
        fp += false_positive(temp_true, temp_pred)
    return tp / (tp + fp)

y_true = [0, 1, 2, 0, 1, 2, 0, 2, 2]
y_pred = [0, 2, 1, 0, 2, 1, 0, 0, 2]
print(micro_average_precision(y_true, y_pred))
print(metrics.precision_score(y_true, y_pred, average='micro'))

然后实现 Weighted average precision 。

import numpy as np
from sklearn import metrics
from collections import Counter

def weighted_average_precision(y_true, y_pred):
    num_classes = len(np.unique(y_true))
    ctr = Counter(y_true)
    result = 0
    for class_ in range(num_classes):
        temp_true = [1 if x == class_ else 0 for x in y_true]
        temp_pred = [1 if x == class_ else 0 for x in y_pred]
        temp_precision = precision(temp_true, temp_pred)
        result += ctr[class_] * temp_precision
    return result / len(y_true)

y_true = [0, 1, 2, 0, 1, 2, 0, 2, 2]
y_pred = [0, 2, 1, 0, 2, 1, 0, 0, 2]
print(weighted_average_precision(y_true, y_pred))
print(metrics.precision_score(y_true, y_pred, average='weighted'))

相似地,我们也可以实现多分类的 Recall 指标,通过 Precision 和 Recall 还可实现 F1 。

下边通过代码演示 Weighted average F1 。

import numpy as np
from sklearn import metrics
from collections import Counter

def weighted_average_f1(y_true, y_pred):
    num_classes = len(np.unique(y_true))
    ctr = Counter(y_true)
    result = 0
    for class_ in range(num_classes):
        temp_true = [1 if x == class_ else 0 for x in y_true]
        temp_pred = [1 if x == class_ else 0 for x in y_pred]
        temp_precision = precision(temp_true, temp_pred)
        temp_recall = recall(temp_true, temp_pred)
        if temp_precision + temp_recall != 0:
            result += ctr[class_] * 2 * temp_precision * temp_recall / (temp_precision + temp_recall)
    return result / len(y_true)

y_true = [0, 1, 2, 0, 1, 2, 0, 2, 2]
y_pred = [0, 2, 1, 0, 2, 1, 0, 0, 2]
print(weighted_average_f1(y_true, y_pred))
print(metrics.f1_score(y_true, y_pred, average='weighted'))

上述转换格式被称为 one-vs-all


Confusion Matrix

在二分类或多分类任务中,Confusion Matrix 也是相当出名,它是 TP、FP、TN、FN 的表,通过使用 Confusion Matrix ,可以直观的看到正确分类的样本数量和错误分类的样本数量。

Confusion Matrix 由 TP、FP、TN、FN 构成,这四项组成元素也是我们计算 Precision、Recall、F1、ROC、AUC 的基础组成。

通常将 FP 称为 Type-I error 一类错误,将 FN 称为 Type-II error 二类错误。

可以非常方便地将 Confusion Matrix 扩展到多标签分类,其形状为 NxN 矩阵。

假如有实际分类 [0, 1, 2, 0, 1, 2, 0, 2, 2],和预测分类 [0, 2, 1, 0, 2, 1, 0, 0, 2],可制作 Confusion Matrix 如图所示。

一个完美的 Confusion Matrix 只在对角线上有数据。

Scikit-Learn 给了生成 Confusion Matrix 的便捷方法,通过一下代码可以生成并绘制 Confusion Matrix。

import matplotlib.pyplot as plt
import seaborn as sns
from sklearn import metrics

y_true = [0, 1, 2, 0, 1, 2, 0, 2, 2]
y_pred = [0, 2, 1, 0, 2, 1, 0, 0, 2]

cm = metrics.confusion_matrix(y_true, y_pred)
plt.figure(figsize=(10, 10))
cmap = sns.cubehelix_palette(
    50,
    hue=0.05,
    rot=0,
    light=0.9,
    dark=0.1,
    as_cmap=True
)
sns.set(font_scale=2.5)
sns.heatmap(cm, annot=True, cmap=cmap, cbar=False)
plt.xlabel('Actual Label')
plt.ylabel('Predict Label')

Multi-Label Classification

目前,我们介绍了二分类与多分类任务中使用的指标,下边介绍另一种分类问题及其评估指标:多标签分类。

在多标签分类任务中,每个样本可以具有一个或多个标签分类。

多标签分类任务的一个例子是:预测图片中有多少个不同的对象。

此类问题的最常用评估指标包括:

  • Precision at k,P@k

  • Average precision at k,AP@k

  • Mean average precision at k,MAP@k

  • Log loss


Precision at k

接下来从 Precision at k 指标开始介绍。

首先要分清此处精确度 precision 的含义,如果对于一个样本,有一组目标类型和一组预测类型,那么 precision 定义为预测数组里前 k 个标签中,匹配到目标类型数组的标签个数除以 k。

如果定义不好理解,下边用代码演示。

def pk(y_true, y_pred, k):
    if k == 0:
        return 0
    y_pred = y_pred[:k]
    pred_set = set(y_pred)
    true_set = set(y_true)
    common_values = pred_set.intersection(true_set)
    return len(common_values) / len(y_pred)

l1 = [1, 2, 3]
l2 = [2, 3, 4]
pk(l1, l2, 3)

Average precision at k

接着介绍 Average precision at k,AP@k 是通过 P@k计算得到的。

例如,如果要计算 AP@3,那么首先要计算 P@1、P@2、P@3,然后对其求和再除以 3。

用代码实现如下。

def apk(y_true, y_pred, k):
    pk_values = []
    for i in range(1, k + 1):
        pk_values.append(pk(y_true, y_pred, i))
    if len(pk_values) == 0:
        return 0
    return sum(pk_values) / len(pk_values)

l1 = [
    [1, 2, 3],
    [0, 2],
    [1],
    [2, 3],
    [1, 0],
    []
]
l2 = [
    [0, 1, 2],
    [1],
    [0, 2, 3],
    [2, 3, 4, 0],
    [0, 1, 2],
    [0]
]
for i in range(len(l1)):
    for j in range(1, 4):
        print(
            f"""
            y_true={l1[i]}
            y_pred={l2[i]}
            AP@{j}={apk(l1[i], l2[i], k=j)}
            """
        )

该代码演示了如何在数据集的每一个样本上计算 AP@k。


Mean average percision at k

机器学习中,如果想要知道所有样本上的指标度量,通常使用 Mean average percision at k,MAP@k,该指标是 AP@k 在数据集上的平均值。

计算代码如下。

def mapk(y_true, y_pred, k):
    apk_values = []
    for i in range(len(y_true)):
        apk_values.append(apk(y_true[i], y_pred[i], k))
    return sum(apk_values) / len(apk_values)

l1 = [
    [1, 2, 3],
    [0, 2],
    [1],
    [2, 3],
    [1, 0],
    []
]
l2 = [
    [0, 1, 2],
    [1],
    [0, 2, 3],
    [2, 3, 4, 0],
    [0, 1, 2],
    [0]
]
mapk(l1, l2, 3)

P@k,AP@k,MAP@k 的取值范围都是 0-1,且 1 表示效果最好。

需要注意的是,上述代码实现里,对于前 k 个元素的取值并不要求有序,而有时候排序很重要,导致这些指标具有另一种不同的实现。


MAE

到目前为止,可以说我们已经介绍了所有二分类、多分类、多标签分类任务的评估指标,接下来开始介绍回归任务的评估指标。

最常见的回归任务指标是 Error 误差,该指标非常容易理解:

Error=TrueValuePredictedValueError = TrueValue - PredictedValue

然后是绝对误差 Absolute Error

AbsoluteError=Abs(Error)AbsoluteError = Abs(Error)

接着是平均绝对误差 Mean Absolute Error,MAE,计算代码如下。

import numpy as np
from sklearn import metrics

def mean_absolute_error(y_true, y_pred):
    error = 0
    for yt, yp in zip(y_true, y_pred):
        error += np.abs(yt - yp)
    return error / len(y_true)

y_true = [1, 2, 3, 1, 2, 3, 1, 2, 3]
y_pred = [2, 1, 3, 1, 2, 3, 3, 1, 2]
print(mean_absolute_error(y_true, y_pred))
print(metrics.mean_absolute_error(y_true, y_pred))

MSE

相似地,回归任务的指标还有平方误差和平均平方误差 Mean Squared Error, MSE

SquaredError=(TrueValuePredictedValue)2SquaredError = (TrueValue - PredictedValue)^2

MSE 的实现代码如下。

import numpy as np
from sklearn import metrics

def mean_squared_error(y_true, y_pred):
    error = 0
    for yt, yp in zip(y_true, y_pred):
        error += (yt - yp) ** 2
    return error / len(y_true)

y_true = [1, 2, 3, 1, 2, 3, 1, 2, 3]
y_pred = [2, 1, 3, 1, 2, 3, 3, 1, 2]
print(mean_squared_error(y_true, y_pred))
print(metrics.mean_squared_error(y_true, y_pred))

MSE 和 RMSE(Root Mean Squared Error)都是最流行最常用的回归任务评估指标。

RMSE=Sqrt(MSE)RMSE = Sqrt(MSE)

RMSLE

另一大类误差类型是 squared logarithmic error,也称为 SLE。而所有样本的 SLE 的平均值被称为 MSLE。再对 MSLE 开方得 RMSLE(Root Mean Squared Logarithmic Error)。

RMSLE 的实现如下。

import numpy as np
from sklearn import metrics

def root_mean_squared_logarithmic_error(y_true, y_pred):
    error = 0
    for yt, yp in zip(y_true, y_pred):
        error += (np.log(1 + yt) - np.log(1 + yp)) ** 2
    return np.sqrt(error / len(y_true))

y_true = [1, 2, 3, 1, 2, 3, 1, 2, 3]
y_pred = [2, 1, 3, 1, 2, 3, 3, 1, 2]
print(root_mean_squared_logarithmic_error(y_true, y_pred))

Percentage Error

接着介绍百分比误差 Percentage Error

PercentageError=((TrueValuePredictedValue)/TrueValue)100PercentageError = ((TrueValue - PredictedValue)/TrueValue)*100

同样可以很容易计算所有样本的平均百分比误差 MPE,将所有样本的 PE 求和再除以样本总量。

在 MPE 的计算过程中,取 TrueValue 与 PredictedValue 的绝对值,可得 MAPE,Mean Absolute Percentage Error。

import numpy as np
from sklearn import metrics

def mean_absolute_percentage_error(y_true, y_pred):
    error = 0
    for yt, yp in zip(y_true, y_pred):
        error += np.abs(yt - yp) / yt
    return error / len(y_true)

y_true = [1, 2, 3, 1, 2, 3, 1, 2, 3]
y_pred = [2, 1, 3, 1, 2, 3, 3, 1, 2]
print(mean_absolute_percentage_error(y_true, y_pred))

回归任务的评估指标通常都是这样易于理解且易于扩展。


R2

接下着介绍另一个指标 R2R^2,也被称为决定系数。

简而言之,R2R^2 表明你的模型对数据的拟合程度,越接近 1.0,说明模型对数据拟合的越好;越接近 0 ,说明模型越差。

在模型做了荒诞的预测时,该指标可能时负值。

计算公式如下。

R2=1i=1N(ytiypi)2i=1N(ytiytmean)2R^2 = 1 - \frac{\sum_{i=1}^{N}(y_{t_i} - y_{p_i})^2}{\sum_{i=1}^N(y_{t_i} - y_{t_{mean}})^2}

代码实现如下。

import numpy as np

def r2(y_true, y_pred):
    mean_true_value = np.mean(y_true)
    numerator = 0
    denominator = 0
    for yt, yp in zip(y_true, y_pred):
        numerator += (yt - yp) ** 2
        denominator += (yt - mean_true_value) ** 2
    ratio = numerator / denominator
    return 1 - ratio

y_true = [1, 2, 3, 1, 2, 3, 1, 2, 3]
y_pred = [2, 1, 3, 1, 2, 3, 3, 1, 2]
print(r2(y_true, y_pred))

当然还有更多指标,如果全都列出来,那就没止尽了。现在学习的指标已经足够应付你想要解决的几乎任何问题。

需要注意的是,上述代码实现都是最简单直接的方式,其执行效率是不够高的,最好使用 numpy 或者 scikit-learn 提供的对应方法。


接下来,简单介绍几个高级指标。

首先是应用广泛的 Quadratic Weighted Kappa,QWK,也被称为 Cohen's Kappa。该指标度量两个比率的同意度 aggrement,两个比率的取值范围是 0-N 之间的实数,预测值当然也在这个范围里。

同意度 aggrement 衡量这两个比率的接近度,同意度越高,比率越接近,QWK 取值越接近 1 。

from sklearn import metrics

y_true = [1, 2, 3, 1, 2, 3, 1, 2, 3]
y_pred = [2, 1, 3, 1, 2, 3, 3, 1, 2]
metrics.cohen_kappa_score(y_true, y_pred, weights='quadratic')

一个效果优秀的模型的 QWK 最好大于 0.85 。

另一个重要的指标是 Matthew‘s Correlation Coefficient,MCC。

该指标取值为 -1 到 1,1 表示预测效果最佳,-1 表示最差,0 表示随机预测。

MCC 公式如下。

MCC=TPTNFPFN[(TP+FP)(FN+TN)(FP+TN)(TP+FN)]0.5MCC = \frac{TP * TN - FP * FN}{[(TP+FP)*(FN+TN)*(FP+TN)*(TP+FN)]^{0.5}}

从公式可知,该指标需要 TP、FP、TN、FN,因此可被用于不平衡数据集。

代码实现如下。

def mcc(y_true, y_pred):
    tp = true_positive(y_true, y_pred)
    fp = false_positive(y_true, y_pred)
    tn = true_negative(y_true, y_pred)
    fn = false_negative(y_true, y_pred)
    
    numerator = (tp * tn) - (fp * fn)
    denominator = (
        (tp + fp) *
        (tn + fn) *
        (fp + tn) *
        (fn + tp)
    )
    return numerator / denominator ** 0.5

y_true = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 
          1, 0, 0, 0, 0, 0, 0, 0, 1, 0]
y_pred = [0, 0, 1, 0, 0, 0, 1, 0, 0, 0,
          1, 0, 0, 0, 0, 0, 0, 0, 1, 0]
mcc(y_true, y_pred)

以上这些评估指标可以帮你理解机器学习问题,并可应用在几乎所有机器学习问题的处理过程中。

必须记得,在评估无监督学习任务,如聚类时,要手动创建标签划分数据集,这样的话,在聚类完成后,可以使用监督学习的评估指标对其评估性能。

一旦我们对给定问题选用何种评估指标熟稔于心后,我们才能更深入地对模型性能进行优化和提升。