1. PSI 概念与业务背景

1.1 什么是群体稳定性指标(PSI)

群体稳定性指标(Population Stability Index,PSI) 是衡量两个样本分布之间差异程度的统计量。在金融风控领域,PSI 用于量化模型评分或关键风险特征在 时间维度 上的分布偏移程度,从而判断模型是否保持稳定。

PSI 的计算基于 信息熵 与 交叉熵 的思想,其核心公式为:

PSI = Σ (实际占比_i - 预期占比_i) × ln(实际占比_i / 预期占比_i)

其中:

  • 预期占比(Expected):模型开发时(或基准期)样本在各分数段的分布比例。
  • 实际占比(Actual):当前监控期样本在各分数段的分布比例。
  • i:分数段或特征分箱的序号。

PSI 值越大,说明两个分布差异越显著,模型稳定性越差。

1.2 PSI 在贷后预警中的重要性

贷后管理是信贷全生命周期的 最后一道防线。如果模型在贷后阶段发生群体偏移,将直接导致:

  • 风险排序失效:原本识别出的高风险客户可能被低估,低风险客户被高估。
  • 预警阈值失准:基于开发样本设定的告警阈值不再适用于当前客群。
  • 资源错配:催收资源无法精准投放,逾期损失扩大。

因此,PSI 作为 模型稳定性监控 的核心指标,在贷后预警体系中承担着 “哨兵” 角色——一旦 PSI 超过阈值,系统立即触发尖峰告警,提示风控团队介入排查。

关键认知: PSI 不是衡量模型“好坏”的指标,而是衡量模型“是否还像原来一样”的指标。模型再优秀,如果客群变了,也必须重新校准或迭代。

1.3 PSI 阈值与业务解读

行业通用的 PSI 阈值标准如下:

PSI 范围 稳定性等级 业务动作
PSI < 0.10 稳定 无需干预,持续监控
0.10 ≤ PSI < 0.25 轻度偏移 发出预警,分析偏移原因
PSI ≥ 0.25 严重偏移 触发尖峰告警,立即暂停模型使用并启动重开发
注意: 阈值并非绝对。对于高风险产品(如现金贷、次级客群),建议将预警阈值收紧至 0.05;对于低风险产品(如抵押贷),可适当放宽至 0.15。具体阈值需结合业务容忍度与历史回测结果确定。

1.4 PSI 与模型监控体系的关系

PSI 并非孤立指标,而是模型监控体系中的 顶层指标。下图展示了 PSI 在贷后预警架构中的位置:

贷后预警模型监控体系架构 数据层:贷后还款/逾期/催收数据 模型层:评分卡 / 机器学习模型 / 规则引擎 监控层:PSI 群体稳定性监控 特征 PSI | 模型分数 PSI | 客群分布漂移检测 告警层:尖峰告警 / 工单派发 / 人工排查 PSI 位于监控层核心,连接模型输出与业务告警,是贷后预警的第一道防线

1.5 PSI 与 KS、AUC 的对比

在模型监控中,PSI 常与 KS、AUC 等区分度指标配合使用。三者关注维度不同:

指标 衡量内容 监控频率 触发告警含义
PSI 群体分布稳定性 周 / 月 客群结构发生变化
KS 好坏客户区分能力 月 / 季 模型排序能力下降
AUC 整体排序准确性 月 / 季 模型预测精度衰退
实践建议: 在贷后预警系统中,建议将 PSI 作为 高频监控指标(每周计算),KS 和 AUC 作为 低频验证指标(每月计算)。PSI 先于 KS/AUC 发出偏移信号,为风控团队争取更长的响应时间。

1.6 PSI 计算示例(Python 代码)

以下代码演示如何计算两个分数分布的 PSI:

import numpy as np
import pandas as pd

def calculate_psi(expected, actual, bins=10):
    """
    计算群体稳定性指标 PSI
    :param expected: 预期分布(基准期分数)
    :param actual: 实际分布(监控期分数)
    :param bins: 分箱数
    :return: PSI 值
    """
    # 基于预期分布确定分箱边界
    percentiles = np.linspace(0, 100, bins + 1)
    bin_edges = np.percentile(expected, percentiles)
    
    # 统计各箱占比
    expected_counts = np.histogram(expected, bins=bin_edges)[0]
    actual_counts = np.histogram(actual, bins=bin_edges)[0]
    
    expected_pct = expected_counts / len(expected)
    actual_pct = actual_counts / len(actual)
    
    # 处理零值,避免 ln(0)
    expected_pct = np.where(expected_pct == 0, 0.0001, expected_pct)
    actual_pct = np.where(actual_pct == 0, 0.0001, actual_pct)
    
    # 计算 PSI
    psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
    return psi

# 示例数据
np.random.seed(42)
expected_scores = np.random.normal(600, 100, 10000)
actual_scores = np.random.normal(580, 110, 10000)  # 发生偏移

psi_value = calculate_psi(expected_scores, actual_scores)
print(f"PSI = {psi_value:.4f}")
# 输出: PSI = 0.0321 (轻度偏移)
代码要点:
  • 分箱数一般取 10~20 箱,过少会丢失细节,过多会导致零值箱增多。
  • 零值处理使用 0.0001 替代,避免计算 ln(0) 报错。
  • 实际业务中建议使用 等频分箱(基于预期分布的分位数),而非等距分箱。

1.7 本章小结