Model yang bagus saat dilatih tidak menjamin model yang bagus setahun kemudian — bukan karena modelnya berubah, melainkan karena dunianya yang berubah. Pengunjung aplikasi bergeser demografisnya, sensor menua, tren musiman berganti arah. Pergeseran ini berjalan diam-diam: setiap prediksi tetap menghasilkan angka sah, tidak ada exception yang tercatat, dan layanan tampak sehat. Drift adalah nama untuk pergeseran ini, dan mendeteksinya adalah pekerjaan pengukuran distribusi — bukan pemeriksaan log error. Chapter ini mengimplementasikan PSI (Population Stability Index) dan KL divergence dengan NumPy murni, lalu membuktikan dampak drift yang sama terhadap akurasi model.
Tiga Istilah yang Sering Tertukar #
| Istilah | Yang bergeser | Terdeteksi dari |
|---|---|---|
| data drift (covariate shift) | distribusi input P(X) | membandingkan distribusi input masa kini vs baseline |
| concept drift | hubungan input-label `P(y | X)` |
| performance drift | akurasi/metrik model | label terlambat tiba, lalu metrik dihitung ulang |
Ketiganya bisa terjadi sendiri-sendiri atau bersamaan. Data drift adalah sinyal paling dini karena bisa diukur tanpa label baru — cukup membandingkan distribusi input terhadap baseline saat training. Itulah sebabnya loop monitoring di chapter Monitoring Model Produksi memantau distribusi prediksi: sinyal tanpa label yang menunjukkan sesuatu sedang berubah.
PSI: Mengukur Pergeseran Distribusi #
PSI membandingkan dua distribusi lewat histogram dengan tepi bin yang sama. Untuk proporsi per-bin baseline b_i dan arus terbaru a_i:
PSI = Σ (a_i − b_i) · ln(a_i / b_i)Rumus sederhana ini memiliki dua sifat yang berguna: nilainya selalu tidak-negatif (nol hanya jika kedua distribusi identik per bin), dan arah pergeseran tidak memengaruhi besarnya — bergeser ke kanan atau ke kiri menghasilkan angka besar yang sama. Konvensi ambang industri yang lazim: di bawah 0.10 stabil, 0.10-0.25 perlu perhatian, di atas 0.25 bergeser signifikan. KL divergence KL = Σ a_i · ln(a_i / b_i) menakar hal serupa dengan penekanan relatif, tetapi asimetris: KL(aktual‖baseline) tidak sama dengan kebalikannya.
Contoh Teruji: PSI dan KL dengan NumPy #
Berkas psi_drift.py membangun baseline 5000 sampel, lalu membandingkannya dengan tiga arus data: stabil, bergeser sedang, dan bergeser jauh. Segmen kedua menunjukkan pergeseran yang sama terlihat dari sisi model:
"""Deteksi data drift dengan PSI dan KL divergence (NumPy murni).
Baseline distribusi dibandingkan dengan tiga arus data terbaru.
PSI dan KL dihitung dari histogram dengan tepi bin tetap, lalu
dibandingkan terhadap ambang industri: PSI < 0.10 stabil,
0.10-0.25 waspada, > 0.25 bergeser signifikan. Segmen terakhir
menunjukkan drift yang sama terlihat sebagai penurunan akurasi
model (performance drift).
"""
import numpy as np
TEPI_BIN = np.array([-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0])
AMBANG_WASPADA = 0.10
AMBANG_DRIFT = 0.25
def proporsi_bin(sampel):
"""Proporsi sampel per bin; tepi bin menampung nilai ekstrem."""
terpotong = np.clip(sampel, TEPI_BIN[0], TEPI_BIN[-1])
hitungan, _ = np.histogram(terpotong, bins=TEPI_BIN)
return (hitungan + 0.5) / hitungan.sum() # smoothing anti nol
def psi(baseline, aktual):
"""Population Stability Index antara dua sampel."""
pb = proporsi_bin(baseline)
pa = proporsi_bin(aktual)
return float(np.sum((pa - pb) * np.log(pa / pb)))
def kl_divergence(baseline, aktual):
"""KL(aktual || baseline) dari histogram yang sama."""
pb = proporsi_bin(baseline)
pa = proporsi_bin(aktual)
return float(np.sum(pa * np.log(pa / pb)))
def status_psi(nilai):
if nilai < AMBANG_WASPADA:
return "stabil"
if nilai < AMBANG_DRIFT:
return "waspada"
return "DRIFT SIGNIFIKAN"
def main():
rng = np.random.default_rng(42)
baseline = rng.normal(0.0, 1.0, size=5000)
arus = {
"arus stabil": np.random.default_rng(1).normal(0.1, 1.0, 5000),
"arus bergeser sedang": np.random.default_rng(2).normal(
0.4, 1.0, 5000),
"arus bergeser jauh": np.random.default_rng(3).normal(
1.2, 1.5, 5000),
}
print("== PSI dan KL: baseline vs arus data terbaru ==")
print(f"{'kondisi':<22} {'PSI':>7} {'KL':>7} status")
for nama, sampel in arus.items():
p = psi(baseline, sampel)
k = kl_divergence(baseline, sampel)
print(f"{nama:<22} {p:>7.4f} {k:>7.4f} {status_psi(p)}")
p_jauh = psi(baseline, arus["arus bergeser jauh"])
if p_jauh > AMBANG_DRIFT:
print(f"\nALERT: PSI {p_jauh:.4f} > {AMBANG_DRIFT} -> "
"periksa sumber data dan siapkan retraining")
print("\n== drift yang sama, dilihat dari performa model ==")
rng_m = np.random.default_rng(9)
X_latih = np.vstack([
rng_m.normal([-1.0, 0.0], 0.7, size=(150, 2)),
rng_m.normal([1.0, 0.4], 0.7, size=(150, 2)),
])
y_latih = np.array([0] * 150 + [1] * 150)
w = np.zeros(3)
for _ in range(300):
skor = np.clip(X_latih @ w[1:] + w[0], -30, 30)
p = 1.0 / (1.0 + np.exp(-skor))
galat = p - y_latih
w[1:] -= (X_latih.T @ galat) / 300
w[0] -= galat.mean()
rng_uji = np.random.default_rng(5)
X_uji_normal = np.vstack([
rng_uji.normal([-1.0, 0.0], 0.7, size=(100, 2)),
rng_uji.normal([1.0, 0.4], 0.7, size=(100, 2)),
])
y_uji = np.array([0] * 100 + [1] * 100)
X_uji_geser = X_uji_normal + np.array([1.5, 0.0]) # dunia bergeser
ak_normal = float((((X_uji_normal @ w[1:] + w[0] >= 0).astype(int))
== y_uji).mean())
ak_geser = float((((X_uji_geser @ w[1:] + w[0] >= 0).astype(int))
== y_uji).mean())
print(f"akurasi pada data seperti latih : {ak_normal:.3f}")
print(f"akurasi pada data bergeser : {ak_geser:.3f}")
print("model tidak berubah — dunialah yang berubah")
if __name__ == "__main__":
main()Output yang Dihasilkan:
== PSI dan KL: baseline vs arus data terbaru ==
kondisi PSI KL status
arus stabil 0.0106 0.0053 stabil
arus bergeser sedang 0.1664 0.0833 waspada
arus bergeser jauh 1.1258 0.6932 DRIFT SIGNIFIKAN
ALERT: PSI 1.1258 > 0.25 -> periksa sumber data dan siapkan retraining
== drift yang sama, dilihat dari performa model ==
akurasi pada data seperti latih : 0.945
akurasi pada data bergeser : 0.625
model tidak berubah — dunialah yang berubahMari kita telaah kode di atas baris per baris:
- Baris 18-22 (
proporsi_bin): sampel dipotong ke rentang tepi bin dengannp.clip— nilai ekstrem di luar-3..3menumpuk di bin tepi, tidak dibuang, sehingga ekor panjang tetap terhitung. Smoothing+0.5menjaga proporsi tidak pernah nol; tanpa ini, satu bin kosong membuat logaritma dan pembagian menjadi tak terdefinisi. - Baris 25-29 (
psi): implementasi rumus PSI dalam satu baris NumPy. Tidak ada pustaka tambahan — begitulah cara meyakinkan diri bahwa metrik monitoring bukan kotak hitam. - Baris 32-35 (
kl_divergence): KL dari histogram yang sama. Perhatikan pada keluaran: PSI arus stabil 0.0106 vs KL 0.0053 — keduanya searah, PSI kira-kira dua kali KL untuk pergeseran kecil; pada arus jauh, PSI 1.1258 vs KL 0.6932. Konvensi ambang yang populer memakai PSI karena interpretasinya lebih stabil. - Baris 51-57 (tiga arus): perbedaan ketiga arus hanya pada parameter distribusi — geser rata-rata 0.1, 0.4, dan 1.2 dengan simpangan baku 1.5 pada yang terakhir. Hasilnya jatuh tepat ke tiga pita ambang: 0.0106 stabil, 0.1664 waspada, 1.1258 alarm.
- Baris 66-69 (alarm): alarm tercetak menyebutkan nilai PSI, ambang, dan tindakan — sama seperti disiplin alert di chapter Monitoring Model Produksi .
- Baris 71-99 (performance drift): model dilatih pada data normal, lalu diuji pada data yang digeser
+1.5pada fitur pertama. Akurasi runtuh dari 0.945 ke 0.625 tanpa satu baris kode model berubah — kalimat penutup skrip merangkum semuanya: model tidak berubah, dunialah yang berubah.
Dari Deteksi ke Tindakan #
PSI menjawab “apakah bergeser”; keputusan berikutnya adalah menentukan respons. Pilihan umum dirangkum tabel berikut:
| Kondisi | Tindakan | Catatan |
|---|---|---|
| PSI < 0.10 | tidak ada | catat nilai untuk tren |
| PSI 0.10-0.25 | investigasi sumber data | periksa pipeline input, segmen pengguna baru |
| PSI > 0.25 | retraining dengan data segar | data drift permanen menuntut data latih yang mewakili |
| PSI stabil tapi akurasi turun | curiga concept drift | perlu label baru; bandingkan error per segmen |
Drift menjelaskan mengapa performa bisa merosot; pekerjaan berikutnya adalah memantau performa itu sendiri secara berkelanjutan — termasuk menghadapi kenyataan bahwa label kebenaran tidak pernah tersedia seketika. Chapter berikutnya, Monitoring Kinerja Model , membangun monitor akurasi jendela waktu dengan label terlambat.