Model churn yang kita finalisasi pada chapter Memilih Model Terbaik
kini menunggu satu langkah sebelum benar-benar berguna bagi kedai: keluar dari notebook. Selama ini model hidup sebagai objek di memori — begitu kernel dimatikan atau sesi berakhir, hasil pelatihan hilang dan harus diulang dari awal setiap kali dibutuhkan. Menyimpan model (model persistence) berarti membekukan objek terlatih menjadi sebuah berkas artefak yang bisa dimuat kembali kapan pun dan di mesin mana pun. Pada chapter ini kita belajar menyimpan serta memuat model dengan joblib, memahami apa saja yang benar-benar ikut tersimpan di dalam berkas, lalu memberi versi pada artefak supaya pengelolaan model di produksi tetap tertib.
Dari Memori Menjadi Berkas Artefak #
Model scikit-learn yang selesai dilatih adalah objek Python biasa yang tinggal di memori: kumpulan angka parameter (koefisien, intersep, ambang pohon keputusan) beserta struktur yang menjelaskan cara membacanya. Menyimpan model berkerja lewat serialisasi — menerjemahkan objek di memori menjadi rangkaian byte yang ditulis ke sebuah berkas, lalu menerjemahkannya kembali saat dibutuhkan. Dua alat yang paling sering dipakai adalah modul pickle bawaan Python dan library joblib; keduanya memakai format yang kompatibel satu sama lain.
| Aspek | pickle | joblib |
|---|---|---|
| Asal | modul bawaan Python | library yang ikut terpasang bersama scikit-learn |
| Objek besar berisi array NumPy | menyalin data, lebih lambat | dioptimalkan untuk array NumPy, lebih efisien |
| Kompresi berkas | tidak tersedia | tersedia lewat parameter compress |
| Posisi di ekosistem | serialisasi umum untuk objek Python apa pun | pilihan standar untuk model scikit-learn |
Karena model machine learning hampir selalu berisi array NumPy, kita memakai joblib pada seluruh contoh di babak deployment ini. Prinsip keduanya sama persis, jadi kebiasaan yang kita bangun tetap berlaku bila suatu hari tim memakai pickle.
Menyimpan dan Memuat Kembali Model #
Kita mulai dari alur paling mendasar: melatih ulang model churn pada dataset 60 pelanggan yang sama, menyimpannya ke berkas, lalu memuatnya kembali dan membuktikan bahwa prediksinya identik. Kode berikut memakai folder sementara dari tempfile agar contoh ini tidak menyisakan berkas percobaan di folder materi.
import os
import tempfile
import joblib
import numpy as np
from sklearn.linear_model import LogisticRegression
rng = np.random.default_rng(42)
n = 60
kunjungan = rng.integers(1, 15, n)
belanja = rng.integers(50, 350, n)
skor = 0.8 * (8 - kunjungan) + 0.02 * (belanja - 180) + rng.normal(0, 3, n)
churn = (skor > np.quantile(skor, 0.6)).astype(int)
X = np.column_stack([kunjungan, belanja])
model = LogisticRegression(max_iter=1000).fit(X, churn)
folder_kerja = tempfile.mkdtemp()
nama_artefak = "model_churn_v1.joblib"
lokasi_artefak = os.path.join(folder_kerja, nama_artefak)
joblib.dump(model, lokasi_artefak)
ukuran_kb = os.path.getsize(lokasi_artefak) / 1024
model_dimuat = joblib.load(lokasi_artefak)
pelanggan_baru = np.array([[2, 300], [12, 90], [7, 180]])
peluang_awal = model.predict_proba(pelanggan_baru)[:, 1]
peluang_dimuat = model_dimuat.predict_proba(pelanggan_baru)[:, 1]
print(f"artefak : {nama_artefak}")
print(f"ukuran artefak : {ukuran_kb:.1f} KB")
print(f"prediksi identik : {np.array_equal(peluang_awal, peluang_dimuat)}")
for fitur, p in zip(pelanggan_baru, peluang_dimuat):
print(f"kunjungan={fitur[0]:>2}, belanja={fitur[1]:>3} -> peluang churn {p:.3f}")Output yang Dihasilkan:
artefak : model_churn_v1.joblib
ukuran artefak : 0.9 KB
prediksi identik : True
kunjungan= 2, belanja=300 -> peluang churn 0.989
kunjungan=12, belanja= 90 -> peluang churn 0.007
kunjungan= 7, belanja=180 -> peluang churn 0.395Mari kita telaah bagian-bagian pentingnya:
- Baris 8-14 (regenerasi data): dataset churn dibangun ulang dengan
default_rng(42)sehingga identik dengan data pada chapter pemilihan model — bukti kegunaan seed yang tetap kita jaga sepanjang Course. - Baris 16 (objek di memori):
fit()menghasilkan objekmodelyang siap dipakai tetapi hanya hidup selama program berjalan. - Baris 18-22 (pembekuan):
tempfile.mkdtemp()menyiapkan folder kerja;joblib.dump(model, lokasi_artefak)menuliskan seluruh isi objek ke berkas;os.path.getsize()melaporkan ukurannya — di bawah 1 KB, sekecil kartu nama, karena regresi logistik hanya menyimpan segelintir angka. - Baris 24 (kebangkitan):
joblib.load()membaca berkas dan menghidupkan objek baru bernamamodel_dimuat— objek ini terpisah penuh darimodeldi memori. - Baris 26-34 (uji pulang-pergi): tiga pelanggan yang sama diprediksi oleh kedua objek, lalu
np.array_equal()membandingkan hasilnya. NilaiTrueadalah jaminan bahwa menyimpan-lalu-memuat tidak mengubah perilaku model sedikit pun.
Angka 0,989, 0,007, dan 0,395 pada output sama persis dengan prediksi model final di chapter Memilih Model Terbaik — artefak memang menyimpan hasil pelatihan, bukan sekadar jenis modelnya.
Apa yang Ikut Tersimpan — dan yang Tidak #
Berkas joblib menyimpan hasil pelatihan, bukan prosesnya. Pemahaman atas batas ini menentukan berkas apa lagi yang harus kita simpan berdampingan dengan artefak:
| Ikut tersimpan dalam artefak | Tidak ikut tersimpan |
|---|---|
| Seluruh parameter terlatih (koefisien, intersep) | Data latih — hanya angka akhir parameter yang tersimpan |
Jenis model dan hyperparameter (mis. max_iter) | Kode pelatihan yang menghasilkan artefak |
| Objek prapemrosesan bila dirangkai sebagai satu pipeline | Nilai metrik evaluasi seperti recall 0,790 |
| Versi struktur internal objek | Nama kolom, bila data dilatih sebagai array NumPy tanpa nama |
Dua baris terakhir tabel itu adalah sumber kejutan paling umum. Artefak tidak membawa metrik evaluasinya, jadi kartu model dari chapter Memilih Model Terbaik harus disimpan sebagai dokumen terpisah. Artefak juga tidak menyimpan nama kolom — model kita mengenali “kolom 0 adalah kunjungan” hanya dari posisi, sehingga tertukarnya urutan kolom saat prediksi akan menghasilkan jawaban yang salah tanpa peringatan apa pun.
Memberi Versi pada Artefak #
Satu berkas telanjang tidak cukup untuk produksi. Begitu model diperbarui berkali-kali, kita membutuhkan cara membedakan artefak lama dan baru, kapan ia dibuat, dan dari data apa ia belajar. Praktik yang ringan namun kuat: satu folder per versi, berisi berkas model plus berkas metadata.json kecil yang menjelaskan konteksnya.
import json
import os
import tempfile
import joblib
import numpy as np
import sklearn
from sklearn.linear_model import LogisticRegression
rng = np.random.default_rng(42)
n = 60
kunjungan = rng.integers(1, 15, n)
belanja = rng.integers(50, 350, n)
skor = 0.8 * (8 - kunjungan) + 0.02 * (belanja - 180) + rng.normal(0, 3, n)
churn = (skor > np.quantile(skor, 0.6)).astype(int)
X = np.column_stack([kunjungan, belanja])
model = LogisticRegression(max_iter=1000).fit(X, churn)
folder_versi = os.path.join(tempfile.mkdtemp(), "model_churn_v1")
os.makedirs(folder_versi)
joblib.dump(model, os.path.join(folder_versi, "model.joblib"))
metadata = {
"versi": "v1",
"daftar_fitur": ["kunjungan", "belanja"],
"versi_scikit_learn": sklearn.__version__,
"jumlah_baris_latih": int(n),
"tanggal_disetujui": "2026-01-15",
}
with open(os.path.join(folder_versi, "metadata.json"), "w") as berkas:
json.dump(metadata, berkas, indent=2)
print("isi folder artefak model_churn_v1:")
for nama in sorted(os.listdir(folder_versi)):
ukuran = os.path.getsize(os.path.join(folder_versi, nama))
print(f" {nama:15} {ukuran:>5} byte")
print()
print(json.dumps(metadata, indent=2))Output yang Dihasilkan:
isi folder artefak model_churn_v1:
metadata.json 173 byte
model.joblib 879 byte
{
"versi": "v1",
"daftar_fitur": [
"kunjungan",
"belanja"
],
"versi_scikit_learn": "1.5.2",
"jumlah_baris_latih": 60,
"tanggal_disetujui": "2026-01-15"
}Mari kita telaah bagian-bagian pentingnya:
- Baris 19-21 (folder per versi): nama folder
model_churn_v1menjadi alamat artefak versi pertama; versi berikutnya tinggal menempati foldermodel_churn_v2dan seterusnya, sehingga tidak pernah ada berkas yang tertimpa.joblib.dumpkemudian menuliskan model ke berkasmodel.joblibdi dalamnya. - Baris 23-29 (metadata): kamus kecil berisi daftar fitur (penangkal masalah urutan kolom tadi), versi scikit-learn, jumlah baris latih, dan tanggal persetujuan — informasi yang tidak muat di dalam artefak tetapi selalu dibutuhkan saat ada masalah.
- Baris 30-31 (penulisan JSON):
json.dumpdenganindent=2menghasilkan berkas yang tetap terbaca manusia; siapa pun bisa membukanya tanpa menjalankan Python.
Kolom versi_scikit_learn bukan hiasan: artefak joblib tidak dijamin bisa dimuat oleh versi library yang berbeda jauh. Kebiasaan mencatat versi ini akan kita kembali jumpai pada chapter Batasan Deployment
. Untuk tim yang mengelola puluhan model, pencatatan versi semacam ini biasanya dipindahkan ke alat pelacakan seperti MLflow agar bisa dicari dan dibandingkan.
Rangkuman #
- Menyimpan model berarti membekukan objek terlatih menjadi berkas artefak;
joblib.dump()danjoblib.load()adalah pasangan simpan-muat standar untuk model scikit-learn. - Uji pulang-pergi — bandingkan prediksi model asli dan model termuat dengan
np.array_equal()— memastikan artefak tidak mengubah perilaku model. - Artefak menyimpan hasil (parameter, hyperparameter) tetapi bukan proses: data latih, metrik evaluasi, dan nama kolom tidak ikut tersimpan, sehingga kartu model dan metadata wajib menemani.
- Satu folder per versi berisi
model.joblibplusmetadata.jsonmenjaga artefak tetap tertelusur; jangan pernah memuat artefak dari sumber yang tidak dipercaya.
Artefak model kini aman di berkas, tetapi ada satu kelemahan yang belum kita tangani: model yang baru saja disimpan masih menuntut data masukan yang sudah dipersiapkan secara manual. Pada chapter berikutnya kita merangkai prapemrosesan dan model menjadi satu pipeline utuh sehingga prediksi bisa menerima data mentah apa adanya: Membuat Pipeline Prediksi .
Referensi: