3. PSI与KL散度、JS散度的关系:三者的数学联系与区别
在信用评分模型监控中,群体稳定性指标(PSI)并非孤立存在。它与信息论中的KL散度(Kullback-Leibler Divergence)和JS散度(Jensen-Shannon Divergence)有着深刻的数学渊源。理解三者的关系,能帮助风控建模专家从信息论视角更本质地把握PSI的对称性、有界性及业务含义。
3.1 从KL散度到PSI:非对称性的代价
KL散度衡量两个概率分布 \( P \)(实际分布)和 \( Q \)(预期分布)之间的差异,其定义为:
KL(P || Q) = Σ P(i) * log( P(i) / Q(i) )
然而,KL散度具有非对称性:\( KL(P||Q) \neq KL(Q||P) \)。在信用评分场景中,这意味着“实际分布相对于预期分布的变化”与“预期分布相对于实际分布的变化”会给出不同的数值,这显然不符合业务直觉——我们只关心两个分布之间的整体偏移程度,而不关心方向。
PSI巧妙地解决了这个问题。PSI的定义为:
PSI = Σ (Actual_i - Expected_i) * ln(Actual_i / Expected_i)
其中 \( Actual_i = P(i) \),\( Expected_i = Q(i) \)。
通过简单的代数变换,可以证明:
PSI = KL(P || Q) + KL(Q || P)
即PSI是两个方向KL散度之和。这一对称化处理,使得PSI天然成为衡量分布偏移的对称性指标。
3.2 从PSI到JS散度:有界性与归一化
JS散度是KL散度的另一种对称化变体,其定义为:
JS(P || Q) = 0.5 * KL(P || M) + 0.5 * KL(Q || M)
其中 \( M = 0.5 * (P + Q) \),即两个分布的平均分布。
JS散度与PSI的核心区别在于:
- 参考分布不同: PSI直接使用 \( P \) 和 \( Q \) 互相对比;JS散度引入中间分布 \( M \),分别计算 \( P \) 和 \( Q \) 到 \( M \) 的KL散度。
- 有界性: JS散度的值域为 \( [0, \ln 2] \)(使用自然对数时),具有明确的上界;而PSI理论上无上界,但在实际风控应用中,通常以0.1、0.25等经验阈值作为判断标准。
- 平滑性: JS散度对分布差异的度量更为平滑,对极端值不敏感;PSI则对尾部概率变化更为敏感。
3.3 三者对比:一张表看懂核心差异
| 对比维度 | KL散度 | PSI | JS散度 |
|---|---|---|---|
| 对称性 | 非对称 | 对称 | 对称 |
| 值域 | [0, +∞) | [0, +∞) | [0, ln2] |
| 参考分布 | P vs Q(单向) | P vs Q(双向求和) | P vs M, Q vs M |
| 对尾部敏感度 | 高 | 高 | 中 |
| 风控应用 | 理论基石,不直接使用 | 行业标准,模型监控 | 学术研究,特征筛选 |
| 计算复杂度 | 低 | 低 | 中(需计算M) |
3.4 数学关系全景图
下面的SVG图展示了KL散度、PSI与JS散度之间的数学联系与区别,帮助建立直观的认知框架。
3.5 为什么PSI是衡量分布偏移的对称性指标?
从数学构造和业务应用两个维度,可以清晰理解PSI的对称性本质:
- 数学构造层面: PSI = KL(P||Q) + KL(Q||P)。交换P和Q的位置,公式变为 KL(Q||P) + KL(P||Q),结果完全相同。这种“双向求和”的构造,使得PSI天然满足对称性公理。
- 业务含义层面: 在信用评分监控中,我们关心的是“当前评分分布”与“建模时评分分布”之间的整体偏移程度,而不关心是哪个分布“主动”偏离了哪个。PSI的对称性恰好匹配这一业务需求——无论将哪个分布视为实际、哪个视为预期,计算出的偏移量都是一致的。
- 与KL散度的对比: KL散度在信息论中常用于衡量“用Q编码P时的信息损失”,天然具有方向性。而PSI剥离了这种方向性,专注于纯粹的分布差异度量,更适合工程化应用。
3.6 代码实现:验证三者的数学关系
以下Python代码演示了如何计算KL散度、PSI和JS散度,并验证PSI = KL(P||Q) + KL(Q||P)的数学关系:
import numpy as np
def kl_divergence(p, q):
"""计算KL散度 KL(P || Q)"""
p = np.array(p, dtype=float)
q = np.array(q, dtype=float)
# 避免除零和log(0)
p = np.clip(p, 1e-10, 1)
q = np.clip(q, 1e-10, 1)
return np.sum(p * np.log(p / q))
def psi(p, q):
"""计算PSI"""
p = np.array(p, dtype=float)
q = np.array(q, dtype=float)
p = np.clip(p, 1e-10, 1)
q = np.clip(q, 1e-10, 1)
return np.sum((p - q) * np.log(p / q))
def js_divergence(p, q):
"""计算JS散度"""
p = np.array(p, dtype=float)
q = np.array(q, dtype=float)
m = 0.5 * (p + q)
return 0.5 * kl_divergence(p, m) + 0.5 * kl_divergence(q, m)
# 示例:两个评分分布(10个分数段)
expected = [0.1, 0.15, 0.2, 0.2, 0.15, 0.1, 0.05, 0.03, 0.015, 0.005]
actual = [0.08, 0.12, 0.18, 0.22, 0.18, 0.12, 0.06, 0.02, 0.015, 0.005]
# 计算KL散度(两个方向)
kl_peq = kl_divergence(actual, expected)
kl_qep = kl_divergence(expected, actual)
print(f"KL(Actual || Expected) = {kl_peq:.6f}")
print(f"KL(Expected || Actual) = {kl_qep:.6f}")
print(f"KL(Actual||Expected) + KL(Expected||Actual) = {kl_peq + kl_qep:.6f}")
# 计算PSI
psi_value = psi(actual, expected)
print(f"PSI = {psi_value:.6f}")
# 验证PSI = KL(P||Q) + KL(Q||P)
print(f"验证:PSI ≈ KL(P||Q) + KL(Q||P) ? {np.isclose(psi_value, kl_peq + kl_qep)}")
# 计算JS散度
js_value = js_divergence(actual, expected)
print(f"JS散度 = {js_value:.6f}")
print(f"JS散度最大值(ln2) = {np.log(2):.6f}")
运行上述代码,输出结果将清晰展示:PSI恰好等于两个方向KL散度之和,而JS散度则落在[0, ln2]的有界区间内。