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 #

RagamMekanismeContoh
Preprocessing leakageStatistik penyesuaian dihitung dari seluruh data — termasuk data ujiScaler dinormalisasi sebelum split
Target leakageFitur dibuat dari informasi yang baru ada SETELAH target terjadiKolom “telepon retensi” yang terisi justru bagi pelanggan yang churn
Kebocoran waktu & duplikatData masa depan ikut dilatih; baris kembar tersebar di dua sisi splitPrediksi 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:

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

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

  1. 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.
  2. 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.
  3. 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:

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

text
Akurasi fitur jujur       : 0.833
Akurasi ada fitur bocor   : 1.000

Mari kita telaah hasilnya:

  1. 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_retensi memang menyalin jawabannya sendiri.
  2. 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.
  3. 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:

  1. Pisahkan data uji lebih dulu, sebelum menyentuh transformasi apa pun.
  2. Fit setiap transformasi — scaler, encoder, imputer, PCA, seleksi fitur — hanya pada data latih.
  3. Periksa setiap fitur dengan uji ketersediaan: adakah nilainya sebelum prediksi dibuat?
  4. Untuk deret waktu, pisahkan menurut urutan waktu; untuk data entitas, bereskan duplikat sebelum split.
  5. 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.

Referensi #

  1. scikit-learn — Preprocessing
  2. scikit-learn — Model selection: cross-validation
  3. scikit-learn — Metrics and scoring: model evaluation