2. PSI 计算原理与公式推导
群体稳定性指标(Population Stability Index, PSI)是衡量两个分布之间差异的标准化度量。在风控特征监控中,PSI 用于量化特征在 预期分布(如建模样本)与 实际分布(如线上实时数据)之间的偏移程度。其核心思想是通过逐箱比较占比差异,并利用对数变换放大尾部偏差。
2.1 核心公式与逐项拆解
PSI 的标准计算公式如下:
PSI = Σ (实际占比_i - 预期占比_i) * ln(实际占比_i / 预期占比_i)
其中,i 表示第 i 个分箱(bin),实际占比_i 为当前监控周期内落在该箱的样本比例,预期占比_i 为基准周期(如模型训练集)在该箱的样本比例。公式由两部分构成:
- 差异项 (实际占比 - 预期占比):衡量两个分布在该箱上的绝对偏差,方向性决定正负贡献。
- 权重项 ln(实际占比 / 预期占比):对比例差异进行非线性放大。当实际占比远小于预期占比时,对数负值增大;当实际占比远大于预期占比时,对数正值增大。这种设计使得 PSI 对尾部箱的偏移更为敏感。
直观理解: 如果两个分布完全一致,每个箱的 (实际占比 - 预期占比) 为 0,PSI = 0。PSI 值越大,分布偏移越严重。通常认为 PSI < 0.1 表示稳定性良好,0.1 ≤ PSI < 0.25 需关注,PSI ≥ 0.25 表示显著偏移。
2.2 分箱策略:等频与等距
分箱是 PSI 计算的前提。不同的分箱策略会直接影响 PSI 的敏感度和稳定性。主流策略包括等频分箱和等距分箱。
| 策略 | 定义 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 等频分箱 | 每个箱包含大致相同数量的样本(如 10 分箱,每箱 10% 样本)。 | 避免空箱;对长尾分布友好;每个箱的统计稳定性高。 | 箱边界随数据分布变化,跨时间可比性稍弱;对集中分布区域的分辨率不足。 | 大多数风控特征(如收入、年龄、信用分),尤其是分布不均匀的特征。 |
| 等距分箱 | 将特征值域等分为固定宽度的区间(如 0-100, 100-200)。 | 箱边界固定,跨时间可比性强;物理意义明确。 | 容易产生空箱或样本极少的箱,导致 ln(0) 或除以零问题;对分布集中区域过度细分。 | 物理意义明确的特征(如利率、金额区间),或需要严格对齐业务阈值的场景。 |
避坑提示: 等距分箱在特征分布严重偏斜时,大量样本会集中在少数几个箱中,而两端箱可能样本极少甚至为零,这会直接导致 PSI 计算失败或产生异常大值。建议优先使用等频分箱,或对等距分箱结果进行空箱合并处理。
2.3 平滑处理与零值规避
在 PSI 计算中,ln(实际占比 / 预期占比) 要求分母(预期占比)和分子(实际占比)均不能为零。当某个箱的样本数为 0 时,占比为 0%,直接计算会导致除零错误或负无穷大。平滑处理是解决这一问题的标准方法。
2.3.1 常见平滑方法
| 方法 | 操作 | 优点 | 缺点 |
|---|---|---|---|
| 加常数平滑 | 在每个箱的计数上加一个极小常数 ε(如 0.5 或 1),再计算占比。 | 实现简单;对所有箱一视同仁。 | ε 的选择对结果有轻微影响;对样本量极大的场景影响可忽略。 |
| 贝叶斯平滑 | 引入先验分布(如 Beta 分布),用后验期望替代原始计数。 | 理论严谨;能利用全局信息。 | 实现复杂;需要估计先验参数。 |
| 合并空箱 | 将样本数为 0 的箱与相邻箱合并,直到所有箱都有样本。 | 不引入额外参数;保持分布形状。 | 箱数减少,可能降低 PSI 灵敏度;合并策略需人工定义。 |
2.3.2 加常数平滑的工程实现
以下代码展示了使用加常数平滑(ε = 0.5)计算 PSI 的 Python 实现:
import numpy as np
def calculate_psi(expected_counts, actual_counts, epsilon=0.5):
"""
计算 PSI,使用加常数平滑处理零值。
Args:
expected_counts: 预期分布各箱样本数 (list or array)
actual_counts: 实际分布各箱样本数 (list or array)
epsilon: 平滑常数,默认 0.5
Returns:
psi: 群体稳定性指标值
"""
# 将计数转换为 numpy 数组
expected = np.array(expected_counts, dtype=float)
actual = np.array(actual_counts, dtype=float)
# 加常数平滑
expected_smooth = expected + epsilon
actual_smooth = actual + epsilon
# 计算占比
expected_pct = expected_smooth / np.sum(expected_smooth)
actual_pct = actual_smooth / np.sum(actual_smooth)
# 计算 PSI
psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
return psi
# 示例:10 个分箱的计数
expected = [100, 200, 150, 180, 220, 190, 160, 140, 110, 50]
actual = [95, 210, 145, 190, 230, 180, 155, 135, 115, 45]
psi_value = calculate_psi(expected, actual)
print(f"PSI = {psi_value:.4f}") # 输出: PSI = 0.0032
工程要点: 加常数平滑中,ε 的取值不宜过大(通常 0.1~1),否则会过度稀释真实分布差异。对于样本量较大的场景(单箱样本数 > 1000),ε 的影响可忽略。建议在监控系统中统一使用 ε = 0.5,保持跨特征可比性。
2.4 公式推导的数学直觉
PSI 公式可以看作 散度度量 的一种离散形式。从信息论角度,它衡量的是两个概率分布之间的 KL 散度(Kullback-Leibler divergence)的对称化变体。具体推导直觉如下:
- KL 散度基础: KL(P||Q) = Σ P(i) * ln(P(i)/Q(i)),衡量用 Q 近似 P 时的信息损失。
- PSI 的对称化: 标准 PSI 公式 = Σ (P(i) - Q(i)) * ln(P(i)/Q(i)) = Σ P(i)*ln(P(i)/Q(i)) - Σ Q(i)*ln(P(i)/Q(i)) = KL(P||Q) + KL(Q||P)。
- 物理意义: PSI 同时考虑了从实际分布到预期分布的 KL 散度,以及从预期分布到实际分布的 KL 散度,因此是一个对称的、非负的分布差异度量。
关键结论: PSI 值永远 ≥ 0,当且仅当两个分布完全一致时 PSI = 0。值越大,分布差异越大。在风控监控中,PSI 的对称性保证了无论分布向哪个方向偏移,都能被有效捕捉。