分类模型阈值怎么选?用精确率召回率曲线核对取舍

用验证集 PR 曲线在召回约束下选择分类阈值,再用独立测试集核对误报、漏报和复核量。给出完整代码及一次未达到目标的真实教学运行结果。

分类阈值决定模型输出多大的正类概率时采取行动。把阈值降低,通常能找回更多正类,也会带来更多误报;阈值升高则可能减少复核量,同时漏掉更多正类。选阈值应先确定业务约束,再在验证集比较精确率、召回率与复核比例,最后用独立测试集验收。

下面用人工合成的 0/1 二分类数据做完整演示:1 表示“需要复核”,0 表示“无需复核”。我们假设希望召回率至少 80%,这个值只是教学目标,不能直接用于医疗、工业放行或其他实际业务。

分类模型阈值怎么选?用精确率召回率曲线核对取舍

阈值、精确率和召回率分别回答什么

  • 阈值:分数达到多少就判为正类。本例采用 score >= threshold。
  • 精确率 Precision:报为正类的样本中,真正的正类占多少,即 TP/(TP+FP)。
  • 召回率 Recall:所有真实正类中,找出了多少,即 TP/(TP+FN)。
  • 复核比例:全部样本中,有多少会触发复核;它与人工处理容量直接相关。

这些定义与数组行为依据 precision_recall_curve 官方 API。这里需要的是正类概率,不是已经取成 0/1 的预测标签;也不能默认 predict_proba 的第二列永远是你的业务正类,要先核对 classes_。

如果你只有成对的预测标签与真值,先看站内 准确率与 F1 计算教程;工业质检还要明确误检、漏检与现场成本,可看 工业视觉质检评估。本文进一步解决的是“拿到概率后怎样选判定阈值”。

训练、选阈值、验收分开做

本例用 1200 条训练、400 条验证、400 条测试数据。模型只在训练集拟合;阈值只在验证集选定;测试集在阈值冻结后才使用。官方分类阈值说明提醒,训练模型与调阈值使用同一批数据会增加过拟合风险。

本文代码在 Windows、Python 3.11.15、scikit-learn 1.9.1、pandas 3.0.6、NumPy 2.4.6、Matplotlib 3.11.2 下运行核验。建立并激活独立 Python 环境后安装:

python -m venv .venv
# Windows PowerShell
.\.venv\Scripts\Activate.ps1
python -m pip install "scikit-learn==1.9.1" "pandas==3.0.6" "numpy==2.4.6" "matplotlib==3.11.2"

保存为 choose_threshold.py 并运行

在满足验证集召回率至少 80% 的候选阈值中,代码先选精确率更高的;精确率相同则选复核比例更低的,再按阈值确定顺序。这是一个可复核的教学选择规则,真实项目还应加上最大复核量、漏报成本和不同人群的限制。

from pathlib import Path
import json
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_recall_curve, precision_score, recall_score, confusion_matrix

# 教学用合成数据:1表示需要复核,0表示无需复核。
X, y = make_classification(n_samples=2000, n_features=10, n_informative=5,
                            weights=[0.9, 0.1], flip_y=0.01, random_state=42)
X_fit, X_test, y_fit, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42)
X_train, X_val, y_train, y_val = train_test_split(
    X_fit, y_fit, test_size=0.25, stratify=y_fit, random_state=42)
model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
model.fit(X_train, y_train)
positive = int(np.flatnonzero(model.classes_ == 1)[0])
val_score = model.predict_proba(X_val)[:, positive]
precision, recall, thresholds = precision_recall_curve(y_val, val_score, pos_label=1)
# 最后一个PR点没有对应阈值,必须移除才能逐行配对。
table = pd.DataFrame({"threshold": thresholds, "precision": precision[:-1],
                      "recall": recall[:-1],
                      "alert_rate": [(val_score >= t).mean() for t in thresholds]})
min_recall = 0.80  # 教学约束,实际值需由业务决定。
candidates = table[table.recall >= min_recall]
if candidates.empty:
    raise RuntimeError("验证集没有满足召回约束的阈值,不应强行上线")
chosen = candidates.sort_values(
    ["precision", "alert_rate", "threshold"], ascending=[False, True, False]).iloc[0]
threshold = float(chosen.threshold)
table.to_csv("thresholds.csv", index=False)
print("validation choice:", chosen.to_dict())
plt.plot(recall, precision, label="validation PR")
plt.scatter([chosen.recall], [chosen.precision], label=f"chosen t={threshold:.3f}")
plt.xlabel("Recall")
plt.ylabel("Precision")
plt.legend()
plt.tight_layout()
plt.savefig("pr_curve.png", dpi=160)

# 阈值冻结后才看测试集;失败后不要继续在测试集上调参。
test_score = model.predict_proba(X_test)[:, positive]
results = []
for name, t in [("default", 0.5), ("selected", threshold)]:
    pred = (test_score >= t).astype(int)
    tn, fp, fn, tp = confusion_matrix(y_test, pred, labels=[0, 1]).ravel()
    row = {"name": name, "threshold": t, "precision": precision_score(y_test, pred, zero_division=0),
           "recall": recall_score(y_test, pred, zero_division=0),
           "alert_rate": float(pred.mean()), "tn": int(tn), "fp": int(fp),
           "fn": int(fn), "tp": int(tp)}
    results.append(row)
    print(json.dumps(row))
Path("threshold_result.json").write_text(json.dumps(
    {"validation": chosen.to_dict(), "test": results}, indent=2), encoding="utf-8")
python choose_threshold.py

成功后得到 thresholds.csv、pr_curve.png 和 threshold_result.json。CSV 的每一行都对应一个真实候选阈值;图显示验证集 PR 曲线和最终选中的点。

留意 PR 曲线的数组长度

precision 和 recall 比 thresholds 多一个元素,最后的点是 precision=1、recall=0,没有对应阈值。代码用 precision[:-1]、recall[:-1] 与 thresholds 配对。若把最后的 precision=1 误当成一个可用阈值,容易选出“看起来完美”却没有实际决策含义的点。

阈值变动不修改模型已经输出的概率,也不提高模型识别能力;它改变的是行动规则。召回率在固定数据上随阈值升高不会上升,但精确率可能局部波动,所以应看实际曲线和计数,不要机械假设每一步都单调。

本次运行结果:验证目标没有在测试集兑现

下面是上述代码在合成教学数据上的实际输出,并非真实业务成绩。验证集选出的阈值约为 0.1292,精确率约 30.8%,召回率约 80.5%,复核比例为 26.75%。

独立测试集策略 TP FP FN Precision Recall 复核比例
阈值 0.5 0 2 41 0% 0% 0.5%
验证集选定的 0.1292 26 75 15 25.7% 63.4% 25.25%

降低阈值找回了部分正类,但 101 条需要复核的记录里只有 26 条是真正正类,且仍漏掉 15 条。因此独立测试集召回率没有达到 80% 目标,这个模型与阈值组合不能据此通过验收。只说“召回提高了”会掩盖误报负担和未达目标的事实。

默认策略没有真正例,precision_score 通过 zero_division=0 返回 0,避免无正例命中时产生歧义。混淆矩阵的顺序是 TN、FP、FN、TP;比较时先确认标签 1 的业务含义,不能把 TN 当 TP。

失败后怎样继续

  1. 先查看漏报与误报样本,检查标签质量、特征是否能区分目标,以及正类是不是混合了不同问题。
  2. 对数据划分、特征、模型或概率校准做有依据的修改,重新使用训练与验证流程。测试集上的失败结果用于记录风险,不用于反复寻找一个更漂亮的阈值。
  3. 如果已经根据测试结果改动模型或规则,后续最终验收应使用新的、未参与改动决策的数据,或预先设计的嵌套交叉验证。
  4. 同时检查分群表现、正类样本数、复核容量和时间稳定性。验证集 80.5% 只是有限样本上的观测值,不是对未来召回率的保证。

如果候选阈值不满足约束,先报告“无合格候选”。不要放宽业务条件而不说明,或只调低阈值让所有记录都进入人工队列。类别比例变化、采集口径改变和模型升级后,也要重新核验同一个阈值。

把方案换成自己的预测结果

保留样本 ID、真实标签、正类分数、数据集角色和模型版本,替换代码中的合成数据。生产时间序列宜按时间划分;同一用户或设备的相关记录需要按组隔离,不能为获得高指标随意随机跨集。

本例实测覆盖合成数据训练、验证集选阈值、图表生成与独立测试计算;未在真实风控、医疗或产线数据验证。示例的 80% 目标与人工复核语义都是教学假设,正式阈值应由实际成本、容量和适用要求决定。文档核验日期:2026-10-01。

Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/30472.html

赞 (0)
AI小管家的头像AI小管家
PyTorch inference_mode 和 no_grad 怎么选?检查梯度与推理张量
上一篇 1天前
用智谱 AI 写需求变更说明:从事实卡生成影响分析和待确认项
下一篇 1天前

相关推荐

联系我们

联系我们

1

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
关注微信
分享本页
返回顶部