Episode ini adalah yang paling “matematika” sejauh seri ini, dan saya menulisnya sambil sedikit gemetar. Tapi ternyata inti dari melatih model bisa dijelaskan tanpa satu pun turunan yang ditulis lengkap — cukup dengan analogi yang familiar buat programmer: training itu optimasi parameter, dan semua istilah menakutkannya cuma bagian dari satu loop yang sama.
Loss function: mengukur seberapa salah
Sebelum bisa belajar, model butuh cara mengukur kesalahannya. Itu tugas loss function: fungsi yang menerima prediksi dan jawaban benar, lalu mengembalikan angka “seberapa buruk”. Prediksi pas dengan kunci jawaban? Loss mendekati nol. Melenceng jauh? Loss membengkak.
Dua contoh yang paling umum:
- MSE (Mean Squared Error) — untuk regresi. Rata-rata kuadrat selisih prediksi vs kenyataan. Dikuadratkan supaya error besar dihukum jauh lebih berat.
- Cross-entropy — untuk klasifikasi. Menghukum prediksi yang percaya diri tapi salah.
Analogi programmer: loss function itu seperti assertion di test suite — dia yang menentukan lulus atau tidaknya. Bedanya, kita tidak berhenti di lulus/gagal, tapi menghitung seberapa jauh dari lulus.
Gradient descent: turun gunung dalam gelap
Sekarang pertanyaan besarnya: kalau loss mengukur kesalahan, siapa yang memperbaiki parameter? Jawabannya gradient descent, dan analogi klasiknya masih terbaik yang pernah saya baca: kita berdiri di lereng gunung berkabut, ingin mencapai lembah (loss minimum). Kabut tebal — tidak bisa lihat peta. Yang bisa dilakukan: meraba kemiringan tanah di bawah kaki, lalu melangkah ke arah yang paling menurun. Ulangi ribuan kali.
“Meraba kemiringan” itulah gradien — arah penurunan loss terhadap tiap parameter. Framework seperti PyTorch menghitungnya otomatis lewat diferensiasi otomatis; kita tidak perlu menurunkan rumus manual. Loop pelatihannya secara konsep sesederhana ini:
for epoch in range(epochs):
y_pred = model(X_train) # 1. forward pass
loss = loss_fn(y_pred, y_train) # 2. hitung loss
optimizer.zero_grad() # 3. reset gradien
loss.backward() # 4. hitung gradien
optimizer.step() # 5. update bobotLima baris ini adalah seluruh deep learning pada dasarnya. Forward, ukur, mundur, geser. Ulangi.
Epoch: berapa kali membaca buku yang sama
Satu epoch = satu putaran penuh model melewati seluruh data latih. Seperti membaca ulang buku yang sama: sekali belum tentu hafal, sepuluh kali mulai nempel, seratus kali malah hafal kata per kata tanpa paham (ini overfitting — bahas tersendiri nanti). Terlalu sedikit epoch, model belum belajar; terlalu banyak, dia menghafal.
Learning rate: seberapa besar langkah
Learning rate menentukan panjang langkah tiap update bobot. Ini hyperparameter paling sensitif yang saya temui sejauh ini:
- Terlalu besar — langkah melompati lembah, loss naik-turun liar atau malah divergen.
- Terlalu kecil — progres nyaris tidak terasa, training makan waktu berjam-jam untuk hasil biasa saja.
- Pas — loss turun stabil lalu melandai.
| Istilah | Analogi | Risiko salah setting |
|---|---|---|
| Loss function | Assertion di test suite | Mengukur hal yang salah |
| Gradient descent | Turun gunung berkabut | Terjebak di titik tinggi |
| Epoch | Baca ulang buku | Underfit / overfit |
| Learning rate | Panjang langkah | Divergen / lambat sekali |
Visualisasi loss curve: mata ke dalam training
Saran paling practical yang bisa saya berikan: selalu plot loss curve. Tanpa grafik, training seperti mengemudi menutup mata. Kode berikut menghasilkan kurva training loss vs validation loss — dua garis ini menceritakan hampir segalanya tentang kondisi model Anda:
import matplotlib.pyplot as plt
train_losses = [0.92, 0.61, 0.45, 0.33, 0.25, 0.20, 0.17, 0.15, 0.14, 0.13]
val_losses = [0.89, 0.58, 0.44, 0.36, 0.32, 0.30, 0.31, 0.33, 0.35, 0.38]
plt.plot(train_losses, label="Training Loss")
plt.plot(val_losses, label="Validation Loss")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.title("Training vs Validation Loss")
plt.show()Jika training loss turun tapi validation loss mulai naik dari epoch ke-6, itu tanda overfitting — model mulai menghafal data latih. Jika kedua-duanya masih tinggi, model belum cukup belajar atau arsitekturnya terlalu sederhana. Kurva ini adalah diagnosis yang jauh lebih informatif daripada sekadar melihat angka akhir.
Batch vs mini-batch vs stochastic gradient descent
Dalam loop gradient descent di atas, pertanyaannya: berapa banyak data yang dilihat sebelum satu langkah update?
Stochastic Gradient Descent (SGD): update bobot setelah satu sampel. Cepat per langkah, tapi gradien sangat noise — seperti menuruni gunung dengan langkah kecil-kecil acak. Batch Gradient Descent: update setelah melihat seluruh dataset. Gradien stabil, tapi menghitungnya sekaligus untuk jutaan sampel membutuhkan memori besar. Mini-batch: kompromi — update setelah sekelompok kecil data (misalnya 32, 64, atau 256 sampel). Ini yang paling umum dipakai di dunia nyata.
| Metode | Sampel per update | Kecepatan langkah | Stabilitas gradien | Memori |
|---|---|---|---|---|
| Stochastic (SGD) | 1 | Sangat cepat | Noise tinggi | Sangat kecil |
| Mini-batch | 32–256 | Cepat | Sedang | Sedang |
| Batch | Seluruh dataset | Lambat | Sangat stabil | Besar |
Dalam praktiknya, library seperti PyTorch menyediakan DataLoader yang memecah dataset menjadi mini-batch secara otomatis. Cukup tentukan batch_size, sisanya diurus framework.
Learning rate scheduling: langkah yang cerdas
Learning rate tetap (constant) berfungsi, tapi ada strategi yang lebih baik: mulai dengan langkah besar untuk eksplorasi cepat, lalu perkecil seiring model mendekati titik optimal. Beberapa teknik populer:
Step decay: kurangi learning rate secara berkala (misalnya dibagi 10 setiap 30 epoch). Sederhana dan sering efektif. Cosine annealing: kurangi learning rate mengikuti kurva cosinus — pelan di awal, cepat di tengah, pelan lagi di akhir. Hasilnya lebih halus. Warmup: mulai dengan learning rate sangat kecil (misalnya 1e-7), naikkan perlahan ke target, lalu turunkan. Warmup mencegah gradien liar di awal training saat bobot masih acak.
from torch.optim.lr_scheduler import StepLR, CosineAnnealingLR, OneCycleLR # Step decay: kurangi lr setiap 30 epoch scheduler = StepLR(optimizer, step_size=30, gamma=0.1) # Cosine annealing: kurangi lr mengikuti kurva cosinus scheduler = CosineAnnealingLR(optimizer, T_max=100) # Warmup + cosine (OneCycleLR): kombinasi umum di practice scheduler = OneCycleLR(optimizer, max_lr=0.01, total_steps=epochs)
Pengalaman saya: switching dari constant LR ke OneCycleLR sering memberikan peningkatan akurasi 2–5% tanpa mengubah arsitektur model sama sekali. Hyperparameter kecil, dampak besar.
Optimizer: lebih dari vanilla SGD
Vanilla SGD memperbarui bobot dengan rumus sederhana: w = w - lr * gradient. Tapi ada optimizer yang lebih canggih:
| Optimizer | Ide utama | Kapan pakai |
|---|---|---|
| SGD + momentum | Akumulasi gradien sebelumnya seperti bola menggelinding | CV tasks, robust & well-tuned |
| RMSprop | Adaptif learning rate per-parameter | RNN, data sequence |
| Adam | Momentum + adaptif LR (kombinasi dua ide di atas) | Default paling aman untuk hampir semua kasus |
| AdamW | Adam + weight decay yang benar | Transformer, NLP, fine-tuning |
Saran praktis: mulai dengan Adam atau AdamW — keduanya memiliki default yang bekerja dengan baik di banyak task. Jika Anda perlu squeeze performa ekstra, barulah beralih ke SGD + momentum dengan learning rate yang di-tuning manual. AdamW khususnya menjadi standar di training model transformer (GPT, BERT, dsb.) karena weight decay yang lebih benar dibandingkan Adam biasa.
Early stopping: berhenti sebelum terlambat
Jika validation loss mulai naik sementara training loss terus turun, itu tanda overfitting. Early stopping mengatasi masalah ini dengan memantau validation loss dan menghentikan training jika tidak ada perbaikan selama beberapa epoch berturut-turut (disebut patience).
best_val_loss = float('inf')
patience = 5
patience_counter = 0
for epoch in range(max_epochs):
train_loss = train_one_epoch(model, train_loader)
val_loss = evaluate(model, val_loader)
if val_loss < best_val_loss:
best_val_loss = val_loss
patience_counter = 0
torch.save(model.state_dict(), "best_model.pt")
else:
patience_counter += 1
if patience_counter >= patience:
print(f"Early stopping di epoch {epoch}")
breakKode di atas hanya 15 baris, tapi menyelamatkan banyak waktu pelatihan dan mencegah model terlalu menghafal data latih. Ini seperti code review yang tahu kapan harus bilang “cukup, jangan tambah fitur lagi.”
Vanishing & exploding gradient: musuh dalam jaringan dalam
Ketika jaringan memiliki banyak layer, gradien harus dikalikan melewati semua layer selama backpropagation. Masalahnya:
- Vanishing gradient: jika nilai gradien < 1, perkalian berulang membuatnya mendekati nol. Layer awal hampir tidak pernah diperbarui. Model di layer dangkal belajar, yang dalam tidak.
- Exploding gradient: kebalikannya — gradien membengkak menjadi angka sangat besar, bobot berubah drastis, training divergen. Loss tiba-tiba menjadi NaN.
Penyebab utama: fungsi aktivasi sigmoid/tanh yang mempersempit gradien (sigmoid memotong gradien di 0.25 maksimal per layer), dan inisialisasi bobot yang kurang tepat.
Solusi: Batch normalization menormalisasi output setiap layer sehingga gradien tetap di skala wajar. Residual connection (skip connection) seperti di ResNet memberi jalur alternatif bagi gradien, memotong jalur panjang. Dan penggunaan fungsi aktivasi ReLU (atau varian seperti LeakyReLU) menghindari saturasi negatif yang menyebabkan vanishing gradient. Ketiga teknik ini adalah alasan arsitektur dalam ratusan layer bisa dilatih secara stabil.
Analogi untuk programmer: training loop = CI/CD pipeline
Jika Anda terbiasa dengan CI/CD, training loop terasa mirip: data latih adalah “kode”, model adalah “build artifact”, loss function adalah “test suite”, dan epoch adalah “pipeline run” yang berulang. Early stopping seperti conditional deployment — hentikan jika metrik memburuk. Learning rate scheduling seperti progressive rollout: mulai agresif, perlahan stabilkan. Batch size seperti parallelism dalam pipeline: lebih besar lebih cepat tapi butuh lebih banyak resources. Analologi ini membantu saya melatih mentalitas: training bukan sekali jalan lalu selesai, tapi proses iteratif yang membutuhkan monitoring dan tuning berkelanjutan.
Pitfall: membaca kurva training yang salah
Kesalahan umum yang saya buat di awal:
- Learning rate terlalu besar: loss naik-turun liar, seperti grafik yang “bergetar”. Saya awalnya mengira model sedang “belajar dengan semangat” — ternyata justru melompati minimum dan tidak pernah konvergen.
- Learning rate terlalu kecil: loss turun tapi nyaris horizontal. “Perlahan pasti sampai” tidak berlaku di sini — yang terjadi adalah buang waktu komputasi tanpa hasil yang layak.
- Overfitting terlihat dari kurva: training loss mendekati nol, validation loss naik. Model sudah “menghafal jawaban ujian” bukan memahami polanya.
- Underfitting: kedua loss masih tinggi. Modelnya kurang kompleks untuk menangkap pola data, atau epoch belum cukup.
Catatan penting: jangan hanya melihat angka loss akhir. Perhatikan shape kurva — apakah turun monoton, naik-turun liar, atau sudah plateau. Bentuk kurva memberi petunjuk diagnostik yang jauh lebih kaya daripada satu angka terakhir.
Catatan untuk diri sendiri
Kesulitan terbesar saya: dulu menganggap training sebagai kotak hitam ajaib. Setelah memecahnya jadi lima langkah di loop di atas, rasa takutnya hilang. Saran praktis kalau baru mulai: plot kurva loss per epoch. Grafik itu adalah “kamera” satu-satunya ke dalam proses training — naik berarti learning rate kebesaran, datar berarti terlalu kecil atau model salah arsitektur.
Pengalaman pertama yang tidak akan saya lupakan: ketika training loop berjalan dan saya melihat angka loss turun dari 2.3 menjadi 0.8 dalam beberapa epoch pertama — perasaan itu seperti melihat “Hello World!” pertama kali. Angka abstrak tiba-tiba bermakna. Model sedang belajar. Bukan sihir, bukan kotak hitam — hanya gradien, loss, dan iterasi yang bekerja persis seperti yang seharusnya. Sejak saat itu saya percaya: siapa pun yang bisa menulis for-loop bisa memahami deep learning.
Komentar Terbaru