Notebook menawarkan kelancaran eksplorasi, tetapi menyimpan keadaan pada kernel, yaitu proses yang menjalankan sel-sel notebook, sehingga hasil akhirnya bergantung pada urutan sel yang pernah dieksekusi. Topik ini membahas pemindahan alur kerja scikit-learn yang sudah stabil dari notebook ke satu modul Python: fungsi-fungsi kecil dengan tanggung jawab tunggal, konfigurasi di bagian atas berkas, gerbang main, dan cara mengimpor kembali fungsi-fungsi tersebut dari notebook maupun skrip lain.
Kelemahan urutan eksekusi notebook#
Seluruh sel berbagi satu ruang nama kernel, dan urutan eksekusinya mengikuti urutan penekanan tombol, bukan urutan sel di halaman. Sel paling bawah dapat dijalankan lebih dulu, sel atas dapat dijalankan ulang setelah variabelnya diubah di tengah jalan, dan berkas .ipynb yang tersimpan menampilkan keluaran lama yang tidak menjamin alur dari atas ke bawah menghasilkan keadaan yang sama. Notebook yang tampak rapi pun belum tentu dapat direproduksi tanpa menjalankan ulang seluruh selnya dari awal.
Pada alur scikit-learn, kerentanan ini mudah berubah menjadi kesalahan yang senyap. Model dilatih pada X versi awal, lalu di sel lain X dibuang sebagian barisnya atau diganti nama kolomnya, dan sel prediksi dijalankan ulang dengan pasangan model dan data yang sudah tidak sinkron. Tidak ada error yang muncul, tetapi angka yang dihasilkan tidak lagi berasal dari proses yang dapat dijelaskan. Kebiasaan penanggulangannya adalah memindahkan alur yang sudah stabil ke satu modul .py, tempat urutan eksekusi ditentukan oleh struktur program, bukan oleh riwayat klik.
Pola modul dengan gerbang main#
Modul berikut menata alur latihan pada iris menjadi tiga fungsi: load_data memuat dan memecah data, bangun_pipeline merangkai scaler dan model, sedangkan evaluasi melatih dan mengukur skor. Konfigurasi berupa konstanta RANDOM_STATE dan TEST_SIZE diletakkan di bagian atas berkas, dan alur utama dibungkus pernyataan if name == “main” sehingga hanya berjalan ketika berkas dieksekusi langsung dengan python3, bukan ketika diimpor.
"""Modul latihan: memuat data, membangun pipeline, dan mengevaluasi model.
Penataan mengikuti tiga konvensi:
- konfigurasi dikumpulkan sebagai konstanta di bagian atas berkas;
- setiap fungsi memiliki satu tanggung jawab;
- alur utama hanya dijalankan ketika berkas dieksekusi langsung.
"""
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
# konfigurasi
RANDOM_STATE = 42
TEST_SIZE = 0.25
def load_data():
"""Mengembalikan data iris yang sudah dipecah menjadi latih dan uji."""
iris = load_iris()
return train_test_split(
iris.data, iris.target,
test_size=TEST_SIZE, stratify=iris.target, random_state=RANDOM_STATE,
)
def bangun_pipeline():
"""Membangun pipeline yang belum dilatih, scaler diikuti regresi logistik."""
return make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
def evaluasi(model, X_train, X_test, y_train, y_test):
"""Melatih model lalu mengembalikan rata-rata skor CV dan skor test."""
skor_cv = cross_val_score(model, X_train, y_train, cv=5)
model.fit(X_train, y_train)
return float(skor_cv.mean()), float(model.score(X_test, y_test))
if __name__ == "__main__":
X_train, X_test, y_train, y_test = load_data()
pipeline = bangun_pipeline()
rata_cv, skor_test = evaluasi(pipeline, X_train, X_test, y_train, y_test)
print("rata-rata skor CV :", round(rata_cv, 3))
print("skor data uji :", round(skor_test, 3))rata-rata skor CV : 0.964
skor data uji : 0.921Eksekusi langsung menghasilkan dua angka yang saling menopang: rata-rata skor CV 0.964 menggambarkan stabilitas model pada data latih, sedangkan skor data uji 0.921 menggambarkan kemampuannya pada data yang tidak ikut pelatihan. Setiap fungsi menerima data dan model sebagai argumen lalu mengembalikan nilai, sehingga tidak ada keadaan global tersembunyi: fungsi tidak membaca variabel yang tidak dideklarasikannya dan tidak mengubah data di luar nilai kembaliannya. Konfigurasi juga tidak tersebar, sehingga perubahan TEST_SIZE cukup dilakukan di satu baris.
Mengimpor fungsi dari modul#
Modul yang sudah jadi dapat digunakan ulang tanpa menyalin kodenya. Pernyataan import menjalankan isi modul satu kali, tetapi gerbang main menahan alur utamanya, sehingga mengimpor latihan_model tidak memicu pelatihan maupun pencetakan skor. Fungsi dan konstanta modul kemudian dapat dipanggil dari berkas yang mengimpornya.
Contoh berikut meniru cara berkas lain menggunakan modul tersebut. Pada lingkungan tempat contoh ini dieksekusi, berkas latihan_model.py disalin terlebih dahulu ke /tmp, lalu direktori itu ditambahkan ke sys.path agar dapat diimpor dari lokasi mana pun. Pola yang sama berlaku ketika modulnya berada di folder pustaka proyek, dan asumsinya jelas: salinan latihan_model.py harus ada di direktori kerja sebelum perintah salinnya dijalankan.
import shutil
import sys
# berkas modul disalin ke /tmp agar dapat diimpor dari lokasi mana pun
shutil.copy("latihan_model.py", "/tmp/latihan_model.py")
sys.path.insert(0, "/tmp")
import latihan_model
X_train, X_test, y_train, y_test = latihan_model.load_data()
model = latihan_model.bangun_pipeline()
rata_cv, skor_test = latihan_model.evaluasi(model, X_train, X_test, y_train, y_test)
print("RANDOM_STATE modul :", latihan_model.RANDOM_STATE)
print("rata-rata skor CV :", round(rata_cv, 3))
print("skor data uji :", round(skor_test, 3))RANDOM_STATE modul : 42
rata-rata skor CV : 0.964
skor data uji : 0.921Keluaran menunjukkan konstanta RANDOM_STATE terbaca dari modul, dan skornya sama persis dengan eksekusi langsung, yaitu 0.964 dan 0.921, karena random_state-nya sama. Selama proses impor berlangsung, tidak ada satu pun baris alur utama yang dieksekusi, dan skor hanya muncul setelah fungsi-fungsinya dipanggil. Notebook yang tadinya memuat seluruh logika kini cukup memanggil tiga fungsi, sementara logikanya berada di berkas yang dapat diuji, dibandingkan antar versi, dan diimpor dari berkas lain.
Kesalahan umum#
Kesalahan pertama adalah membiarkan alur utama tanpa gerbang main. Setiap pernyataan import ikut melatih model dan mencetak skor, sehingga berkas pemanggil membeku beberapa detik tanpa alasan yang jelas. Membungkus alur utama dengan if name == “main” membuat modul tetap dapat diimpor dengan murah.
Kesalahan kedua adalah menulis fungsi yang membaca dan mengubah variabel global. Fungsi semacam itu hanya bekerja pada satu notebook tertentu, dan pemakaiannya di berkas lain menuntut variabel global dengan nama yang sama persis. Fungsi yang menerima data sebagai argumen dan mengembalikan nilai, sebagaimana evaluasi pada modul di atas, dapat dipanggil dari lingkungan mana pun.
Kesalahan ketiga adalah menyebarkan angka ajaib di dalam fungsi, misalnya test_size=0.25 yang ditulis ulang di beberapa tempat. Sebagian tempat diperbarui dan sebagian tidak, sehingga perilaku modulnya tidak konsisten. Konstanta konfigurasi di bagian atas berkas menuntut setiap nilai hanya memiliki satu tempat asal.
Ringkasan#
- Notebook menyimpan keadaan kernel yang urutan eksekusinya sulit dijamin, sehingga alur yang sudah stabil dipindahkan ke modul .py.
- Modul disusun dari konstanta konfigurasi di bagian atas, fungsi kecil bertanggung jawab tunggal, dan gerbang if name == “main”.
- Impor tidak memicu alur utama, sehingga fungsi modul dapat dipanggil dari notebook atau skrip lain tanpa efek samping.
- Hasilnya adalah kode yang keadaannya dapat dijelaskan dari struktur berkas, bukan dari riwayat eksekusi sel.
Referensi#
- Getting Started — scikit-learn documentation — Gambaran alur fit dan predict scikit-learn yang ditata ulang menjadi modul pada topik ini.
- sklearn.model_selection.train_test_split — scikit-learn documentation — Pemecahan data yang dibungkus fungsi load_data.
- sklearn.model_selection.cross_val_score — scikit-learn documentation — Evaluasi berlipat yang digunakan fungsi evaluasi.
- sklearn.datasets.load_iris — scikit-learn documentation — Dataset yang digunakan pada modul latihan.