问题与目标

提高阈值通常减少预测正类,精确率与召回率因此沿不同方向变化。
分类模型输出概率后,还需要阈值才能形成决策。默认 0.5 不一定符合漏报和误报代价。
完成标准:训练逻辑回归,读取正类概率,比较多个阈值下的精确率和召回率。
核心概念
逻辑回归先计算线性得分,再通过 Sigmoid 映射到 0~1。训练通常优化对数损失;系数表示特征对对数几率的影响,不应直接解释成概率增加量。
降低阈值通常提高召回率并增加误报,提高阈值通常提高精确率并增加漏报。阈值必须在验证集按业务代价选择,测试集只用于最终报告。
从线性得分到概率
线性得分可以是任意实数,Sigmoid 1/(1+exp(-z)) 将其压缩到 0~1。概率为 0.5 对应线性得分 0,因此默认边界仍是线性的。对数损失会重罚“非常自信但预测错误”的样本。
系数更适合解释为对数几率变化。对标准化后的特征,正系数使正类倾向增加,负系数使其降低;相关特征、正则化和类别编码都会影响系数,不能把它当因果效应。
import numpy as np
scores = np.array([-3.0, -1.0, 0.0, 1.0, 3.0])
probabilities = 1 / (1 + np.exp(-scores))
print(np.round(probabilities, 3))
输出展示得分与概率的非线性对应关系,极端得分附近概率变化会变缓。
阈值选择要写清代价
若漏掉高风险任务代价远高于误报,可以在验证集设置最低召回率,再从满足条件的阈值中选择精确率较高者。阈值不是模型超参数的一部分,但必须和模型版本一起保存。
可运行实现
import numpy as np
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score, recall_score
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
X, y = make_classification(
n_samples=1000, n_features=6, n_informative=4,
weights=[0.8, 0.2], class_sep=1.0, random_state=7,
)
X_train, X_valid, y_train, y_valid = train_test_split(
X, y, test_size=0.3, stratify=y, random_state=42,
)
model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
model.fit(X_train, y_train)
probability = model.predict_proba(X_valid)[:, 1]
for threshold in (0.3, 0.5, 0.7):
prediction = (probability >= threshold).astype(int)
print(
threshold,
"precision=", round(precision_score(y_valid, prediction, zero_division=0), 3),
"recall=", round(recall_score(y_valid, prediction), 3),
)
输入是带二分类标签的固定随机数据,输出是三个阈值下的精确率和召回率。选择阈值前应先定义哪种错误成本更高。
多分类与概率校准
逻辑回归可以通过多分类策略输出多个类别分数。无论二分类还是多分类,predict_proba 都不保证概率天然校准。可以把预测分成若干概率区间,比较每个区间的平均预测概率和真实发生率;偏差明显时,再在独立数据上使用校准方法。
类别权重会改变训练时对错误的惩罚,但不能替代阈值选择和真实指标评估。启用 class_weight="balanced" 后仍需检查概率校准。
把阈值实验整理成表
仅打印三行数字不容易追溯。将阈值、预警数、精确率、召回率和业务代价放在同一张表中:
import pandas as pd
rows = []
false_negative_cost = 8
false_positive_cost = 1
for threshold in np.arange(0.15, 0.86, 0.05):
prediction = (probability >= threshold).astype(int)
false_negative = int(((y_valid == 1) & (prediction == 0)).sum())
false_positive = int(((y_valid == 0) & (prediction == 1)).sum())
rows.append({
"threshold": round(float(threshold), 2),
"alerts": int(prediction.sum()),
"precision": precision_score(y_valid, prediction, zero_division=0),
"recall": recall_score(y_valid, prediction, zero_division=0),
"cost": false_negative * false_negative_cost + false_positive * false_positive_cost,
})
threshold_report = pd.DataFrame(rows)
print(threshold_report.sort_values("cost").head())
这个代价函数是显式的业务假设,不是算法自动推导的答案。当人力容量只能处理固定数量的预警时,还要把 alerts 加入约束。
检查概率是否可信
假设所有预测为 0.8 的样本中,真实正类只有约 50%,那么排序能力可能尚可,但“0.8”本身不可当作发生概率。校准曲线可用于诊断:
from sklearn.calibration import calibration_curve
observed, predicted = calibration_curve(
y_valid, probability, n_bins=8, strategy="quantile"
)
for mean_prediction, positive_rate in zip(predicted, observed):
print(round(mean_prediction, 3), round(positive_rate, 3))
校准也必须在训练流程内完成,不能用最终测试集同时拟合校准器和报告效果。
进一步验证
- 给出一个漏报代价和误报代价,计算并记录最佳阈值。
- 把漏报代价加倍,解释阈值为什么通常会向下移动。
- 保存模型时一并保存阈值、正类定义和选择日期。
常见问题与排查
- 把概率当作已校准置信度:用校准曲线验证,必要时校准。
- 在测试集选择阈值:会让最终指标偏乐观。
- 类别不平衡只看准确率:同时看召回、精确率、F1 和 PR 曲线。
- 特征尺度差异很大:将标准化放进 Pipeline。
小结
逻辑回归连接了线性得分、分类概率和决策阈值。模型训练与业务决策是两个层次,阈值应由错误代价决定。
License: CC BY-NC 4.0
Updated 2 hours ago
Was this article helpful? Give it a like.
0 comments


