Ana içeriğe geç

Orta13 dk

Dengesiz veri seti

Önkoşul:Sınıflandırma başarı metrikleri

Kanca

  1. derste hastalık taramasında dengesizliğin doğruluğu yanıltıcı yaptığını gördük. Ama sorun daha derin olabilir: bazen model, azınlık sınıfını TEK BİR ÖRNEKTE bile doğru tahmin edemez. Neden, ve nasıl düzeltilir?

Sezgi

Bir model, kayıp fonksiyonunu (ortalama hatayı) minimize etmeye çalışır. Eğer veri setinin %97’si “dolandırıcılık değil” ise, model her zaman “dolandırıcılık değil” diyerek zaten çok düşük bir ortalama hata elde edebilir — azınlık sınıfını öğrenmeye hiç “zorlanmaz”. Bu, dengesiz verinin temel tuzağıdır.

İki basit çözüm yolu var:

  • class_weight=‘balanced’: azınlık sınıfındaki bir hatayı, çoğunluk sınıfındaki bir hatadan DAHA AĞIR cezalandır.
  • Oversampling: azınlık sınıfının örneklerini, çoğunlukla aynı sayıya gelene kadar (tekrar tekrar) yeniden örnekle.

Mekanizma

Gerçek bir örnek: 1000 kredi kartı işleminden sadece 30’u dolandırıcılık.

Dengesiz veri: %3 dolandırıcılık

# Kredi kartı dolandırıcılığı: 1000 işlemden sadece 30'u (%3) dolandırıcılık.
n = 1000
dolandiricilik = np.zeros(n, dtype=int)
dolandiricilik[:30] = 1
rng.shuffle(dolandiricilik)

oznitelik = np.where(
    dolandiricilik == 1, rng.normal(0.7, 0.2, n), rng.normal(0.35, 0.2, n)
)
oznitelik = np.clip(oznitelik, 0, 1).reshape(-1, 1)
print(f"{n} işlem, {dolandiricilik.sum()} tanesi dolandırıcılık (%{dolandiricilik.mean() * 100:.1f})")
1000 işlem, 30 tanesi dolandırıcılık (%3.0)

Düz model tamamen başarısız

model = LogisticRegression()
model.fit(oznitelik, dolandiricilik)
tahmin = model.predict(oznitelik)
print(f"Tahmin edilen dolandırıcılık sayısı: {tahmin.sum()} / gerçek: {dolandiricilik.sum()}")
print(f"Duyarlılık (recall): {recall_score(dolandiricilik, tahmin):.3f}")
print(f"Kesinlik (precision): {precision_score(dolandiricilik, tahmin):.3f}")
print("Model, azınlık sınıfını (dolandırıcılık) yakalamakta zorlanıyor -- çoğunluk sınıfına 'çekiliyor'.")
Tahmin edilen dolandırıcılık sayısı: 0 / gerçek: 30
Duyarlılık (recall): 0.000
Kesinlik (precision): 0.000
Model, azınlık sınıfını (dolandırıcılık) yakalamakta zorlanıyor -- çoğunluk sınıfına 'çekiliyor'.

Model, 30 gerçek dolandırıcılık işleminden HİÇBİRİNİ yakalayamadı — duyarlılık tam olarak 0.000! Şimdi class_weight='balanced' ile deneyelim:

class_weight='balanced' çözümü

# class_weight='balanced': azınlık sınıfının HATASINI daha ağır cezalandır.
model_agirlikli = LogisticRegression(class_weight="balanced")
model_agirlikli.fit(oznitelik, dolandiricilik)
tahmin_agirlikli = model_agirlikli.predict(oznitelik)
print(f"\nclass_weight='balanced' ile tahmin edilen dolandırıcılık: {tahmin_agirlikli.sum()}")
print(f"Duyarlılık: {recall_score(dolandiricilik, tahmin_agirlikli):.3f}")
print(f"Kesinlik: {precision_score(dolandiricilik, tahmin_agirlikli):.3f}")
print(f"F1: {f1_score(dolandiricilik, tahmin_agirlikli):.3f}")

class_weight='balanced' ile tahmin edilen dolandırıcılık: 235
Duyarlılık: 0.833
Kesinlik: 0.106
F1: 0.189

Duyarlılık 0’dan 0.833’e fırladı — 30 dolandırıcılıktan 25’ini yakalıyor artık. Şimdi oversampling’i deneyelim:

Oversampling çözümü

# Azınlık sınıfını, çoğunlukla AYNI sayıya gelene kadar (yerine koyarak) yeniden örnekle.
X_azinlik = oznitelik[dolandiricilik == 1]
X_cogunluk = oznitelik[dolandiricilik == 0]
X_azinlik_buyutulmus = resample(X_azinlik, replace=True, n_samples=len(X_cogunluk), random_state=14)

X_dengeli = np.vstack([X_cogunluk, X_azinlik_buyutulmus])
y_dengeli = np.concatenate([np.zeros(len(X_cogunluk)), np.ones(len(X_azinlik_buyutulmus))])
print(f"\nOversampling sonrası: {len(y_dengeli)} satır, sınıf 0: {(y_dengeli == 0).sum()}, sınıf 1: {(y_dengeli == 1).sum()}")

model_oversample = LogisticRegression()
model_oversample.fit(X_dengeli, y_dengeli)
# Orijinal (dengesiz) test verisinde değerlendir -- gerçek dünyada veri hep dengesiz gelir.
tahmin_oversample = model_oversample.predict(oznitelik)
print(f"Duyarlılık: {recall_score(dolandiricilik, tahmin_oversample):.3f}")
print(f"Kesinlik: {precision_score(dolandiricilik, tahmin_oversample):.3f}")
print(f"F1: {f1_score(dolandiricilik, tahmin_oversample):.3f}")

Oversampling sonrası: 1940 satır, sınıf 0: 970, sınıf 1: 970
Duyarlılık: 0.800
Kesinlik: 0.104
F1: 0.184

Benzer bir iyileşme: duyarlılık 0.800. Çoğu kişi “dengesiz veri sorunu, sadece daha fazla veri toplayarak çözülür” sanır. Kısmen doğru, ama pratikte genelde mümkün değil (dolandırıcılık, nadir hastalık gibi olaylar doğası gereği az örneklenir) — bu yüzden var olan veriyi AKILLICA ağırlıklandırmak veya yeniden örneklemek, çok daha pratik bir çözümdür.

Matematik

class_weight nasıl çalışır?
Ag˘ırlıklı Kayıp=iwyikayıpi\text{Ağırlıklı Kayıp} = \sum_i w_{y_i} \cdot \text{kayıp}_iwsınıf=n2nsınıfw_{\text{sınıf}} = \frac{n}{2 \cdot n_{\text{sınıf}}}
SembolAnlamı
wyiw_{y_i}ii‘inci örneğin ait olduğu sınıfın ağırlığı
nnToplam örnek sayısı
nsınıfn_{\text{sınıf}}O sınıftaki örnek sayısı

Azınlık sınıfının nsınıfn_{\text{sınıf}}‘ı küçük olduğu için wsınıfw_{\text{sınıf}} büyür — yani azınlık sınıfındaki HER hatanın kayıp fonksiyonuna katkısı büyütülür. Model artık azınlık sınıfını göz ardı ederek kaçamaz; onu yanlış tahmin etmek pahalıya mal olur.

Kod

Üç yaklaşımı yan yana karşılaştıralım:

Üç yöntemin karşılaştırması

print(f"\n{'Yöntem':<25} {'Duyarlılık':>10} {'Kesinlik':>10} {'F1':>8}")
for isim, t in [("Düz model", tahmin), ("class_weight=balanced", tahmin_agirlikli), ("Oversampling", tahmin_oversample)]:
    print(f"{isim:<25} {recall_score(dolandiricilik, t):>10.3f} {precision_score(dolandiricilik, t):>10.3f} {f1_score(dolandiricilik, t):>8.3f}")

Yöntem                    Duyarlılık   Kesinlik       F1
Düz model                      0.000      0.000    0.000
class_weight=balanced          0.833      0.106    0.189
Oversampling                   0.800      0.104    0.184
Üç yöntemi (düz model, class_weight=balanced, oversampling) duyarlılık ve kesinlik açısından karşılaştıran gruplu çubuk grafik; düz modelin her iki çubuğu da sıfıra yakın, diğer ikisinin duyarlılık çubukları belirgin şekilde yüksek.
Düz model duyarlılıkta neredeyse sıfır; class_weight ve oversampling ikisi de duyarlılığı belirgin şekilde artırıyor (kesinlik pahasına).

Dikkat: her iki çözümün de kesinliği düşük (~0.10) — bu 11. dersteki kesinlik-duyarlılık dengesinin bir başka görünümü. Dengesiz veri sorununu çözmek, “her şeyi mükemmel” yapmaz; sadece modelin azınlık sınıfını GÖRMEZDEN GELMESİNİ engeller.

Nerede işe yarar

Dengesiz veri, gerçek dünyada istisna değil kuraldır:

  • Dolandırıcılık tespiti, hastalık taraması, arıza tahmini. “Kötü” olay her zaman nadirdir — bu tam olarak neden önemli olduklarıdır.
  • class_weight, hızlı ve genelde yeterli bir ilk adım. Çoğu scikit-learn modeli bu parametreyi destekler, ek veri işleme gerektirmez.
  • Oversampling/undersampling, daha fazla kontrol istediğinde. SMOTE gibi daha gelişmiş yöntemler, sadece kopyalamak yerine yeni sentetik örnekler üretir.

Bu 3 hatayı yaparsın:

  1. Oversampling’i eğitim/test ayrımından ÖNCE yapmak — bu, aynı örneğin hem eğitimde hem testte görünmesine (veri sızıntısı) yol açar. Her zaman önce böl, sonra sadece eğitim setini yeniden örnekle.
  2. Dengesiz veri sorununu çözdükten sonra hâlâ doğruluğa (accuracy) bakmak — 11. dersi hatırla, doğruluk burada hâlâ yanıltıcı.
  3. class_weight veya oversampling’in “sihirli bir düzeltme” olduğunu düşünmek — bu derste gördüğümüz gibi, kesinlik hâlâ düşük kalabilir; asıl kazanç azınlık sınıfının artık GÖRÜLEBİLİR olması.

Kendini test et

1. Notebook'ta düz (dengesiz) modelin duyarlılığı neden tam olarak 0.000 çıktı?
  1. Kod hatalıydı
  2. Model, azınlık sınıfını (dolandırıcılık) hiç tahmin etmeden bile ortalama hatayı düşük tutabildiği için, azınlık sınıfını öğrenmeye hiç 'zorlanmadı' (doğru cevap)
  3. Veri seti çok küçüktü
  4. scikit-learn dengesiz veriyi desteklemiyor

Neden: Veri setinin %97'si 'dolandırıcılık değil' olduğu için, model her zaman bu sınıfı tahmin ederek zaten düşük bir ortalama hata elde ediyor -- azınlık sınıfını öğrenmesi için hiçbir baskı yok.

2. class_weight='balanced' parametresi ne yapar?
  1. Veri setindeki örnek sayısını değiştirir
  2. Azınlık sınıfındaki bir hatanın kayıp fonksiyonuna katkısını büyüterek, modelin o sınıfı görmezden gelmesini zorlaştırır (doğru cevap)
  3. Sadece çoğunluk sınıfını siler
  4. Modelin hızını artırır

Neden: class_weight, azınlık sınıfındaki örneklerin ağırlığını artırarak, o sınıftaki bir hatanın toplam kayıba katkısını büyütür -- model artık azınlık sınıfını göz ardı ederek "ucuza kurtulamaz".

3. Oversampling işlemini eğitim/test ayrımından ÖNCE yapmak neden bir hatadır?
  1. Daha yavaş çalışır
  2. Aynı (kopyalanmış) örneğin hem eğitim hem test setinde görünmesine yol açarak veri sızıntısı yaratır (doğru cevap)
  3. Hiçbir sorun yaratmaz
  4. Sadece bellek kullanımını artırır

Neden: Oversampling'den önce bölme yapılmazsa, aynı örneğin kopyaları hem eğitim hem test setine dağılabilir -- bu da modelin test setinde daha önce 'gördüğü' verilerle değerlendirilmesine, yani veri sızıntısına yol açar.

Özet

Özet

  • Dengesiz veri setlerinde, düz bir model azınlık sınıfını tamamen göz ardı edebilir (duyarlılık=0 bile olabilir).
  • class_weight="balanced", azınlık sınıfındaki hataları daha ağır cezalandırarak modelin onu öğrenmesini zorlar.
  • Oversampling, azınlık sınıfını çoğunlukla aynı sayıya gelene kadar yeniden örnekler.
  • Her iki yöntem de duyarlılığı artırır ama genelde kesinlik pahasına -- bu 11. dersteki dengenin bir uzantısıdır.
  • Yeniden örnekleme, HER ZAMAN eğitim/test ayrımından SONRA, sadece eğitim setine uygulanmalıdır.
Sonraki adım: Destek vektör makineleri →