机器学习模型评估
现实任务中,我们经常面对多模型选择的难题:同一个问题,有不同的模型可供选择,甚至对同一种模型使用不同的参数配置,也会使模型的性能产生不同的变化。**ROC曲线(Receiver Operating Characteristic Curve)**是以TPR为纵轴,FPR为横轴,通过调整分类阈值,获取不同阈值下的TPR和FPR值,并将其各点连线而成的曲线。最后得到的评估数据即为k次评估的均值。对学习器
文章目录
一、模型评估的意义
在研究机器学习算法模型的过程中,我们需要对模型算法进行深入了解,并想办法提高模型的精度和泛化能力。因此,我们需要对算法模型进行评估。
机器学习模型评估的意义在于:
- 提高模型的预测精度和泛化能力;
- 帮助我们更好地理解模型的内部机制和性能;
- 为模型优化和改进提供依据;
- 应用于实际问题的解决,提高生产力和效率。
二、模型评估方法
1. 模型评估引言
对于一个模型的预测或分类结果,我们可以将其与实际结果相比较。对于一个具有多个样本的数据集,其中分类错误的样本数占数据集样本总数的比例称为错误率;相对的,分类正确的样本数占数据集样本总数的比例称为精度。
更一般地,对于单次预测结果,我们将预测结果与实际结果之间的差异称为误差。模型在训练集上的误差称为训练误差或经验误差,在新样本上的误差称为泛化误差。
显然,我们希望得到精度高、错误率低、泛化误差小的模型。但由于我们并不能知道新样本的特征和规律,因此我们并不能控制模型的泛化误差,只能尽力使训练误差最小化。
但若一个模型在训练集上表现很好,精度高,训练误差小,甚至可以做到100%分类正确,就一定说明这个模型的泛化误差小、适合投入使用吗?事实自然不是这样。这样的模型一般都会表现出过拟合的特征,过度适应、贴合训练集,导致其泛化性能下降,往往无法准确预测和分类新样本。
现实任务中,我们经常面对多模型选择的难题:同一个问题,有不同的模型可供选择,甚至对同一种模型使用不同的参数配置,也会使模型的性能产生不同的变化。我们需要一个方法来评估这些不同模型在不同参数配置下的误差。在测试集有限的情况下,评估过程需要接近实际运用的过程,以使评估的误差接近泛化误差。
在实际评估时,我们一般使用留出法和交叉验证法对模型进行评估验证。
2. 模型评估具体方法
2.1 留出法
将给定的数据集分为两部分,一部分作为训练集,用于训练模型,另一部分作为测试集,用于评估模型。
主要步骤:
- 将原始数据集划分为训练集和测试集;
- 使用训练集训练模型;
- 使用测试集评估模型,计算模型各项性能指标;
- 根据测试集的评估结果对模型进行优化;
- 重复执行步骤2~4,直至模型达到预期要求。
划分数据集时应注意:
- 训练集和测试集的划分应保证数据的一致性,避免数据划分产生的偏差对最终结果造成影响。
- 数据集和测试集的数据应尽量互斥,以避免模型过拟合从而产生”过于乐观“的评估结果。
2.2 交叉验证法
原始留出法仍具有一定偶然性。交叉验证法即是在留出法的基础上进行多次分割验证,从而尽量消除偶然性。
交叉验证法将数据集等分成k份。对于划分完毕的数据集,依次选取其中的一份作为测试集,其余部分作为训练集,直至所有数据集都被选取完毕。最后得到的评估数据即为k次评估的均值。
一般按k值的大小,称这种验证评估的过程为k折交叉验证。
主要步骤:
- 将原始数据集分成k个部分,其中k-1个部分作为训练集,剩余的部分作为测试集;
- 使用k-1个部分训练模型;
- 使用剩余的部分测试模型,计算模型的各项性能指标;
- 重复步骤2~3,直到每个部分都被用作测试集一次;
- 对所有的测试结果进行平均,得到模型的最终性能指标;
- 根据最终性能指标,对模型进行调整和优化;
- 重复步骤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)⋅β2⋅Precision+RecallPrecision⋅Recall
可以看出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曲线:
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曲线下的面积为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=1∑k21(TPRi+TPRi−1)(FPRi−FPRi−1)
实际可用scikit-learn库的auc函数计算模型的AUC值:
from sklearn.metrics import auc
true_labels, labels_proba = cross_verification() # 使用之前交叉验证获取的结果
auc_value = auc(true_labels, labels_proba)
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)