处理机器学习任务时,你会遇到不同类型的指标。有时人们会根据业务问题,创建特殊指标。
解释所有指标超出了本书的范围,但我们会一起动手实践最常用的一些指标。
本书第一章介绍了监督学习和无监督学习,那么我们接下来只关心监督学习相关指标,尽管在无监督学习中也可以使用一些指标。原因在于监督学习中有大量的比较运算,而无监督学习更为主观。
对于分类问题,最常用的指标如下:
-
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)
-
对我们来说,不仅需要理解上述指标的工作方式,还要理解合适使用哪种指标。
对指标的选择离不开对数据和目标值的理解,且目标值对指标的选择影响更大。
下边通过一个简单的例子,来认识这些指标。
假设有这样一个二分类问题,需要对胸部 X 光图像进行分类,类别包括无病与气胸。
因此,需要构建分类器,检测输入的 X 光图像的对应类别概率。
同时,假设已经有等量的气胸和非气胸图像各 100 张,即总共 200 张图片,100 个正例,100 个负例。
数据下载地址:
处理的第一步是将上述数据划分到两个相等的集合,即训练集和验证集。在每个集合中,都有 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 :
下边使用 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 的定义是:
假设我们基于不平衡数据集训练出了一个模型,该模型在 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 为 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 的定义公式为:
将 P 和 R 定义公式代入 F1 的公式中,可得:
即使用 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 函数是为了方便之后使用。
def tpr(y_true, y_pred):
return recall(y_true, y_pred)
TPR 或者 Recall 也被称为敏感性 Sensitivity 。
FPR
FPR,即 False Positive Rate,定义如下:
代码实现如下:
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 也被称为 TNR 、True 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()
得到的曲线如下图所示。
该曲线便是著名的 ROC,Receiver 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,。
在计算训练集的输出概率和 AUC 之后,需要在测试集上做预测。
基于问题和用例,你可能需要输出概率值或者目标类型。如果你需要输出概率,那么模型原始输出就是概率值;如果你需要输出目标类型,你得选择一个 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,其定义为:
其中 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 误差,该指标非常容易理解:
然后是绝对误差 Absolute 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。
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)都是最流行最常用的回归任务评估指标。
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
同样可以很容易计算所有样本的平均百分比误差 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
接下着介绍另一个指标 ,也被称为决定系数。
简而言之, 表明你的模型对数据的拟合程度,越接近 1.0,说明模型对数据拟合的越好;越接近 0 ,说明模型越差。
在模型做了荒诞的预测时,该指标可能时负值。
计算公式如下。
代码实现如下。
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 公式如下。
从公式可知,该指标需要 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)
以上这些评估指标可以帮你理解机器学习问题,并可应用在几乎所有机器学习问题的处理过程中。
必须记得,在评估无监督学习任务,如聚类时,要手动创建标签划分数据集,这样的话,在聚类完成后,可以使用监督学习的评估指标对其评估性能。
一旦我们对给定问题选用何种评估指标熟稔于心后,我们才能更深入地对模型性能进行优化和提升。