4. 拒绝推断入门:为什么需要拒绝推断、拒绝样本的偏差来源、简单拒绝推断方法(硬截断法)

4.1 为什么需要拒绝推断

在信贷额度模型中,我们通常只能观察到“通过审批”的客户表现(好坏标签),而“被拒绝”的客户则没有后续表现数据。这种样本选择偏差会导致模型在训练时只学习了通过人群的规律,却无法对拒绝人群进行有效预测。拒绝推断(Reject Inference)正是为了解决这一问题而设计的。

核心痛点: 如果直接忽略拒绝样本,模型会高估通过客户的信用水平,导致额度分配过于激进,最终增加坏账风险。

拒绝推断的核心目标:

  • 纠正偏差: 将拒绝样本的潜在表现纳入模型训练,使模型更接近真实客群分布。
  • 提升泛化: 避免模型在拒绝人群上“盲猜”,提高额度策略的稳健性。
  • 合规要求: 监管机构(如巴塞尔协议)要求银行在模型开发中考虑拒绝推断,确保公平性和准确性。

4.2 拒绝样本的偏差来源

拒绝样本的偏差主要来源于样本选择机制。下图展示了偏差产生的完整链路:

拒绝样本偏差来源链路 全量申请客群 (未知真实表现) 审批通过客群 (有表现标签) 拒绝客群 (无表现标签) 审批通过 审批拒绝 偏差来源分析 • 选择性偏差:审批策略本身(如评分卡阈值)导致拒绝人群与通过人群分布不同。 • 截断偏差:模型只观察到阈值以上的样本,阈值以下的样本完全缺失。 选择性偏差 审批策略筛选导致 截断偏差 阈值以下数据缺失 标签缺失偏差 拒绝样本无表现标签
关键认知: 拒绝样本的偏差不是随机缺失,而是与审批策略强相关的非随机缺失。如果审批策略本身有偏(例如过度依赖收入),那么拒绝人群的信用分布就会与通过人群显著不同。

4.3 简单拒绝推断方法:硬截断法

硬截断法(Hard Cutoff Method)是最直观的拒绝推断方法,适用于审批策略已经稳定、拒绝率较低的场景。其核心思路是:用通过样本训练的模型,直接对拒绝样本进行预测,然后将预测结果作为“伪标签”加入训练集。

4.3.1 硬截断法操作步骤

  1. 步骤1: 使用通过样本(有标签)训练初始模型 M₀。
  2. 步骤2: 用 M₀ 对拒绝样本进行预测,得到每个拒绝样本的违约概率 p̂。
  3. 步骤3: 设定一个截断阈值(如 p̂ > 0.5 视为坏样本,否则为好样本),为拒绝样本赋予伪标签。
  4. 步骤4: 将带伪标签的拒绝样本与原始通过样本合并,形成新训练集。
  5. 步骤5: 用新训练集重新训练模型 M₁,作为最终模型。

4.3.2 硬截断法的优缺点对比

维度 优点 缺点
实现难度 逻辑简单,代码量少,易于快速落地 对阈值敏感,需要人工调参
数据利用 充分利用了拒绝样本的特征信息 伪标签质量依赖初始模型,可能放大偏差
适用场景 拒绝率较低(<20%)、审批策略稳定 拒绝率较高时,伪标签噪声过大
模型风险 能一定程度缓解样本选择偏差 可能引入“自我实现预言”偏差
⚠️ 硬截断法的陷阱: 如果初始模型 M₀ 本身存在偏差(例如对某些群体预测不准),那么用 M₀ 生成的伪标签会强化这种偏差,导致最终模型 M₁ 在拒绝人群上表现更差。建议在步骤3中引入置信度筛选,只保留预测概率接近0或1的样本。

4.3.3 硬截断法流程示意图

硬截断法流程 通过样本(有标签) 拒绝样本(无标签) 训练初始模型 M₀ 预测伪标签 合并训练集

4.3.4 硬截断法代码示例

import pandas as pd
import numpy as np
from sklearn.linear_model import LogisticRegression

# 假设已有通过样本 approved_df 和拒绝样本 rejected_df
# approved_df 包含特征 X 和标签 y (0=好, 1=坏)
# rejected_df 只包含特征 X

# 步骤1: 训练初始模型
model_0 = LogisticRegression()
model_0.fit(approved_df[['feature1', 'feature2', 'feature3']], approved_df['y'])

# 步骤2: 预测拒绝样本的违约概率
rejected_proba = model_0.predict_proba(rejected_df[['feature1', 'feature2', 'feature3']])[:, 1]

# 步骤3: 设定阈值,生成伪标签(阈值=0.5)
threshold = 0.5
rejected_df['pseudo_y'] = (rejected_proba > threshold).astype(int)

# 步骤4: 合并数据集
combined_df = pd.concat([
    approved_df[['feature1', 'feature2', 'feature3', 'y']],
    rejected_df[['feature1', 'feature2', 'feature3', 'pseudo_y']].rename(columns={'pseudo_y': 'y'})
], ignore_index=True)

# 步骤5: 重新训练最终模型
model_1 = LogisticRegression()
model_1.fit(combined_df[['feature1', 'feature2', 'feature3']], combined_df['y'])

print("硬截断法完成,最终模型已训练。")
实践建议: 硬截断法虽然简单,但在实际项目中常作为基线方法使用。如果拒绝率较高(>30%),建议升级为模糊展开法或两阶段模型,以获得更稳健的推断结果。