Artefak model churn sudah aman tersimpan pada chapter sebelumnya, tetapi model itu masih menyimpan satu kelemahan prosedural: model menerima masukan yang harus dipersiapkan lebih dulu oleh tangan kita. Pada proyek nyata, prapemrosesan — penskalaan, pengodean kategori, penanganan nilai hilang — hampir selalu berdiri di depan model sebagai langkah terpisah, dan setiap langkah terpisah adalah pintu masuk kesalahan. Pipeline scikit-learn merangkai seluruh rangkaian menjadi satu objek yang dilatih, disimpan, dan dipanggil sebagai satu kesatuan. Pada chapter ini kita bedah risiko jalur manual terlebih dahulu, lalu membangun pipeline yang menerima data mentah apa adanya.
Masalah: Prapemrosesan yang Terpisah dari Model #
Chapter Standardisasi
pernah menunjukkan bahwa model berbasis jarak dan gradien bekerja lebih stabil pada fitur yang diskalakan. Untuk churn kita pun penskalaan tetap layak dipakai agar koefisien regresi logistik sebanding antar fitur. Cara manualnya tampak seperti ini: satu objek StandardScaler untuk menyiapkan data, satu objek model untuk memprediksi.
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
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])
penskala = StandardScaler()
X_ternormalisasi = penskala.fit_transform(X)
model = LogisticRegression(max_iter=1000).fit(X_ternormalisasi, churn)
pelanggan_baru = np.array([[2, 300], [12, 90], [7, 180]])
pelanggan_skala = penskala.transform(pelanggan_baru)
peluang = model.predict_proba(pelanggan_skala)[:, 1]
print("jalur manual: dua objek wajib dipakai berurutan")
for fitur, p in zip(pelanggan_baru, peluang):
print(f"kunjungan={fitur[0]:>2}, belanja={fitur[1]:>3} -> peluang churn {p:.3f}")Output yang Dihasilkan:
jalur manual: dua objek wajib dipakai berurutan
kunjungan= 2, belanja=300 -> peluang churn 0.963
kunjungan=12, belanja= 90 -> peluang churn 0.023
kunjungan= 7, belanja=180 -> peluang churn 0.405Mari kita telaah bagian-bagian pentingnya:
- Baris 13-14 (objek penskala):
StandardScalermempelajari rata-rata dan simpangan tiap kolom dari data latih, lalufit_transformmenerapkannya sekaligus — di sinilah statistik penskalaan lahir. - Baris 15 (model belajar pada data berskala): model tidak pernah melihat data mentah; model hanya mengenal versi ternormalisasi.
- Baris 19-20 (dua langkah saat prediksi): pelanggan baru wajib melewati
penskala.transform()sebelummodel.predict_proba()— dan urutan ini tidak dijaga oleh siapa pun kecuali kehati-hatian kita.
Di situlah letak bahayanya. Bayangkan penskala lupa dipanggil di kode produksi: model tetap menghasilkan angka (0 sampai 1, tampak wajar), tidak ada galat yang terangkat, tetapi setiap peluang salah hitung karena skala masukan berbeda ribuan kali dari yang dipelajari model. Kesalahan semacam ini disebut training-serving skew — perilaku saat pelatihan dan saat penyajian tidak sama — dan model tidak akan pernah melapor sendiri.
Merangkai Pipeline scikit-learn #
Kelas Pipeline menyatukan prapemrosesan dan model menjadi satu objek berurutan. Saat fit() dipanggil, setiap langkah mempelajari parameternya lalu meneruskan hasilnya ke langkah berikutnya; saat predict() dipanggil, data mentah menempuh jalur yang sama persis tanpa kita menyusunnya ulang.
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
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)
data_latih = pd.DataFrame({"kunjungan": kunjungan, "belanja": belanja, "churn": churn})
pipeline = Pipeline([
("penskala", StandardScaler()),
("model", LogisticRegression(max_iter=1000)),
])
pipeline.fit(data_latih[["kunjungan", "belanja"]], data_latih["churn"])
pelanggan_mentah = pd.DataFrame({
"kunjungan": [2, 12, 7],
"belanja": [300, 90, 180],
})
peluang = pipeline.predict_proba(pelanggan_mentah)[:, 1]
print("langkah-langkah di dalam pipeline:")
for nama, langkah in pipeline.named_steps.items():
print(f" {nama:10}: {langkah.__class__.__name__}")
print()
print("input mentah -> prediksi tanpa persiapan manual:")
print(pelanggan_mentah.assign(peluang_churn=peluang.round(3)).to_string(index=False))Output yang Dihasilkan:
langkah-langkah di dalam pipeline:
penskala : StandardScaler
model : LogisticRegression
input mentah -> prediksi tanpa persiapan manual:
kunjungan belanja peluang_churn
2 300 0.963
12 90 0.023
7 180 0.405Mari kita telaah bagian-bagian pentingnya:
- Baris 15-18 (daftar langkah): pipeline didefinisikan sebagai daftar pasangan
(nama, objek)— nama bebas kita pilih, objek wajib memilikifit/transform. Urutan daftar adalah urutan eksekusi. - Baris 19 (satu
fituntuk semua):pipeline.fit()menjalankan penskala dan model sekaligus; tidak ada lagi dua pemanggilan terpisah yang bisa saling tertinggal. - Baris 21-25 (prediksi dari data mentah):
pelanggan_mentahadalah DataFrame mentah — angka asli dari kasir, belum diskalakan. Pipeline memanggiltransformlalupredictdi balik layar. - Baris 27-32 (transparansi):
named_stepsmemperlihatkan isi pipeline, dan hasil prediksinya identik dengan jalur manual (0,963 / 0,023 / 0,405) — pipeline tidak mengubah matematika, hanya mengurangi pintu masuk kesalahan.
Perhatikan bahwa kita memakai DataFrame bernama kolom, bukan array anonim. Pipeline yang dilatih pada data_latih[["kunjungan", "belanja"]] ikut mengingat nama dan urutan kolomnya, sehingga salah urutan kolom pada data baru akan langsung terdeteksi sebagai galat alih-alih diterima diam-diam.
Satu Artefak untuk Seluruh Rangkaian #
Keuntungan terbesar pipeline justru terasa saat menyimpan model. Karena seluruh prapemrosesan berada di dalam satu objek, joblib.dump(pipeline, "artefak.joblib") membekukan penskala beserta modelnya sekaligus — kode produksi tinggal memuat satu berkas dan memberinya data mentah. Tabel berikut merangkum bedanya dua pendekatan:
| Aspek | Jalur manual | Pipeline |
|---|---|---|
| Jumlah objek yang dikelola | penskala, model, dan urutannya | satu objek pipeline |
| Risiko lupa satu langkah saat prediksi | tinggi — tidak ada penjaga | tidak ada — urutan menempel pada objek |
| Artefak yang disimpan | minimal dua berkas terpisah | satu berkas utuh |
| Kesiapan dipakai pengguna lain | bergantung dokumentasi | tinggal muat dan panggil |
Rangkuman #
- Prapemrosesan terpisah dari model menciptakan training-serving skew: kesalahan urutan langkah saat penyajian tidak memicu galat, hanya prediksi yang diam-diam salah.
Pipelinemerangkai prapemrosesan dan model sebagai satu objek: satufit, satupredict, urutan yang tidak bisa tertukar.- Melatih pipeline pada DataFrame bernama kolom membuat nama kolom ikut terjaga; salah urutan kolom pada data baru menjadi galat yang terangkat, bukan kesalahan senyap.
- Menyimpan pipeline berarti menyimpan seluruh rangkaian dalam satu artefak — prapemrosesan tidak tertinggal di notebook.
Model kita kini bisa menerima data mentah dan mengembalikan peluang churn. Pertanyaan berikutnya adalah bagaimana pihak lain — aplikasi kasir, tim pemasaran — memanggil model itu tanpa membuka notebook kita. Jawabannya adalah layanan prediksi, yang kita kenal pada chapter berikutnya: Pengenalan API Model .
Referensi: