4. 拒绝推断入门:拒绝推断的必要性、硬截断与软截断、简单插补法
4.1 为什么需要拒绝推断?
在信贷风控建模中,我们通常只能观察到“通过审批”的样本(即放款客户)的贷后表现(好坏标签)。而大量“被拒绝”的申请样本,我们无法获取其真实表现。这种样本选择偏差会导致模型在训练时只学习了通过人群的规律,而在实际全量申请人群上泛化能力下降。
核心问题: 模型训练集 = 通过样本(有偏子集),实际应用集 = 全部申请样本(包含拒绝样本)。模型对拒绝样本的预测往往是盲区,容易低估坏账风险。
拒绝推断(Reject Inference)正是为了解决这一偏差,通过技术手段为被拒绝样本“推断”或“插补”一个表现标签,从而修正模型,使其更贴近真实申请总体的风险分布。
4.2 硬截断与软截断
在介绍具体插补方法前,必须先理解两种常见的拒绝策略,它们决定了后续推断的难度与方式。
| 策略类型 | 定义 | 对建模的影响 | 典型场景 |
|---|---|---|---|
| 硬截断 | 审批规则或旧模型给出一个固定阈值,低于该阈值一刀切拒绝,且拒绝后不再有任何后续信息。 | 拒绝样本完全缺失标签,且与通过样本在风险分布上存在明显断层,推断难度大。 | 早期规则卡、简单评分卡(如:信用分 < 600 直接拒绝)。 |
| 软截断 | 审批过程并非单一阈值,而是结合人工审核、多模型投票、梯度放款等,部分低分样本仍有机会通过。 | 通过样本与拒绝样本的风险分布存在重叠区域,为推断提供了“桥梁”,推断结果更可靠。 | 复杂策略引擎、AI模型辅助审批、人工兜底审核。 |
实战建议: 如果业务场景是硬截断,建议优先尝试简单插补法或两阶段模型;如果是软截断,可以考虑更复杂的扩展法或基于拒绝样本的再训练。
4.3 简单插补法详解
简单插补法是最基础、最易实现的拒绝推断方法,核心思想是:利用已有通过样本的模型,为拒绝样本“预测”一个标签,然后将这些“伪标签”样本加入训练集重新建模。
4.3.1 操作步骤
- 步骤一: 使用通过样本(已知好坏标签)训练一个初始模型 M0。
- 步骤二: 用 M0 对全部拒绝样本进行预测,得到每个拒绝样本的违约概率 p。
- 步骤三: 设定一个概率阈值(通常为 0.5),将 p > 0.5 的拒绝样本标记为“坏样本”,p ≤ 0.5 的标记为“好样本”。
- 步骤四: 将插补标签后的拒绝样本与原始通过样本合并,形成新的训练集。
- 步骤五: 使用新训练集重新训练最终模型 M1。
4.3.2 优缺点分析
| 维度 | 说明 |
|---|---|
| 优点 | 实现简单,计算成本低,可快速验证拒绝推断效果;适用于硬截断场景的初步探索。 |
| 缺点 | 插补标签完全依赖初始模型 M0,而 M0 本身是在有偏样本上训练的,导致插补标签存在系统性偏差;容易强化模型原有偏见,无法真正修正分布。 |
| 适用场景 | 拒绝样本量较大、业务初期快速迭代、作为其他复杂方法的基线对比。 |
避坑提醒: 简单插补法容易造成“自我实现预言”——模型预测拒绝样本为坏,插补为坏,再用这些数据训练,模型更加认为拒绝样本是坏。建议在插补后,对拒绝样本的标签进行随机扰动或加权,以缓解偏差。
4.4 拒绝推断流程总览
下图展示了从原始有偏样本到最终修正模型的完整流程,其中简单插补法位于流程的“标签插补”环节。
4.5 简单插补法的代码示例
以下 Python 代码展示了使用逻辑回归进行简单插补的完整流程:
import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
# 1. 模拟数据:通过样本(有标签)和拒绝样本(无标签)
passed = pd.DataFrame({
'feature1': np.random.randn(1000),
'feature2': np.random.randn(1000),
'label': np.random.choice([0, 1], size=1000, p=[0.8, 0.2])
})
rejected = pd.DataFrame({
'feature1': np.random.randn(500),
'feature2': np.random.randn(500)
})
# 2. 训练初始模型 M0
X_train = passed[['feature1', 'feature2']]
y_train = passed['label']
M0 = LogisticRegression()
M0.fit(X_train, y_train)
# 3. 对拒绝样本预测概率
probs = M0.predict_proba(rejected[['feature1', 'feature2']])[:, 1]
# 4. 简单插补:阈值 0.5
threshold = 0.5
rejected['label_imputed'] = (probs > threshold).astype(int)
# 5. 合并样本
combined = pd.concat([
passed[['feature1', 'feature2', 'label']].rename(columns={'label': 'label_imputed'}),
rejected[['feature1', 'feature2', 'label_imputed']]
], ignore_index=True)
# 6. 训练最终模型 M1
X_combined = combined[['feature1', 'feature2']]
y_combined = combined['label_imputed']
M1 = LogisticRegression()
M1.fit(X_combined, y_combined)
print("初始模型 M0 系数:", M0.coef_)
print("最终模型 M1 系数:", M1.coef_)
扩展思考: 简单插补法虽然快速,但偏差明显。在实际项目中,建议将其作为基线,后续尝试加权插补法(给拒绝样本赋予较低权重)或两阶段模型(Heckman 模型)来进一步修正偏差。