用Python绘制ROC和PR曲线全攻略:从sklearn到Matplotlib的完整代码示例
·
Python实战:ROC与PR曲线的深度解析与可视化技巧
1. 理解分类模型评估的核心指标
在机器学习领域,评估二分类模型性能时,单纯依赖准确率(Accuracy)往往会带来误导,特别是在处理类别不平衡的数据集时。想象一个医疗检测场景:在1000个样本中,只有10例是阳性患者。如果一个模型将所有样本预测为阴性,准确率高达99%,但这个模型实际上毫无价值。
这正是ROC曲线和PR曲线展现价值的地方。它们通过动态调整分类阈值,全面反映模型在不同决策边界下的表现。让我们先明确几个关键概念:
- 真阳性率(TPR/Recall):
TP/(TP+FN),表示模型正确识别正例的能力 - 假阳性率(FPR):
FP/(FP+TN),反映模型误判负例为阳性的比例 - 精确率(Precision):
TP/(TP+FP),衡量预测为正例的样本中实际为正的比例
# 混淆矩阵计算示例
from sklearn.metrics import confusion_matrix
y_true = [1, 0, 1, 1, 0, 1]
y_pred = [1, 1, 1, 0, 0, 1]
tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
print(f"TPR: {tp/(tp+fn):.2f}, FPR: {fp/(fp+tn):.2f}, Precision: {tp/(tp+fp):.2f}")
2. ROC曲线:全面评估模型区分能力
ROC曲线以FPR为横轴,TPR为纵轴,展示阈值变化时模型的性能变化。理想的ROC曲线会紧贴左上角,表示模型能同时实现高召回和低误判。
绘制ROC曲线的关键步骤:
- 计算每个样本的预测概率
- 将概率从高到低排序作为候选阈值
- 在每个阈值下计算TPR和FPR
- 连接所有点形成曲线
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve, auc
# 生成示例数据
y_true = [1, 0, 1, 1, 0, 0, 1, 0]
y_scores = [0.9, 0.8, 0.7, 0.6, 0.4, 0.3, 0.2, 0.1]
fpr, tpr, thresholds = roc_curve(y_true, y_scores)
roc_auc = auc(fpr, tpr)
plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2,
label=f'ROC曲线 (AUC = {roc_auc:.2f})')
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('假阳性率(FPR)')
plt.ylabel('真阳性率(TPR)')
plt.title('ROC曲线示例')
plt.legend(loc="lower right")
plt.show()
解读AUC值:
- 0.5:无区分能力(相当于随机猜测)
- 0.7-0.8:有一定区分度
- 0.8-0.9:区分度良好
-
0.9:区分度优秀
3. PR曲线:聚焦正类预测质量
PR曲线以Recall为横轴,Precision为纵轴,特别适合评估不平衡数据集的模型表现。与ROC曲线不同,PR曲线对正例的预测质量变化更为敏感。
PR曲线的特点:
- 曲线越靠近右上角越好
- 平衡点(BEP)是P=R时的取值
- 曲线下面积(AP)衡量整体表现
from sklearn.metrics import precision_recall_curve
precision, recall, _ = precision_recall_curve(y_true, y_scores)
ap = auc(recall, precision)
plt.figure()
plt.plot(recall, precision, color='blue', lw=2,
label=f'PR曲线 (AP = {ap:.2f})')
plt.xlabel('召回率(Recall)')
plt.ylabel('精确率(Precision)')
plt.title('PR曲线示例')
plt.legend(loc="upper right")
plt.show()
ROC与PR曲线的选择指南:
| 场景特征 | 推荐曲线 | 原因 |
|---|---|---|
| 类别相对平衡 | ROC | 全面评估模型性能 |
| 正例占比<10% | PR | 对正类预测变化更敏感 |
| 关注假阳性成本 | ROC | FPR直接反映误判情况 |
| 关注正类预测准确率 | PR | Precision是核心指标 |
4. 实战:COVID-19检测案例解析
让我们通过一个模拟的COVID-19检测数据集,观察不平衡数据下曲线的表现特点。假设我们有一个包含1000个样本的数据集,其中阳性病例仅占5%。
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
# 生成不平衡数据集
X, y = make_classification(n_samples=1000, n_classes=2, weights=[0.95, 0.05],
random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,
random_state=42)
# 训练模型
model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)
y_scores = model.predict_proba(X_test)[:, 1]
# 绘制双曲线对比
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
fpr, tpr, _ = roc_curve(y_test, y_scores)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, color='darkorange', label=f'ROC (AUC={roc_auc:.2f})')
plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.title('ROC曲线')
plt.legend()
plt.subplot(1, 2, 2)
precision, recall, _ = precision_recall_curve(y_test, y_scores)
pr_auc = auc(recall, precision)
plt.plot(recall, precision, color='blue', label=f'PR (AP={pr_auc:.2f})')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('PR曲线')
plt.legend()
plt.tight_layout()
plt.show()
不平衡数据下的观察:
- ROC曲线可能呈现"乐观"的AUC值
- PR曲线更能反映模型在少数类上的真实表现
- 当正例极少时,PR曲线的波动更为明显
- AP值比AUC更能反映实际应用价值
5. 高级可视化与优化技巧
5.1 多模型对比可视化
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
# 训练多个模型
models = {
"Logistic Regression": LogisticRegression(max_iter=1000),
"Random Forest": RandomForestClassifier(n_estimators=100),
"SVM": SVC(probability=True)
}
plt.figure(figsize=(12, 5))
colors = ['blue', 'green', 'red']
# ROC子图
plt.subplot(1, 2, 1)
for i, (name, model) in enumerate(models.items()):
model.fit(X_train, y_train)
y_prob = model.predict_proba(X_test)[:, 1]
fpr, tpr, _ = roc_curve(y_test, y_prob)
plt.plot(fpr, tpr, color=colors[i],
label=f'{name} (AUC={auc(fpr, tpr):.2f})')
plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.title('多模型ROC对比')
plt.legend()
# PR子图
plt.subplot(1, 2, 2)
for i, (name, model) in enumerate(models.items()):
y_prob = model.predict_proba(X_test)[:, 1]
precision, recall, _ = precision_recall_curve(y_test, y_prob)
plt.plot(recall, precision, color=colors[i],
label=f'{name} (AP={auc(recall, precision):.2f})')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('多模型PR对比')
plt.legend()
plt.tight_layout()
plt.show()
5.2 美化图表专业技巧
- 添加阈值标记:
# 在ROC曲线上标注关键阈值点
thresholds = [0.2, 0.5, 0.8]
for thresh in thresholds:
idx = np.argmin(np.abs(thresholds - thresh))
plt.scatter(fpr[idx], tpr[idx], marker='o', color='red')
plt.text(fpr[idx]+0.02, tpr[idx]-0.02,
f'Thresh={thresh:.1f}', fontsize=9)
- 使用seaborn风格:
import seaborn as sns
sns.set_style("whitegrid")
plt.figure(figsize=(10, 6))
sns.despine()
- 交互式可视化(Jupyter环境):
from plotly.offline import iplot
import plotly.graph_objs as go
trace0 = go.Scatter(x=fpr, y=tpr, mode='lines', name='ROC曲线')
trace1 = go.Scatter(x=[0, 1], y=[0, 1], mode='lines',
line=dict(dash='dash'), showlegend=False)
layout = go.Layout(title='交互式ROC曲线', xaxis=dict(title='FPR'),
yaxis=dict(title='TPR'))
iplot(go.Figure(data=[trace0, trace1], layout=layout))
6. 常见问题与解决方案
问题1:曲线出现锯齿状波动
原因:
- 测试样本量不足
- 阈值变化时TP/FP发生突变
解决方案:
# 使用sklearn的average_precision_score直接计算AP
from sklearn.metrics import average_precision_score
ap = average_precision_score(y_test, y_scores)
问题2:PR曲线起始点异常
原因:
- 最高置信度的预测可能是负例
修正方法:
# 确保曲线从(0,1)开始
precision = np.concatenate([[1], precision])
recall = np.concatenate([[0], recall])
问题3:多类别场景处理
解决方案:
# 使用One-vs-Rest策略
from sklearn.metrics import roc_auc_score
roc_auc = roc_auc_score(y_test, y_scores, multi_class='ovr')
7. 性能优化与生产实践
加速大数据集计算:
# 使用近似计算
from sklearn.metrics import precision_recall_curve
precision, recall, _ = precision_recall_curve(y_test, y_scores,
sample_weight=None)
最佳阈值选择策略:
# 基于F1分数选择最优阈值
f1_scores = 2 * (precision * recall) / (precision + recall)
optimal_idx = np.argmax(f1_scores)
optimal_threshold = thresholds[optimal_idx]
生产环境部署建议:
- 定期监控曲线形态变化
- 设置AUC/AP的报警阈值
- 对不同客户群体分别评估
- 记录历史曲线用于对比分析
# 曲线变化监控示例
def monitor_curve_change(current_auc, baseline_auc, threshold=0.05):
change = (current_auc - baseline_auc) / baseline_auc
if abs(change) > threshold:
print(f"警告:AUC变化超过{threshold*100}%!")
return change
更多推荐
所有评论(0)