3. PSI 概念详解:群体稳定性指标
群体稳定性指标(Population Stability Index,PSI)是量化风控中衡量模型输入因子(特征)或输出分数在不同时间窗口或样本群体之间分布偏移程度的核心指标。PSI 的数值大小直接反映了因子分布的“漂移”程度,是因子库巡查与模型监控的基石。
3.1 数学定义与计算公式
PSI 基于信息论中的 KL 散度(Kullback-Leibler divergence)演变而来,通过比较两个样本群体在各分箱(bin)上的占比差异,量化分布的整体变化。其标准计算公式如下:
PSI = Σ (Actual_i - Expected_i) * ln(Actual_i / Expected_i)
其中:
- i = 1, 2, ..., n,表示将变量分成的 n 个分箱(通常为 10 或 20 等分位箱)
- Actual_i = 当前监控样本在第 i 个分箱中的占比(%)
- Expected_i = 基准样本(如建模样本)在第 i 个分箱中的占比(%)
- ln = 自然对数
计算要点:当某个分箱中 Expected_i 或 Actual_i 为 0 时,为避免对数计算无定义,通常对该分箱占比进行微小平滑处理(如加 0.0001),或将该分箱与相邻分箱合并。
3.2 业务含义与判定阈值
PSI 的数值大小直接映射到因子分布的稳定性等级。在风控实践中,通常采用以下经验阈值进行判定:
| PSI 数值范围 | 稳定性等级 | 业务含义与建议动作 |
|---|---|---|
| PSI < 0.1 | 稳定 | 因子分布无显著变化,模型可继续使用,无需干预。 |
| 0.1 ≤ PSI < 0.25 | 轻度漂移 | 因子分布出现轻微偏移,需关注趋势,排查可能的原因(如客群结构微调)。 |
| PSI ≥ 0.25 | 严重漂移 | 因子分布发生显著变化,模型区分能力可能受损,必须立即深入分析并考虑模型重建或因子替换。 |
注意:阈值并非绝对固定。对于高敏感度的信贷场景(如反欺诈模型),可适当收紧标准(如 PSI > 0.15 即触发预警);对于低频或长周期因子,可适当放宽。
3.3 PSI 与 KL 散度的关系
理解 PSI 与 KL 散度的关系,有助于深入把握其数学本质。PSI 实际上是对称化的 KL 散度的一种变体:
- KL 散度:衡量两个概率分布 P 和 Q 之间的非对称差异,即 D_KL(P || Q)。
- PSI:同时计算 D_KL(Actual || Expected) 和 D_KL(Expected || Actual) 并求和,形成对称指标。
这种对称性使得 PSI 在监控中更为稳健——无论基准分布向哪个方向漂移,PSI 都能敏感捕捉。
3.4 PSI 计算流程示意图
下图展示了从原始因子数据到 PSI 值的完整计算链路:
3.5 PSI 与特征监控的对应关系
在因子库漂移巡查中,PSI 并非孤立使用,而是与以下监控维度协同:
- 单因子 PSI:对每个入模特征独立计算 PSI,定位具体哪个因子发生漂移。
- 模型分数 PSI:对模型输出的信用评分或概率值计算 PSI,评估模型整体稳定性。
- 客群结构 PSI:对关键客群属性(如年龄、收入、地区)计算 PSI,判断客群构成是否变化。
实战经验:在因子库巡查中,建议优先监控入模特征的 PSI。当某个特征的 PSI 超过 0.25 时,应进一步查看该特征的分箱占比变化图,定位具体哪个取值区间发生了偏移,从而快速锁定业务原因(如政策调整、渠道变更、数据源异常等)。
3.6 PSI 计算的代码实现示例
以下 Python 代码展示了 PSI 的核心计算逻辑,便于你在实际巡查中快速集成:
import numpy as np
import pandas as pd
def calculate_psi(expected, actual, bins=10):
"""
计算群体稳定性指标 (PSI)
参数:
expected: array-like, 基准样本数据
actual: array-like, 当前监控样本数据
bins: int, 分箱数量(默认10等分)
返回:
psi_value: float, PSI 值
bin_df: DataFrame, 各分箱的占比详情
"""
# 基于基准样本确定分箱边界(等频分箱)
percentiles = np.linspace(0, 100, bins + 1)
bin_edges = np.percentile(expected, percentiles)
bin_edges[0] = -np.inf
bin_edges[-1] = np.inf
# 分别统计基准和当前样本在各箱的占比
expected_counts, _ = np.histogram(expected, bins=bin_edges)
actual_counts, _ = np.histogram(actual, bins=bin_edges)
expected_pct = expected_counts / len(expected)
actual_pct = actual_counts / len(actual)
# 平滑处理,避免除零或取对数报错
epsilon = 1e-6
expected_pct = np.clip(expected_pct, epsilon, 1 - epsilon)
actual_pct = np.clip(actual_pct, epsilon, 1 - epsilon)
# 计算 PSI
psi_value = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
# 构建结果数据框
bin_df = pd.DataFrame({
'bin': range(1, bins + 1),
'expected_pct': expected_pct,
'actual_pct': actual_pct,
'contribution': (actual_pct - expected_pct) * np.log(actual_pct / expected_pct)
})
return psi_value, bin_df
# 示例调用
# psi, details = calculate_psi(expected_scores, actual_scores, bins=10)
# print(f"PSI = {psi:.4f}")
3.7 PSI 的局限性
尽管 PSI 是行业标准指标,但在实际应用中仍需注意其局限性:
- 分箱敏感性:分箱数量(bins)和分箱方式(等频/等距)会影响 PSI 绝对值,建议统一标准(如固定 10 等分)以便跨时间对比。
- 无法区分漂移方向:PSI 只反映分布变化的幅度,不指示具体是向左还是向右偏移,需结合分箱占比图进一步分析。
- 对样本量敏感:当监控样本量过小时,PSI 可能因随机波动而虚高,建议监控样本量不低于基准样本量的 10%。
最佳实践:在因子库巡查中,不要仅依赖 PSI 的绝对值做一刀切决策。建议结合趋势分析(连续多期 PSI 变化曲线)和业务背景(如是否发生大规模营销活动、政策调整等),综合判断是否需要触发模型更新流程。