资讯动态

临床研究中的ROC曲线与AUC计算:二分类与多分类Python实战指南

发布时间:2026/10/20 7:53:08 来源:尧图企业网站定制
1. 临床研究场景下的分类模型评估需求拆解临床研究里做机器学习十有八九绕不开分类任务。不管是预测患者是否患病二分类还是区分疾病的不同亚型、不同分期多分类模型跑出来之后总得回答一个问题这个模型到底靠不靠谱。准确率这个指标在临床数据上特别容易骗人因为样本往往是不平衡的——患病的人少健康的人多模型全猜“健康”也能拿到很高的准确率但这种模型在临床上毫无价值。所以ROC曲线和AUC值就成了临床研究论文里几乎必报的评估指标。ROC曲线说白了就是横轴画假阳性率、纵轴画真阳性率通过不断移动分类阈值画出来的一条曲线。曲线越往左上角凸说明模型在保证不漏诊的前提下误诊越少。AUC就是曲线下面积0.5等于瞎猜1.0等于完美分类。临床研究里通常认为AUC在0.7到0.8算可接受0.8到0.9算良好0.9以上算优秀。但这里有个坑很多刚接触的同行直接用sklearn的roc_auc_score算一个数就完事了画图的时候却不知道怎么处理多分类的情况或者画出来的图没有置信区间、没有最佳截断点审稿人一看就觉得不够专业。这篇内容就是冲着这个痛点来的。我会把二分类和多分类ROC曲线的完整Python代码拆开讲清楚包括数据准备、模型训练、概率输出、曲线绘制、AUC计算、置信区间估计、最佳截断点选取以及多分类里macro-average、micro-average、weighted-average这几种平均策略到底该怎么选。适合正在做临床预测模型、准备投稿SCI论文、或者需要给科室汇报模型性能的同行参考。代码全部基于sklearn和matplotlib不依赖任何需要额外配置的工具复制到Jupyter里就能跑。2. 二分类ROC曲线从概率输出到曲线绘制2.1 为什么不能直接用预测标签画ROC很多人第一次画ROC曲线的时候会拿模型预测出来的0/1标签直接去画结果发现曲线就是一个直角折线AUC要么是0.5要么是1.0完全没法看。原因很简单ROC曲线的本质是展示模型在不同阈值下的表现而0/1标签已经把阈值固定死了通常是0.5信息量全丢了。正确的做法是拿模型输出的概率值也就是predict_proba返回的正类概率然后用这个连续值去画曲线。在临床场景里这一点尤其重要。比如你用一个逻辑回归模型预测患者是否有某种疾病模型输出某个患者的患病概率是0.73另一个是0.68虽然按0.5的阈值都判为患病但这两个患者的风险程度是不一样的。ROC曲线就是通过遍历所有可能的阈值来看模型在不同灵敏度下的特异度表现。所以第一步永远是拿到概率值而不是标签。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, roc_auc_score, auc import matplotlib.pyplot as plt import numpy as np # 模拟临床二分类数据1000个样本20个特征正类占比30% X, y make_classification(n_samples1000, n_features20, n_informative10, n_classes2, weights[0.7, 0.3], random_state42) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42) model LogisticRegression(max_iter1000, random_state42) model.fit(X_train, y_train) # 关键拿正类的预测概率不是预测标签 y_prob model.predict_proba(X_test)[:, 1]这里有个细节需要注意predict_proba返回的列顺序跟模型的classes_属性有关。二分类通常第一列是负类概率第二列是正类概率但为了保险起见最好用model.classes_确认一下正类的位置。我见过有人因为把负类概率当成正类概率去画ROC结果AUC算出来是0.3还以为是模型有问题其实是列取反了。2.2 计算ROC曲线坐标点和AUC值拿到概率值之后用roc_curve函数就能算出不同阈值下的假阳性率和真阳性率。这个函数返回三个数组fpr、tpr和thresholds。thresholds就是它自动选取的一系列阈值通常是从大到小排列。AUC值用roc_auc_score直接算或者用auc(fpr, tpr)通过梯形积分算两者结果基本一致但roc_auc_score更直接。fpr, tpr, thresholds roc_curve(y_test, y_prob) auc_value roc_auc_score(y_test, y_prob) print(fAUC: {auc_value:.4f})这里要提醒一点roc_curve默认的drop_intermediateTrue会丢掉一些对曲线形状没有影响的阈值点让曲线更平滑。如果你需要精确的阈值列表可以把它设成False但画图的时候通常没必要。另外如果样本量特别大比如上万条画出来的曲线可能会有点抖这时候可以考虑对概率值做一点平滑处理但临床研究里一般不建议这么做因为会引入额外的不确定性。2.3 绘制带置信区间和最佳截断点的ROC曲线光画一条曲线还不够临床论文里通常还要标注AUC的95%置信区间和最佳截断点。置信区间可以用Bootstrap方法估计虽然sklearn没有直接提供但自己写个循环也不复杂。最佳截断点一般用约登指数Youden index来选也就是tpr - fpr最大的那个点对应的阈值就是最佳截断值。# Bootstrap估计AUC的95%置信区间 n_bootstraps 1000 rng np.random.RandomState(42) bootstrapped_aucs [] for i in range(n_bootstraps): indices rng.randint(0, len(y_test), len(y_test)) if len(np.unique(y_test[indices])) 2: continue boot_auc roc_auc_score(y_test[indices], y_prob[indices]) bootstrapped_aucs.append(boot_auc) ci_lower np.percentile(bootstrapped_aucs, 2.5) ci_upper np.percentile(bootstrapped_aucs, 97.5) # 约登指数找最佳截断点 youden_index tpr - fpr best_idx np.argmax(youden_index) best_threshold thresholds[best_idx] best_sensitivity tpr[best_idx] best_specificity 1 - fpr[best_idx] print(fAUC: {auc_value:.4f} (95% CI: {ci_lower:.4f}-{ci_upper:.4f})) print(f最佳截断值: {best_threshold:.4f}) print(f灵敏度: {best_sensitivity:.4f}, 特异度: {best_specificity:.4f})画图的时候我习惯把对角线随机猜测线也画上方便对比。曲线用蓝色实线AUC值放在图例里最佳截断点用红色圆点标出来旁边标注灵敏度和特异度。这样一张图就能把关键信息全展示出来审稿人看着也舒服。plt.figure(figsize(8, 6)) plt.plot(fpr, tpr, color#1f77b4, lw2, labelfROC (AUC {auc_value:.3f}, 95% CI: {ci_lower:.3f}-{ci_upper:.3f})) plt.plot([0, 1], [0, 1], colorgray, lw1, linestyle--, labelRandom) plt.scatter(fpr[best_idx], tpr[best_idx], colorred, s80, zorder5, labelfBest cutoff {best_threshold:.3f}\nSens{best_sensitivity:.3f}, Spec{best_specificity:.3f}) plt.xlabel(False Positive Rate (1 - Specificity), fontsize12) plt.ylabel(True Positive Rate (Sensitivity), fontsize12) plt.title(ROC Curve for Binary Classification, fontsize14) plt.legend(loclower right, fontsize10) plt.grid(alpha0.3) plt.tight_layout() plt.savefig(binary_roc.png, dpi300) plt.show()注意Bootstrap次数建议至少1000次临床研究里有些期刊要求2000次以上。如果样本量很小比如少于100例Bootstrap的置信区间可能会偏窄这时候可以考虑用DeLong方法但实现起来更复杂sklearn没有内置需要额外写代码。3. 多分类ROC曲线三种平均策略的取舍3.1 多分类ROC的本质一对多拆解多分类的ROC曲线跟二分类不一样因为ROC曲线本身是定义在二分类问题上的。处理多分类的标准做法是“一对多”One-vs-Rest也就是对每一个类别把它当作正类其余所有类别当作负类分别画一条ROC曲线。比如你有三类疾病亚型A、B、C那就画三条曲线A vs (BC)、B vs (AC)、C vs (AB)。每条曲线都有自己的AUC值最后再算一个平均AUC作为整体性能指标。sklearn的roc_curve函数支持多分类输入但需要把标签做二值化处理。label_binarize就是干这个的。假设你的标签是0、1、2三个类别label_binarize会把它转成一个n_samples × 3的矩阵每一列对应一个类别的二值标签。from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.preprocessing import label_binarize from sklearn.metrics import roc_curve, auc from itertools import cycle # 模拟三分类临床数据 X_mc, y_mc make_classification(n_samples1500, n_features25, n_informative15, n_classes3, n_clusters_per_class1, weights[0.5, 0.3, 0.2], random_state42) X_train_mc, X_test_mc, y_train_mc, y_test_mc train_test_split( X_mc, y_mc, test_size0.3, stratifyy_mc, random_state42) model_mc RandomForestClassifier(n_estimators200, random_state42) model_mc.fit(X_train_mc, y_train_mc) # 拿到每个类别的预测概率 y_prob_mc model_mc.predict_proba(X_test_mc) # 把标签二值化 classes np.unique(y_mc) y_test_bin label_binarize(y_test_mc, classesclasses) n_classes y_test_bin.shape[1]这里有个容易踩的坑label_binarize返回的列顺序跟classes的顺序一致而predict_proba返回的列顺序跟model.classes_一致。如果训练集和测试集的类别分布不一样或者你手动指定了classes参数一定要确认两者的顺序对得上。我一般会打印一下model.classes_和classes确认它们完全一致再往下走。3.2 Macro、Micro、Weighted三种平均策略怎么选多分类的AUC平均策略有三种每种背后的逻辑不一样临床研究里选哪种取决于你的研究目的。Macro-average对每个类别的AUC直接求算术平均不考虑类别样本量。比如三类AUC分别是0.90、0.80、0.70macro-average就是0.80。这种策略适合你关心每个类别是否被公平对待的场景哪怕小类样本很少它的AUC也会被同等权重地计入。缺点是如果某个小类样本极少AUC估计不稳定会拉低整体指标。Micro-average把所有类别的预测结果汇总成一个大的二分类问题再算AUC。具体做法是把n_samples × n_classes的概率矩阵展平标签矩阵也展平然后算一次ROC。这种策略受大类样本影响更大因为大类样本多在展平后的数据里占比高。适合你更关心整体分类性能、不特别在意小类表现的场景。Weighted-average对每个类别的AUC按该类别的样本量加权平均。比如A类占50%、B类占30%、C类占20%weighted-average就是0.5×AUC_A 0.3×AUC_B 0.2×AUC_C。这种策略介于macro和micro之间既考虑了类别不平衡又保留了每个类别的独立AUC信息。临床研究里如果类别分布不均匀weighted-average通常是最稳妥的选择。fpr_dict {} tpr_dict {} roc_auc_dict {} for i in range(n_classes): fpr_dict[i], tpr_dict[i], _ roc_curve(y_test_bin[:, i], y_prob_mc[:, i]) roc_auc_dict[i] auc(fpr_dict[i], tpr_dict[i]) # Macro-average all_fpr np.unique(np.concatenate([fpr_dict[i] for i in range(n_classes)])) mean_tpr np.zeros_like(all_fpr) for i in range(n_classes): mean_tpr np.interp(all_fpr, fpr_dict[i], tpr_dict[i]) mean_tpr / n_classes fpr_macro all_fpr tpr_macro mean_tpr auc_macro auc(fpr_macro, tpr_macro) # Micro-average fpr_micro, tpr_micro, _ roc_curve(y_test_bin.ravel(), y_prob_mc.ravel()) auc_micro auc(fpr_micro, tpr_micro) # Weighted-average class_counts np.sum(y_test_bin, axis0) weights class_counts / np.sum(class_counts) auc_weighted np.sum([roc_auc_dict[i] * weights[i] for i in range(n_classes)]) print(fMacro-average AUC: {auc_macro:.4f}) print(fMicro-average AUC: {auc_micro:.4f}) print(fWeighted-average AUC: {auc_weighted:.4f})提示临床研究里如果三类样本量差不多macro和weighted结果会很接近如果样本量差异大建议同时报告macro和weighted让审稿人自己判断。micro-average在临床里用得相对少因为它会把所有类别混在一起丢失类别间的区分信息。3.3 多分类ROC曲线的可视化技巧多分类ROC曲线画图的时候如果类别多曲线会挤在一起看起来乱。我一般用两种方式处理一是用不同颜色和线型区分二是在图例里把每个类别的AUC值标出来。如果类别超过5个建议只画macro-average和micro-average两条曲线或者把每个类别的曲线分小图展示。plt.figure(figsize(10, 8)) colors cycle([#1f77b4, #ff7f0e, #2ca02c, #d62728, #9467bd]) for i, color in zip(range(n_classes), colors): plt.plot(fpr_dict[i], tpr_dict[i], colorcolor, lw2, labelfClass {classes[i]} (AUC {roc_auc_dict[i]:.3f})) plt.plot(fpr_macro, tpr_macro, colornavy, lw2, linestyle:, labelfMacro-average (AUC {auc_macro:.3f})) plt.plot(fpr_micro, tpr_micro, colordeeppink, lw2, linestyle:, labelfMicro-average (AUC {auc_micro:.3f})) plt.plot([0, 1], [0, 1], colorgray, lw1, linestyle--) plt.xlabel(False Positive Rate, fontsize12) plt.ylabel(True Positive Rate, fontsize12) plt.title(Multi-class ROC Curve, fontsize14) plt.legend(loclower right, fontsize10) plt.grid(alpha0.3) plt.tight_layout() plt.savefig(multiclass_roc.png, dpi300) plt.show()画多分类ROC的时候np.interp那一步很关键。因为每个类别的fpr数组长度可能不一样不能直接相加必须先把它们插值到同一个fpr网格上。我见过有人直接对tpr数组求平均结果报错说维度不匹配就是因为忽略了这一步。另外插值的时候要用np.interp(all_fpr, fpr_dict[i], tpr_dict[i])注意参数顺序是“目标x坐标、原始x坐标、原始y坐标”别搞反了。4. 实操中的常见问题与排查技巧4.1 AUC算出来是0.5或者异常低怎么办AUC等于0.5通常意味着模型没有学到任何有效信息跟随机猜测没区别。遇到这种情况先检查三件事第一标签有没有搞反比如把正类概率取成了负类概率第二训练集和测试集的标签编码是否一致比如训练集用的是0/1测试集用的是1/2第三特征里有没有混入标签泄露的列比如某个特征其实是标签的衍生变量。如果这三项都没问题那可能是模型本身不适合这个数据换一个模型或者重新做特征工程。AUC异常低比如0.3左右大概率是概率列取反了。predict_proba返回的列顺序是跟model.classes_对应的二分类里如果classes_是[0, 1]那第二列是正类概率如果classes_是[1, 0]那第一列才是正类概率。最保险的做法是用model.classes_找到正类对应的索引再取概率。# 确认正类概率的列索引 pos_class_idx np.where(model.classes_ 1)[0][0] y_prob model.predict_proba(X_test)[:, pos_class_idx]4.2 多分类标签二值化后的维度对不上label_binarize返回的矩阵列数等于classes参数的长度。如果你传入的classes跟实际标签的unique值不一致就会出问题。比如实际标签是[0, 1, 2]但你传了classes[0, 1]那类别2的样本会被全部标成0导致AUC计算错误。正确的做法是用np.unique(y)自动获取所有类别或者手动确认classes跟model.classes_完全一致。另一个常见问题是y_test_bin和y_prob_mc的列顺序不一致。label_binarize按classes参数的顺序排列列而predict_proba按model.classes_的顺序排列列。如果两者顺序不同算出来的AUC会完全乱掉。我一般会在代码里加一行断言assert np.array_equal(classes, model_mc.classes_), 类别顺序不一致4.3 样本不平衡时ROC曲线的局限性ROC曲线对样本不平衡不敏感这是它的优点也是缺点。优点是AUC不会因为正负样本比例变化而剧烈波动缺点是它不能反映模型在特定阈值下的实际临床价值。比如一个模型AUC很高但在你关心的灵敏度下特异度很低临床上可能还是不能用。这时候建议补充画精确率-召回率曲线PR曲线PR曲线对不平衡更敏感能更好地反映正类的识别质量。另外如果正类样本极少比如少于50例ROC曲线会变得很抖AUC的置信区间也会很宽。这种情况下可以考虑做分层Bootstrap或者用交叉验证的方式估计AUC的稳定性。临床研究里如果样本量小建议报告交叉验证的AUC均值±标准差而不是单次划分的AUC。4.4 常见问题速查表问题现象可能原因排查方法解决方案AUC 0.5概率列取反或模型无效检查model.classes_和概率列索引用np.where定位正类概率列AUC异常低0.4标签编码不一致对比训练集和测试集的标签unique值统一标签编码多分类AUC计算报错二值化矩阵维度不匹配检查label_binarize的classes参数用np.unique(y)自动获取类别曲线抖动严重样本量小或概率值离散查看测试集样本量和概率分布增加样本量或做交叉验证置信区间过宽Bootstrap次数不足或样本量小检查Bootstrap次数增加到2000次以上多分类曲线挤在一起类别过多查看类别数量只画macro和micro平均曲线实操心得临床研究里画ROC曲线图上的信息量比代码本身更重要。我习惯在图上标注AUC、95%置信区间、最佳截断值、对应的灵敏度和特异度这样一张图就能回答审稿人大部分问题。另外图的配色尽量用色盲友好的方案比如蓝色和橙色搭配避免红绿搭配因为有些审稿人可能是色盲。5. 完整代码整合与临床报告建议5.1 二分类完整代码模板把前面的代码整合成一个可直接运行的脚本方便复制到Jupyter里跑。我习惯把数据准备、模型训练、评估、画图分成四个函数这样换数据的时候只需要改数据准备部分。import numpy as np import matplotlib.pyplot as plt 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, roc_auc_score, auc def prepare_data(): X, y make_classification(n_samples1000, n_features20, n_informative10, n_classes2, weights[0.7, 0.3], random_state42) return train_test_split(X, y, test_size0.3, stratifyy, random_state42) def train_model(X_train, y_train): model LogisticRegression(max_iter1000, random_state42) model.fit(X_train, y_train) return model def evaluate_binary(model, X_test, y_test): pos_idx np.where(model.classes_ 1)[0][0] y_prob model.predict_proba(X_test)[:, pos_idx] fpr, tpr, thresholds roc_curve(y_test, y_prob) auc_value roc_auc_score(y_test, y_prob) n_bootstraps 1000 rng np.random.RandomState(42) boot_aucs [] for _ in range(n_bootstraps): indices rng.randint(0, len(y_test), len(y_test)) if len(np.unique(y_test[indices])) 2: continue boot_aucs.append(roc_auc_score(y_test[indices], y_prob[indices])) ci_lower np.percentile(boot_aucs, 2.5) ci_upper np.percentile(boot_aucs, 97.5) youden tpr - fpr best_idx np.argmax(youden) return fpr, tpr, thresholds, auc_value, ci_lower, ci_upper, best_idx def plot_binary_roc(fpr, tpr, thresholds, auc_value, ci_lower, ci_upper, best_idx): plt.figure(figsize(8, 6)) plt.plot(fpr, tpr, color#1f77b4, lw2, labelfROC (AUC {auc_value:.3f}, 95% CI: {ci_lower:.3f}-{ci_upper:.3f})) plt.plot([0, 1], [0, 1], colorgray, lw1, linestyle--) plt.scatter(fpr[best_idx], tpr[best_idx], colorred, s80, zorder5, labelfCutoff {thresholds[best_idx]:.3f}) plt.xlabel(False Positive Rate, fontsize12) plt.ylabel(True Positive Rate, fontsize12) plt.title(Binary Classification ROC, fontsize14) plt.legend(loclower right) plt.grid(alpha0.3) plt.tight_layout() plt.savefig(binary_roc.png, dpi300) plt.show() X_train, X_test, y_train, y_test prepare_data() model train_model(X_train, y_train) fpr, tpr, thresholds, auc_value, ci_lower, ci_upper, best_idx evaluate_binary(model, X_test, y_test) plot_binary_roc(fpr, tpr, thresholds, auc_value, ci_lower, ci_upper, best_idx)5.2 多分类完整代码模板多分类的代码稍微长一点但结构跟二分类类似。关键是把label_binarize和三种平均策略的计算封装好。from sklearn.ensemble import RandomForestClassifier from sklearn.preprocessing import label_binarize from itertools import cycle def prepare_multiclass_data(): X, y make_classification(n_samples1500, n_features25, n_informative15, n_classes3, n_clusters_per_class1, weights[0.5, 0.3, 0.2], random_state42) return train_test_split(X, y, test_size0.3, stratifyy, random_state42) def train_multiclass_model(X_train, y_train): model RandomForestClassifier(n_estimators200, random_state42) model.fit(X_train, y_train) return model def evaluate_multiclass(model, X_test, y_test): classes np.unique(y_test) assert np.array_equal(classes, model.classes_), 类别顺序不一致 y_test_bin label_binarize(y_test, classesclasses) y_prob model.predict_proba(X_test) n_classes len(classes) fpr_dict, tpr_dict, auc_dict {}, {}, {} for i in range(n_classes): fpr_dict[i], tpr_dict[i], _ roc_curve(y_test_bin[:, i], y_prob[:, i]) auc_dict[i] auc(fpr_dict[i], tpr_dict[i]) all_fpr np.unique(np.concatenate([fpr_dict[i] for i in range(n_classes)])) mean_tpr np.zeros_like(all_fpr) for i in range(n_classes): mean_tpr np.interp(all_fpr, fpr_dict[i], tpr_dict[i]) mean_tpr / n_classes auc_macro auc(all_fpr, mean_tpr) fpr_micro, tpr_micro, _ roc_curve(y_test_bin.ravel(), y_prob.ravel()) auc_micro auc(fpr_micro, tpr_micro) counts np.sum(y_test_bin, axis0) weights counts / counts.sum() auc_weighted np.sum([auc_dict[i] * weights[i] for i in range(n_classes)]) return fpr_dict, tpr_dict, auc_dict, all_fpr, mean_tpr, auc_macro, fpr_micro, tpr_micro, auc_micro, auc_weighted, classes def plot_multiclass_roc(fpr_dict, tpr_dict, auc_dict, all_fpr, mean_tpr, auc_macro, fpr_micro, tpr_micro, auc_micro, classes): plt.figure(figsize(10, 8)) colors cycle([#1f77b4, #ff7f0e, #2ca02c, #d62728, #9467bd]) for i, color in zip(range(len(classes)), colors): plt.plot(fpr_dict[i], tpr_dict[i], colorcolor, lw2, labelfClass {classes[i]} (AUC {auc_dict[i]:.3f})) plt.plot(all_fpr, mean_tpr, colornavy, lw2, linestyle:, labelfMacro-average (AUC {auc_macro:.3f})) plt.plot(fpr_micro, tpr_micro, colordeeppink, lw2, linestyle:, labelfMicro-average (AUC {auc_micro:.3f})) plt.plot([0, 1], [0, 1], colorgray, lw1, linestyle--) plt.xlabel(False Positive Rate, fontsize12) plt.ylabel(True Positive Rate, fontsize12) plt.title(Multi-class ROC, fontsize14) plt.legend(loclower right, fontsize10) plt.grid(alpha0.3) plt.tight_layout() plt.savefig(multiclass_roc.png, dpi300) plt.show() X_train, X_test, y_train, y_test prepare_multiclass_data() model train_multiclass_model(X_train, y_train) results evaluate_multiclass(model, X_test, y_test) plot_multiclass_roc(*results[:-1], results[-1])5.3 临床论文报告ROC结果的建议临床研究里报告ROC结果光给一个AUC值是不够的。我一般会按以下格式报告AUC值、95%置信区间、最佳截断值、该截断值下的灵敏度和特异度、以及约登指数。如果是多分类还会报告macro-average和weighted-average两个指标。表格里可以这样列指标二分类示例多分类示例AUC0.85Macro: 0.82, Weighted: 0.8495% CI0.81-0.89Macro: 0.78-0.86最佳截断值0.42不适用灵敏度0.80各类别分别报告特异度0.78各类别分别报告另外图的标题和图例要写清楚不要只写“ROC Curve”要写“ROC Curve for [具体模型名称] in [具体临床场景]”。图的坐标轴标签用英文全称不要用缩写因为有些期刊要求图能独立于正文被理解。最后原始数据和代码建议作为补充材料提交现在很多期刊都鼓励这样做能增加研究的可重复性。最后分享一个小技巧如果审稿人要求比较两个模型的ROC曲线可以用DeLong检验但sklearn没有内置。一个替代方案是画两条曲线在同一张图上然后用Bootstrap估计AUC差值的置信区间如果置信区间不包含0说明两个模型的AUC有显著差异。这个方法虽然不如DeLong检验严谨但在临床研究里被广泛接受实现起来也简单。我在实际项目里踩过最大的坑就是多分类的类别顺序问题有一次跑了一个三分类模型AUC算出来只有0.55排查了半天才发现是label_binarize的classes参数跟model.classes_顺序不一致导致概率列和标签列错位。从那以后我养成了一个习惯只要涉及多分类第一件事就是加断言检查类别顺序。这个习惯帮我省了很多调试时间也建议你这么做。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑