目录
Accuracy:模型准确率可以通过以下公式算出,指的是被正确分类/判断的数据占比
Precision:也叫查准率,衡量的是所有预测为正例的结果中,预测正确的(为真正例)比例。
Recall:也叫查全率,衡量的是实际的正例有多少被模型预测为正例。
科学家门捷列夫说「没有测量,就没有科学」,在AI场景下我们同样需要定量的数值化指标来指导我们更好地应用模型对数据进行学习和建模。
事实上,在机器学习领域,对模型的测量和评估至关重要。选择与问题相匹配的评估方法,能帮助我们快速准确地发现在模型选择和训练过程中出现的问题,进而对模型进行优化和迭代。本文我们系统地讲解一下机器学习模型评估相关知识。
一、模型评估
在评估一个机器学习模型/算法是好是坏时,首先需要事先准备好测试数据集。注意测试数据集一定一定不能拿来训练,因为它是模拟被测模型/算法从未见过的数据,这样才能保证评估有效。
测试数据集一定不能有任何倾向:数据需要随机抽取
在评估和优化机器学习算法时有一个概念叫做超参数优化。超参数指的是在模型训练前选定的一系列和模型/算法相关的参数,例如 KNN 当中的 k。超参数优化指的就是找到最优的超参数,使得最后的模型评估最优。
最容易想到的方法就是选择不同的参数值在训练数据集上进行训练,然后选择表现最好的模型和参数。注意这里不能通过测试数据集来判断来评估模型表现,因为这样做会使得测试数据集也变成了训练数据集:因为你这么做的话相当于基于测试数据集来选参数,变相地把测试数据集变成了训练数据集。
最正确的做法是把数据分为三个部分:训练,验证,测试。常用的分配比例为 60%/20%/20% 或者是 80%/10%/10%。训练数据集用来训练模型,验证数据集用来判断哪个参数下的模型表现最好,最后再用测试数据集进行评估。
二、评估标准
构建混淆矩阵(Confusion matrix)是用来评判一个模型表现的方法。很多评估数据都是基于混淆矩阵而生成的。
另外一张更浅显的图:
上图将算法预测的结果分成四种情况:
正确肯定(True Positive,TP):预测为真,实际为真
正确否定(True Negative,TN):预测为假,实际为假
错误肯定(False Positive,FP):预测为真,实际为假
错误否定(False Negative,FN):预测为假,实际为真
对此,有几个数据需要知悉:
Accuracy:模型准确率可以通过以下公式算出,指的是被正确分类/判断的数据占比
Precision:也叫查准率,衡量的是所有预测为正例的结果中,预测正确的(为真正例)比例。
Recall:也叫查全率,衡量的是实际的正例有多少被模型预测为正例。
高召回率,低精确率,意味着有很多“是”的确被模型正确分出来了,但是有很多假阳性(模型判断为“是”,但是为“否”)。
低召回率,高精确率,意味着有很多“是”未被正确识别出来,但是至少模型输出为“是”的数据点实际上的确也是“是”。
如果是仅有两种分类(是或否),那么可以直接用混淆矩阵算出精确率/召回率等。如果分类有两种以上,那么计算过程中如果属于这个类别则按照“是”判断,如果是其他类别则直接取“否”。
三、过度拟合
过度拟合指的是虽然在训练数据集上的表现不错,但是在测试或者是真实世界中收集的数据集上表现欠佳
拟合不足指的是在训练数据集和真实世界/测试数据集上的表现都不好
选择的特征越多越容易过度拟合
发生场景:
- 模型过于复杂,例如有太多个特征
- 数据集代表不了真实世界中的情况,也就是有偏颇
- 模型训练时间过长,例如在神经网络中
解决方法:
- 适当的对模型复杂度进行缩减,例如选择更少的特征
- 获取更多数据
- 提早停止学习/训练
四、曲线绘制
PR:
精确率和召回率是一对相互矛盾的指标,一般来说高精准往往低召回,相反亦然。其实这个是比较直观的,比如我们想要一个模型准确率达到 100%,那就意味着要保证每一个结果都是真正例,这就会导致有些正例被放弃;相反,要保证模型能将所有正例都预测为正例,意味着有些反例也会混进来。这背后的根本原因就在于我们的数据往往是随机、且充满噪声的,并不是非黑即白。
根据精准率(P)和召回率(R)的计算公式:
得到 P 和 R 后就可以画出更加直观的P-R 图(P-R 曲线),横坐标为召回率,纵坐标是精准率。绘制方法如下:
from typing import List, Tuple
import matplotlib.pyplot as plt
def get_confusion_matrix(y_pred: List[int], y_true: List[int]) -> Tuple[int, int, int, int]:
"""
计算混淆矩阵
Args:
y_pred (List[int]): 模型的预测标签
y_true (List[int]): 真实标签
Returns:
Tuple[int, int, int, int]: (TP, FP, TN, FN)
"""
length = len(y_pred)
assert length == len(y_true)
tp, fp, fn, tn = 0, 0, 0, 0
for i in range(length):
if y_pred[i] == y_true[i] and y_pred[i] == 1:
tp += 1
elif y_pred[i] == y_true[i] and y_pred[i] == 0:
tn += 1
elif y_pred[i] == 1 and y_true[i] == 0:
fp += 1
elif y_pred[i] == 0 and y_true[i] == 1:
fn += 1
return tp, fp, tn, fn
def calc_p(tp: int, fp: int) -> float:
"""
计算Precision
Args:
tp (int): True Positive
fp (int): False Positive
Returns:
float: Precision
"""
return tp / (tp + fp)
def calc_r(tp: int, fn: int) -> float:
"""
计算Recall
Args:
tp (int): True Positive
fn (int): False Negative
Returns:
float: Recall
"""
return tp / (tp + fn)
def get_pr_pairs(y_pred_prob: List[float], y_true: List[int]) -> Tuple[List[int], List[int]]:
"""
获取Precision-Recall曲线上的点
Args:
y_pred_prob (List[float]): 模型的预测概率值
y_true (List[int]): 真实标签
Returns:
Tuple[List[int], List[int]]: Precision 和 Recall 列表
"""
ps = [1]
rs = [0]
for prob1 in y_pred_prob:
y_pred_i = []
for prob2 in y_pred_prob:
if prob2 < prob1:
y_pred_i.append(0)
else:
y_pred_i.append(1)
tp, fp, tn, fn = get_confusion_matrix(y_pred_i, y_true)
p = calc_p(tp, fp)
r = calc_r(tp, fn)
ps.append(p)
rs.append(r)
ps.append(0)
rs.append(1)
return ps, rs
# 示例数据
y_pred_prob = [0.9, 0.8, 0.7, 0.6, 0.55, 0.54, 0.53, 0.52, 0.51, 0.505,
0.4, 0.39, 0.38, 0.37, 0.36, 0.35, 0.34, 0.33, 0.3, 0.1]
y_true = [1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0]
# 计算Precision-Recall曲线上的点
ps, rs = get_pr_pairs(y_pred_prob, y_true)
# 绘制Precision-Recall曲线
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(12, 5))
ax.plot(rs, ps)
plt.show()
代码运行效果:
ROC(接受者操作特征曲线):
ROC(Receiver Operating Characteristic)曲线,又称接受者操作特征曲线。该曲线最早应用于雷达信号检测领域,用于区分信号与噪声。后来人们将其用于评价模型的预测能力,ROC曲线是基于混淆矩阵得出的。
ROC曲线中的主要两个指标就是真正率和假正率,上面也解释了这么选择的好处所在。其中横坐标为假正率(FPR)
,纵坐标为真正率(TPR)
,下面就是一个标准的ROC曲线图。
示例:
在这个示例中,我使用了一个二元分类模型,采用了逻辑回归算法。我首先生成了一个模拟数据集 X
和目标标签 y
。数据集 X
包含两个特征,而目标标签 y
是二元分类标签,只有两个取值,0 或 1。
接下来,我对这个模型进行了训练,使用了一些超参数,如迭代次数和学习率。我训练的目标是找到最佳参数(theta
),以使逻辑回归模型的输出概率与实际观察到的标签 y
尽可能吻合。
随后,我使用训练好的模型来预测每个样本的概率得分,并根据这些概率得分生成了一个ROC曲线。ROC曲线以图形方式显示了在不同概率阈值下,模型的性能表现,即真正例率(True Positive Rate,也称召回率)和假正例率(False Positive Rate)之间的权衡关系。
最终的结果是一条ROC曲线,通过计算曲线下方的面积(AUC,Area Under the Curve),我们可以度量模型的性能。AUC值越接近1,说明模型的性能越好。
源码:
import numpy as np
import matplotlib.pyplot as plt
# 生成模拟数据
np.random.seed(0)
X = np.random.randn(100, 2)
y = (X[:, 0] + 2 * X[:, 1] > 1).astype(int)
# Sigmoid函数
def sigmoid(z):
return 1 / (1 + np.exp(-z))
# 逻辑回归训练函数
def logistic_regression(X, y, num_iterations, learning_rate):
m, n = X.shape
X = np.hstack((np.ones((m, 1)), X))
theta = np.zeros(n + 1)
for _ in range(num_iterations):
z = np.dot(X, theta)
h = sigmoid(z)
gradient = np.dot(X.T, (h - y)) / m
theta -= learning_rate * gradient
return theta
# 预测概率
def predict_prob(X, theta):
X = np.hstack((np.ones((X.shape[0], 1)), X))
z = np.dot(X, theta)
return sigmoid(z)
# 生成ROC曲线
def plot_roc_curve(y_true, y_scores):
thresholds = np.linspace(0, 1, 100)
tpr_list, fpr_list = [], []
for threshold in thresholds:
y_pred = (y_scores >= threshold).astype(int)
tp = np.sum((y_pred == 1) & (y_true == 1))
tn = np.sum((y_pred == 0) & (y_true == 0))
fp = np.sum((y_pred == 1) & (y_true == 0))
fn = np.sum((y_pred == 0) & (y_true == 1))
tpr = tp / (tp + fn)
fpr = fp / (fp + tn)
tpr_list.append(tpr)
fpr_list.append(fpr)
plt.figure(figsize=(8, 6))
plt.plot(fpr_list, tpr_list, color='darkorange', lw=2)
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.show()
# 训练逻辑回归模型
num_iterations = 10000
learning_rate = 0.1
theta = logistic_regression(X, y, num_iterations, learning_rate)
# 获取预测概率
y_scores = predict_prob(X, theta)
# 生成并显示ROC曲线
plot_roc_curve(y, y_scores)
结果:
五、实验心得
总的来说,模型评估是一个持续学习和改进的过程。通过实验和不断的实践,我初步理解了机器学习,并学会了如何将它们应用于实际问题中。这些经验将对我未来在数据分析和机器学习项目中有大的作用。