3. 训练集与测试集分布一致性:PSI(群体稳定性指标)计算与解读
在信用评分模型开发与监控中,群体稳定性指标(Population Stability Index, PSI)是衡量模型评分或特征分布在时间维度(如训练集 vs. 测试集、开发样本 vs. 近期样本)上是否发生显著偏移的核心工具。PSI 值越小,说明两个样本的分布越一致,模型在跨时间或跨样本上的泛化能力越有保障。
3.1 PSI 的计算原理与步骤
PSI 基于信息论中的 KL 散度(Kullback-Leibler divergence) 变体,通过将连续变量分箱后,比较每个箱内实际占比与预期占比的差异。标准计算步骤如下:
- 分箱(Binning):将特征或评分值划分为 k 个等宽或等频区间(通常 10~20 箱)。
- 计算实际占比(Actual%):测试集(或近期样本)中落在第 i 箱的样本数占总样本数的比例。
- 计算预期占比(Expected%):训练集(或基准样本)中落在第 i 箱的样本数占总样本数的比例。
- 计算每个箱的贡献值:
箱贡献 = (Actual% - Expected%) × ln(Actual% / Expected%) - 求和得到 PSI:
PSI = Σ(箱贡献)
⚠️ 注意事项: 当某个箱的 Expected% 或 Actual% 为 0 时,为避免对数计算无定义,通常对该箱占比做微小平滑处理(如加 0.0001),或将该箱与相邻箱合并。
3.2 PSI 的判定阈值与业务解读
业界通常采用以下经验阈值判断分布偏移的严重程度:
| PSI 范围 | 偏移程度 | 业务建议 |
|---|---|---|
| PSI < 0.10 | 无显著偏移 | 特征/评分分布稳定,模型可继续使用 |
| 0.10 ≤ PSI < 0.25 | 轻度偏移 | 需关注偏移原因,建议加强监控频率 |
| PSI ≥ 0.25 | 严重偏移 | 必须重新训练模型或进行特征工程调整 |
💡 解读技巧: PSI 仅反映分布形状的差异,不直接说明模型性能下降。当 PSI 偏高时,应结合特征重要性、业务逻辑和外部环境变化(如政策调整、客群迁移)综合判断是否需要干预。
3.3 PSI 计算与可视化流程
下图展示了从原始数据到 PSI 输出及决策的完整流程:
3.4 代码实现示例(Python)
以下代码展示了如何对单个特征计算 PSI,并输出每个箱的详细贡献:
import numpy as np
import pandas as pd
def calculate_psi(expected, actual, bins=10, epsilon=1e-6):
"""
计算群体稳定性指标 (PSI)
Parameters:
- expected: 训练集(基准)数据
- actual: 测试集(实际)数据
- bins: 分箱数量
- epsilon: 平滑因子,防止对数计算无定义
Returns:
- psi: 总体 PSI 值
- psi_table: 每个箱的详细贡献表
"""
# 基于合并数据确定分箱边界(等宽分箱)
combined = np.concatenate([expected, actual])
bin_edges = np.linspace(np.min(combined), np.max(combined), bins + 1)
# 计算每个箱的频数
exp_counts, _ = np.histogram(expected, bins=bin_edges)
act_counts, _ = np.histogram(actual, bins=bin_edges)
# 计算占比并加入平滑
exp_pct = exp_counts / len(expected) + epsilon
act_pct = act_counts / len(actual) + epsilon
# 归一化确保总和为 1
exp_pct = exp_pct / np.sum(exp_pct)
act_pct = act_pct / np.sum(act_pct)
# 计算每个箱的 PSI 贡献
contribution = (act_pct - exp_pct) * np.log(act_pct / exp_pct)
psi = np.sum(contribution)
# 构建详细表格
psi_table = pd.DataFrame({
'箱区间': [f'[{bin_edges[i]:.2f}, {bin_edges[i+1]:.2f})' for i in range(bins)],
'预期占比%': np.round(exp_pct * 100, 2),
'实际占比%': np.round(act_pct * 100, 2),
'箱贡献': np.round(contribution, 6)
})
return psi, psi_table
# 示例用法
np.random.seed(42)
train_score = np.random.normal(600, 100, 10000) # 训练集评分
test_score = np.random.normal(620, 110, 5000) # 测试集评分(轻微偏移)
psi_value, detail = calculate_psi(train_score, test_score, bins=10)
print(f"总体 PSI: {psi_value:.4f}")
print("\n各箱贡献明细:")
print(detail.head(5)) # 仅展示前 5 行
3.5 常见误区与避坑指南
- 分箱数量不当:箱数过少会掩盖局部偏移,箱数过多则导致稀疏箱使 PSI 不稳定。建议根据样本量选择 10~20 箱。
- 忽略零占比箱:直接使用原始占比可能导致对数计算报错,务必加入平滑处理或合并空箱。
- 过度依赖单一阈值:PSI 阈值 0.10/0.25 是经验值,对于高基数特征或极端分布,应结合业务上下文调整判断标准。
- 混淆 PSI 与模型性能:PSI 低不代表模型预测准确,仅说明分布稳定;反之,PSI 高也不一定导致模型失效,需验证 AUC/K-S 等性能指标。
🚨 关键提醒: 在模型上线后的持续监控中,建议对每个特征和最终评分按月/周计算 PSI,并设置预警机制。当 PSI 连续上升或单次超过 0.25 时,应立即触发模型复审流程。
3.6 PSI 与特征筛选的联动
在特征一致性校验中,PSI 常与以下指标配合使用:
| 指标 | 关注点 | 与 PSI 的互补关系 |
|---|---|---|
| IV(信息量) | 特征对目标变量的区分能力 | PSI 高但 IV 低的特征可考虑剔除 |
| 特征重要性 | 模型对特征的依赖程度 | 高重要性 + 高 PSI 的特征需优先排查 |
| 缺失率变化 | 数据质量稳定性 | 缺失率突变常伴随 PSI 升高 |
通过将 PSI 与上述指标结合,可以更精准地定位导致分布偏移的根因,为模型迭代提供数据驱动的决策依据。