4. 样本选择偏差:仅对“已授信用户”建模的偏差,如何通过拒绝推断(Reject Inference)修正切分
在信用额度模型开发中,最隐蔽也最危险的陷阱之一就是样本选择偏差。当我们仅使用“已授信用户”(即历史审批通过的客户)的数据来训练模型时,模型学到的规律天然带有幸存者偏差——它从未见过那些被拒绝的“坏客户”长什么样,导致模型在真实全量客群上的表现严重失真。
4.1 偏差的本质:缺失的“拒绝样本”
假设我们有一个完整的申请样本池,但只有通过审批的客户才有后续的还款表现(Y标签)。被拒绝的客户既没有额度,也没有还款记录。如果直接用通过样本训练模型,相当于用“筛选后的数据”预测“全量客群”,必然高估整体信用水平。
核心问题:训练集与目标分布不一致。模型只见过“好人”,却要在全量客群(含“坏人”)上做预测,导致额度分配过于激进,坏账率飙升。
下表总结了仅用通过样本与使用拒绝推断后的关键差异:
| 对比维度 | 仅用通过样本 | 使用拒绝推断 |
|---|---|---|
| 训练集分布 | 偏向高信用群体 | 接近真实申请分布 |
| 模型泛化能力 | 差,对低信用客群失效 | 强,覆盖全客群 |
| 额度分配结果 | 过度授信,风险低估 | 风险定价更合理 |
| 坏账率控制 | 实际坏账远高于预期 | 预期与实际情况更吻合 |
4.2 拒绝推断的核心逻辑
拒绝推断(Reject Inference)的核心思想是:为被拒绝样本“推断”出一个合理的Y标签,然后将它们补充进训练集,使训练分布更接近真实申请分布。常见的推断方法包括硬截断法、模糊扩展法和两阶段模型法。
关键前提:拒绝推断的有效性依赖于“拒绝机制”是已知且稳定的。如果审批策略频繁变动,推断结果会引入额外噪声。
下图展示了拒绝推断如何修正训练集切分:
4.3 三种主流拒绝推断方法对比
不同的拒绝推断方法在实现复杂度、偏差修正效果和稳定性上各有优劣。下表对比了三种最常用的方法:
| 方法 | 核心思路 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 硬截断法 | 用通过样本训练模型,预测拒绝样本的违约概率,按阈值打标签 | 实现简单,计算快 | 引入模型偏差,可能放大误判 | 拒绝率较低,模型区分度好 |
| 模糊扩展法 | 将拒绝样本按预测概率加权加入训练集,不硬性打标签 | 保留不确定性,减少偏差 | 权重设定敏感,调参复杂 | 拒绝率中等,需要稳健估计 |
| 两阶段模型法 | 第一阶段预测“通过概率”,第二阶段结合通过概率修正Y标签 | 理论上偏差最小,可解释性强 | 实现复杂,需要强假设 | 拒绝率高,数据量大 |
4.4 实践中的切分策略与避坑
在训练集切分时引入拒绝推断,需要遵循以下步骤:
- 收集历史审批数据:包含所有申请样本的特征、审批结果(通过/拒绝)以及通过样本的还款表现。
- 分析拒绝机制:确认审批策略是否稳定,是否存在人为规则变动。如果策略频繁调整,需要分段处理。
- 选择推断方法:根据拒绝率和数据量选择硬截断、模糊扩展或两阶段模型。
- 生成推断标签:为拒绝样本赋予概率或类别标签,并记录推断置信度。
- 构建修正训练集:将通过样本(真实Y)与拒绝样本(推断Y)合并,按比例或权重切分训练/验证/测试集。
- 验证修正效果:对比修正前后模型在通过样本上的预测分布,确认偏差是否缩小。
常见避坑:
- 不要将拒绝推断后的样本直接用于测试集——测试集应保持真实分布,仅用通过样本的真实Y评估。
- 不要忽略拒绝样本的权重——推断标签的不确定性应通过样本权重体现,否则会高估模型置信度。
- 不要在拒绝率极高(>80%)时依赖硬截断法——此时推断误差会主导训练集,建议使用两阶段模型。
4.5 拒绝推断对切分比例的影响
引入拒绝样本后,训练集的样本量和分布都会发生变化。下图展示了修正前后训练集切分比例的典型变化:
4.6 代码示例:使用硬截断法进行拒绝推断
以下Python代码展示了硬截断法的基本实现流程:
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
# 1. 加载数据:通过样本(有Y)和拒绝样本(无Y)
approved = pd.read_csv('approved_data.csv') # 包含特征 + y_label
rejected = pd.read_csv('rejected_data.csv') # 仅包含特征
# 2. 在通过样本上训练初始模型
X_approved = approved.drop('y_label', axis=1)
y_approved = approved['y_label']
model = LogisticRegression()
model.fit(X_approved, y_approved)
# 3. 预测拒绝样本的违约概率
rejected_proba = model.predict_proba(rejected)[:, 1]
# 4. 硬截断:按阈值打标签(例如概率>0.5标记为违约)
threshold = 0.5
rejected['y_inferred'] = (rejected_proba > threshold).astype(int)
# 5. 合并训练集
X_rejected = rejected.drop('y_inferred', axis=1)
y_rejected = rejected['y_inferred']
X_combined = pd.concat([X_approved, X_rejected], axis=0)
y_combined = pd.concat([y_approved, y_rejected], axis=0)
# 6. 切分训练/验证/测试集(测试集仅用通过样本)
X_train, X_temp, y_train, y_temp = train_test_split(
X_combined, y_combined, test_size=0.3, random_state=42)
X_val, X_test, y_val, y_test = train_test_split(
X_temp, y_temp, test_size=0.5, random_state=42)
# 注意:实际生产环境中,测试集应只包含通过样本的真实Y
# 此处为演示简化,正式使用时需单独切分通过样本作为测试集
实践建议:硬截断法虽然简单,但会引入“自我实现预言”的偏差——模型预测为好的样本被标记为好,进一步强化模型偏好。建议在拒绝率低于30%时使用,并配合交叉验证评估推断稳定性。