Pada Chapter Loss dan Optimizer
kita sudah menjalankan model.fit() dengan tiga argumen dan mendapat model terlatih dalam sebaris kode. Bab ini membedah panggilan tersebut secara utuh: bagaimana data validasi disisipkan lewat validation_split atau validation_data, apa saja yang terekam di objek History, dan bagaimana callback mengubah fit() dari mesin buta jumlah epoch menjadi pelatihan yang bisa berhenti pada saat tepat serta menyimpan model terbaiknya sendiri. Kita akan membandingkan dua konfigurasi pada dataset yang sama — SGD dengan validation_split versus Adam dengan EarlyStopping dan ModelCheckpoint — lalu membaca hasilnya dari tabel history.
Anatomi model.fit #
model.compile() menyiapkan tiga bahan — loss, optimizer, dan metrik — sedangkan model.fit() memutar siklus epoch-batch yang selama ini kita susun manual: ambil batch, forward di dalam GradientTape, hitung loss, hitung gradien, perbarui bobot. Argumen yang paling sering kita pakai dirangkum pada tabel berikut:
| Argumen | Peran | Nilai pada contoh |
|---|---|---|
x, y | data latih — array, tuple, atau objek tf.data.Dataset | X_latih, y_latih |
epochs | jumlah putaran penuh menelusuri data | 30 dan 120 |
batch_size | jumlah sampel per pembaruan bobot | 32 |
validation_split | potong bagian akhir x sebagai data validasi | 0.2 |
validation_data | pasangan data validasi eksplisit | (X_val, y_val) |
callbacks | daftar objek yang dipanggil tiap akhir epoch | [early, checkpoint] |
verbose | tampilan progres — 0 senyap, 1 bar progres, 2 satu baris per epoch | 0 |
Objek Dataset dari Chapter Dataset dan tf.data
dapat langsung menggantikan x dan y; fit() mengenali pipeline bertanda (fitur, label) dan memakai ukuran batch dari pipeline itu, sehingga argumen batch_size boleh ditinggalkan.
Dua Konfigurasi yang Dibandingkan #
Berkas fit_dua_konfigurasi.py memakai dataset make_moons 200 sampel yang dibagi 150 sampel latih dan 50 sampel validasi. Kedua model berarsitektur sama — Dense(64) ReLU dan keluaran logit dua kelas — sehingga satu-satunya perbedaan adalah cara pelatihannya dikendalikan:
"""model.fit() dengan dua konfigurasi: SGD + validation_split vs Adam + callbacks."""
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)
X, y = make_moons(n_samples=200, 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
)
def buat_model():
return tf.keras.Sequential(
[
tf.keras.Input(shape=(2,)),
tf.keras.layers.Dense(64, activation="relu"),
tf.keras.layers.Dense(2),
]
)
def kompilasi(model, optimizer):
model.compile(
optimizer=optimizer,
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=["accuracy"],
)
# ---------- Konfigurasi 1: SGD + validation_split ----------
tf.keras.utils.set_random_seed(42)
model_sgd = buat_model()
kompilasi(model_sgd, tf.keras.optimizers.SGD(learning_rate=0.5))
h_sgd = model_sgd.fit(
X_latih, y_latih,
epochs=30,
batch_size=32,
validation_split=0.2,
verbose=0,
)
# ---------- Konfigurasi 2: Adam + validation_data + callbacks ----------
tf.keras.utils.set_random_seed(42)
model_adam = buat_model()
kompilasi(model_adam, tf.keras.optimizers.Adam(learning_rate=0.01))
early = tf.keras.callbacks.EarlyStopping(
monitor="val_loss", patience=4, restore_best_weights=True
)
checkpoint = tf.keras.callbacks.ModelCheckpoint(
"model_terbaik.keras", monitor="val_loss", save_best_only=True
)
h_adam = model_adam.fit(
X_latih, y_latih,
epochs=120,
batch_size=32,
validation_data=(X_val, y_val),
callbacks=[early, checkpoint],
verbose=0,
)
# ---------- Ringkasan hasil dari history ----------
print("kunci history:", list(h_adam.history.keys()))
print()
val_loss = h_adam.history["val_loss"]
print("val_loss Adam tiap 10 epoch:",
[round(val_loss[i], 4) for i in range(0, len(val_loss), 10)])
terbaik = int(np.argmin(val_loss)) + 1
print("val_loss terendah di epoch:", terbaik)
if early.stopped_epoch == 0:
print("EarlyStopping : tidak berhenti dini")
else:
print("EarlyStopping : berhenti di epoch", early.stopped_epoch + 1)
print()
print(f"{'model':5s} | {'loss':>8s} | {'akurasi':>8s} | {'val_loss':>8s} | {'val_akurasi':>11s} | epoch")
for nama, h in (("SGD", h_sgd), ("Adam", h_adam)):
d = h.history
print(f"{nama:5s} | {d['loss'][-1]:8.4f} | {d['accuracy'][-1]:8.4f} | "
f"{d['val_loss'][-1]:8.4f} | {d['val_accuracy'][-1]:11.4f} | {len(d['loss']):5d}")
print()
for nama, model in (("SGD", model_sgd), ("Adam", model_adam)):
loss, akurasi = model.evaluate(X_val, y_val, verbose=0)
print(f"evaluate {nama:4s} pada validasi: loss {loss:.4f}, akurasi {akurasi:.4f}")
print()
print("berkas checkpoint dibuat:", os.path.exists("model_terbaik.keras"))
os.remove("model_terbaik.keras")
print("berkas demo dihapus agar bundle tetap bersih")Output yang Dihasilkan:
kunci history: ['accuracy', 'loss', 'val_accuracy', 'val_loss']
val_loss Adam tiap 10 epoch: [0.4274, 0.2814, 0.2271, 0.1779, 0.1636, 0.1618]
val_loss terendah di epoch: 47
EarlyStopping : berhenti di epoch 51
model | loss | akurasi | val_loss | val_akurasi | epoch
SGD | 0.2832 | 0.8583 | 0.3080 | 0.8667 | 30
Adam | 0.0723 | 0.9733 | 0.1618 | 0.9400 | 51
evaluate SGD pada validasi: loss 0.3006, akurasi 0.8200
evaluate Adam pada validasi: loss 0.1603, akurasi 0.9400
berkas checkpoint dibuat: True
berkas demo dihapus agar bundle tetap bersihMari kita telaah kode di atas baris per baris:
- Baris 12-13 (seed ganda): seed NumPy menjaga pembangkitan data dan seed Keras menjaga inisialisasi bobot; seed direset lagi pada baris 40 dan 52 agar kedua model berangkat dari bobot awal yang identik — syarat perbandingan yang adil.
- Baris 15-18 (data dan pembagian): 150 sampel latih, 50 sampel validasi;
make_moonsmengacak urutan sampel secara bawaan sehingga kedua subset berimbang kelasnya. - Baris 21-36 (dua fungsi bantu):
buat_modeldankompilasidipakai bergilir; loss memakaifrom_logits=Truekarena layer terakhir linear — konsistensi yang dibahas tuntas pada Chapter Loss dan Optimizer . - Baris 40-49 (konfigurasi SGD):
validation_split=0.2memotong 20% bagian akhir data latih — 30 dari 150 sampel — sebagai validasi; angkaval_*pada history SGD berasal dari potongan 30 sampel itu. - Baris 56-61 (dua callback):
EarlyStoppingmengawasival_lossdan memberi kesempatan 4 epoch untuk membaik sebelum berhenti;restore_best_weights=Truememutar bobot kembali ke epoch terbaik.ModelCheckpointmenyimpan seluruh model ke berkas.kerassetiap kalival_lossmemecahkan rekor terendah. - Baris 63-70 (konfigurasi Adam): batas
epochs=120hanya langit-langit;validation_datamemakai 50 sampel validasi eksplisit, dan kedua callback digantung pada argumencallbacks. - Baris 73-83 (membaca history):
h_adam.historyberperilaku seperti dictionary dengan kunciloss,accuracy,val_loss,val_accuracy— masing-masing daftar sepanjang jumlah epoch yang benar-benar berjalan.early.stopped_epochbernilai nol bila pelatihan selesai tanpa berhenti dini. - Baris 85-89 (tabel akhir): SGD berhenti tepat di 30 epoch sesuai perintah; Adam berhenti sendiri di epoch ke-51 dari kuota 120 — akurasi validasinya
0.9400mengungguli0.8667milik SGD. - Baris 92-94 (evaluate silang): kedua model dinilai ulang pada 50 sampel validasi yang sama. Nilai Adam
0.1603lebih rendah daripadaval_lossepoch terakhirnya (0.1618) — inilah efekrestore_best_weights: model yang dievaluasi adalah bobot epoch 47, bukan epoch 51. - Baris 97-99 (pembersihan): berkas
model_terbaik.kerasbenar-benar tercipta — buktiModelCheckpointbekerja — lalu dihapus agar bundle chapter tetap bersih.
validation_split atau validation_data #
Kedua argumen menghasilkan kolom val_* pada history, tetapi jalurnya berbeda. validation_split=0.2 memotong bagian akhir data latih sebelum epoch pertama dimulai — praktis untuk eksperimen cepat, tetapi potongannya tidak pernah ikut dilatih. validation_data=(X_val, y_val) menyerahkan kontrol penuh: kita yang membagi, menghitung standardisasi dari data latih saja, dan memakai subset yang sama lintas eksperimen sehingga hasilnya sebanding.
Callback: Mata dan Tangan fit() #
Callback adalah objek yang diberi kesempatan bertindak di titik-titik tertentu pelatihan — paling sering di akhir tiap epoch. EarlyStopping menghentikan pelatihan ketika metrik yang dipantau berhenti membaik selama patience epoch; mekanisme dan variasinya dibahas mendalam pada Chapter Early Stopping
. ModelCheckpoint menyimpan model ke disk tepat ketika rekor baru tercapai, sehingga pelatihan panjang tidak kehilangan model terbaiknya bila kualitas justru menurun setelahnya — pola simpan-muatnya kita bedah pada Chapter Penyimpanan dan Pemuatan Model
.
Padanan manual dari seluruh mekanisme ini — loop epoch-batch tulisan tangan yang memeriksa val_loss, menghitung kesabaran, dan memanggil model.save — sudah kita tulis pada Chapter Training Loop versi PyTorch
. Daftar lengkap argumen fit() beserta perilakunya tersedia di keras.io/api/models/model_training_apis
, dan panduan resmi pelatihan dengan API bawaan di tensorflow.org/guide/keras
.
Ringkasan #
Kita telah mengendalikan model.fit() pada tiga level: argumen dasar (epochs, batch_size, verbose=0), sumber validasi (validation_split untuk cepat, validation_data untuk kontrol), dan callback yang mengubah siklus pelatihan — EarlyStopping menghentikan Adam di epoch 51 dengan bobot terbaik epoch 47, sementara ModelCheckpoint menyimpan rekor ke berkas .keras. Objek History merekam seluruh perjalanan metrik sebagai daftar Python yang siap diplot. Namun fit() tetap kotak hitam berpintu rapat: bila kita butuh aturan learning rate yang aneh, loss multi-komponen, atau logging kustom per batch, pintu itu harus dibuka — lewat custom training loop, topik Chapter berikutnya.