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.

Aspekpicklejoblib
Asalmodul bawaan Pythonlibrary yang ikut terpasang bersama scikit-learn
Objek besar berisi array NumPymenyalin data, lebih lambatdioptimalkan untuk array NumPy, lebih efisien
Kompresi berkastidak tersediatersedia lewat parameter compress
Posisi di ekosistemserialisasi umum untuk objek Python apa punpilihan 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.

python
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:

text
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.395

Mari kita telaah bagian-bagian pentingnya:

  1. 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.
  2. Baris 16 (objek di memori): fit() menghasilkan objek model yang siap dipakai tetapi hanya hidup selama program berjalan.
  3. 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.
  4. Baris 24 (kebangkitan): joblib.load() membaca berkas dan menghidupkan objek baru bernama model_dimuat — objek ini terpisah penuh dari model di memori.
  5. Baris 26-34 (uji pulang-pergi): tiga pelanggan yang sama diprediksi oleh kedua objek, lalu np.array_equal() membandingkan hasilnya. Nilai True adalah 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 artefakTidak 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 pipelineNilai metrik evaluasi seperti recall 0,790
Versi struktur internal objekNama 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.

python
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:

text
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:

  1. Baris 19-21 (folder per versi): nama folder model_churn_v1 menjadi alamat artefak versi pertama; versi berikutnya tinggal menempati folder model_churn_v2 dan seterusnya, sehingga tidak pernah ada berkas yang tertimpa. joblib.dump kemudian menuliskan model ke berkas model.joblib di dalamnya.
  2. 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.
  3. Baris 30-31 (penulisan JSON): json.dump dengan indent=2 menghasilkan 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() dan joblib.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.joblib plus metadata.json menjaga 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: