Ada satu kesalahan yang mampu membuat seluruh kerja feature engineering berakhir sia-sia, dan ia bekerja tanpa pesan galat apa pun: data leakage, bocornya informasi yang seharusnya tidak diketahui model — informasi dari data uji, dari masa depan, atau dari akibat yang belum terjadi — ke dalam proses pelatihan. Model yang bocor tampak luar biasa saat diuji lalu runtuh saat dipakai sungguhan; skornya sempurna justru karena ujiannya ikut dibocorkan ke siswa sebelum ujian dimulai. Chapter penutup Section ini membedah tiga ragam kebocoran yang paling sering terjadi, mendemonstrasikan masing-masing dengan bukti angka, lalu menegakkan satu disiplin yang menutup semuanya.
Tiga Ragam Kebocoran #
| Ragam | Mekanisme | Contoh |
|---|---|---|
| Preprocessing leakage | Statistik penyesuaian dihitung dari seluruh data — termasuk data uji | Scaler dinormalisasi sebelum split |
| Target leakage | Fitur dibuat dari informasi yang baru ada SETELAH target terjadi | Kolom “telepon retensi” yang terisi justru bagi pelanggan yang churn |
| Kebocoran waktu & duplikat | Data masa depan ikut dilatih; baris kembar tersebar di dua sisi split | Prediksi bulan depan dilatih dengan data bulan ini |
Ketiganya berbeda mekanisme tetapi berujung sama: skor uji berbohong tentang kemampuan model di dunia nyata.
Kebocoran Preprocessing: Statistik yang Tidak Boleh Melihat Ujian #
Kesalahan paling umum: menormalkan atau menskalakan seluruh dataset sebelum memisahkannya. Pelanggarannya tidak terasa — angka-angkanya sah, kodenya berjalan — tetapi StandardScaler yang di-fit pada seluruh data menghitung rata-rata dan simpangan baku yang tersentuh nilai-nilai data uji. Model kini “tahu” sesuatu tentang ujiannya melalui skala yang dipakainya. Bukti angkanya terlihat pada satu nilai ekstrem:
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
rng = np.random.default_rng(42)
belanja = rng.integers(20, 500, 40).astype(float)
belanja[-1] = 4800.0 # satu nilai ekstrem yang nyatanya milik data uji
latih, uji = train_test_split(belanja, test_size=0.25, random_state=42)
# CARA BOCOR: fit di seluruh data (mean/std tersentuh nilai ekstrem uji)
scaler_bocor = StandardScaler().fit(belanja.reshape(-1, 1))
# CARA BERSIH: fit hanya pada latih
scaler_bersih = StandardScaler().fit(latih.reshape(-1, 1))
contoh = np.array([[100.0]])
print("Nilai ekstrem di data (milik uji): 4800")
print("Mean latih :", round(latih.mean(), 1))
print("Mean total :", round(belanja.mean(), 1))
print()
print("Skala 100 ribu, cara BOCOR :", scaler_bocor.transform(contoh).ravel().round(3))
print("Skala 100 ribu, cara BERSIH :", scaler_bersih.transform(contoh).ravel().round(3))Output yang Dihasilkan:
Nilai ekstrem di data (milik uji): 4800
Mean latih : 268.5
Mean total : 386.5
Skala 100 ribu, cara BOCOR : [-0.399]
Skala 100 ribu, cara BERSIH : [-1.229]Mari kita telaah hasilnya:
- Mean yang tersentuh: satu nilai 4.800 menarik rata-rata seluruh data dari 268,5 menjadi 386,5 — padahal nilai ekstrem itu milik data uji yang seharusnya tak terlihat saat pelatihan.
- Angka skala yang berbeda tiga kali lipat: pelanggan yang berbelanja 100 ribu di-z-score-kan menjadi -0,399 oleh scaler bocor, tetapi -1,229 oleh scaler yang benar. Model yang belajar pada skala pertama tahu “101 tergolong di bawah rata-rata” — pengetahuan yang hanya bisa datang dari isi ujian yang bocor.
- Aturannya tunggal: semua transformasi yang belajar dari data — scaler, encoder kategori, imputer, PCA, seleksi fitur — di-fit hanya pada data latih; data uji hanya pernah dilewati
transform().
Target Leakage: Fitur yang Lahir Setelah Nasibnya Terjadi #
Bentuk kedua lebih halus: sebuah kolom terlihat seperti fitur prediktor yang hebat, padahal ia adalah akibat dari target — dibuat setelah nasib pelanggan diketahui. Contoh klasiknya di dunia churn: tim retensi menelepon pelanggan yang diprediksi akan berhenti, sehingga kolom “pernah ditelepon tim retensi” nyaris identik dengan kolom churn itu sendiri. Model yang dilatih dengan kolom ini akan memakainya habis-habisan — dan bangkrut saat dipakai, karena saat memprediksi pelanggan baru, status telepon masa depan belum ada:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
rng = np.random.default_rng(42)
n = 60
frekuensi = rng.integers(1, 20, n)
skor = 0.5 * (20 - frekuensi) + rng.normal(0, 4.0, n)
churn = (skor > skor.mean()).astype(int)
df = pd.DataFrame({"frekuensi": frekuensi, "churn": churn})
# fitur bocor: terisi justru karena nasib pelanggan sudah diketahui
df["telepon_retensi"] = (df["churn"] == 1).astype(int)
for nama, X in [("fitur jujur", df[["frekuensi"]]),
("ada fitur bocor", df[["frekuensi", "telepon_retensi"]])]:
Xl, Xu, yl, yu = train_test_split(X, df["churn"], test_size=0.3,
random_state=42, stratify=df["churn"])
model = LogisticRegression(max_iter=1000).fit(Xl, yl)
print(f"Akurasi {nama:<18}: {model.score(Xu, yu):.3f}")Output yang Dihasilkan:
Akurasi fitur jujur : 0.833
Akurasi ada fitur bocor : 1.000Mari kita telaah hasilnya:
- Skor sempurna 1,000 adalah bendera merah, bukan berita baik: akurasi 100% pada tugas nyata hampir selalu menandakan ada yang bocor — dunia sungguhan selalu memuat derau yang tak bisa diprediksi siapa pun.
telepon_retensimemang menyalin jawabannya sendiri. - Lompatan dari 0,833 ke 1,000 terjadi hanya dengan menambahkan satu kolom yang “terlalu tahu”. Pertanyaan pembedanya selalu satu: apakah nilai fitur ini sudah tersedia pada saat prediksi dibuat? Bila jawabannya “baru tersedia setelahnya” — setelah pelanggan churn, setelah transaksi selesai, setelah pengiriman diketahui hasilnya — kolom itu bukan fitur; ia jawaban yang menyamar.
- Di lapangan kebocoran seperti ini jarang setulus contoh: biasanya ia bersembunyi sebagai kolom status berbelit atau timestamp yang selaras dengan target. Uji “apakah tersedia saat prediksi” adalah penyaringnya.
Kebocoran Waktu dan Duplikat #
Ragam ketiga menyusup lewat pemisahan data. Pada data lintas waktu, train_test_split acak membiarkan baris “masa depan” ikut dilatih — model yang seharusnya belajar dari masa lalu sudah mencuri pandangan masa depan, seperti yang didemonstrasikan chapter Generalisasi Model
dengan pembagian kronologis. Ragam kembarannya: baris duplikat yang tersebar di kedua sisi split — pelanggan yang sama muncul di latih dan uji, sehingga model “mengenali” soal ujiannya. Keduanya dicegah dengan aturan yang sudah kita pegang: pisahkan menurut waktu untuk deret waktu, dan singkirkan duplikat sebelum split (chapter Menangani Data Duplikat
).
Disiplin Penutup: Satu Alur yang Menutup Semua #
Seluruh kebocoran di atas tertutup oleh satu disiplin alur yang sama — dan sekaligus menjadi rangkuman perjalanan Section ini:
- Pisahkan data uji lebih dulu, sebelum menyentuh transformasi apa pun.
- Fit setiap transformasi — scaler, encoder, imputer, PCA, seleksi fitur — hanya pada data latih.
- Periksa setiap fitur dengan uji ketersediaan: adakah nilainya sebelum prediksi dibuat?
- Untuk deret waktu, pisahkan menurut urutan waktu; untuk data entitas, bereskan duplikat sebelum split.
- Perlakukan skor terlalu bagus sebagai tersangka bocor — telusuri sebelum merayakannya.
Feature engineering yang baik bukan hanya menghasilkan fitur yang kuat, melainkan fitur yang kuat secara jujur — kuat karena menangkap sinyal yang memang akan tersedia saat model dipakai. Dengan itu seluruh peralatan bahan baku model sudah lengkap; saatnya menyusun fondasi cara kerja machine learning itu sendiri di Section berikutnya: Dasar Machine Learning .
Rangkuman #
- Data leakage membuat skor uji berbohong: model tampak hebat lalu gagal di lapangan, tanpa satu pun pesan galat.
- Kebocoran preprocessing: scaler/encoder/imputer yang di-fit pada seluruh data memberi model informasi tersembunyi tentang ujiannya — fit hanya pada data latih.
- Target leakage: fitur yang lahir dari akibat target (status setelah kejadian) menyediakan jawaban menyamar; ujiannya: apakah nilainya tersedia saat prediksi dibuat?
- Kebocoran waktu dan duplikat: pisahkan deret waktu secara kronologis dan singkirkan baris kembar sebelum split.
- Disiplin baku: uji disisihkan paling awal, semua transformasi fit di latih saja, dan skor yang terlalu bagus selalu menjadi tersangka.