まとめ- PSI(Population Stability Index)は学習時と本番時の特徴量分布のずれを定量化する監視指標。
- PSI < 0.1 は安定、0.1–0.25 は要注意、> 0.25 は分布が大きく変化したと判断する。
- 各特徴量の PSI を定期的に計算し、モデルの再学習タイミングを客観的に判断できる。
直感
#
与信モデルを半年前のデータで学習したが、景気が変わって申込者層が変化したかもしれない。PSI は「学習時の分布」と「今の分布」を比較し、ずれの大きさをスコア化する。直感的には KL ダイバージェンスの対称版で、ビン分割した分布の差異を測る。
詳細な解説
#
計算式
#
$$
\text{PSI} = \sum_{i=1}^{B} (p_i - q_i) \cdot \ln\frac{p_i}{q_i}
$$- $p_i$: 学習時のビン $i$ の割合
- $q_i$: 本番時のビン $i$ の割合
- $B$: ビン数(通常 10–20)
Python 実装
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import numpy as np
def calculate_psi(expected, actual, bins=10):
"""PSI を計算する。"""
breakpoints = np.linspace(0, 100, bins + 1)
breakpoints = np.percentile(expected, breakpoints)
expected_counts = np.histogram(expected, bins=breakpoints)[0]
actual_counts = np.histogram(actual, bins=breakpoints)[0]
expected_pct = expected_counts / len(expected) + 1e-6
actual_pct = actual_counts / len(actual) + 1e-6
psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
return psi
|
使用例
#
1
2
3
4
5
6
7
| np.random.seed(42)
train_feature = np.random.normal(50, 10, 5000)
prod_stable = np.random.normal(50, 10, 5000)
prod_drifted = np.random.normal(55, 12, 5000)
print(f"安定時の PSI: {calculate_psi(train_feature, prod_stable):.4f}")
print(f"ドリフト時の PSI: {calculate_psi(train_feature, prod_drifted):.4f}")
|
判定基準
#
| PSI 値 | 判定 | アクション |
|---|
| < 0.1 | 安定 | 監視継続 |
| 0.1 – 0.25 | やや変化 | 原因調査・注視 |
| > 0.25 | 大きな変化 | モデルの再学習を検討 |