Ana içeriğe geç

İleri14 dk

Veri sızıntısı (leakage): en pahalı hata

Önkoşul:Öznitelik mühendisliği, ölçekleme ve kodlama

Kanca

Aynı veri, aynı model, aynı test kümesi. Tek fark: kategori kodlamasını train_test_split’ten ÖNCE mi yoksa SONRA mı yaptığın. Bu tek sıralama farkı, test R²’sini 0.739’dan 0.677’ye düşürüyor. Hangisi gerçek, ve neden bu kadar fark var?

Sezgi

  1. derste kategorik verileri sayıya çevirmeyi (kodlama) öğrendik. Ama bir soruyu atladık: bu kodlamayı NE ZAMAN yapmalısın — veriyi eğitim/test diye bölmeden önce mi, sonra mı?

Veri sızıntısı, test verisi hakkındaki bilginin, farkında olmadan eğitim sürecine karışmasıdır. En sinsi biçimi tam olarak bu: kategori ortalaması gibi bir öznitelik, TÜM veriden (test dahil) hesaplanırsa, her test satırının KENDİ hedef değeri, o satırı tahmin etmek için kullanılan sayıya karışmış olur. Sonuç: model gerçekte olduğundan çok daha “başarılı” görünür — ama bu başarı sahte, üretime çıkınca kaybolur.

❌ Sızıntılı — kategori ortalaması TÜM veriden

Test R² = 0.773

Test satırlarının kendi y değeri, hesaplanan kategori ortalamasına sızmış — skor gerçekte olduğundan iyi görünüyor.

✓ Doğru — kategori ortalaması SADECE eğitimden

Test R² = 0.710

Test verisi, ortalama hesabına hiç karışmadı — bu skor modelin gerçek dünyada vereceği performansı yansıtıyor.

144 eğitim / 56 test satırı. Kategori sayısını artır (grup başına düşen satır azalır) — sızıntının etkisi büyüsün.

Kategori sayısını artır (grup başına düşen satır azalsın): sızıntılı ve doğru skorlar arasındaki fark büyüyor. Küçük gruplarda, her satırın kendi değeri grup ortalamasını çok daha fazla etkiliyor — sızıntı da o kadar şiddetleniyor.

Mekanizma

Aynı veriyle iki farklı sırayı deneyelim. Önce YANLIŞ sıra: kodlamayı bölmeden önce yap.

Veri: 200 satır, 25 kategori

# 200 satır, 25 kategori (grup başına ortalama 8 satır — küçük gruplar, sızıntıya müsait).
n, kategori_sayisi = 200, 25
gercek_ortalamalar = rng.uniform(0, 100, size=kategori_sayisi)
kategoriler = rng.integers(0, kategori_sayisi, size=n)
y = gercek_ortalamalar[kategoriler] + rng.normal(0, 15, size=n)

veri = pd.DataFrame({"kategori": kategoriler, "y": y})
egitim, test = train_test_split(veri, test_size=0.3, random_state=42)
print(f"Eğitim: {len(egitim)} satır, Test: {len(test)} satır, {kategori_sayisi} kategori")
Eğitim: 140 satır, Test: 60 satır, 25 kategori

Yanlış: TÜM veriden kodlama

# YANLIŞ: kategori ortalamasını TÜM veriden (eğitim + test) hesaplıyoruz —
# yani test satırlarının KENDİ y değeri, kullandığımız ortalamaya karışıyor.
tum_ortalamalar = veri.groupby("kategori")["y"].mean()

test_sizintili = test.copy()
test_sizintili["tahmin"] = test_sizintili["kategori"].map(tum_ortalamalar)

r2_sizintili = r2_score(test_sizintili["y"], test_sizintili["tahmin"])
print(f"\nSızıntılı R² (test): {r2_sizintili:.3f}")
print("Bu skor gerçek olamayacak kadar iyi — çünkü test satırının kendi y'si, tahminine karıştı.")

Sızıntılı R² (test): 0.739
Bu skor gerçek olamayacak kadar iyi — çünkü test satırının kendi y'si, tahminine karıştı.

R²=0.739 — kulağa iyi geliyor. Şimdi DOĞRU sırayı deneyelim: önce böl, kodlamayı SADECE eğitim verisinden öğren, sonra test verisine uygula.

Doğru: sadece eğitimden kodlama

# DOĞRU: kategori ortalamasını SADECE eğitim verisinden hesaplıyoruz, test'e SONRADAN uyguluyoruz.
egitim_ortalamalari = egitim.groupby("kategori")["y"].mean()
genel_ortalama = egitim["y"].mean()

test_dogru = test.copy()
test_dogru["tahmin"] = test_dogru["kategori"].map(egitim_ortalamalari).fillna(genel_ortalama)

r2_dogru = r2_score(test_dogru["y"], test_dogru["tahmin"])
print(f"\nDoğru R² (test): {r2_dogru:.3f}")
print("Bu, modelin YENİ, hiç görmediği veride vereceği gerçekçi performans.")

Doğru R² (test): 0.677
Bu, modelin YENİ, hiç görmediği veride vereceği gerçekçi performans.

R²=0.677 — daha düşük ama gerçek. Çoğu kişi “veri aynı, sonuç da yakın olmalı, ikisi de kabul edilebilir” der. Değil, çünkü aradaki fark rastgele bir dalgalanma değil, sistematik bir yanılgı: sızıntılı versiyonda her test satırının kendi hedef değeri, kendisini “tahmin etmek” için kullanılan sayıya doğrudan katkı veriyor — bu, sınavdan önce cevap anahtarına göz atmak gibi.

Matematik

Sızıntının aritmetiği
sızıntılı ortalamak=1nkigrup kyi(TU¨M veri, test dahil)\text{sızıntılı ortalama}_k = \frac{1}{n_k}\sum_{i \in \text{grup } k} y_i \quad (\text{TÜM veri, test dahil})dog˘ru ortalamak=1nkeg˘itimigrup keg˘itimyi\text{doğru ortalama}_k = \frac{1}{n_k^{eğitim}}\sum_{i \in \text{grup } k \cap \text{eğitim}} y_i
DeğişkenAnlamı
nkn_kk kategorisindeki toplam satır sayısı (sızıntılı hesapta test dahil)
nkeg˘itimn_k^{eğitim}k kategorisindeki SADECE eğitim satırı sayısı

nkn_k küçüldükçe (az satırlı kategoriler), bir test satırının kendi yiy_i değeri, grup ortalamasının daha büyük bir kısmını oluşturur — 1nk\frac{1}{n_k} payı büyür. Bu yüzden yüksek kardinaliteli (çok kategorili, az örnekli) değişkenlerde sızıntı çok daha tehlikelidir.

Kod

Kategori sayısı arttıkça (grup boyutu küçüldükçe) sızıntının nasıl şiddetlendiğini doğrudan ölçelim:

Kategori sayısının sızıntıya etkisi

# Sızıntı, grup boyutu küçüldükçe (kategori sayısı arttıkça) daha da şiddetleniyor.
for k in [5, 10, 25, 50]:
    gercek_ort_k = rng.uniform(0, 100, size=k)
    kat_k = rng.integers(0, k, size=n)
    y_k = gercek_ort_k[kat_k] + rng.normal(0, 15, size=n)
    veri_k = pd.DataFrame({"kategori": kat_k, "y": y_k})
    egitim_k, test_k = train_test_split(veri_k, test_size=0.3, random_state=42)

    tum_ort_k = veri_k.groupby("kategori")["y"].mean()
    r2_siz_k = r2_score(test_k["y"], test_k["kategori"].map(tum_ort_k))

    egt_ort_k = egitim_k.groupby("kategori")["y"].mean()
    tahmin_dogru_k = test_k["kategori"].map(egt_ort_k).fillna(egitim_k["y"].mean())
    r2_dog_k = r2_score(test_k["y"], tahmin_dogru_k)

    print(f"kategori={k:>3} (grup başına ~{n // k} satır)  sızıntılı R²={r2_siz_k:.3f}  doğru R²={r2_dog_k:.3f}  fark={r2_siz_k - r2_dog_k:.3f}")
kategori=  5 (grup başına ~40 satır)  sızıntılı R²=0.829  doğru R²=0.810  fark=0.019
kategori= 10 (grup başına ~20 satır)  sızıntılı R²=0.817  doğru R²=0.767  fark=0.050
kategori= 25 (grup başına ~8 satır)  sızıntılı R²=0.770  doğru R²=0.669  fark=0.101
kategori= 50 (grup başına ~4 satır)  sızıntılı R²=0.831  doğru R²=0.731  fark=0.101

Grup başına ~40 satırda fark sadece 0.019 — küçük. Grup başına ~4-8 satıra düşünce fark 0.101’e çıkıyor — beş kat daha büyük bir yanılgı. Az örnekli kategoriler, sızıntının en tehlikeli olduğu yerdir.

Nerede işe yarar

Veri sızıntısı, makine öğrenmesi projelerinin en sinsi ve en pahalı hatasıdır:

  • Hedef kodlama (target encoding). Bu derste gördüğümüz tam senaryo — kategori başına hedef ortalaması, her zaman sadece eğitim verisinden öğrenilmeli.
  • Ölçekleme ve normalizasyon. 15. derste gördüğümüz StandardScaler, fit() işlemini SADECE eğitim verisiyle yapmalı, test verisine sadece transform() uygulanmalı.
  • Zaman serisi verisi. Geleceğe ait bilginin (örn. “bu ayın toplam satışı”) geçmişi tahmin etmek için kullanılan bir özniteliğe karışması, klasik bir zaman sızıntısıdır.

Bu 3 hatayı yaparsın:

  1. Herhangi bir istatistiği (ortalama, std, kodlama) train_test_split’ten ÖNCE hesaplamak.
  2. Yüksek performanslı bir test skorunu sorgulamadan kabul etmek — özellikle “gerçek olamayacak kadar iyi” görünüyorsa.
  3. Çapraz doğrulama (cross-validation) kullanırken her kat (fold) için kodlamayı yeniden hesaplamayı unutmak — aynı sızıntı orada da olabilir.

Kendini test et

1. Veri sızıntısı tam olarak nedir?
  1. Veri setinde eksik değer olması
  2. Test verisi hakkındaki bilginin, farkında olmadan eğitim sürecine karışması (doğru cevap)
  3. Modelin çok yavaş çalışması
  4. Veri setinin çok büyük olması

Neden: Veri sızıntısı, normalde modelin bilmemesi gereken test/gelecek bilgisinin, eğitim veya öznitelik hesaplama sürecine sızmasıdır — bu da yapay olarak şişirilmiş bir performans görüntüsü yaratır.

2. Notebook örneğinde sızıntılı R² (0.739) neden doğru R'den (0.677) daha yüksek çıktı?
  1. Sızıntılı hesaplama daha doğru bir yöntemdir
  2. Her test satırının kendi y değeri, o satırı 'tahmin etmek' için kullanılan kategori ortalamasına katkıda bulunuyordu (doğru cevap)
  3. Rastgele bir sapma, önemi yok
  4. Test seti çok küçüktü

Neden: Kategori ortalaması TÜM veriden (test dahil) hesaplanınca, her test satırının kendi hedef değeri kendi tahminine karışıyor — bu suni bir avantaj yaratıyor.

3. Kategori sayısı arttıkça (grup boyutu küçüldükçe) sızıntının etkisi neden büyüyor?
  1. Büyümüyor, sabit kalıyor
  2. Küçük gruplarda bir satırın kendi değeri, grup ortalamasının daha büyük bir payını oluşturur (doğru cevap)
  3. Küçük gruplarda hesaplama hatası artar
  4. Kategori sayısının sızıntıyla ilgisi yoktur

Neden: Bir grupta n_k satır varsa, tek bir satırın ortalamaya katkısı 1/n_k'dır. n_k küçüldükçe (az örnekli kategoriler) bu katkı büyür, sızıntı şiddetlenir.

Özet

Özet

  • Veri sızıntısı, test verisi hakkındaki bilginin eğitim sürecine (öznitelik hesaplama dahil) karışmasıdır.
  • Kategori kodlama, ölçekleme gibi her istatistik, SADECE eğitim verisinden öğrenilmeli, sonra test verisine uygulanmalıdır.
  • Sızıntı, test skorunu yapay olarak şişirir — üretimde bu performans kaybolur.
  • Az örnekli (yüksek kardinaliteli) kategorik değişkenlerde sızıntının etkisi çok daha şiddetlidir.
  • Bir test skoru "gerçek olamayacak kadar iyi" görünüyorsa, ilk şüphelenilmesi gereken şey veri sızıntısıdır.