3. 训练集与测试集分布一致性:PSI(群体稳定性指标)计算与解读

在信用评分模型开发与监控中,群体稳定性指标(Population Stability Index, PSI)是衡量模型评分或特征分布在时间维度(如训练集 vs. 测试集、开发样本 vs. 近期样本)上是否发生显著偏移的核心工具。PSI 值越小,说明两个样本的分布越一致,模型在跨时间或跨样本上的泛化能力越有保障。

3.1 PSI 的计算原理与步骤

PSI 基于信息论中的 KL 散度(Kullback-Leibler divergence) 变体,通过将连续变量分箱后,比较每个箱内实际占比与预期占比的差异。标准计算步骤如下:

  1. 分箱(Binning):将特征或评分值划分为 k 个等宽或等频区间(通常 10~20 箱)。
  2. 计算实际占比(Actual%):测试集(或近期样本)中落在第 i 箱的样本数占总样本数的比例。
  3. 计算预期占比(Expected%):训练集(或基准样本)中落在第 i 箱的样本数占总样本数的比例。
  4. 计算每个箱的贡献值:
    箱贡献 = (Actual% - Expected%) × ln(Actual% / Expected%)
  5. 求和得到 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 输出及决策的完整流程:

PSI 计算与决策流程 PSI 计算与决策流程 原始数据输入 分箱处理(10~20箱) 计算 Actual% 与 Expected% 计算每箱贡献值 输出 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 与上述指标结合,可以更精准地定位导致分布偏移的根因,为模型迭代提供数据驱动的决策依据。