3. PSI基础原理:群体稳定性指标的定义、计算公式、分箱逻辑与数学推导
群体稳定性指标(Population Stability Index, PSI)是风控模型监控中最核心的量化指标,用于衡量模型评分或特征分布在两个时间窗口(通常为建模样本与当前样本)之间的偏移程度。PSI 值越小,说明群体分布越稳定;PSI 值越大,说明模型或特征发生了显著漂移。
3.1 PSI 的定义与业务含义
PSI 本质上是一种基于信息论中 Kullback-Leibler 散度(KL 散度)的对称化变体,用于比较两个概率分布之间的差异。在风控场景中,PSI 通常用来回答以下问题:
- 模型评分分布:当前月与建模月相比,评分分布是否发生了系统性偏移?
- 特征分布:某个关键特征(如收入、负债率)的分布是否发生了结构性变化?
- 客群结构:进件客群是否与建模客群存在显著差异?
3.2 PSI 计算公式与数学推导
PSI 的计算基于分箱后的实际占比与预期占比。假设我们将评分或特征值划分为 n 个区间(分箱),则 PSI 的通用计算公式为:
PSI = Σ (Actual_i - Expected_i) × ln(Actual_i / Expected_i)
其中:
- Actual_i = 当前样本在第 i 个分箱中的占比(实际分布)
- Expected_i = 建模样本在第 i 个分箱中的占比(预期分布)
- ln = 自然对数
- i = 1, 2, ..., n(分箱编号)
数学推导说明:
- PSI 的数学基础是 KL 散度:
KL(P||Q) = Σ P_i × ln(P_i / Q_i),其中 P 为实际分布,Q 为预期分布。 - KL 散度是非对称的,即
KL(P||Q) ≠ KL(Q||P)。PSI 通过同时计算KL(P||Q) + KL(Q||P)实现对称化,因此 PSI 也被称为 对称化 KL 散度。 - 当 Actual_i 与 Expected_i 完全相等时,
ln(1) = 0,PSI = 0,表示分布完全一致。 - 当分布差异越大,
ln(Actual_i / Expected_i)的绝对值越大,PSI 值越高。
3.3 分箱逻辑:等距分箱 vs 等频分箱
PSI 的计算依赖于分箱策略。不同的分箱方式会直接影响 PSI 的数值和敏感性。风控实践中主要使用以下两种分箱方法:
| 分箱方式 | 定义 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 等距分箱 | 将评分或特征值按固定宽度划分为 n 个区间(如每 50 分一个箱) | 区间边界固定,便于跨时间对比;计算简单 | 样本分布不均时,某些箱可能为空或占比极低,导致 PSI 不稳定 | 评分卡模型(如 300-900 分,每 50 分一箱) |
| 等频分箱 | 将样本按分位数划分为 n 个区间,每个箱包含大致相同的样本量 | 每个箱都有足够的样本,PSI 计算更稳健 | 区间边界随样本分布变化,跨时间对比时边界可能不一致 | 特征分布监控、非评分类模型输出 |
ln(Actual_i / 0) 无定义。此时需要做平滑处理,常见做法是将 0 替换为一个极小值(如 0.0001),或将该箱与相邻箱合并。
3.4 PSI 计算的分步流程
以下为 PSI 计算的标准操作步骤,适用于评分或特征监控:
- 确定基准分布:选取建模样本或历史稳定期的评分/特征值作为预期分布(Expected)。
- 确定当前分布:选取当前监控周期的样本作为实际分布(Actual)。
- 选择分箱方式:根据业务场景选择等距分箱或等频分箱,通常建议分箱数量为 10-20 个。
- 计算各箱占比:分别统计预期分布和实际分布在每个分箱中的样本占比。
- 计算每个分箱的贡献值:
(Actual_i - Expected_i) × ln(Actual_i / Expected_i)。 - 求和得到 PSI:将所有分箱的贡献值相加,得到最终的 PSI 值。
- 阈值判断:根据 PSI 值判断分布稳定性,触发相应动作。
3.5 PSI 计算示例(等距分箱)
假设某评分卡模型输出范围为 300-900 分,建模样本与当前样本各 1000 条,按等距分箱(每 100 分一箱)计算 PSI:
| 评分区间 | 预期样本数 | 预期占比 (Expected_i) | 实际样本数 | 实际占比 (Actual_i) | 贡献值 |
|---|---|---|---|---|---|
| 300-400 | 50 | 0.05 | 80 | 0.08 | (0.08-0.05)×ln(0.08/0.05) = 0.03×0.470 = 0.0141 |
| 400-500 | 150 | 0.15 | 200 | 0.20 | (0.20-0.15)×ln(0.20/0.15) = 0.05×0.287 = 0.0144 |
| 500-600 | 300 | 0.30 | 280 | 0.28 | (0.28-0.30)×ln(0.28/0.30) = (-0.02)×(-0.069) = 0.0014 |
| 600-700 | 300 | 0.30 | 250 | 0.25 | (0.25-0.30)×ln(0.25/0.30) = (-0.05)×(-0.182) = 0.0091 |
| 700-800 | 150 | 0.15 | 130 | 0.13 | (0.13-0.15)×ln(0.13/0.15) = (-0.02)×(-0.143) = 0.0029 |
| 800-900 | 50 | 0.05 | 60 | 0.06 | (0.06-0.05)×ln(0.06/0.05) = 0.01×0.182 = 0.0018 |
| 合计 | 1000 | 1.00 | 1000 | 1.00 | PSI = 0.0437 |
本例中 PSI = 0.0437 < 0.1,说明当前样本分布与建模样本分布基本一致,模型稳定性良好。
3.6 PSI 与 KL 散度的关系
为了更直观地理解 PSI 的数学本质,以下对比 PSI 与 KL 散度的异同:
| 指标 | 公式 | 对称性 | 取值范围 | 风控应用 |
|---|---|---|---|---|
| KL 散度 | KL(P||Q) = Σ P_i × ln(P_i / Q_i) | 非对称 | [0, +∞) | 较少直接使用,作为 PSI 的理论基础 |
| PSI | Σ (P_i - Q_i) × ln(P_i / Q_i) | 对称 | [0, +∞) | 广泛用于模型监控、特征漂移检测 |
从数学上可以证明:PSI = KL(P||Q) + KL(Q||P),即 PSI 是 KL 散度的对称化版本。这使得 PSI 在衡量分布差异时更加均衡,不会因为基准分布的选择而偏向某一方向。
3.7 PSI 计算流程示意图
以下 SVG 图展示了从原始数据到 PSI 输出的完整计算流程:
3.8 PSI 分箱策略对比示意图
以下 SVG 图直观对比等距分箱与等频分箱在相同数据分布下的差异:
3.9 PSI 计算的代码实现(Python)
以下为 PSI 计算的 Python 实现,包含等距分箱与等频分箱两种方式:
import numpy as np
import pandas as pd
def calculate_psi(expected, actual, bins=10, method='equal_width'):
"""
计算群体稳定性指标 (PSI)
参数:
expected: array-like, 建模样本(预期分布)
actual: array-like, 当前样本(实际分布)
bins: int, 分箱数量
method: str, 'equal_width' 或 'equal_frequency'
返回:
psi: float, PSI 值
bin_df: DataFrame, 各分箱明细
"""
# 合并数据确定分箱边界
all_data = np.concatenate([expected, actual])
if method == 'equal_width':
# 等距分箱
bin_edges = np.linspace(np.min(all_data), np.max(all_data), bins + 1)
elif method == 'equal_frequency':
# 等频分箱(基于预期分布的分位数)
bin_edges = np.percentile(expected, np.linspace(0, 100, bins + 1))
# 确保边界唯一
bin_edges = np.unique(bin_edges)
else:
raise ValueError("method 必须为 'equal_width' 或 'equal_frequency'")
# 计算各箱占比
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)
# 平滑处理:将 0 替换为极小值
eps = 1e-6
expected_pct = np.where(expected_pct == 0, eps, expected_pct)
actual_pct = np.where(actual_pct == 0, eps, actual_pct)
# 计算每个分箱的贡献值
contribution = (actual_pct - expected_pct) * np.log(actual_pct / expected_pct)
# 计算 PSI
psi = np.sum(contribution)
# 构建明细 DataFrame
bin_df = pd.DataFrame({
'bin_start': bin_edges[:-1],
'bin_end': bin_edges[1:],
'expected_count': expected_counts,
'actual_count': actual_counts,
'expected_pct': expected_pct,
'actual_pct': actual_pct,
'contribution': contribution
})
return psi, bin_df
# 示例用法
np.random.seed(42)
expected_scores = np.random.normal(600, 100, 10000)
actual_scores = np.random.normal(620, 110, 10000)
psi_value, psi_detail = calculate_psi(expected_scores, actual_scores, bins=10, method='equal_width')
print(f"PSI 值: {psi_value:.4f}")
print(psi_detail.head())
- 等频分箱时,分箱边界基于预期分布的分位数确定,确保预期分布各箱样本量大致相等。
- 平滑处理(eps)是防止分母为 0 导致计算错误的关键步骤。
- 返回的明细 DataFrame 可用于排查具体哪个分箱贡献了最大的偏移。
3.10 PSI 的局限性
尽管 PSI 是风控稳定性监控的核心指标,但在实际应用中仍需注意以下局限性:
- 对分箱数量敏感:分箱越多,PSI 越容易受噪声影响;分箱过少,可能掩盖局部偏移。
- 无法区分偏移方向:PSI 只反映分布差异的大小,不反映偏移的方向(如客群变好还是变差)。
- 对样本量敏感:样本量过小时,PSI 的统计稳定性下降,容易出现假阳性。
- 无法定位原因:PSI 只能检测到分布发生了偏移,但无法自动识别偏移的原因(如特征缺失、客群变化、外部环境变化等)。