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 在贷后预警架构中的位置:
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) 报错。
- 实际业务中建议使用 等频分箱(基于预期分布的分位数),而非等距分箱。