Versioning memberi setiap model identitas permanen, tetapi belum menjawab pertanyaan organisasi: di antara belas versi yang tersimpan, yang mana yang sedang melayani pengguna? Jawaban yang bergantung pada ingatan orang — “yang terakhir kali kita deploy itu lho” — adalah sumber insiden klasik. Model registry menutup celah tersebut dengan menjadi satu-satunya tempat yang mencatat status setiap versi: mana yang masih kandidat, mana yang melayani produksi, dan mana yang sudah pensiun. Chapter ini membangun registry mini lengkap dengan gerbang kualitas dan siklus stage yang benar-benar dieksekusi.
Peran Registry #
Registry adalah single source of truth untuk pertanyaan “model apa yang berjalan sekarang”. Tiga fungsi intinya:
- Status terpusat: setiap versi berada pada satu stage yang jelas; tidak ada jawaban produksi yang bersumber dari ingatan orang.
- Gerbang promosi: versi tidak bisa naik stage tanpa melewati pemeriksaan — metrik minimum, hasil evaluasi, persetujuan.
- Lineage: setiap entri menautkan versi ke run eksperimen, dataset, dan kode yang menghasilkannya, sehingga pertanyaan “dari mana model ini berasal” selalu bisa dijawab.
Siklus Hidup Stage #
Praktik umum (mengikuti terminologi MLflow Model Registry ) memakai empat stage:
| Stage | Arti | Dipindahkan oleh |
|---|---|---|
none / belum terdaftar | hasil eksperimen, belum kandidat rilis | pencatat run |
staging | kandidat yang sedang divalidasi di lingkungan mirip produksi | pipeline evaluasi atau data scientist |
production | melayani traffic nyata | keputusan promosi dengan gerbang |
archived | pensiun, tidak melayani, tetap bisa diaudit | promosi versi pengganti |
Siklusnya membentuk alur satu arah dengan satu cabangan penolakan:
Aturan penting yang jarang ditulis eksplisit: satu registry memelihara paling banyak satu versi di production untuk satu nama model. Ketika versi baru naik, versi lama otomatis turun ke archived — mekanisme inilah yang mencegah dua “model produksi” hidup bersamaan tanpa disadari.
Contoh Teruji: Promosi dengan Gerbang Akurasi #
Berkas registry_mini.py melatih tiga kandidat, mendaftarkannya ke registry.json, lalu mempromosikan satu per satu melewati gerbang akurasi 0.85. Kandidat ketiga sengaja dilatih pada data yang bergeser distribusinya — gerbang harus menahannya:
"""Model registry mini: stage staging -> production -> archived.
Tiga kandidat model dilatih pada data klasifikasi. Setiap kandidat
didaftarkan ke registry (berkas registry.json), lalu dipromosikan
melewati gerbang validasi akurasi. Model yang naik ke production
otomatis mengarsipkan pendahulunya; kandidat yang gagal gerbang
tetap tinggal di daftar tanpa naik stage.
"""
import json
import shutil
import numpy as np
STORE = "registry_store"
AMBANG_AKURASI = 0.85 # gerbang kualitas: akurasi validasi minimum
def buat_data(seed, n=120, geser=0.0):
"""Dua gumpalan Gaussian dua kelas, diacak secara deterministik.
Parameter geser memindahkan kedua gumpalan sejauh nilai tersebut —
simulasi data latih yang berasal dari distribusi berbeda.
"""
rng = np.random.default_rng(seed)
kelas0 = rng.normal(loc=[-1.0 + geser, 0.0], scale=0.7, size=(n, 2))
kelas1 = rng.normal(loc=[1.0 + geser, 0.4], scale=0.7, size=(n, 2))
X = np.vstack([kelas0, kelas1])
y = np.array([0] * n + [1] * n)
urut = rng.permutation(len(y))
X, y = X[urut], y[urut]
batas = min(160, len(y))
return X[:batas], y[:batas], X[160:], y[160:]
def latih(X, y, epoch=300, lr=1.0):
n = len(y)
w = np.zeros(3) # [bias, w1, w2]
for _ in range(epoch):
skor = np.clip(X @ w[1:] + w[0], -30, 30)
p = 1.0 / (1.0 + np.exp(-skor))
galat = p - y
w[1:] -= lr * (X.T @ galat) / n
w[0] -= lr * galat.mean()
return w
def akurasi(w, X, y):
skor = X @ w[1:] + w[0]
return float((((skor >= 0).astype(int)) == y).mean())
def baca_registry():
with open(f"{STORE}/registry.json", encoding="utf-8") as f:
return json.load(f)
def tulis_registry(reg):
with open(f"{STORE}/registry.json", "w", encoding="utf-8") as f:
json.dump(reg, f, indent=2)
def daftarkan(reg, nama, versi, metrik):
reg.append({"nama": nama, "versi": versi, "stage": "tidak_lolos",
"akurasi_valid": round(metrik, 3)})
tulis_registry(reg)
print(f" daftar {nama} v{versi}: akurasi_valid={metrik:.3f}")
def promosikan(reg, nama, versi, target):
entri = next(e for e in reg if e["nama"] == nama and e["versi"] == versi)
if entri["akurasi_valid"] < AMBANG_AKURASI:
print(f" GATE GAGAL {nama} v{versi} -> {target}: akurasi "
f"{entri['akurasi_valid']:.3f} < {AMBANG_AKURASI:.2f}")
return
if target == "production":
for lain in reg: # pendahulu di production otomatis diarsipkan
if lain["stage"] == "production" and lain is not entri:
lain["stage"] = "archived"
print(f" {lain['nama']} v{lain['versi']} -> archived "
f"(digantikan di production)")
entri["stage"] = target
tulis_registry(reg)
print(f" promosi {nama} v{versi} -> {target}")
def tampilkan(reg, judul):
print(f"\n{judul}")
print(f" {'model':<12} {'versi':>5} {'akurasi':>8} stage")
for e in reg:
print(f" {e['nama']:<12} {e['versi']:>5} "
f"{e['akurasi_valid']:>8.3f} {e['stage']}")
def main():
shutil.rmtree(STORE, ignore_errors=True)
import os
os.makedirs(STORE)
# data validasi tetap — semua kandidat diukur dengan tolok ukur sama
_, _, X_valid, y_valid = buat_data(seed=42)
print("== melatih tiga kandidat model ==")
X1, y1, _, _ = buat_data(seed=42) # data sehat
X2, y2, _, _ = buat_data(seed=43, n=240) # data 2x lebih besar
X3, y3, _, _ = buat_data(seed=44, geser=1.5) # data bergeser
ak1, ak2, ak3 = (akurasi(latih(X1, y1), X_valid, y_valid),
akurasi(latih(X2, y2, epoch=600), X_valid, y_valid),
akurasi(latih(X3, y3), X_valid, y_valid))
print(f" kandidat A (data sehat) : {ak1:.3f}")
print(f" kandidat B (data 2x + epoch 2x) : {ak2:.3f}")
print(f" kandidat C (data latih bergeser) : {ak3:.3f}")
reg = []
print("\n== operasi registry ==")
daftarkan(reg, "klasifikasi", "1", ak1)
promosikan(reg, "klasifikasi", "1", "staging")
promosikan(reg, "klasifikasi", "1", "production")
daftarkan(reg, "klasifikasi", "2", ak2)
promosikan(reg, "klasifikasi", "2", "staging")
promosikan(reg, "klasifikasi", "2", "production")
daftarkan(reg, "klasifikasi", "3", ak3)
promosikan(reg, "klasifikasi", "3", "staging")
reg = baca_registry()
tampilkan(reg, "== status akhir registry (dibaca ulang dari disk) ==")
produksi = next(e for e in reg if e["stage"] == "production")
print(f"\nmodel produksi saat ini: {produksi['nama']} v{produksi['versi']}"
f" (akurasi {produksi['akurasi_valid']:.3f})")
if __name__ == "__main__":
try:
main()
finally:
shutil.rmtree(STORE, ignore_errors=True)Output yang Dihasilkan:
== melatih tiga kandidat model ==
kandidat A (data sehat) : 0.925
kandidat B (data 2x + epoch 2x) : 0.938
kandidat C (data latih bergeser) : 0.738
== operasi registry ==
daftar klasifikasi v1: akurasi_valid=0.925
promosi klasifikasi v1 -> staging
promosi klasifikasi v1 -> production
daftar klasifikasi v2: akurasi_valid=0.938
promosi klasifikasi v2 -> staging
klasifikasi v1 -> archived (digantikan di production)
promosi klasifikasi v2 -> production
daftar klasifikasi v3: akurasi_valid=0.738
GATE GAGAL klasifikasi v3 -> staging: akurasi 0.738 < 0.85
== status akhir registry (dibaca ulang dari disk) ==
model versi akurasi stage
klasifikasi 1 0.925 archived
klasifikasi 2 0.938 production
klasifikasi 3 0.738 tidak_lolos
model produksi saat ini: klasifikasi v2 (akurasi 0.938)Mari kita telaah kode di atas baris per baris:
- Baris 19-33 (
buat_data): parametergesermemindahkan kedua gumpalan kelas sejauh nilai tersebut. Kandidat C dilatih pada data bergeser 1.5 — simulasi dataset dari sumber berbeda yang sekilas tampak wajar. - Baris 63-67 (
daftarkan): pendaftaran hanya menulis fakta (nama, versi, metrik) tanpa penilaian. Stage awalnyatidak_lolos— registry tidak menganggap siapa pun layak sebelum dibuktikan. - Baris 70-84 (
promosikan): inti registry. Pemeriksaanakurasi_valid < AMBANGmenghentikan promosi dengan pesan yang menyebutkan angka, ambang, dan versi. Untuk targetproduction, seluruh entri lain yang sedang berada diproductiondipindahkan kearchivedlebih dulu. - Baris 87-92 (
tampilkan): status registry dibaca ulang dari disk dan dicetak sebagai tabel — satu tabel inilah jawaban resmi atas “model apa yang berjalan sekarang”. - Baris 116-129 (urutan operasi): v1 naik dua tahap menjadi production pertama; v2 naik dan otomatis mengarsipkan v1; v3 ditahan gerbang. Tiga pola dasar siklus — promosi, penggantian, penolakan — semuanya terjadi dalam belasan baris.
Dua hal pada keluaran layak digarisbawahi. Pertama, baris klasifikasi v1 -> archived (digantikan di production) terjadi sebelum promosi klasifikasi v2 -> production — pengarsipan pendahulu adalah bagian dari transaksi promosi, bukan tindakan terpisah yang bisa terlupakan. Kedua, kandidat C tetap berada di registry dengan metriknya — gagal gerbang bukan berarti dihapus; jejak kegagalan adalah bahan audit juga.
Dari Registry File ke Alat Ekosistem #
Registry mini dengan registry.json cukup untuk satu tim kecil. MLflow Model Registry
menambahkan di atasnya: antarmuka web untuk melihat dan memindahkan stage, API transition_model_version_stage yang bisa dipanggil pipeline otomatis, anotasi dan deskripsi per versi, serta notifikasi ketika stage berubah. Stage dan aturan gerbangnya tetap milik kita — alat hanya menyediakan tempat dan mekanismenya.
Registry menjawab “versi mana yang boleh melayani”. Pertanyaan berikutnya: siapa yang melatih kandidat-kandidat itu secara teratur, dan siapa yang memanggil fungsi promosikan tanpa menunggu manusia? Chapter berikutnya, Pipeline Training Otomatis
, merangkai seluruh proses menjadi alur yang berjalan sendiri.