Model yang sudah dideploy bukan proyek yang selesai — melainkan sistem yang mulai menua sejak detik pertamanya berjalan. Server bisa kehabisan memori, latensi bisa merayap naik seiring bertambahnya beban, dan yang paling senyap: dunia bisa berubah sehingga input yang datang tidak lagi menyerupai data training, tanpa satu pun error terlihat di log. Monitoring model produksi menjawab ketiga risiko itu dengan pengukuran berkelanjutan dan ambang yang jelas. Chapter ini memetakan apa saja yang dipantau, lalu membangun loop monitoring mini yang benar-benar mendeteksi drift pada arus prediksi sungguhan.
Tiga Kelompok Metrik yang Dipantau #
Metrik sistem menjawab kesehatan layanan: latensi P95/P99 (materi Real-Time Inference ), error rate (persentase request gagal), tingkat pemanfaatan CPU/GPU dan memori, serta status health check yang kita pasang sejak chapter Model Serving . Metrik model menjawab kesehatan keputusan: distribusi kelas prediksi, distribusi skor keyakinan, dan laju input yang ditolak validasi. Metrik bisnis menjawab dampak: laju konversi, keluhan pengguna, atau label yang baru tersedia belakangan untuk evaluasi ulang.
Tabel berikut merangkum ambang dan aksi tipikalnya:
| Metrik | Ambang contoh | Aksi bila terlampaui |
|---|---|---|
| Latensi P95 | > 100 ms | periksa beban, thread, ukur jalur per tahap |
| Error rate | > 1% | telusuri traceback, turunkan versi bila perlu |
| Geser proporsi kelas | > 0,15 dari baseline | periksa drift input, evaluasi ulang model |
| Skor keyakinan rata-rata | turun tajam | input di luar distribusi — periksa preprocessing |
| Health check gagal | 3 kali berurutan | mulai ulang proses, alihkan traffic |
Praktik: Loop Monitoring dengan Deteksi Drift #
Berkas monitoring_mini.py menjalankan dua fase arus request terhadap model terlatih. Fase pertama membangun baseline proporsi kelas; fase kedua menggeser satu fitur input — simulasi dunia yang berubah — sambil terus mencatat latensi nyata tiap request dan membandingkan distribusi prediksi terhadap baseline:
"""Monitoring mini: latensi, distribusi prediksi, dan deteksi drift.
Alur: latih model klasifikasi -> jalankan arus permintaan fase 1
(distribusi normal) sebagai baseline -> jalankan fase 2 (input
digeser, mensimulasikan drift) -> tiap 25 request cetak ringkasan
log -> bandingkan distribusi prediksi kedua fase -> picu alert bila
proporsi kelas bergeser melewati ambang.
"""
import time
import torch
import torch.nn as nn
from sklearn.datasets import make_moons
from torch.utils.data import DataLoader, TensorDataset
torch.manual_seed(42)
X, y = make_moons(n_samples=512, noise=0.20, random_state=42)
X = (X - X.mean(axis=0)) / X.std(axis=0)
loader = DataLoader(
TensorDataset(
torch.tensor(X, dtype=torch.float32),
torch.tensor(y, dtype=torch.long),
),
batch_size=32,
shuffle=True,
num_workers=0,
generator=torch.Generator().manual_seed(42),
)
model = nn.Sequential(nn.Linear(2, 16), nn.ReLU(), nn.Linear(16, 2))
kriteria = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.5)
for _ in range(20):
for X_batch, y_batch in loader:
optimizer.zero_grad()
loss = kriteria(model(X_batch), y_batch)
loss.backward()
optimizer.step()
model.eval()
AMBANG_LATENSI_MS = 5.0
AMBANG_GESER = 0.15 # selisih proporsi kelas yang memicu alert
def proporsi_kelas(riwayat_prediksi):
"""Pecahan prediksi kelas 1 dari seluruh riwayat."""
if not riwayat_prediksi:
return 0.0
return sum(riwayat_prediksi) / len(riwayat_prediksi)
def jalankan_fase(nama, X_fase, riwayat_prediksi):
"""Jalankan arus request: catat latensi nyata dan prediksi."""
latensi = []
for baris in X_fase:
x = torch.tensor([baris], dtype=torch.float32)
mulai = time.perf_counter()
with torch.no_grad():
prediksi = int(model(x).argmax(dim=1))
latensi.append((time.perf_counter() - mulai) * 1000)
riwayat_prediksi.append(prediksi)
posisi = len(riwayat_prediksi)
if posisi % 25 == 0:
batch_baru = latensi[-25:]
p95 = sorted(batch_baru)[23] # nearest-rank untuk 25 sampel
print(f"[{nama}] request {posisi:3d} | kelas_1 "
f"{proporsi_kelas(riwayat_prediksi):.2f} | "
f"P95 {p95:.3f} ms")
return latensi
# ---------- Fase 1: arus normal (baseline) ----------
torch.manual_seed(11)
X_normal = X[:100]
prediksi_semua = []
latensi_1 = jalankan_fase("fase-1", X_normal, prediksi_semua)
baseline = proporsi_kelas(prediksi_semua)
print(f"baseline proporsi kelas_1 : {baseline:.2f}\n")
# ---------- Fase 2: input digeser (simulasi drift) ----------
X_geser = X[100:200].copy()
X_geser[:, 0] += 1.6 # fitur pertama bergeser jauh dari distribusi latih
latensi_2 = jalankan_fase("fase-2", X_geser, prediksi_semua)
# ---------- Ringkasan dan deteksi ----------
semua_latensi = latensi_1 + latensi_2
terurut = sorted(semua_latensi)
print(f"\nlatensi seluruh arus : P50 {terurut[99]:.3f} ms | "
f"P95 {terurut[189]:.3f} ms | max {max(semua_latensi):.3f} ms")
jumlah_overhead = sum(1 for v in semua_latensi if v > AMBANG_LATENSI_MS)
print(f"request melewati ambang {AMBANG_LATENSI_MS:.0f} ms : {jumlah_overhead}")
proporsi_akhir = proporsi_kelas(prediksi_semua[100:])
geser = abs(proporsi_akhir - baseline)
print(f"proporsi kelas_1 fase 2 : {proporsi_akhir:.2f} "
f"(baseline {baseline:.2f}, geser {geser:.2f})")
if geser > AMBANG_GESER:
print(f"ALERT: distribusi prediksi bergeser {geser:.2f} "
f"> ambang {AMBANG_GESER} -> periksa drift input")
else:
print("distribusi prediksi masih dalam ambang normal")
print("catatan: nilai latensi bervariasi antar eksekusi;")
print("prediksi dan deteksi drift deterministik berkat seed.")Output yang Dihasilkan:
[fase-1] request 25 | kelas_1 0.44 | P95 0.036 ms
[fase-1] request 50 | kelas_1 0.46 | P95 0.034 ms
[fase-1] request 75 | kelas_1 0.44 | P95 0.025 ms
[fase-1] request 100 | kelas_1 0.43 | P95 0.038 ms
baseline proporsi kelas_1 : 0.43
[fase-2] request 125 | kelas_1 0.49 | P95 0.024 ms
[fase-2] request 150 | kelas_1 0.54 | P95 0.020 ms
[fase-2] request 175 | kelas_1 0.57 | P95 0.023 ms
[fase-2] request 200 | kelas_1 0.58 | P95 0.020 ms
latensi seluruh arus : P50 0.017 ms | P95 0.027 ms | max 0.182 ms
request melewati ambang 5 ms : 0
proporsi kelas_1 fase 2 : 0.73 (baseline 0.43, geser 0.30)
ALERT: distribusi prediksi bergeser 0.30 > ambang 0.15 -> periksa drift input
catatan: nilai latensi bervariasi antar eksekusi;
prediksi dan deteksi drift deterministik berkat seed.Mari kita telaah kode di atas baris per baris:
- Baris 35-36 (dua ambang): seluruh kebijakan monitoring berdiri di dua konstanta — ambang latensi dan ambang geser distribusi. Monitoring yang sehat selalu bisa dijawab satu pertanyaan: “berapa nilainya, dan apa yang terjadi bila terlampaui?”
- Baris 48-67 (
jalankan_fase): setiap request nyata diukur denganperf_countersementara prediksinya dicatat ke riwayat — dua kelompok metrik (sistem dan model) dikumpulkan dalam satu jalur. Tiap 25 request, satu baris log ringkas dicetak: posisi arus, proporsi kelas berjalan, dan P95 jendela terakhir. - Baris 71-75 (baseline): fase pertama menetapkan apa itu “normal” — proporsi kelas 0,43. Tanpa baseline, tidak ada kata “bergeser”; monitoring selalu membandingkan masa kini terhadap referensi yang dipilih secara sadar.
- Baris 78-80 (simulasi drift): satu fitur digeser
+1.6— dunia berubah tanpa satu pun kode server berubah. Model tetap merespons dengan angka yang sah dan keyakinan yang wajar; tidak ada error yang bisa ditangkap try/except. Hanya distribusi prediksi yang membocorkannya: proporsi kelas 1 merambat dari 0,43 ke 0,73. - Baris 83-101 (deteksi): selisih 0,30 melampaui ambang 0,15 sehingga alert tercetak — lengkap dengan nilai, ambang, dan aksi berikutnya. Latensi sepanjang arus tetap jauh di bawah ambangnya, menegaskan bahwa kedua kelompok metrik saling melengkapi: sistem yang sehat bisa menyembunyikan model yang sudah melenceng.
Dari Loop Mini ke Sistem Monitoring Sungguhan #
Loop di atas memperlihatkan mekanisme inti dalam dua puluh baris; sistem produksi menambahkan tiga lapisan di atasnya. Penyimpanan deret waktu: metrik ditulis ke basis data time-series (misalnya Prometheus atau VictoriaMetrics) alih-alih dicetak, sehingga tren berhari-hari bisa dilihat. Dashboard: grafik P95, error rate, dan distribusi prediksi dalam satu layar untuk tim operasional — pola logging standar Python (docs.python.org/3/library/logging.html
) menjadi fondasi pencatatannya. Alerting: notifikasi terstruktur (email, pesan, tiket) dengan aturan “berapa kali terlampaui dalam berapa menit” agar alarm tidak berbunyi karena satu lonjakan sesaat.
Dengan monitoring, siklus deployment akhirnya menutup penuh: paket produksi dari chapter pertama menjadi layanan yang terukur, teroptimasi, dan terawasi. Section berikutnya membawa kita ke wilayah Production dan Operasional — tempat drift, keandalan, dan biaya menjadi warga kelas satu.