PSI 数学原理与公式推导
群体稳定性指标(Population Stability Index, PSI)是衡量两个分布之间差异程度的统计量,在评分卡监控中用于量化模型评分分布随时间或样本集的变化幅度。理解 PSI 的数学本质,是正确设定阈值、解读漂移信号的前提。
2.1 PSI 的数学定义
PSI 的计算基于将连续评分划分为 k 个等宽或等频区间(通常为 10–20 个分箱),然后比较每个区间内实际分布(当前评分)与期望分布(建模时评分)的占比差异。其标准公式为:
PSI = Σ (Actual_i - Expected_i) × ln(Actual_i / Expected_i)
其中:
- Expected_i:建模样本(基准集)在第 i 个分箱中的样本占比;
- Actual_i:当前监控样本(实际集)在第 i 个分箱中的样本占比;
- ln(Actual_i / Expected_i):实际占比与期望占比的自然对数比;
- 求和:对所有非空分箱的贡献值求和。
2.2 基于信息论的解读:相对熵视角
PSI 在数学上等价于 Kullback-Leibler 散度(KL 散度) 的对称化形式。KL 散度衡量用一个分布 P 近似另一个分布 Q 时的信息损失:
KL(P || Q) = Σ P_i × ln(P_i / Q_i)
PSI 实际上是 KL(Actual || Expected) + KL(Expected || Actual) 的对称版本,即:
PSI = Σ (Actual_i - Expected_i) × ln(Actual_i / Expected_i)
= Σ Actual_i × ln(Actual_i / Expected_i) + Σ Expected_i × ln(Expected_i / Actual_i)
= KL(Actual || Expected) + KL(Expected || Actual)
这种对称性使得 PSI 对两个分布的角色不敏感——无论将哪个分布视为基准,计算结果一致。这与 KL 散度的非对称性形成对比,也是 PSI 在工业监控中更受欢迎的原因之一。
| 指标 | 对称性 | 取值范围 | 与 PSI 关系 |
|---|---|---|---|
| KL 散度 | 非对称 | [0, +∞) | PSI = KL(Actual||Expected) + KL(Expected||Actual) |
| JS 散度 | 对称 | [0, ln2] | JS = 0.5 × KL(P||M) + 0.5 × KL(Q||M),M=(P+Q)/2 |
| PSI | 对称 | [0, +∞) | 直接用于评分卡漂移监控 |
2.3 分布差异的直观解释:对数比求和
PSI 的核心计算单元是 (Actual_i - Expected_i) × ln(Actual_i / Expected_i)。这个乘积可以拆解为两个因子的作用:
- 差异方向因子 (Actual_i - Expected_i):表示当前分布相对于基准分布是“偏多”还是“偏少”。正号表示该分箱中实际样本占比上升,负号表示下降。
- 差异幅度因子 ln(Actual_i / Expected_i):表示偏离的比例。当 Actual_i = Expected_i 时,该因子为 0;偏离越大,绝对值越大。
两个因子相乘后,每个分箱的贡献始终为非负值(因为当 Actual_i > Expected_i 时,两项均为正;当 Actual_i < Expected_i 时,两项均为负,乘积仍为正)。因此 PSI 是一个非负指标,值越大表示分布差异越大。
2.4 分箱策略对 PSI 的影响
PSI 对分箱方式敏感,不同分箱策略可能导致同一组数据计算出不同的 PSI 值。实践中需统一分箱规则,确保基准集与监控集使用相同的分箱边界。
| 分箱方式 | 方法描述 | 适用场景 | 注意事项 |
|---|---|---|---|
| 等宽分箱 | 按评分值等间距划分 | 评分范围固定、分布均匀时 | 极端值可能导致空箱 |
| 等频分箱 | 按样本量等比例划分 | 分布偏态、长尾数据 | 分箱边界随样本变化 |
| 自定义分箱 | 按业务含义(如信用等级阈值) | 业务规则驱动、监管要求 | 需定期验证边界合理性 |
2.5 公式推导的完整步骤
为便于编程实现,下面给出 PSI 计算的完整推导步骤,附带 Python 伪代码:
- 确定分箱边界:基于基准集评分,使用等宽或等频策略生成 k 个分箱的边界数组
bins。 - 计算基准占比:统计基准集在每个分箱的样本数
count_exp,除以总样本数得到Expected_i。 - 计算实际占比:使用相同
bins对监控集评分分箱,统计count_act,得到Actual_i。 - 逐箱计算贡献:对每个分箱 i,计算
(Actual_i - Expected_i) * log(Actual_i / Expected_i)。 - 求和:将所有分箱的贡献值相加,得到 PSI。
import numpy as np
def calculate_psi(expected_scores, actual_scores, bins=10):
# 1. 基于基准集确定分箱边界(等频)
percentiles = np.linspace(0, 100, bins + 1)
bin_edges = np.percentile(expected_scores, percentiles)
bin_edges[0] = -np.inf
bin_edges[-1] = np.inf
# 2. 基准占比
exp_counts, _ = np.histogram(expected_scores, bins=bin_edges)
exp_ratio = exp_counts / len(expected_scores)
# 3. 实际占比
act_counts, _ = np.histogram(actual_scores, bins=bin_edges)
act_ratio = act_counts / len(actual_scores)
# 4. 逐箱计算(平滑处理避免除零)
psi = 0.0
for i in range(bins):
e = exp_ratio[i] + 1e-10 # 平滑
a = act_ratio[i] + 1e-10
psi += (a - e) * np.log(a / e)
return psi
• 使用
np.percentile 生成等频分箱边界,确保基准集每个分箱样本数大致相等。• 平滑值 1e-10 远小于实际占比(通常 > 0.01),不会显著影响结果,但能避免 log(0) 错误。
• 监控集样本量过小时(如 < 1000),建议增加分箱数量或使用 Bootstrap 重采样稳定 PSI 估计。