9-3 决策阈值与ROC-AUC评估

🎯 教学目标与重难点…

【三维目标】

  • 📚 知识目标
    • 理解决策阈值(Decision Threshold)对分类预测结果的影响机制。
    • 掌握决策阈值调整过程中 Precision 与 Recall 的反向权衡关系(Trade-off)。
    • 掌握 ROC 曲线的构成要素(FPR 与 TPR/Recall)以及 AUC 指标的物理意义。
  • ⚙️ 能力目标
    • 能够熟练使用 model.predict_proba() 提取预测概率值,并实现自定义阈值的逻辑截断。
    • 能够使用 sklearn.metrics 中的 roc_curveroc_auc_score 绘制 ROC 曲线并计算 AUC 分数。
    • 能够根据安全预警(防漏报)或极准干预(防误报)等实际业务需求,灵活选择并调整模型的分类策略。
  • 💡 素养目标
    • 建立“业务驱动算法”的工程视角,理解模型调优没有绝对优劣,只有是否契合实际场景需求。

【重点与难点】

  • 🟢 教学重点:决策阈值对 P/R 的调控逻辑、predict_proba() 代码实战、ROC 曲线两轴的含义与 AUC 评估标准。
  • 🟡 教学难点:理解 ROC 曲线在所有阈值下全局评估模型的物理意义,以及假阳性率(FPR)与真阳性率(TPR)的动态变化原理。

📌 一、 课程导入(10分钟)

在上节课《9-2 分类模型评估》中,我们回答了课后拓展思考题:在医疗诊断或风险预警等场景中,“漏诊(FN)”的后果远比“误诊(FP)”更严重,我们更追求高的 Recall(召回率)

抛出“固定门槛”矛盾: 逻辑回归模型输出的是样本属于正类的概率值 $P(Y=1\vert{}X)$。默认情况下,系统采用 $0.5$ 作为判定临界点(即概率 $\ge 0.5$ 判为正类)。

  • 思考:在宿管晚归预警系统中,一个学生晚上未归概率是 0.35,默认模型会判定为“正常(0)”。但在安全管理中,这已经存在很高风险,必须预警!
  • 问题:如果概率门槛固定死在 0.5,我们该如何控制模型,让它变“严”或者变“宽”?

核心结论: 我们需要通过手动调整 决策阈值(Decision Threshold) 来打破默认限制,使模型精准契合不同场景的业务需求。

  • 👨‍🏫 教师活动:在黑板上画出 0 到 1 的概率轴,标记默认 0.5 刻度,提问:“在安检防爆或风险预警时,概率达到多少就应该拦截?”
  • 🧑‍🎓 学生活动:思考并回答:应该把标准降低(如 0.2 或 0.3),宁可误报也不能漏过危险品。

通过业务场景的实际需求引发认知冲突,帮助学生理解:模型预测不是一成不变的,人为设定门槛(阈值)是控制模型偏向的关键手法。


📖 二、 解决问题过程(一):决策阈值调整与 ROC-AUC 全局评估(25分钟)

1. 决策阈值(Decision Threshold)的权衡

  • 降低阈值(如 $T = 0.3$)
    • 门槛放宽,更容易被判为正类 $\rightarrow$ Recall(查全率)提升 ↑,但 Precision(查准率)下降 ↓
    • 应用场景:宿管晚归预警、医疗重病筛查(宁可错杀/误报,绝不漏报)。
  • 提高阈值(如 $T = 0.7$)
    • 门槛变高,要求极有把握才判为正类 $\rightarrow$ Precision(查准率)提升 ↑,但 Recall(查全率)下降 ↓
    • 应用场景:垃圾短信拦截、升学风险高精准干预(追求极高准确度,避免误伤正常学生)。

2. ROC 曲线与 AUC 指标

单一阈值只能评估某个具体门槛下的表现。要评估模型在 所有可能阈值下的综合能力,需要使用 ROC 曲线AUC 得分

  • ROC 曲线的两大核心轴
    • 纵轴:TPR(True Positive Rate,真阳性率) $= \frac{\text{TP}}{\text{TP} + \text{FN}}$ (即 Recall,越接近 1 越好)。
    • 横轴:FPR(False Positive Rate,假阳性率) $= \frac{\text{FP}}{\text{FP} + \text{TN}}$ (负样本中被误报的比例,越接近 0 越好)。
  • AUC(Area Under Curve)
    • ROC 曲线下方的面积大小,数值范围在 $0.5 \sim 1.0$ 之间。
    • $\text{AUC} = 0.5$:等同于瞎猜(对角线);$\text{AUC} \ge 0.85$:优质模型,分类区分度极强。
  • 👨‍🏫 教师活动:画出以 FPR 为横轴、TPR 为纵轴的坐标系,对比展示两条不同的 ROC 曲线,解释“越靠近左上方(TPR 高且 FPR 低),模型性能越优”。
  • 🧑‍🎓 学生活动:对比不同模型的 AUC 面积大小,讨论并归纳为什么 AUC 能不受具体阈值选择的影响而客观评价模型。

将阈值调控的单点动态过程升华为全局评估视角(ROC-AUC),培养学生多维度评价算法性能的综合能力。


💻 三、 解决问题过程(二):自定义阈值截断与 ROC-AUC 代码实战(25分钟)

在 Python 中,通过 model.predict_proba() 获取概率数组,再配合布尔掩码快速实现自定义阈值拦截;使用 sklearn.metrics 绘制 ROC 曲线。

 1import numpy as np
 2import matplotlib.pyplot as plt
 3from sklearn.linear_model import LogisticRegression
 4from sklearn.metrics import classification_report, roc_curve, roc_auc_score
 5
 6# 1. 模拟学生夜不归宿风险预测数据集 [晚归次数, 异常考勤数]
 7X = np.array([
 8    [0, 1], [0, 2], [1, 0], [1, 1], [0, 3],  # 正常学生 (0)
 9    [2, 3], [3, 4], [4, 5], [2, 6], [5, 4]   # 风险学生 (1)
10])
11y = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])
12
13# 2. 训练逻辑回归模型
14model = LogisticRegression()
15model.fit(X, y)
16
17# 3. 核心 API:获取样本属于正类(1)的预测概率
18y_probs = model.predict_proba(X)[:, 1]
19
20# 4. 手动设置低阈值 T = 0.35 (防漏报策略)
21custom_threshold = 0.35
22y_pred_custom = (y_probs >= custom_threshold).astype(int)
23
24print(f"--- 🎯 阈值设定为 {custom_threshold} 下的评估报告 ---")
25print(classification_report(y, y_pred_custom, target_names=['正常(0)', '风险(1)']))
26
27# 5. 计算 ROC 曲线与 AUC 得分
28fpr, tpr, thresholds = roc_curve(y, y_probs)
29auc_score = roc_auc_score(y, y_probs)
30print(f"模型的全局 AUC 得分为: {auc_score:.4f}")
  • 👨‍🏫 教师活动:在 Jupyter Notebook 中演示 predict_proba(X)[:, 1] 的截取逻辑,引导学生观察将阈值改为 0.7 后 Console 输出中 Precision 与 Recall 的反向变化。
  • 🧑‍🎓 学生活动:同步敲击代码,修改 custom_threshold 参数值,验证阈值高低与评估指标之间的变动规律。

通过代码实战突破 predict_proba 概率拦截与类型转换语法,直观体会改参带来的指标变化,达到学以致用的目的。


✍️ 四、 解决问题过程(三):课堂练习与巩固(20分钟)

📝 任务一:宿舍晚归风险预警系统的阈值拦截实验…

背景与题目: 某宿管预警系统对 10 名学生的预测概率值 y_probs 与真实风险标签 y_true 如下:

  • 真实风险标签:y_true = [0, 0, 0, 0, 0, 0, 1, 1, 1, 1](0为正常,1为存在风险)
  • 模型预测正类概率:y_probs = [0.10, 0.15, 0.20, 0.32, 0.45, 0.12, 0.38, 0.65, 0.80, 0.90]

任务要求

  1. 如果采用默认阈值 $T = 0.5$,请问有几个风险学生被漏报(FN)?计算此时对风险类(标签1)的 Recall。
  2. 为了防止漏报,如果将阈值降低到 $T = 0.35$,请问 Recall 和 Precision 会发生什么变化?
🔍 查看参考代码与解析…
 1import numpy as np
 2from sklearn.metrics import classification_report
 3
 4y_true = np.array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1])
 5y_probs = np.array([0.10, 0.15, 0.20, 0.32, 0.45, 0.12, 0.38, 0.65, 0.80, 0.90])
 6
 7# 1. 默认阈值 T = 0.5
 8y_pred_default = (y_probs >= 0.5).astype(int)
 9print("=== 默认阈值 (T=0.5) ===")
10print(classification_report(y_true, y_pred_default, target_names=['正常', '风险']))
11# 解析:概率 0.38 的风险学生被判定为 0,发生了 1 次漏报(FN=1)。Recall = 3/4 = 75%
12
13# 2. 自定义低阈值 T = 0.35
14y_pred_low = (y_probs >= 0.35).astype(int)
15print("=== 降低阈值 (T=0.35) ===")
16print(classification_report(y_true, y_pred_low, target_names=['正常', '风险']))
17# 解析:概率 0.38 的学生被成功拦截,Recall 升至 100% (4/4);但概率 0.45 的正常学生被误报,Precision 降至 67% (4/6)。

📝 五、 课堂小结(5 分钟)

flowchart LR
    root["📊 9-3 阈值调优与 ROC-AUC"]

    subgraph C1["⚖️ 决策阈值 Threshold"]
        direction TB
        A1["降低阈值 (T < 0.5):提高 Recall (防漏报)"]
        A2["提高阈值 (T > 0.5):提高 Precision (防误报)"]
    end

    subgraph C2["📈 ROC-AUC 全局评估"]
        direction TB
        B1["纵轴 TPR:真阳性率 (Recall)"]
        B2["横轴 FPR:假阳性率 (误报率)"]
        B3["AUC 面积:越接近 1 模型性能越优"]
    end

    subgraph C3["💻 代码 API"]
        direction TB
        C1_node["predict_proba 函数取概率"]
        C2_node["(probs >= T).astype(int) 逻辑拦截"]
        C3_node["roc_curve / roc_auc_score 函数"]
    end

    root --> C1
    root --> C2
    root --> C3

    style root fill:#4b6cb7,stroke:#253b6e,color:#fff,stroke-width:2px,rx:8px,ry:8px
    style C1 fill:#e3f2fd,stroke:#2196f3,stroke-width:1px
    style C2 fill:#fff3e0,stroke:#ff9800,stroke-width:1px
    style C3 fill:#e8f5e9,stroke:#4caf50,stroke-width:1px

✏️ 随堂检测与互动练习

点击展开:随堂测试题(带解析)

一、 单选题

  1. 在某医疗 AI 辅助诊断系统中,医生希望尽量减少漏诊(即不能把有病的人误诊为无病),在调节逻辑回归模型的决策阈值(Threshold)时,应该如何操作?
  • A. 将阈值从 0.5 提高到 0.8
  • B. 将阈值从 0.5 降低到 0.3
  • C. 保持 0.5 阈值不变,增加训练迭代次数
  • D. 随机调整阈值
【答案】

【解析】B。降低阈值会放宽正类的判定条件,使模型更容易把样本预测为正类,从而提高查全率(Recall),减少漏诊。

  1. 关于 ROC 曲线与 AUC 指标,下列说法错误的是?
  • A. ROC 曲线的纵坐标是真阳性率(TPR),横坐标是假阳性率(FPR)
  • B. AUC 的取值范围一般在 0.5 到 1 之间
  • C. 一个随机猜想模型的 AUC 指标通常接近 0.5
  • D. 阈值设定得越高,AUC 的数值就会变得越大
【答案】

【解析】D。AUC 是 ROC 曲线下的面积,是对模型在所有可能阈值下的全局评估,不会因为你单方面选定了某个特定的阈值而改变。

二、 代码填空题

题目:假设你正在开发一套“学生退学风险预警系统”,需要设定阈值为 0.35 进行风险拦截,请补充代码:

1# 1. 提取测试集预测为正类的概率
2y_probs = _________________________________
3
4# 2. 设定阈值 0.35 并生成 0/1 预测结果
5threshold = 0.35
6y_pred_custom = ___________________________
【答案】
  1. model.predict_proba(X_test)[:, 1]
  2. (y_probs >= threshold).astype(int)

📮 六、 课后作业与拓展

📮 课后作业…

1. 基础巩固题(共 5 题,含完整测试数据集)

【题目 1:AI 辅助医疗诊断系统的阈值放宽测试】

  • 背景:某肿瘤检测 AI 模型输出 8 名受检者的患病概率及实际患病情况如下:
1y_true1 = [0, 0, 0, 0, 1, 1, 1, 1]  # 0:健康, 1:患病
2y_probs1 = [0.05, 0.12, 0.28, 0.40, 0.35, 0.60, 0.75, 0.88]
  • 任务:请写出代码,比较在默认阈值 0.5 和自定义低阈值 0.30 下的 Recall(召回率),说明降低阈值是否成功解决了漏诊问题。

【题目 2:垃圾短信拦截系统的阈值提高测试】

  • 背景:拦截系统对 10 条短信的预测概率(标签 1 为垃圾短信)如下:
1y_true2 = [0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
2y_probs2 = [0.05, 0.10, 0.15, 0.20, 0.55, 0.62, 0.70, 0.80, 0.85, 0.95]
  • 任务:为了防止将“重要通知(正常短信 0)”误拦截,请将阈值设为 0.65。编写代码输出分类报告,观察 Precision(精确率) 的变化。

【题目 3:网络防火墙异常流量判定】

  • 背景:防火墙检测到 8 个数据包的攻击概率值如下:
1y_true3 = [0, 0, 0, 0, 1, 1, 1, 1]
2y_probs3 = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
  • 任务:写出代码计算该模型在不同阈值下的 roc_auc_score 得分,并说明该模型的 AUC 为什么表现良好。

【题目 4:银行信用卡欺诈交易预警截断】

  • 背景:风控系统预测了 8 笔交易的欺诈概率:
1y_true4 = [0, 0, 0, 0, 0, 1, 1, 1]
2y_probs4 = [0.02, 0.05, 0.18, 0.25, 0.42, 0.30, 0.78, 0.92]
  • 任务:如果银行规定“只要欺诈概率达到 0.25 必须短信提醒客户”,请用代码生成对应的预测标签数组 y_pred,并打印混淆矩阵。

【题目 5:算法逻辑分析与概率切片】

  • 背景:已知模型预测代码如下:
1probs = model.predict_proba(X_test)
2y_pred = (probs[:, 1] >= 0.4).astype(int)
  • 任务
  1. 解释代码中 probs[:, 1] 的含义是什么?为什么要取第 1 列而不是第 0 列?
  2. 语句 (probs[:, 1] >= 0.4).astype(int) 执行了什么逻辑操作?最终 y_pred 的数据类型是什么?

2. 拓展思考题

  • 如果一个数据集正负样本极度不平衡(如欺诈交易仅占 0.1%),ROC-AUC 的得分可能依然高达 0.95 以上,显得非常理想。请查阅资料,思考此时为什么 PR 曲线(Precision-Recall Curve) 会比 ROC 曲线更能反映模型的真实拦截能力?

📋 七、 板书设计

🛠️ 板书设计…
 19-3 决策阈值调优与 ROC-AUC 全局评估
 2
 3一、决策阈值 (Threshold)                三、ROC 曲线与 AUC 指标
 4  · 默认刻度 : T = 0.5                    1. 纵轴 (TPR) = TP / (TP + FN)  [即 Recall]
 5  · 降低阈值 (T < 0.5) -> Recall ↑        2. 横轴 (FPR) = FP / (FP + TN)  [误报率]
 6  · 提高阈值 (T > 0.5) -> Precision ↑     3. AUC 面积   : 范围 0.5 ~ 1.0 (越大越优)
 7
 8二、核心代码 API                         四、场景应用原则
 9  1. probs = model.predict_proba(X)[:,1]  · 宁可错杀,不使漏网 -> 低阈值 (如安检/重病)
10  2. y_pred = (probs >= T).astype(int)    · 宁缺毋滥,避免误伤 -> 高阈值 (如拦截/干预)
11  3. roc_auc_score(y_true, probs)