尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python ROC曲线与AUC计算实战:从二分类到多分类避坑指南

Python ROC曲线与AUC计算实战:从二分类到多分类避坑指南 简介这份PDF资料面向机器学习初学者与需要评估二分类模型的开发者系统讲解如何用Python绘制ROC曲线并计算AUC值。内容从真阳性率与假阳性率的计算入手逐步构建坐标点、连接曲线并借助梯形法则求取曲线下面积同时给出基于sklearn.metrics.roc_curve与auc的标准用法以及一套自定义AUC计算代码适用于没有内置评估模块的模型场景。资源包共1个PDF文件约81KB篇幅精炼便于快速查阅与对照实践。目前已有16264人学习下载热度较高。读者可从中掌握ROC与AUC的核心原理、数据格式要求预测分数、负样本数、正样本数以及按分数排序累加小矩形面积的实现思路并了解采样或等距划分阈值等效率优化方向为模型对比与选型提供可复用的评估方法。1. 从一张“看起来很美”的 ROC 曲线说起很多做 Python 数据分析与可视化的朋友第一次画 ROC 曲线都是被业务方逼出来的模型训完了准确率 0.93 看着挺唬人结果一上不平衡样本就露馅——负样本占 95%全猜负类也有 0.95。这时候 ROC 曲线和 AUC 值就成了救命稻草它不依赖阈值能直接告诉你模型把正样本排在负样本前面的能力到底有多强。但真动手时坑一个接一个roc_curve返回的fpr、tpr到底哪个是横轴auc传参顺序反了为什么结果还是 0.5 以上多分类怎么画这篇就把 Python 画 ROC 曲线和计算 AUC 值的完整路径拆开从二分类最小可跑代码到多分类、交叉验证、阈值挑选再到几个我踩过的血泪坑全部落到能抄作业的代码块上。适合刚入门 Python、正在做分类模型评估的从业者也适合想把评估环节做扎实的老手。2. 把 ROC 和 AUC 的数学底子先立住为什么它比准确率靠谱2.1 混淆矩阵到 TPR/FPR 的推导链ROC 曲线的本质是把分类器在不同阈值下的表现画成一条线。要理解它得先从混淆矩阵出发。假设二分类问题正类记为 1负类记为 0模型输出的是概率y_score我们设一个阈值t概率大于等于t判为正否则判为负。于是有四个量TP真实为正、预测为正FN真实为正、预测为负FP真实为负、预测为正TN真实为负、预测为负真正率 TPR TP / (TP FN)也叫召回率、灵敏度衡量正样本被找出来的比例。假正率 FPR FP / (FP TN)衡量负样本被误判为正的比例。ROC 曲线就是以 FPR 为横轴、TPR 为纵轴把阈值从 1 降到 0 的过程中所有 (FPR, TPR) 点连起来。这里有个反直觉的点阈值从高到低扫曲线是从左下往右上走的。阈值极高时几乎所有样本都判负TPR 和 FPR 都接近 0点在左下角阈值极低时几乎所有样本都判正TPR 和 FPR 都接近 1点在右上角。所以一条好的 ROC 曲线会尽量往左上角凸。AUC 就是这条曲线下的面积取值 0 到 1。0.5 表示和随机猜没区别1.0 表示完美分类。它有一个非常实用的概率解释随机取一个正样本和一个负样本模型给正样本打分高于负样本的概率就等于 AUC。这个解释在不平衡数据里特别有价值因为它不受类别比例影响。2.2 为什么不用准确率一个不平衡样本的算例假设 1000 个样本正类 50 个负类 950 个。模型 A 把所有样本都判为负准确率 950 / 1000 0.95。模型 B 能找出 40 个正类但误判了 100 个负类准确率 (40 850) / 1000 0.89。单看准确率A 更好但 A 对正类毫无识别能力业务上完全没用。ROC 曲线下A 的 AUC 约等于 0.5B 的 AUC 会明显高于 0.5。这就是为什么做风控、医疗诊断、欺诈检测这类不平衡场景ROC 和 AUC 是标配。2.3 选型理由sklearn 的 roc_curve 和 auc 怎么配合Python 里画 ROC 曲线主流做法是用sklearn.metrics下的roc_curve和auc。roc_curve接收真实标签和预测概率或决策函数值返回三个数组fpr、tpr、thresholds。auc接收fpr和tpr返回曲线下面积。注意auc的参数顺序是(fpr, tpr)不是(tpr, fpr)虽然面积在对称情况下可能碰巧接近但逻辑上是错的后面避坑章节会细说。为什么不自己手写积分因为roc_curve内部做了阈值去重和边界处理返回的thresholds第一个值是inf对应 (0,0) 点最后一个值是最小得分对应 (1,1) 点。自己手写容易在重复得分和边界上翻车。常见做法是直接用 sklearn除非你要做自定义的代价敏感曲线。提示roc_curve的y_score必须是连续值比如predict_proba输出的正类概率或者decision_function的输出。直接传predict的 0/1 标签曲线会退化成三个点AUC 也不准。3. 二分类最小可跑代码从数据到 ROC 曲线和 AUC 值3.1 环境准备与依赖安装先确认 Python 环境。如果你还在纠结 python 安装教程、vscode python 环境配置、pycharm 配置 python 环境这些事建议直接用 Anaconda 或 venv 建一个干净环境避免包冲突。核心依赖就三个numpy、scikit-learn、matplotlib。安装命令如下# 建议在虚拟环境中执行避免污染全局 python -m venv roc_env source roc_env/bin/activate # Windows 用 roc_env\Scripts\activate pip install numpy scikit-learn matplotlib参数说明venv是 Python 自带模块不需要额外装。source在 Linux/macOS 下激活Windows 用反斜杠路径。如果公司网络慢可以加-i指定镜像源但这里不展开。3.2 用 make_classification 造一份可复现的数据为了让你直接跑通我用make_classification造一份二分类数据固定随机种子保证每次结果一致。import numpy as np from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_curve, auc # 造 2000 个样本20 个特征其中 5 个是有效特征 X, y make_classification( n_samples2000, n_features20, n_informative5, n_redundant2, weights[0.9, 0.1], # 正类只占 10%模拟不平衡 random_state42 ) # 分层切分保证训练集和测试集正负比例一致 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42 ) print(训练集正类比例:, y_train.mean()) print(测试集正类比例:, y_test.mean())逻辑说明weights[0.9, 0.1]让负类占 90%正类占 10%模拟真实不平衡场景。stratifyy是关键参数如果不加切分后测试集可能正类极少AUC 波动大。random_state42保证可复现。3.3 训练模型并拿到预测概率# 用逻辑回归因为它能直接输出概率 clf LogisticRegression(max_iter1000, class_weightbalanced) clf.fit(X_train, y_train) # 拿正类的预测概率注意 predict_proba 返回两列取第二列 y_score clf.predict_proba(X_test)[:, 1] print(前 5 个预测概率:, np.round(y_score[:5], 4))参数说明max_iter1000防止默认迭代次数不够导致不收敛警告。class_weightbalanced让模型自动按类别频率加权在不平衡数据上通常比不加好。predict_proba返回形状(n_samples, 2)第一列是负类概率第二列是正类概率所以取[:, 1]。如果你用的是 SVM可能没有predict_proba那就用decision_function效果一样。3.4 计算 FPR、TPR 并画图import matplotlib.pyplot as plt # 计算 ROC 曲线的三个关键数组 fpr, tpr, thresholds roc_curve(y_test, y_score) # 计算 AUC 值注意参数顺序是 (fpr, tpr) roc_auc auc(fpr, tpr) plt.figure(figsize(8, 6)) plt.plot(fpr, tpr, colordarkorange, lw2, labelROC curve (AUC %0.4f) % roc_auc) plt.plot([0, 1], [0, 1], colornavy, lw1, linestyle--, labelRandom guess) plt.xlim([-0.02, 1.02]) plt.ylim([-0.02, 1.02]) plt.xlabel(False Positive Rate (FPR)) plt.ylabel(True Positive Rate (TPR)) plt.title(ROC Curve - Logistic Regression) plt.legend(loclower right) plt.grid(alpha0.3) plt.show() print(AUC 值:, round(roc_auc, 4)) print(阈值数组前 5 个:, np.round(thresholds[:5], 4))逻辑说明roc_curve返回的thresholds第一个是inf对应起点 (0,0)。auc用梯形积分算面积。plt.plot([0,1],[0,1])画对角线代表随机猜测AUC0.5。xlim和ylim稍微留点边距避免点贴边。跑完你应该能看到 AUC 在 0.9 左右具体取决于随机种子。注意如果你的 AUC 低于 0.5先别怀疑模型检查y_score是不是取反了或者auc参数顺序写反了。低于 0.5 通常意味着预测方向和真实标签相反。4. 多分类与交叉验证ROC 曲线不止二分类4.1 多分类 ROC 的两种策略OvR 与 OvO二分类好办多分类就得多想一步。sklearn 的roc_curve只支持二分类多分类需要先做二值化。常见两种策略One-vs-RestOvR对每个类别把它当正类其余所有类当负类画一条 ROC 曲线最后可以算宏平均或微平均 AUC。One-vs-OneOvO每两个类别之间画一条类别多时曲线数量爆炸一般不用于可视化。实操里 OvR 更常用。sklearn.preprocessing.label_binarize可以把多分类标签转成 one-hot 形式然后对每一列调用roc_curve。from sklearn.datasets import load_iris from sklearn.multiclass import OneVsRestClassifier from sklearn.preprocessing import label_binarize from sklearn.metrics import roc_curve, auc from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression import numpy as np import matplotlib.pyplot as plt # 加载鸢尾花数据3 分类 iris load_iris() X, y iris.data, iris.target # 标签二值化得到 3 列 0/1 y_bin label_binarize(y, classes[0, 1, 2]) n_classes y_bin.shape[1] X_train, X_test, y_train, y_test train_test_split( X, y_bin, test_size0.3, random_state42 ) # 用 OvR 包装逻辑回归 clf OneVsRestClassifier(LogisticRegression(max_iter1000)) clf.fit(X_train, y_train) # 拿到每个类别的预测概率 y_score clf.predict_proba(X_test) fpr dict() tpr dict() roc_auc dict() for i in range(n_classes): fpr[i], tpr[i], _ roc_curve(y_test[:, i], y_score[:, i]) roc_auc[i] auc(fpr[i], tpr[i]) # 画三条曲线 plt.figure(figsize(8, 6)) colors [aqua, darkorange, cornflowerblue] for i, color in zip(range(n_classes), colors): plt.plot(fpr[i], tpr[i], colorcolor, lw2, labelClass {0} (AUC {1:0.4f}).format(i, roc_auc[i])) plt.plot([0, 1], [0, 1], k--, lw1) plt.xlim([-0.02, 1.02]) plt.ylim([-0.02, 1.02]) plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.title(Multi-class ROC (One-vs-Rest)) plt.legend(loclower right) plt.grid(alpha0.3) plt.show()逻辑说明label_binarize把标签转成 one-hotOneVsRestClassifier内部为每个类别训练一个二分类器。predict_proba返回形状(n_samples, n_classes)每一列是该类别的概率。循环里对每一列算 ROC 和 AUC。colors列表长度要和类别数一致类别多时建议改用 colormap。参数说明test_size0.3是常见切分比例数据量小可以调到 0.2。random_state42保证可复现。如果类别极不平衡OneVsRestClassifier里可以给每个基分类器加class_weightbalanced。4.2 宏平均与微平均 AUC 的计算多分类下除了看每条曲线还常算两个汇总指标宏平均macro先算每个类别的 AUC再取算术平均每个类别权重相同。微平均micro把所有类别的预测展平当成一个二分类问题算 AUC受样本多的类别影响大。# 宏平均 macro_auc np.mean(list(roc_auc.values())) print(宏平均 AUC:, round(macro_auc, 4)) # 微平均把所有列展平 fpr_micro, tpr_micro, _ roc_curve(y_test.ravel(), y_score.ravel()) micro_auc auc(fpr_micro, tpr_micro) print(微平均 AUC:, round(micro_auc, 4))逻辑说明y_test.ravel()把 one-hot 矩阵拉成一维y_score.ravel()同样拉平这样每个样本的每个类别都变成一个二分类判断。微平均 AUC 通常比宏平均高因为样本多的类别贡献大。选哪个看业务如果每个类别同等重要用宏平均如果更关心整体表现用微平均。4.3 交叉验证下的 AUC 稳定性评估单次切分的 AUC 有随机性换一个随机种子可能差 0.02 到 0.05。要评估稳定性用cross_val_score配合roc_auc评分。from sklearn.model_selection import cross_val_score, StratifiedKFold # 用原始标签不用 one-hot因为 roc_auc 默认处理二分类 # 这里用二分类数据演示 X2, y2 make_classification( n_samples2000, n_features20, n_informative5, weights[0.9, 0.1], random_state42 ) cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) clf2 LogisticRegression(max_iter1000, class_weightbalanced) scores cross_val_score(clf2, X2, y2, cvcv, scoringroc_auc) print(5 折 AUC:, np.round(scores, 4)) print(均值:, round(scores.mean(), 4), 标准差:, round(scores.std(), 4))参数说明StratifiedKFold保证每折正负比例一致shuffleTrue打乱顺序random_state固定。scoringroc_auc直接调 sklearn 内置的 AUC 计算。标准差大于 0.03 时说明模型对数据划分敏感建议增加数据量或做特征筛选。提示多分类做交叉验证时scoringroc_auc_ovr或roc_auc_ovo可以指定策略具体看 sklearn 版本支持。老版本可能只支持二分类升级到较新版本即可。5. 避坑与排查ROC 曲线和 AUC 计算里最容易翻车的 5 个点5.1 坑一auc 参数顺序写反结果看着正常其实是错的现象代码跑通AUC 算出 0.85但换一组数据后 AUC 变成 0.15低于 0.5。原因auc(fpr, tpr)的参数顺序写成了auc(tpr, fpr)。虽然梯形积分在曲线对称时可能碰巧接近但大多数情况下会算错。更隐蔽的是有些人把roc_curve的返回值直接解包成tpr, fpr, thresholds顺序反了后面全错。解决记住roc_curve返回顺序是fpr, tpr, thresholdsauc接收顺序是(fpr, tpr)。写代码时变量名别偷懒用fpr和tpr全称别用x和y。5.2 坑二把 predict 的 0/1 标签传给 roc_curve现象ROC 曲线只有三个点AUC 算出来是 0.5 或某个奇怪值。原因roc_curve需要连续的预测得分传predict的硬标签只有 0 和 1 两个值阈值扫描时只能产生三个点曲线退化成折线AUC 严重失真。解决用predict_proba取正类概率或用decision_function取决策值。如果模型两者都没有考虑用CalibratedClassifierCV包装或者换一个支持概率输出的模型。5.3 坑三多分类直接传原始标签给 roc_curve现象报错ValueError: multiclass format is not supported。原因roc_curve只支持二分类多分类标签直接传进去会报错。解决先用label_binarize做 one-hot再对每一列调用roc_curve。或者用OneVsRestClassifier包装模型predict_proba返回多列概率。5.4 坑四测试集正类样本太少AUC 波动大现象同一模型换一个随机种子AUC 从 0.92 掉到 0.78。原因测试集切分时没有分层正类样本本来就少切分后测试集可能只有几个正类AUC 估计极不稳定。解决train_test_split加stratifyy交叉验证用StratifiedKFold。如果正类比例低于 5%考虑增加数据量或改用 PR 曲线Precision-RecallPR 曲线在小样本正类下更敏感。5.5 坑五忽略阈值只看 AUC 就上线现象AUC 0.95上线后业务方反馈误报太多。原因AUC 衡量的是排序能力不关心具体阈值。实际业务需要一个明确的判定阈值AUC 高不代表某个阈值下精确率和召回率都满足要求。解决画完 ROC 曲线后结合业务需求选阈值。可以用thresholds数组找到使tpr - fpr最大的点约登指数或者固定召回率求精确率。下面这段代码演示怎么找最优阈值# 找约登指数最大的阈值 youden tpr - fpr best_idx np.argmax(youden) best_threshold thresholds[best_idx] print(最优阈值:, round(best_threshold, 4)) print(对应 TPR:, round(tpr[best_idx], 4)) print(对应 FPR:, round(fpr[best_idx], 4))逻辑说明tpr - fpr最大处曲线离对角线最远通常是不错的平衡点。但业务上如果更看重召回可以手动选一个 TPR 更高的点接受更高的 FPR。6. 进阶技巧用 PR 曲线补 ROC 的盲区以及一个我常用的验证习惯ROC 曲线在正类极稀少时有个盲区FPR 的分母是负类数量负类多时 FPR 变化不明显曲线看着还行但精确率可能很低。这时候 PR 曲线Precision-Recall更敏感。PR 曲线的横轴是召回率纵轴是精确率AUC 对应的是 Average Precision。sklearn 里用precision_recall_curve和average_precision_score。from sklearn.metrics import precision_recall_curve, average_precision_score precision, recall, _ precision_recall_curve(y_test, y_score) ap average_precision_score(y_test, y_score) plt.figure(figsize(8, 6)) plt.plot(recall, precision, colorgreen, lw2, labelPR curve (AP %0.4f) % ap) plt.xlabel(Recall) plt.ylabel(Precision) plt.title(Precision-Recall Curve) plt.legend(loclower left) plt.grid(alpha0.3) plt.show() print(Average Precision:, round(ap, 4))参数说明precision_recall_curve返回precision、recall、thresholds注意precision和recall长度比thresholds多 1最后一个点是 (1, 0)。average_precision_score直接算 AP比梯形积分更准。我一般会同时看 ROC 和 PR 两条曲线。如果 ROC 的 AUC 高但 PR 的 AP 低说明模型在正类上的精确率不够需要调阈值或加特征。如果两者都高才敢往上线推。还有一个习惯每次算完 AUC我会把y_score的分布画出来看看正类和负类的得分有没有明显重叠。重叠多的话AUC 再高也有限因为模型本身区分度不够。这个习惯帮我省了好几次返工。# 看正负类得分分布 plt.figure(figsize(8, 6)) plt.hist(y_score[y_test 0], bins50, alpha0.5, labelNegative, colorblue) plt.hist(y_score[y_test 1], bins50, alpha0.5, labelPositive, colorred) plt.xlabel(Predicted Probability) plt.ylabel(Count) plt.title(Score Distribution by Class) plt.legend() plt.grid(alpha0.3) plt.show()逻辑说明y_score[y_test 0]取负类得分y_score[y_test 1]取正类得分。两个分布重叠越少AUC 越高。如果重叠严重考虑加特征、换模型或做概率校准。最后说个教训我曾经为了图快直接拿predict的标签算 AUC结果 0.5排查了半天才发现是标签问题。从那以后我养成了一个习惯——每次算 AUC 前先打印y_score的前 10 个值确认是连续概率而不是 0/1。这个动作花不了 10 秒但能省下半小时的排查。希望帮到你。本文还有配套的精品资源点击获取
返回列表