3. ROC 曲线与 AUC:从绘制原理到多分类扩展
ROC 曲线(Receiver Operating Characteristic Curve)与 AUC(Area Under the Curve)是二分类模型评估中最具鲁棒性的工具之一。它们不依赖固定的分类阈值,能够全面反映模型在不同决策边界下的区分能力。本节从绘制原理出发,逐步深入到 AUC 的统计意义与多分类扩展。
3.1 ROC 曲线绘制原理
ROC 曲线的核心思想是:通过遍历所有可能的分类阈值,计算每个阈值下的 真正例率(TPR) 与 假正例率(FPR),并以 FPR 为横轴、TPR 为纵轴绘制曲线。
| 指标 | 公式 | 含义 |
|---|---|---|
| TPR(召回率) | TP / (TP + FN) | 所有真实正例中被正确预测的比例 |
| FPR(假正例率) | FP / (FP + TN) | 所有真实负例中被错误预测为正例的比例 |
| 阈值 | θ ∈ [0, 1] | 模型输出概率的决策边界 |
绘制步骤:
- 模型对每个样本输出一个概率值(或置信度分数)。
- 将所有样本按概率从高到低排序。
- 从最高概率开始,依次将每个概率值作为阈值 θ:
- 概率 ≥ θ 预测为正例,否则为负例。
- 计算当前阈值下的 TPR 与 FPR。
- 将 (FPR, TPR) 点对依次连接,得到 ROC 曲线。
关键理解: 理想模型在左上角(FPR=0, TPR=1)附近;随机模型的 ROC 曲线接近对角线(y=x);曲线越向左上角凸起,模型区分能力越强。
3.2 AUC 的统计意义
AUC(Area Under the ROC Curve)是 ROC 曲线下的面积,取值范围为 [0, 1]。其核心统计意义可以从三个角度理解:
| 解释角度 | 含义 | 数学表达 |
|---|---|---|
| 排序能力 | 随机抽取一个正例和一个负例,模型将正例排在负例前面的概率 | P(Pscore > Nscore) |
| 平均精度 | 对所有可能阈值下的 TPR 取平均(以 FPR 为权重) | ∫01 TPR(FPR) d(FPR) |
| Wilcoxon 统计量 | 等价于 Mann-Whitney U 统计量的标准化形式 | AUC = U / (np · nn) |
实用解读: AUC = 0.5 表示模型无区分能力(等同于随机猜测);AUC = 1.0 表示完美排序;AUC > 0.8 通常认为模型具有较好的区分能力。AUC 对类别不平衡不敏感,因此在正负样本比例悬殊的场景下比准确率更可靠。
3.3 多分类 ROC 扩展
当面对 K 类分类问题时,ROC 曲线需要从二分类扩展到多分类场景。常用的扩展策略有三种:
- OvR(One-vs-Rest)宏平均: 对每个类别分别计算 ROC 曲线与 AUC,然后取算术平均。每个类别被视为正类,其余所有类别合并为负类。
- OvR 微平均: 将所有类别的 TP、FP、FN 汇总后,统一计算 TPR 与 FPR,再绘制 ROC 曲线。微平均更关注大类别的表现。
- OvO(One-vs-One)平均: 对每对类别组合计算 AUC,然后取所有组合的平均值。适用于类别数较少且需要细粒度比较的场景。
| 方法 | 计算方式 | 适用场景 | 特点 |
|---|---|---|---|
| OvR 宏平均 | 每个类别 AUC 的算术平均 | 类别均衡、关注小类别 | 对小类别敏感,受类别不平衡影响较大 |
| OvR 微平均 | 全局汇总后计算 AUC | 类别严重不平衡 | 受大类别主导,小类别贡献被稀释 |
| OvO 平均 | 所有类别对的 AUC 平均 | 类别数少、需细粒度对比 | 计算复杂度 O(K²),类别多时开销大 |
实践注意: 在多分类场景中,如果类别数较多(如 K > 10),OvO 方法的计算复杂度会急剧上升(K×(K-1)/2 个二分类模型)。此时优先使用 OvR 宏平均或微平均。另外,多分类 AUC 的解读应结合各类别的样本量,避免被大类别主导的微平均结果误导。
3.4 代码实现:ROC 曲线与 AUC 计算
以下代码使用 scikit-learn 演示二分类与多分类场景下的 ROC 曲线绘制与 AUC 计算:
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, auc, roc_auc_score
from sklearn.preprocessing import label_binarize
# ========== 二分类示例 ==========
X, y = make_classification(n_samples=1000, n_features=10, 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()
model.fit(X_train, y_train)
y_prob = model.predict_proba(X_test)[:, 1]
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)
print(f"二分类 AUC: {roc_auc:.4f}")
# ========== 多分类示例(OvR 宏平均) ==========
X_multi, y_multi = make_classification(n_samples=1000, n_features=10, n_classes=3, n_informative=5, random_state=42)
X_train_m, X_test_m, y_train_m, y_test_m = train_test_split(X_multi, y_multi, test_size=0.3, random_state=42)
model_multi = LogisticRegression(multi_class='ovr')
model_multi.fit(X_train_m, y_train_m)
y_prob_multi = model_multi.predict_proba(X_test_m)
# 将标签二值化
y_test_bin = label_binarize(y_test_m, classes=[0, 1, 2])
n_classes = y_test_bin.shape[1]
# 计算每个类别的 ROC 与 AUC
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_multi[:, i])
roc_auc_dict[i] = auc(fpr_dict[i], tpr_dict[i])
# 宏平均 AUC
macro_auc = np.mean(list(roc_auc_dict.values()))
print(f"多分类 OvR 宏平均 AUC: {macro_auc:.4f}")
print(f"各类别 AUC: {roc_auc_dict}")
关键参数说明:
roc_curve(y_true, y_score):返回 fpr、tpr、thresholds 三个数组。auc(x, y):使用梯形法则计算曲线下面积。roc_auc_score(y_true, y_score, multi_class='ovr'):直接计算多分类 AUC,支持 'ovr' 与 'ovo' 参数。- 多分类时务必使用
label_binarize将标签转换为 one-hot 格式,或直接使用roc_auc_score的 multi_class 参数。