前言
性能评估在机器学习中起着重要的作用,帮助我们理解和衡量模型的效果,并为模型选择、参数调优和特征选择提供指导,以达到更好的性能和预测能力。
一、P-R曲线
1、基本概念
在信息检索中,我们经常会关心“检索出的信息中有多少比例是用户感兴趣的”“用户感兴趣的信息中有多少被检索出来了”。“查准率”(precision)与“查全率”(recall)是更为适用于此类需求的性能度量。
P——precision
R——recall
既然要画出这个曲线,那我们就要计算出这两个值,所以我们先来了解一下混淆矩阵:
对于二分类问题,可将样例根据其真实类别与学习器预测类别的组合划 分为真正例(true positive) 、假正例(false positive) 、真反倒(true negative) 、假反例(false negative) 四种情形
真实情况 | 预结果测 | |
正例 | 反例 | |
正例 | TP(真正例) | FN(假反例) |
反例 | FP(假正例) | TN(真反例) |
TP:被正确划分为正例的个数,即实际为正例且被分类器划分为正例的样例数
FP:被错误地划分为正例的个数,即实际为反例但被分类器划分为正例的样例数
FN:被错误地划分为反例的个数,即实际为正例但被分类器划分为反例的样例数
TN:被正确划分为反例的个数,即实际为反例且被分类器划分为反例的样例数
通过这两个公式我们就能够计算每个阈值下P和R的值,然后将它们在坐标系上的点连线起来就是一副P-R曲线,类似于图1-1:
图1-1
2、评估P-R曲线
若一个学习器的P-R曲线被另一个学习器完全”包住”,则后者的性能优于前者。当存在交叉时,可以计算曲线围住面积,但比较麻烦,平衡点(查准率=查全率,BEP)是一种度量方式。
但BEP还是过于简化了些,更常用的是F1和度量,它们分别是查准率和查全率的调和平均和加权调和平均。设样例总数为N,定义如下:
其中β>0度量了查全率对查准率的相对重要性. β = 1时退化为标准的F1; β >1时查全率有更大影响;β<1时查准率有更大影响。
二、Roc曲线
1、基本概念
ROC曲线是机器学习中用于评估二分类模型性能的一种常用工具。它以真正例率(True Positive Rate)为纵轴,假正例率(False Positive Rate)为横轴的曲线。在ROC曲线中,真正例率和假正例率的定义如下所示:
其中,TP表示真正例,FN表示假反例,FP表示假正例,TN表示真反例。这四个属性依旧 是基于混淆矩阵,具体可见上一点中P-R曲线有解释。
依旧是通过这两个公式算出每个阈值下的TPR和FPR,然后将它们在坐标系中画出并连线,即可得到Roc曲线图,如图1-2:
图1-2
2、评估模型
ROC曲线可以反映模型对于不同阈值下真正例率和假正例率的权衡关系,从而帮助我们选择合适的分类阈值以平衡真正例和假正例的权衡。ROC曲线越接近左上角,说明模型的性能越好。
通常情况下,我们还会计算ROC曲线下的面积(AUC),这个数值可以用来比较不同模型之间的性能表现。ROC曲线下面积越大,表示该模型性能越好。一般来说,AUC值在0.5到1之间,0.5表示随机预测分类结果,而1则表示完美的分类器。
三、绘制P-R曲线和Roc曲线
获取数据集并划分
在绘制图像之前,我们需要先获取一个数据集并且对其进行训练和预测结果
# 生成数据集
X, y = make_classification(n_samples=1000, n_features=10, n_informative=5, random_state=1)
# 将数据集分成训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
这里我们直接sklearn函数库里的make_classification()函数为我们生成1000个样本,其中有10个特征和一个标签,是一个二元分类数据集
训练模型
本次实验我们选择k—近邻算法作为我们本次的训练模型,我们先写一个knn分类器:
class KNNClassifier:
def __init__(self, k):
self.k = k
def fit(self, X_train, y_train):
self.X_train = X_train
self.y_train = y_train
def _euclidean_distance(self, x1, x2):
return np.sqrt(np.sum((x1 - x2)**2))
def predict(self, X_test):
y_pred = []
for x_test in X_test:
distances = [self._euclidean_distance(x_test, x_train) for x_train in self.X_train]
k_indices = np.argsort(distances)[:self.k]
k_nearest_labels = [self.y_train[i] for i in k_indices]
y_pred.append(max(set(k_nearest_labels), key=k_nearest_labels.count))
return np.array(y_pred)
训练
接着将训练集用于模型训练,并且预测结果:
knn = KNNClassifier(k=5)
knn.fit(X_train, y_train)
y_pred = knn.predict(X_test)
现在我们看看本次预测的准确率:
accuracy = accuracy_score(y_test, y_pred)
print("预测准确率:", accuracy)
可以发现我们本次的准确率还是蛮高的,说明我们的k值取得比较好,预测比较准确
混淆矩阵
def confusion_matrix(y_true, y_pred):
# 获取所有类别
classes = list(set(y_true))
classes.sort()
# 初始化混淆矩阵
conf_matrix = [[0 for i in range(len(classes))] for j in range(len(classes))]
# 计算混淆矩阵
for i in range(len(y_true)):
true_index = classes.index(y_true[i])
pred_index = classes.index(y_pred[i])
conf_matrix[true_index][pred_index] += 1
return conf_matrix
# 计算混淆矩阵
conf_matrix = confusion_matrix(y_test, y_pred)
print("混淆矩阵:")
print(conf_matrix)
这就是我们本次预测的混淆矩阵,可以看到TP,FP,FN,TN都被计算了出来
P-R曲线
def calculate_precision_recall(y_true, y_pred):
thresholds = sorted(set(y_pred), reverse=True)
precision = []
recall = []
for threshold in thresholds:
y_pred_binary = [1 if y >= threshold else 0 for y in y_pred]
true_positives = sum([1 for i in range(len(y_true)) if y_true[i] == 1 and y_pred_binary[i] == 1])
false_positives = sum([1 for i in range(len(y_true)) if y_true[i] == 0 and y_pred_binary[i] == 1])
true_negatives = sum([1 for i in range(len(y_true)) if y_true[i] == 0 and y_pred_binary[i] == 0])
false_negatives = sum([1 for i in range(len(y_true)) if y_true[i] == 1 and y_pred_binary[i] == 0])
if true_positives + false_positives == 0:
precision.append(1.0)
else:
precision.append(float(true_positives) / (true_positives + false_positives))
if true_positives + false_negatives == 0:
recall.append(1.0)
else:
recall.append(float(true_positives) / (true_positives + false_negatives))
return precision, recall
# 绘制PR曲线
precision, recall = calculate_precision_recall(y_test, y_pred)
plt.figure()
plt.step(recall, precision, where='post')
plt.xlabel('召回率')
plt.ylabel('精确率')
plt.title('PR 曲线')
plt.ylim([0.0, 1.05])
plt.xlim([0.0, 1.0])
plt.show()
运行结果:
可以发现本次运行出来的图形较为平滑,可能是因为本次训练出来的正确率较高
ROC曲线
def custom_roc_curve(y_true, y_scores):
thresholds = np.unique(y_scores) # 所有不重复的预测分数
thresholds = np.append(thresholds, thresholds.max() + 1) # 添加一个额外的阈值,确保最终点在图上
tpr = [] # TPR
fpr = [] # FPR
for threshold in thresholds:
y_pred = (y_scores >= threshold).astype(int)
true_positives = np.sum((y_true == 1) & (y_pred == 1))
false_positives = np.sum((y_true == 0) & (y_pred == 1))
true_negatives = np.sum((y_true == 0) & (y_pred == 0))
false_negatives = np.sum((y_true == 1) & (y_pred == 0))
tpr_value = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0
fpr_value = false_positives / (false_positives + true_negatives) if (false_positives + true_negatives) > 0 else 0
tpr.append(tpr_value)
fpr.append(fpr_value)
return fpr, tpr
def custom_auc(fpr, tpr):
auc = 0
for i in range(1, len(fpr)):
auc += 0.5 * (fpr[i] - fpr[i - 1]) * (tpr[i] + tpr[i - 1])
return auc
# 绘制ROC曲线
fpr, tpr = custom_roc_curve(y_test, y_pred)
roc_auc = custom_auc(fpr, tpr)
#fpr, tpr, _ = roc_curve(y_test, y_pred)
#roc_auc = auc(fpr, tpr)
plt.figure()
plt.plot(fpr, tpr, label='ROC 曲线 (AUC = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], 'k--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('假正率')
plt.ylabel('真正率')
plt.title('ROC 曲线')
plt.legend(loc="lower right")
plt.show()
运行结果:
可以发现曲线也是趋于平滑的,且AUC的值基本接近于0了,说明了本次预测很成功,knn模型适用于本次数据集的测试
四、实验小结
本次实验实现的P-R曲线和Roc曲线是评判机器学习模型的重要标准。PR曲线和ROC曲线的优势在于它们不受类别不平衡问题的影响,能够帮助我们更全面地评估模型的性能。除了直观地展示模型的性能之外,这些曲线还可以帮助我们根据具体的需求调整分类阈值,从而平衡召回率、精确度、真阳性率和假阳性率等指标。此外,通过在多个模型上比较PR曲线和ROC曲线,我们可以找到最佳的模型,并进一步优化性能。
总的来说,PR曲线和ROC曲线的绘制和分析对于机器学习模型评估至关重要。它们提供了一种可视化的方式来理解模型在不同分类阈值下的性能表现,并帮助我们选择合适的阈值以满足具体需求。同时,它们也是比较不同模型性能的重要工具,有助于选择最优模型并进行性能改进。