Pelatihan model pada chapter-chapter sebelumnya berlangsung hitungan detik, tetapi model nyata dilatih berjam-jam bahkan berhari-hari di mesin yang sewa jamnya berharga. Kehilangan bobot terlatih karena proses berakhir sama saja dengan membuang seluruh biaya itu, dan setiap proses inference di produksi tentu tidak boleh memulai pelatihan dari nol. Keras menyediakan dua jalur penyimpanan yang saling melengkapi: model.save() yang membungkus seluruh model menjadi satu berkas .keras, dan save_weights() yang hanya menyimpan bobot untuk arsitektur yang dibangun ulang di kode. Chapter ini menguji keduanya secara nyata — termasuk pembuktian bahwa prediksi model sebelum dan sesudah penyimpanan berimpit penuh — lalu memetakan kapan tiap format dipakai.
Format .keras: Seluruh Model dalam Satu Berkas #
model.save("regresi_mini.keras") membungkus tiga hal sekaligus: arsitektur (daftar layer dan koneksinya), bobot hasil pelatihan, dan konfigurasi compile (loss, optimizer, metrik). Pemanggilan tf.keras.models.load_model("regresi_mini.keras") membangun kembali seluruh objek dari berkas itu — model hasil muat langsung bisa dipakai predict dan evaluate tanpa compile ulang, karena konfigurasi pelatihannya ikut tersimpan. Ekstensi .keras adalah format standar Keras 3 berbasis zip; penamaannya sekaligus menandai isi berkas secara eksplisit.
save_weights: Bobot Tanpa Arsitektur #
Jalur kedua memisahkan dua keputusan itu: model.save_weights("regresi_mini.weights.h5") hanya menyimpan nilai bobot per layer. Memuatnya menuntut arsitektur dibangun ulang lewat kode — fungsi buat_model() harus kita miliki — lalu model_baru.load_weights(...) menimpa bobot awal yang acak. Pola ini terasa merepotkan untuk model tunggal, tetapi justru menjadi pintu transfer: arsitektur sama dibangun segar di mesin lain atau di skrip lain, lalu bobot terlatih ditanamkan ke sana. Padanan lengkapnya di sisi PyTorch — state_dict beserta pola simpan-muat-resume — sudah kita bahas pada Chapter Penyimpanan dan Pemuatan Model versi PyTorch
.
Simpan, Muat, Buktikan #
Berkas simpan_muat_model.py melatih model regresi mini 60 epoch, menyimpannya lewat kedua jalur, memuat kembali keduanya, lalu memverifikasi bahwa prediksi ketiga model pada tiga sampel uji berimpit penuh:
"""Simpan dan muat model Keras: format .keras vs save_weights."""
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
import numpy as np
import tensorflow as tf
np.random.seed(42)
tf.keras.utils.set_random_seed(42)
X = np.random.uniform(-2.0, 2.0, size=(200, 2)).astype(np.float32)
y = (X[:, 0:1] * 0.8 + X[:, 1:2] * 0.4 + 0.5).astype(np.float32)
def buat_model():
tf.keras.utils.set_random_seed(42)
return tf.keras.Sequential(
[
tf.keras.Input(shape=(2,)),
tf.keras.layers.Dense(16, activation="relu"),
tf.keras.layers.Dense(1),
]
)
model = buat_model()
model.compile(optimizer="adam", loss="mse")
model.fit(X, y, epochs=60, verbose=0)
X_uji = np.array([[0.5, -1.0], [-1.5, 1.0], [1.2, 0.8]], dtype=np.float32)
pred_asli = model.predict(X_uji, verbose=0)
# ---------- 1. Format .keras: seluruh model satu berkas ----------
model.save("regresi_mini.keras")
model_muat = tf.keras.models.load_model("regresi_mini.keras")
pred_muat = model_muat.predict(X_uji, verbose=0)
np.testing.assert_allclose(pred_asli, pred_muat, rtol=1e-5, atol=1e-6)
print("format .keras : prediksi identik ->", np.array_equal(pred_asli, pred_muat))
print("evaluate tanpa compile ulang -> loss:",
round(float(model_muat.evaluate(X, y, verbose=0)), 4))
# ---------- 2. save_weights: bobot saja, arsitektur dibangun ulang di kode ----------
model.save_weights("regresi_mini.weights.h5")
model_baru = buat_model()
model_baru.load_weights("regresi_mini.weights.h5")
pred_baru = model_baru.predict(X_uji, verbose=0)
np.testing.assert_allclose(pred_asli, pred_baru, rtol=1e-5, atol=1e-6)
print("save/load_weights : prediksi identik ->", np.array_equal(pred_asli, pred_baru))
print()
print("prediksi model asli :", [round(float(v), 4) for v in pred_asli.ravel()])
print("prediksi model muat :", [round(float(v), 4) for v in pred_muat.ravel()])
for berkas in ("regresi_mini.keras", "regresi_mini.weights.h5"):
os.remove(berkas)
print()
print("berkas demo dihapus agar bundle tetap bersih")Output yang Dihasilkan:
format .keras : prediksi identik -> True
evaluate tanpa compile ulang -> loss: 0.0205
save/load_weights : prediksi identik -> True
prediksi model asli : [0.3649, -0.2175, 1.8556]
prediksi model muat : [0.3649, -0.2175, 1.8556]
berkas demo dihapus agar bundle tetap bersihMari kita telaah kode di atas baris per baris:
- Baris 10-14 (data sintetis): 200 sampel regresi dengan hubungan linear ditambah fungsi
buat_model— arsitektur yang sama bisa dibangun ulang kapan pun lewat kode, syarat jalursave_weights. - Baris 17-25 (
buat_modeldengan seed): seed dipanggil di dalam fungsi sehingga tiap model baru berangkat dari bobot awal yang sama — bobot acakmodel_barunanti memang ditimpa, tetapi kesamaan awal membuat eksperimen ini sepenuhnya deterministik. - Baris 28-30 (latih): 60 epoch Adam pada data 200 sampel menurunkan MSE hingga
0.0205— angka yang muncul kembali setelah pemuatan. - Baris 32-33 (prediksi acuan): tiga sampel uji dan prediksinya menjadi tolok ukur; seluruh verifikasi berikutnya dibandingkan terhadap nilai ini.
- Baris 36-42 (jalur .keras):
savelaluload_model;np.testing.assert_allcloseakan menghentikan skrip bila ada selisih melebihi toleransi — keluaranTruedarinp.array_equalmembuktikan prediksi berimpit bit demi bit;evaluatetanpacompileulang berhasil karena konfigurasi ikut tersimpan di berkas. - Baris 45-50 (jalur save_weights): berkas bobot dibuat, arsitektur dibangun segar lewat
buat_model()— inisialisasi acaknya langsung ditimpaload_weights— dan prediksi kembali terbukti identik. Inilah pola transfer: kode arsitektur plus berkas bobot, tanpa berkas model utuh. - Baris 52-54 (pembuktian visual): kedua daftar prediksi
[0.3649, -0.2175, 1.8556]tercetak sama persis — pembaca tidak perlu percaya kataTruesaja. - Baris 56-59 (pembersihan): kedua berkas demo dihapus agar bundle chapter hanya berisi berkas sumber yang bermakna.
Checkpoint Otomatis Selama Pelatihan #
Menyimpan setelah pelatihan selesai menimbulkan risiko: proses terputus di tengah berarti semua hasil hilang. Callback ModelCheckpoint yang sudah kita jalankan pada Chapter Training dengan model.fit()
menutup celah ini dengan menyimpan model terbaik ke berkas .keras setiap kali metrik yang dipantau memecahkan rekor — pola monitor="val_loss" dengan save_best_only=True. Berkas hasilnya dimuat dengan load_model yang sama seperti di atas, sehingga pelatihan berjam-jam bisa dilanjutkan evaluasinya di sesi terpisah.
Peta Format dan Kapan Dipakainya #
| Format | Isi berkas | Kapan dipakai |
|---|---|---|
.keras (model.save) | arsitektur + bobot + konfigurasi compile | standar: simpan, muat, kirim model utuh |
.weights.h5 (save_weights) | bobot per layer saja | transfer ke arsitektur sama yang dibangun ulang di kode |
SavedModel (direktori) | graph lengkap untuk runtime TensorFlow | deployment TF Serving, TF Lite, TF.js |
.h5 HDF5 warisan | model utuh format lama | membuka model era Keras 2 — bukan pilihan baru |
Panduan resmi serialisasi — termasuk perbandingan format dan peringatan kompatibilitasnya — tersedia di tensorflow.org/guide/keras/save_and_serialize
, dan perilaku save, save_weights, serta load_model di keras.io/api/models
.
Ringkasan #
Model terlatih kini punya dua jalur penyelamatan: model.save() membungkus seluruh model ke satu berkas .keras yang dimuat kembali dengan load_model tanpa compile ulang, sedangkan save_weights() menyimpan bobot saja untuk ditanamkan ke arsitektur yang dibangun ulang — pola transfer yang terbukti menghasilkan prediksi identik pada contoh kita. Checkpoint otomatis lewat ModelCheckpoint menjaga hasil terbaik selama pelatihan masih berjalan. Dengan ini rantai section TensorFlow tuntas: dari tensor, model, loss, data, pelatihan, evaluasi, hingga penyimpanan. Section berikutnya melangkah ke dimensi data yang belum kita sentuh — citra — lewat convolutional neural network.