一、模型评估的意义

在研究机器学习算法模型的过程中,我们需要对模型算法进行深入了解,并想办法提高模型的精度和泛化能力。因此,我们需要对算法模型进行评估。

机器学习模型评估的意义在于:

  • 提高模型的预测精度和泛化能力;
  • 帮助我们更好地理解模型的内部机制和性能;
  • 为模型优化和改进提供依据;
  • 应用于实际问题的解决,提高生产力和效率。

二、模型评估方法

1. 模型评估引言

对于一个模型的预测或分类结果,我们可以将其与实际结果相比较。对于一个具有多个样本的数据集,其中分类错误的样本数占数据集样本总数的比例称为错误率;相对的,分类正确的样本数占数据集样本总数的比例称为精度

更一般地,对于单次预测结果,我们将预测结果与实际结果之间的差异称为误差。模型在训练集上的误差称为训练误差经验误差,在新样本上的误差称为泛化误差

显然,我们希望得到精度高、错误率低、泛化误差小的模型。但由于我们并不能知道新样本的特征和规律,因此我们并不能控制模型的泛化误差,只能尽力使训练误差最小化。

但若一个模型在训练集上表现很好,精度高,训练误差小,甚至可以做到100%分类正确,就一定说明这个模型的泛化误差小、适合投入使用吗?事实自然不是这样。这样的模型一般都会表现出过拟合的特征,过度适应、贴合训练集,导致其泛化性能下降,往往无法准确预测和分类新样本。

现实任务中,我们经常面对多模型选择的难题:同一个问题,有不同的模型可供选择,甚至对同一种模型使用不同的参数配置,也会使模型的性能产生不同的变化。我们需要一个方法来评估这些不同模型在不同参数配置下的误差。在测试集有限的情况下,评估过程需要接近实际运用的过程,以使评估的误差接近泛化误差。

在实际评估时,我们一般使用留出法交叉验证法对模型进行评估验证。

2. 模型评估具体方法

2.1 留出法

将给定的数据集分为两部分,一部分作为训练集,用于训练模型,另一部分作为测试集,用于评估模型。

主要步骤:

  1. 将原始数据集划分为训练集和测试集;
  2. 使用训练集训练模型;
  3. 使用测试集评估模型,计算模型各项性能指标;
  4. 根据测试集的评估结果对模型进行优化;
  5. 重复执行步骤2~4,直至模型达到预期要求。

划分数据集时应注意:

  • 训练集和测试集的划分应保证数据的一致性,避免数据划分产生的偏差对最终结果造成影响。
  • 数据集和测试集的数据应尽量互斥,以避免模型过拟合从而产生”过于乐观“的评估结果。

2.2 交叉验证法

原始留出法仍具有一定偶然性。交叉验证法即是在留出法的基础上进行多次分割验证,从而尽量消除偶然性。

交叉验证法将数据集等分成k份。对于划分完毕的数据集,依次选取其中的一份作为测试集,其余部分作为训练集,直至所有数据集都被选取完毕。最后得到的评估数据即为k次评估的均值。

一般按k值的大小,称这种验证评估的过程为k折交叉验证。

主要步骤:

  1. 将原始数据集分成k个部分,其中k-1个部分作为训练集,剩余的部分作为测试集;
  2. 使用k-1个部分训练模型;
  3. 使用剩余的部分测试模型,计算模型的各项性能指标;
  4. 重复步骤2~3,直到每个部分都被用作测试集一次;
  5. 对所有的测试结果进行平均,得到模型的最终性能指标;
  6. 根据最终性能指标,对模型进行调整和优化;
  7. 重复步骤2~6,直到模型性能达到预期要求。
  • 与留出法同理,交叉验证法也应尽量满足训练集和测试集的互斥。

3. 具体实现

本文的具体代码实现均以对scikit-learn库的KNeighborsClassifier模型评估为例。其中kNN算法模型的k值为5,其余参数均为默认值。

首先应获取数据集以进行评估。可以使用scikit-learn库自带的数据集,也可以用库中的数据集生成函数来生成数据集,如:

from sklearn.datasets import make_classification
from sklearn.preprocessing import StandardScaler

def get_dataset():
    dataset, labels = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
    dataset_scaled = StandardScaler().fit_transform(dataset)	# 对数据进行归一化

    return dataset_scaled, labels

该函数生成了一个含有1000个样本的数据集,每个样本都有20个特征值,为方便后续评估度量,标签总数为2,即标签只包含所谓“正例”和“反例”。

随后将数据集分为k折进行交叉验证,如:

import numpy as np
from sklearn.neighbors import KNeighborsClassifier

def k_fold(dataset, labels, k = 10):
    n = len(dataset)

    data_indices = np.arange(n)
    np.random.shuffle(data_indices)		# 在已知数据本身无序的情况下随机打乱数据排布, 防止数据排布产生的偶然性影响结果

    fold_size = n // k

    train_sets = []
    train_labels = []
    test_sets = []
    test_labels = []

    for i in range(k):
        train_indices = np.concatenate([data_indices[:i * fold_size], data_indices[(i + 1) * fold_size:]])
        test_indices = data_indices[i * fold_size:(i + 1) * fold_size]

        train_sets.append([dataset[x] for x in train_indices])
        train_labels.append([labels[x] for x in train_indices])
        test_sets.append([dataset[x] for x in test_indices])
        test_labels.append([labels[x] for x in test_indices])

    train_sets = np.array(train_sets)
    train_labels = np.array(train_labels)
    test_sets = np.array(test_sets)
    test_labels = np.array(test_labels)

    return train_sets, train_labels, test_sets, test_labels


def cross_verification():
    kNNClassifier = KNeighborsClassifier(n_neighbors=5)

    dataset, labels = get_dataset()
    train_sets, train_labels, test_sets, test_labels = k_fold(dataset, labels)

    true_labels = []
    labels_proba = []

    n = train_sets.shape[0]
    for i in range(n):
        kNNClassifier.fit(train_sets[i], train_labels[i])

        true_labels.extend(test_labels[i])
        labels_proba.extend(kNNClassifier.predict_proba(test_sets[i])[:, 1])		# 获取预测结果为正例的概率, 因kNNClassifier的predict_proba方法会存储了返回每一个标签的预测概率的矩阵, 而我们只需要正例概率, 因此结果只取矩阵的第二列(从左向右, 下标从1开始)

    return true_labels, labels_proba

同时还可以使用scikit-learn库中的cross_val_predict函数完成交叉验证,如:

from sklearn.model_selection import cross_val_predict

dataset, labels = get_dataset()
true_labels = cross_val_predict(KNeighborsClassifier(n_neighbors=5), dataset, labels)

其中true_labels返回模型预测的结果。
如果无需进行后续性能度量,函数返回值即为预期的结果。
如需进行度量,也可以直接使用cross_val_score或cross_validate函数获取模型的评分。本文后续不对此进行展开。

三、模型性能度量

对学习器的泛化性能进行评估,不仅需要有效可行的实验估计方法,还需要有衡量模型泛化能力的评价标准,这就是性能度量的意义。

1. 混淆矩阵

在进行评估验证后,我们能得到模型多次预测的结果。
可根据真实结果和预测结果生成一个二维矩阵来反映数据,矩阵结构如下所示:
混淆矩阵
这样的矩阵被称为混淆矩阵。混淆矩阵的行值为真实标签,列值为预测结果。

相较于多值,二值标签混淆矩阵更简单,如下方所示:
二值标签混淆矩阵
混淆矩阵可以帮助我们处理性能度量,即模型选择问题。

2. 查准率、查全率、Fβ值和P-R曲线

2.1 查准率和查全率

查准率,也称精度(Precision)精确率,与前文提到的精度概念基本相同。在这里指结果中真正例(True Positive,简称TP)预测结果中正例的比例。即:
P r e c i s i o n = T P T P + F N Precision = \frac{TP}{TP+FN} Precision=TP+FNTP
查准率反映了模型对于样本识别的准确程度。查准率越高,模型对于样本识别能力越强。

查全率,也称回归率(Recall),指结果中真正例占实际正例的比例。即:
R e c a l l = T P T P + F P Recall = \frac{TP}{TP+FP} Recall=TP+FPTP
查全率反映了模型对于样本识别的全面性。查全率越高,模型的识别能力越全面。

查准率和查全率是一对矛盾的量。通常二者其一会随着另一者的升高而降低。

2.2 F1值和Fβ

F1是查准率和查全率的调和平均值。即:
F 1 = 2 × P r e c i s i o n × R e c a l l P r e c i s i o n + R e c a l l F_1 = 2×\frac{Precision×Recall}{Precision+Recall} F1=2×Precision+RecallPrecision×Recall
它可用于综合评价模型的性能。F1值越高,说明模型在准确性和可靠性方面的表现都较好。

此外还存在Fβ,定义如下:
F β = ( 1 + β 2 ) ⋅ P r e c i s i o n ⋅ R e c a l l β 2 ⋅ P r e c i s i o n + R e c a l l F_β = (1+\beta^2)·\frac{Precision·Recall}{\beta^2·Precision+Recall} Fβ=(1+β2)β2Precision+RecallPrecisionRecall
可以看出F1值实际为Fβ值的特殊情况,即β = 1时的Fβ值。

Fβ值的参数β决定了查准率和查全率的权重比例:
β < 1:更重视查全率;
β > 1:更重视查准率;
β = 1:与F1值相同,查准率和查全率权重一致。

实际应用时可根据实际情况灵活改变β值,以在合适的标准下度量模型。

2.3 P-R曲线

P-R曲线(Precision-Recall Curve),即查准率-查全率曲线,是反映查准率和查全率变化情况的曲线。

实际可用scikit-learn库的precision_recall_curve函数来计算查准率和查全率的值,并以此画出P-R曲线:

from sklearn.metrics import precision_recall_curve

true_labels, labels_proba = cross_verification()	# 使用之前交叉验证获取的结果

precisions, recalls, _ = precision_recall_curve(true_labels, labels_proba)

plt.plot(recalls, precisions)
plt.title("Precision-Recall Curve")
plt.xlabel("Recalls")
plt.ylabel("Precisions")
plt.show()

以下为绘制出的P-R曲线:
P-R曲线

3. ROC曲线和AUC

3.1 真正例率和假正例率

真正例率(True Positive Rate,简称TPR),又称真阳性率,是正确识别的正例样本比例,定义与查准率相同,即:
T P R = T P T P + F N TPR = \frac{TP}{TP+FN} TPR=TP+FNTP
它反映了模型捕捉正类的能力。

假正例率(False Positive Rate,简称FPR),又称假阳性率。与真正例率相对,假正例率为被误判为正例的样本比例,即:
F P R = F P F P + T N FPR = \frac{FP}{FP+TN} FPR=FP+TNFP
它反映了模型对负类的误判程度。

3.2 ROC曲线和AUC

**ROC曲线(Receiver Operating Characteristic Curve)**是以TPR为纵轴,FPR为横轴,通过调整分类阈值,获取不同阈值下的TPR和FPR值,并将其各点连线而成的曲线。它反映了模型在不同阈值下的性能。

实际可用scikit-learn库的roc_curve函数获取TPR,FPR及分类阈值集合,然后以此绘制出ROC曲线:

from sklearn.metrics import roc_curve

true_labels, labels_proba = cross_verification()	# 使用之前交叉验证获取的结果

fpr, tpr, thresholds = roc_curve(true_labels, labels_proba)

plt.plot(fpr, tpr)
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title("ROC Curve")
plt.show()

以下为绘制出的ROC曲线:
ROC曲线
同时我们称ROC曲线下的面积为AUC(Area Under Curve)。一般认为AUC越大的模型性能越好。一般AUC的值为0.5~1.0,大于0.9即证明其性能优秀,值越小性能越差,AUC = 0.5时,该模型预测准确度几乎随机,与抛硬币无异。

由定义可知:
A U C = ∑ i = 1 k 1 2 ( T P R i + T P R i − 1 ) ( F P R i − F P R i − 1 ) AUC = \sum^{k}_{i=1}{\frac{1}{2}(TPR_i+TPR_{i-1})(FPR_i-FPR_{i-1})} AUC=i=1k21(TPRi+TPRi1)(FPRiFPRi1)

实际可用scikit-learn库的auc函数计算模型的AUC值:

from sklearn.metrics import auc

true_labels, labels_proba = cross_verification()	# 使用之前交叉验证获取的结果

auc_value = auc(true_labels, labels_proba)
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐