model.fit() menuntaskan 90% kebutuhan pelatihan, tetapi konsensus itu berbalik ketika aturan mainnya berada di luar kendalinya. Loss yang menggabungkan beberapa komponen dengan bobot berubah-ubah, pembaruan bobot yang bergantung kondisi antar batch, pelatihan dua model yang saling mengejar seperti pada GAN, atau sekadar kebutuhan mencatat gradien per batch — semuanya menuntut kita memegang kemudi tiap langkah. Kabar baiknya, seluruh bahan sudah kita kuasai: GradientTape dari Chapter Automatic Differentiation
, optimizer dari Chapter Loss dan Optimizer
, dan pasokan batch dari Chapter Dataset dan tf.data
. Chapter ini merakit semuanya menjadi loop pelatihan utuh yang setara fit() — lalu memetakan setiap bagiannya terhadap training loop PyTorch yang sudah kita tulis.
Anatomi Loop: Epoch, Batch, Tape #
Satu epoch menelusuri seluruh data latih batch demi batch. Di dalamnya, setiap batch melewati lima langkah — dan kelima langkah itu kini kita tulis sendiri, bukan dijalankan oleh fit():
Dua hal membedakan pola ini dari PyTorch. Pertama, tidak ada zero_grad — tape dibuat baru untuk setiap batch, sehingga gradien selalu dihitung dari kondisi bobot terkini dan tidak pernah menumpuk sisa batch sebelumnya. Kedua, mode latih dan evaluasi tidak diatur lewat model.train() atau model.eval(), melainkan lewat argumen training=True/False saat memanggil model — keduanya mengendalikan perilaku layer seperti Dropout dan BatchNormalization yang akan kita bahas pada section Regularisasi dan Generalisasi
.
Loop Utuh yang Diuji Nyata #
Berkas loop_custom.py melatih MLP [2, 16, 2] pada make_moons 300 sampel: 225 sampel latih dialirkan lewat pipeline tf.data, 75 sampel validasi dinilai tiap akhir epoch:
"""Custom training loop: GradientTape, apply_gradients, train vs eval step."""
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
import numpy as np
import tensorflow as tf
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
np.random.seed(42)
tf.keras.utils.set_random_seed(42)
# ---------- 1. Data: make_moons 300 sampel + standardisasi ----------
X, y = make_moons(n_samples=300, noise=0.20, random_state=42)
X_latih, X_val, y_latih, y_val = train_test_split(
X, y, test_size=0.25, random_state=42
)
rata2 = X_latih.mean(axis=0)
simpangan = X_latih.std(axis=0)
X_latih = ((X_latih - rata2) / simpangan).astype(np.float32)
X_val = ((X_val - rata2) / simpangan).astype(np.float32)
y_latih = y_latih.astype(np.int32)
y_val = y_val.astype(np.int32)
# ---------- 2. Pasokan batch lewat tf.data (Chapter sebelumnya) ----------
dataset = (
tf.data.Dataset.from_tensor_slices((X_latih, y_latih))
.shuffle(buffer_size=len(X_latih), seed=42)
.batch(32)
.prefetch(tf.data.AUTOTUNE)
)
# ---------- 3. Model, loss, optimizer ----------
model = tf.keras.Sequential(
[
tf.keras.Input(shape=(2,)),
tf.keras.layers.Dense(16, activation="relu"),
tf.keras.layers.Dense(2),
]
)
loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
optimizer = tf.keras.optimizers.SGD(learning_rate=0.5)
# ---------- 4. Dua langkah inti: latih dan evaluasi ----------
def langkah_latih(X_batch, y_batch):
"""Satu batch pelatihan: forward dalam tape -> loss -> gradien -> update."""
with tf.GradientTape() as tape:
logits = model(X_batch, training=True)
loss = loss_fn(y_batch, logits)
gradien = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradien, model.trainable_variables))
return loss
def langkah_eval(X, y):
"""Evaluasi seluruh subset di luar tape: tanpa gradien, tanpa pembaruan."""
logits = model(X, training=False)
loss = loss_fn(y, logits)
benar = tf.cast(tf.equal(tf.argmax(logits, axis=1), y), tf.float32)
return loss, tf.reduce_mean(benar)
# ---------- 5. Loop epoch-batch utuh ----------
JUMLAH_EPOCH = 20
for epoch in range(1, JUMLAH_EPOCH + 1):
total_loss = 0.0
jumlah_batch = 0
for X_batch, y_batch in dataset:
total_loss += float(langkah_latih(X_batch, y_batch))
jumlah_batch += 1
loss_latih = total_loss / jumlah_batch
loss_val, akurasi_val = langkah_eval(X_val, y_val)
if epoch in (1, 5, 10, 15, 20):
print(f"epoch {epoch:2d} | loss latih {loss_latih:.4f} | "
f"loss val {float(loss_val):.4f} | akurasi val {float(akurasi_val):.3f}")
# ---------- 6. Akurasi akhir kedua subset ----------
_, akurasi_latih = langkah_eval(X_latih, y_latih)
print()
print(f"akurasi akhir: latih {float(akurasi_latih):.3f} | "
f"validasi {float(akurasi_val):.3f}")Output yang Dihasilkan:
epoch 1 | loss latih 0.4960 | loss val 0.2846 | akurasi val 0.893
epoch 5 | loss latih 0.2718 | loss val 0.2292 | akurasi val 0.920
epoch 10 | loss latih 0.2893 | loss val 0.2498 | akurasi val 0.907
epoch 15 | loss latih 0.2369 | loss val 0.1869 | akurasi val 0.920
epoch 20 | loss latih 0.2236 | loss val 0.1787 | akurasi val 0.933
akurasi akhir: latih 0.916 | validasi 0.933Mari kita telaah kode di atas baris per baris:
- Baris 12-13 (seed ganda): data, inisialisasi bobot, dan urutan
shuffle(seed=42)terkunci pada satu nilai seed sehingga keluaran identik di setiap eksekusi. - Baris 16-26 (data dan standardisasi): 225 sampel latih dan 75 sampel validasi; rata-rata dan simpangan dihitung dari data latih saja lalu diterapkan ke keduanya — kebiasaan yang sama dengan Chapter Training Loop PyTorch .
- Baris 29-34 (pasokan batch): pipeline
tf.datadari chapter sebelumnya — shuffle ber-seed, batch 32, prefetch — menggantikanDataLoader; 225 sampel menghasilkan 8 batch per epoch. - Baris 37-45 (tiga komponen): model dibangun tanpa
compile— loop kustom tidak membutuhkannya;loss_fndanoptimizerdipakai sebagai objek biasa, bukan argumencompile. - Baris 49-56 (
langkah_latih): seluruh forward pass berada di dalamwith tf.GradientTape()— syarat agar operasi terekam;tape.gradient(loss, model.trainable_variables)menghasilkan daftar gradien yang sejajar dengan daftar bobot;apply_gradients(zip(...))memasangkan keduanya lalu memperbarui bobot. - Baris 59-64 (
langkah_eval): evaluasi berjalan di luar tape — tanpa perekaman, tanpa pembaruan — dengantraining=Falseagar layer berperilaku mode inferensi; akurasi dihitung dariargmaxlogits dibanding label. - Baris 68-76 (loop utama): loop dalam menghitung rata-rata loss latih dari seluruh batch; loop luar menutup tiap epoch dengan evaluasi validasi — kerangka yang sama dengan cetakan PyTorch, hanya bahasanya yang berbeda.
- Baris 78-80 (log berkala): mencetak lima epoch terpilih mencegah terminal penuh teks pada pelatihan panjang.
- Baris 83-86 (akurasi akhir): akurasi latih
0.916dan validasi0.933— selisih kecil yang menandakan model belajar tanpa terjebak menghafal data latih.
Padanan API dengan PyTorch #
Cetakan loop ini sengaja disusun sejajar dengan Chapter Training Loop versi PyTorch agar kedua framework bisa dibaca berdampingan. Tabel berikut memetakan setiap baris kuncinya:
| Peran | PyTorch | TensorFlow/Keras |
|---|---|---|
| Bersihkan gradien lama | optimizer.zero_grad() | tidak diperlukan — tape baru tiap batch |
| Forward pass | logits = model(X_batch) | logits = model(X_batch, training=True) |
| Hitung gradien | loss.backward() lalu baca param.grad | tape.gradient(loss, model.trainable_variables) |
| Perbarui bobot | optimizer.step() | optimizer.apply_gradients(zip(gradien, vars)) |
| Mode latih / evaluasi | model.train() / model.eval() | argumen training=True / training=False |
| Prediksi tanpa graf | with torch.no_grad(): | operasi di luar blok with GradientTape() |
Rujukan resmi untuk pola ini — termasuk versi yang dibungkus kelas keras.Model dengan train_step kustom — tersedia di tensorflow.org/guide/keras
dan halaman API tf.GradientTape
; daftar metode optimizer seperti apply_gradients di keras.io/api/optimizers
.
Ringkasan #
Custom training loop memberi kendali penuh: lima langkah yang biasa disembunyikan fit() — forward dalam tape, loss, gradien, pembaruan bobot, evaluasi — kini kita tulis eksplisit dan bisa kita ubah di titik mana pun. Loop teruji ini melatih model sampai akurasi validasi 0.933 dengan struktur yang persis sepadan dengan versi PyTorch-nya, sehingga berpindah framework tinggal soal menerjemahkan tabel padanan di atas. Model yang telah terlatih menimbulkan pertanyaan berikutnya: seberapa baik sebenarnya kinerjanya? Chapter berikutnya menjawab dengan evaluate, predict, confusion matrix, dan ambang keputusan.