Kontrol level dewa tanpa menulis ulang library: sistem callback Ultralytics dengan hook on_train_epoch_end dan on_val_end, integrasi W&B/MLflow, modifikasi perilaku training, serta praktik logging eksperimen lengkap

Setelah tracking di episode 16, kita kembali ke sisi training untuk membuka kontrol terakhir yang belum kalian miliki: menyisipkan logika kustom di tengah siklus hidup Ultralytics — tanpa fork library, tanpa menulis ulang training loop.
Kebutuhan ini muncul lebih cepat dari dugaan: tim minta notifikasi Slack tiap epoch selesai, manajemen ingin grafik mAP real-time di W&B, atau kalian perlu snapshot otomatis ke storage saat mAP menembus target. Semuanya adalah pekerjaan hook — dan Ultralytics menyediakan sistem callback yang rapi untuk itu.
Training Ultralytics memancarkan event pada titik-titik penting; callback kalian "menempel" di event tersebut:
Cara paling bersih memasangnya: dekorator @hub tidak diperlukan — cukup daftarkan fungsi ke trainer lewat argumen callbacks:
from ultralytics import YOLO
def on_epoch_end(trainer):
e = trainer.epoch
loss = trainer.loss_items.mean().item() if trainer.loss_items else -1
print(f"[HOOK] epoch {e + 1} selesai | loss rata-rata {loss:.3f}")
def on_fit_end(trainer):
print(f"[HOOK] training selesai! best.pt: {trainer.best}")
model = YOLO("yolo26n.pt")
model.add_callback("on_train_epoch_end", on_epoch_end)
model.add_callback("on_train_end", on_fit_end)
model.train(data="proyek-helm/data.yaml", epochs=10, name="cb-demo")Objek trainer yang diterima callback adalah pintu ke semua state: trainer.epoch, trainer.loss_items, trainer.best, sampai trainer.metrics. Untuk event inference ada pasangannya (on_predict_start, dsb.), sehingga pola ini juga berlaku saat serving.
| Hook | Saat Terpanggil | Use Case Tipikal |
|---|---|---|
on_train_start | Sekali sebelum loop | Validasi config, catat versi dataset |
on_train_epoch_end | Tiap akhir epoch | Logging kustom, notifikasi |
on_model_save | Tiap checkpoint disimpan | Upload best.pt ke cloud storage |
on_val_end | Tiap validasi selesai | Gate mAP, alert regresi |
on_train_end | Akhir seluruh run | Ringkasan, cleanup, deploy trigger |
Untuk experiment tracking serius, dua jalur tersedia:
Ultralytics punya integrasi siap pakai — cukup install package-nya:
pip install wandb
export WANDB_API_KEY=xxxxx
yolo detect train model=yolo26n.pt data=proyek-helm/data.yaml epochs=30Dengan integrasi aktif, tiap run mengirim kurva loss, mAP per epoch, config lengkap, bahkan artefak best.pt — tanpa satu baris kode callback pun.
Kalau butuh kendali penuh (misal hanya metrik tertentu, atau platform internal):
import mlflow
from ultralytics import YOLO
mlflow.set_experiment("proyek-helm")
def log_metrics(trainer):
metrics = trainer.metrics # dict mAP50, mAP50-95, dll.
mlflow.log_metrics(
{k: float(v) for k, v in metrics.items()},
step=trainer.epoch,
)
model = YOLO("yolo26n.pt")
model.add_callback("on_fit_epoch_end", log_metrics)
mlflow.log_params({"epochs": 40, "imgsz": 640, "data": "helm-v2"})
model.train(data="proyek-helm/data.yaml", epochs=40)Perbedaan penting kedua jalur: integrasi bawaan = nol effort tapi format tetapannya bawaan; callback kustom = sedikit kode tapi kalian menentukan persis apa yang dicatat. Untuk tim dengan standar tracking sendiri, jalur kedua adalah jalan yang benar — fondasinya sudah kita siapkan lewat disiplin eksperimen episode 15.
Note
Aturan praktis: mulai dari integrasi bawaan sampai benar-benar terhalang keterbatasan. Callback kustom yang ditulis terburu-buru sering menjadi beban maintenance — setiap upgrade versi ultralytics bisa mengubah nama hook/atribut trainer, dan callback lama bisa diam-diam berhenti bekerja.
Callback juga dipakai untuk mengubah jalannya training, bukan cuma mencatat:
from ultralytics import YOLO
TARGET_MAP = 0.85
def gate_target(trainer):
map5095 = float(trainer.metrics.get("metrics/mAP50-95(B)", 0))
if map5095 >= TARGET_MAP:
trainer.stop_training = True # sinyal stop elegan
print(f"Target {TARGET_MAP} tercapai ({map5095:.3f}) — berhenti awal")
model = YOLO("yolo26n.pt")
model.add_callback("on_fit_epoch_end", gate_target)
model.train(data="proyek-helm/data.yaml", epochs=100)Contoh lain yang lazim di produksi: auto-upload checkpoint ke S3 di on_model_save, kirim webhook Discord/Slack di on_train_end, atau dump sample prediksi tiap N epoch untuk review visual. Modifikasi loss function secara dalam (mengganti komponen CIoU/BCE) secara teknis mungkin lewat subclassing trainer — tapi itu ranah riset, bukan produksi; hampir selalu ada parameter resmi yang lebih aman.
"on_train_epoch_end" vs "on_fit_epoch_end" — keduanya ada dan momennya beda; cek daftar resmi sebelum debug kenapa hook tak terpanggil.Rangkuman episode ini:
on_train_epoch_end, on_model_save, on_train_end) lewat model.add_callback.trainer memberi akses penuh ke state run: epoch, loss, metrics, path best.Di episode 18 kita keluar dari zona nyaman PyTorch: export model ke ONNX, TensorRT, CoreML, dan OpenVINO — cara membaca peta 20+ format, jebakan engine TensorRT yang tidak portable, dan protokol verifikasi agar hasil export identik dengan asalnya. Sampai jumpa!