İki iş sorusu, iki problem türü
ml-giris dersinde ML'in iskeletini kurdun: bölme, fit, predict, metrik. Şimdi iki gerçekçi soruyu derinlemesine çözeceğiz. Anadolu Market "bir müşterinin sepet tutarı ne olacak?" diye soruyor; bu bir regresyon (sayısal hedef). Ilgaz Bank ise "bu kredi başvurusu temerrüde düşer mi?" diye soruyor; bu bir sınıflandırma (kategorik hedef). İkisinde de model kurmak işin yarısıdır; diğer yarısı modelin ne söylediğini yorumlamak ve iş kararına (kampanya bütçesi, kredi onayı) çevirmektir.
Örnek veriler kurgusaldır ve numpy.random.default_rng(seed) ile üretilir; aynı tohumla her çalıştırmada aynı tablo gelir. Gerçek projede bunlar veri ambarından gelir, ama yöntem aynıdır.
import numpy as np
def sepet_verisi(n=500, seed=42):
"""Anadolu Market kurgusal sepet verisi: ürün adedi, üye mi, kampanya, mesafe (km)."""
rng = np.random.default_rng(seed)
urun = rng.integers(1, 15, n)
uye = rng.integers(0, 2, n)
kampanya = rng.integers(0, 2, n)
mesafe = rng.uniform(1, 30, n)
sepet = 40 + 35 * urun + 60 * uye - 25 * kampanya + rng.normal(0, 30, n)
return np.column_stack([urun, uye, kampanya, mesafe]), sepet
X, y = sepet_verisi()
print(X.shape, round(y.mean(), 1))
print(X[:3].round(1))
Doğrusal ve çoklu regresyon: katsayıyı okumak
Çoklu doğrusal regresyon tahmini ŷ = b0 + b1·urun + b2·uye + b3·kampanya + b4·mesafe biçiminde kurar. Her katsayı (coefficient) şunu söyler: diğer özellikler sabitken bu özellik bir birim artarsa tahmin ne kadar değişir? Bu yorum, modeli iş birimine anlatmanın en güçlü aracıdır.
import numpy as np
def sepet_verisi(n=500, seed=42):
"""Anadolu Market kurgusal sepet verisi: ürün adedi, üye mi, kampanya, mesafe (km)."""
rng = np.random.default_rng(seed)
urun = rng.integers(1, 15, n)
uye = rng.integers(0, 2, n)
kampanya = rng.integers(0, 2, n)
mesafe = rng.uniform(1, 30, n)
sepet = 40 + 35 * urun + 60 * uye - 25 * kampanya + rng.normal(0, 30, n)
return np.column_stack([urun, uye, kampanya, mesafe]), sepet
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
X, y = sepet_verisi()
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=0)
model = LinearRegression().fit(X_tr, y_tr)
for ad, k in zip(["urun", "uye", "kampanya", "mesafe"], model.coef_):
print(f"{ad:>9}: {k:+.2f} TL")
print(f" sabit: {model.intercept_:.2f} TL")
print("Test R²:", round(model.score(X_te, y_te), 3))
Okuma: her ek ürün sepete yaklaşık 35 TL ekliyor, üye müşteriler ortalama 60 TL daha fazla harcıyor, kampanyalı sepetler ~25 TL daha düşük (indirim etkisi), mesafenin katsayısı sıfıra yakın: etkisi yok. Veriyi biz ürettiğimiz için gerçek değerleri biliyoruz ve modelin onları bulduğunu görüyoruz.
Düzenlileştirme: Ridge ve Lasso
Özellik sayısı arttıkça ya da özellikler birbiriyle güçlü ilişkiliyse (çoklu doğrusallık, multicollinearity) doğrusal regresyon katsayıları şişer ve aşırı öğrenir. Düzenlileştirme (regularization), büyük katsayılara ceza ekler:
| Model | Ceza | Etkisi | Ne zaman |
|---|---|---|---|
LinearRegression | Yok | Saf en küçük kareler | Az ve anlamlı özellik |
Ridge(alpha) | Katsayıların karesi (L2) | Hepsini küçültür, sıfırlamaz | İlişkili çok özellik |
Lasso(alpha) | Katsayıların mutlak değeri (L1) | Gereksizleri tam sıfıra indirir | Özellik seçimi istendiğinde |
ElasticNet | L1 + L2 karışımı | İkisinin dengesi | Çok sayıda ilişkili özellik |
import numpy as np
def sepet_verisi(n=500, seed=42):
"""Anadolu Market kurgusal sepet verisi: ürün adedi, üye mi, kampanya, mesafe (km)."""
rng = np.random.default_rng(seed)
urun = rng.integers(1, 15, n)
uye = rng.integers(0, 2, n)
kampanya = rng.integers(0, 2, n)
mesafe = rng.uniform(1, 30, n)
sepet = 40 + 35 * urun + 60 * uye - 25 * kampanya + rng.normal(0, 30, n)
return np.column_stack([urun, uye, kampanya, mesafe]), sepet
from sklearn.linear_model import Ridge, Lasso
X, y = sepet_verisi()
for alpha in [0.1, 10]:
r = Ridge(alpha=alpha).fit(X, y)
l = Lasso(alpha=alpha).fit(X, y)
print(f"alpha={alpha:<4} Ridge: {r.coef_.round(1)} Lasso: {l.coef_.round(1)}")
alpha cezanın gücüdür: büyüdükçe katsayılar küçülür. Lasso yüksek alpha'da etkisiz mesafe'yi ve hatta zayıf kampanya'yı sıfırlar. Doğru alpha'yı çapraz doğrulamayla (cross-validation) seçersin (RidgeCV, LassoCV ya da GridSearchCV).
Lojistik regresyon ve olasılık
Adı regresyon olsa da lojistik regresyon (logistic regression) bir sınıflandırıcıdır. Doğrusal skoru sigmoid fonksiyonundan geçirip 0-1 arası bir olasılığa çevirir. predict_proba her sınıfın olasılığını verir; sütun sırası model.classes_ ile aynıdır, ikili problemde pozitif sınıf (1 = temerrüt) ikinci sütundur: [:, 1]. predict ise varsayılan olarak 0.5 eşiğini uygular.
import numpy as np
def kredi_verisi(n=2000, seed=0):
"""Ilgaz Bank kurgusal kredi verisi: gelir (bin TL), borç oranı, gecikme sayısı, yaş."""
rng = np.random.default_rng(seed)
gelir = rng.normal(35, 12, n).clip(8, None)
borc_orani = rng.uniform(0, 0.9, n)
gecikme = rng.poisson(0.6, n)
yas = rng.integers(21, 66, n)
logit = -4.3 + 3.0 * borc_orani + 0.9 * gecikme - 0.05 * (gelir - 35)
y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
return np.column_stack([gelir, borc_orani, gecikme, yas]), y
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
X, y = kredi_verisi()
print("temerrüt oranı:", round(y.mean(), 3))
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=0, stratify=y)
model = LogisticRegression(max_iter=1000).fit(X_tr, y_tr)
olasilik = model.predict_proba(X_te[:5])
print(model.classes_)
print(olasilik.round(3))
print("P(temerrüt):", olasilik[:, 1].round(3))
print("predict :", model.predict(X_te[:5]))
Ağaçlar ve topluluklar
Karar ağacı doğrusal olmayan ilişkileri ve etkileşimleri (yüksek borç oranı ve gecikme) kendiliğinden yakalar ama tek başına kolayca aşırı öğrenir. Topluluk (ensemble) yöntemleri birçok ağacı birleştirir:
- Rastgele orman (
RandomForestClassifier): yüzlerce ağacı verinin ve özelliklerin rastgele alt kümelerinde eğitip oylatır; varyansı düşürür, ayarlaması kolaydır. - Gradient boosting (
HistGradientBoostingClassifier): ağaçları sırayla eğitir, her yeni ağaç öncekilerin hatasını düzeltir. Tablo verisinde genellikle en güçlü aile; eksik değerleri kendisi işler ve büyük veride hızlıdır (XGBoost, LightGBM aynı fikrin kütüphaneleridir).
import numpy as np
def kredi_verisi(n=2000, seed=0):
"""Ilgaz Bank kurgusal kredi verisi: gelir (bin TL), borç oranı, gecikme sayısı, yaş."""
rng = np.random.default_rng(seed)
gelir = rng.normal(35, 12, n).clip(8, None)
borc_orani = rng.uniform(0, 0.9, n)
gecikme = rng.poisson(0.6, n)
yas = rng.integers(21, 66, n)
logit = -4.3 + 3.0 * borc_orani + 0.9 * gecikme - 0.05 * (gelir - 35)
y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
return np.column_stack([gelir, borc_orani, gecikme, yas]), y
from sklearn.ensemble import RandomForestClassifier, HistGradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
X, y = kredi_verisi()
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=0, stratify=y)
modeller = {
"lojistik": LogisticRegression(max_iter=1000),
"orman": RandomForestClassifier(n_estimators=200, min_samples_leaf=5, random_state=0),
"boosting": HistGradientBoostingClassifier(max_depth=3, random_state=0),
}
for ad, m in modeller.items():
m.fit(X_tr, y_tr)
print(f"{ad:>9} ROC-AUC: {roc_auc_score(y_te, m.predict_proba(X_te)[:, 1]):.3f}")
orman = modeller["orman"]
for ad, o in zip(["gelir", "borc_orani", "gecikme", "yas"], orman.feature_importances_):
print(f"{ad:>11}: {o:.3f}")
Bu veri gerçekte doğrusal bir kuralla üretildiği için lojistik regresyon çok iyi iş çıkarır; karmaşık model her zaman kazanmaz. Özellik önemi (feature importance) ise ağaç modellerinde her özelliğin bölmelere katkısını gösterir. Dikkat: yas hedefle ilgisiz olduğu hâlde sıfır olmayan önem alabilir; çok değerli sayısal özellikler ağaçlarda önemi şişirebilir. Daha güvenilir ölçü için sklearn.inspection.permutation_importance kullan.
Sınıflandırma metrikleri
Temerrüt oranı %10 civarındaysa "herkes öder" diyen model %90 doğruluk alır. Bu yüzden karışıklık matrisine (confusion matrix) bakarız:
| Tahmin: 0 (öder) | Tahmin: 1 (temerrüt) | |
|---|---|---|
| Gerçek: 0 | TN (doğru negatif) | FP (yanlış alarm: iyi müşteriyi reddettin) |
| Gerçek: 1 | FN (kaçırılan: kötü krediyi onayladın) | TP (doğru yakalanan) |
- Precision (kesinlik) = TP / (TP + FP): "Riskli dediklerimin ne kadarı gerçekten riskli?"
- Recall (duyarlılık) = TP / (TP + FN): "Gerçek temerrütlerin ne kadarını yakaladım?"
- F1 = precision ve recall'ın harmonik ortalaması; ikisini tek sayıda dengeler.
- ROC-AUC: eşikten bağımsız sıralama kalitesi. Rastgele bir temerrütçüye, rastgele bir iyi müşteriden daha yüksek risk verme olasılığı; 0.5 yazı-tura, 1.0 kusursuz.
from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score, classification_report
gercek = [0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
tahmin = [0, 0, 0, 0, 1, 1, 1, 1, 1, 0]
print(confusion_matrix(gercek, tahmin).tolist()) # [[TN, FP], [FN, TP]]
print("precision:", round(precision_score(gercek, tahmin), 3))
print("recall :", round(recall_score(gercek, tahmin), 3))
print("f1 :", round(f1_score(gercek, tahmin), 3))
print(classification_report(gercek, tahmin, target_names=["öder", "temerrüt"]))
Dengesiz sınıflar ve eşik seçimi
Dengesiz veride model azınlık sınıfını görmezden gelmeye meyillidir. İki temel kol var: class_weight="balanced" ile azınlık sınıfının hatalarını daha pahalı yapmak, ve eşiği (threshold) iş maliyetine göre seçmek. Eşiği düşürmek daha çok başvuruyu riskli sayar: recall artar, precision düşer. Doğru eşik bir matematik sorusu değil, iş sorusudur: kaçırılan bir temerrüt bankaya ortalama ne kadara mal oluyor, yanlış reddedilen iyi müşteri ne kadar kâr kaybettiriyor?
import numpy as np
def kredi_verisi(n=2000, seed=0):
"""Ilgaz Bank kurgusal kredi verisi: gelir (bin TL), borç oranı, gecikme sayısı, yaş."""
rng = np.random.default_rng(seed)
gelir = rng.normal(35, 12, n).clip(8, None)
borc_orani = rng.uniform(0, 0.9, n)
gecikme = rng.poisson(0.6, n)
yas = rng.integers(21, 66, n)
logit = -4.3 + 3.0 * borc_orani + 0.9 * gecikme - 0.05 * (gelir - 35)
y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
return np.column_stack([gelir, borc_orani, gecikme, yas]), y
import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import precision_score, recall_score
from sklearn.model_selection import train_test_split
X, y = kredi_verisi()
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=0, stratify=y)
for cw in [None, "balanced"]:
m = LogisticRegression(max_iter=1000, class_weight=cw).fit(X_tr, y_tr)
p = m.predict(X_te)
print(f"class_weight={str(cw):<8} recall={recall_score(y_te, p):.2f} precision={precision_score(y_te, p, zero_division=0):.2f}")
m = LogisticRegression(max_iter=1000).fit(X_tr, y_tr)
risk = m.predict_proba(X_te)[:, 1]
for esik in [0.5, 0.3, 0.15]:
p = (risk >= esik).astype(int)
print(f"eşik={esik:<4} recall={recall_score(y_te, p):.2f} precision={precision_score(y_te, p, zero_division=0):.2f} riskli sayılan={p.mean():.0%}")
average_precision_score) karşılaştır. Kazanan modelin eşiğini sonra iş maliyetiyle belirle. Eşiği de test setinde değil doğrulama verisinde seç.Model seçimi: bir yol haritası
Yeni bir regresyon ya da sınıflandırma problemine başladığında hangi modeli deneyeceğini rastgele seçme; aşağıdaki sıra hem zaman kazandırır hem de sonuçlarını savunmanı kolaylaştırır.
| Adım | Regresyon | Sınıflandırma | Neden |
|---|---|---|---|
| 1. Taban çizgi | DummyRegressor (ortalama) | DummyClassifier (en sık sınıf) | Her modelin geçmesi gereken alt sınır |
| 2. Basit ve yorumlanabilir | LinearRegression / Ridge | LogisticRegression | Katsayılar iş birimine anlatılabilir |
| 3. Doğrusal olmayan | RandomForestRegressor | RandomForestClassifier | Etkileşimleri kendiliğinden yakalar, az ayar ister |
| 4. En güçlü aday | HistGradientBoostingRegressor | HistGradientBoostingClassifier | Tablo verisinde genellikle en iyi skor |
| 5. Karar | Skor farkı, yorumlanabilirlik ihtiyacı, eğitim/tahmin süresi ve bakım maliyeti birlikte tartılır | "En yüksek skor" tek ölçüt değildir | |
Regresyonda hata metriklerini de iş diline çevirmeyi unutma. "RMSE 31.4" yerine "sepet tutarı tahminlerimiz tipik olarak ±30 TL sapıyor; bu, kampanya bütçesi planlamasında yüzde birkaçlık bir hata payı demek" cümlesi karar vericinin işine yarar. Hataların dağılımına da bak: model küçük sepetlerde iyi, büyük kurumsal siparişlerde kötüyse ortalama metrik bunu gizler. Tahmin ile gerçeği karşılaştıran bir saçılım grafiği ya da hataların segment bazında (üye/üye değil, şehir) ortalaması, tek bir sayının saklayacağı sorunları ortaya çıkarır. Sınıflandırmada da aynı ilke geçerli: genel AUC iyiyken belirli bir müşteri grubunda recall çok düşükse, bu hem iş hem de adalet (fairness) sorunudur ve model kartında açıkça yazılmalıdır.
Son olarak, aynı veride çok sayıda model ve ayar deneyip en iyisini seçmek de bir tür aşırı öğrenmedir (overfitting). Karşılaştırmaları sabit bir çapraz doğrulama düzeninde yap, test setini yalnızca en sonda seçilen tek model için kullan. Bunun sistematik yolu (Pipeline, cross_val_score, GridSearchCV) bir sonraki dersin konusu.
Derinleş: gradient boosting uygulamada (HistGradientBoosting, XGBoost, LightGBM)
Tablo verisinde (satış, kredi, lojistik) en sık kazanan model ailesi gradient boosting'dir: sığ ağaçlar sırayla eklenir ve her yeni ağaç öncekilerin hatasını (artıkları, residuals) düzeltmeye çalışır. Tahmin, başlangıç değeri artı her ağacın learning_rate ile küçültülmüş katkılarının toplamıdır. Bu bölümde scikit-learn'ün hızlı uygulaması HistGradientBoosting'i Lezzet Kurye'nin gerçek teslimat verisiyle (lezzet_kurye, 6638 teslim edilmiş sipariş) ayarlayacak, sonra aynı ayarların XGBoost ve LightGBM'deki karşılıklarını göreceksin.
artik_tahmin = [30.0] * 4 # başlangıç: ortalama süre (dk)
gercek = [25.0, 40.0, 35.0, 20.0]
for tur in range(3):
artik = [g - t for g, t in zip(gercek, artik_tahmin)]
duzeltme = [a * 0.5 for a in artik] # "ağaç" artığı mükemmel bilseydi; lr = 0.5
artik_tahmin = [t + d for t, d in zip(artik_tahmin, duzeltme)]
print(tur + 1, [round(t, 1) for t in artik_tahmin])Her tur hatanın yarısını kapatır; küçük learning_rate daha çok tur ister ama genellikle daha iyi genelleşir. Gerçekte ağaç artığı tam bilemez; derinliği ve yaprak sayısı sınırlı olduğu için genel örüntüyü öğrenir.
Neden "Hist"?
Klasik GradientBoostingClassifier her bölme için tüm benzersiz değerleri dener. HistGradientBoostingRegressor/Classifier ise önce her sütunu en fazla 255 kutuya (bin) ayırır ve bölmeleri kutu sınırlarında arar; on binlerce satırda çok daha hızlıdır. Ayrıca eksik değerleri (NaN) kendisi yönetir ve kategorik sütunları one-hot yapmadan doğrudan kullanabilir.
import veri
import pandas as pd
def kurye_verisi():
o = veri.oku("lezzet_kurye", "lk_orders")
r = veri.oku("lezzet_kurye", "lk_restaurants")
k = veri.oku("lezzet_kurye", "lk_couriers")
s = veri.oku("lezzet_kurye", "lk_order_lines")
o = o[o["status"] == "teslim"].copy()
zaman = pd.to_datetime(o["placed_at"])
o["saat"] = zaman.dt.hour
o["hafta_sonu"] = (zaman.dt.dayofweek >= 5).astype(int)
o["sure_dk"] = (pd.to_datetime(o["delivered_at"]) - zaman).dt.total_seconds() / 60
adet = s.groupby("order_id")["qty"].sum().rename("adet").reset_index()
d = (o.merge(r[["restaurant_id", "cuisine", "city"]], on="restaurant_id")
.merge(k[["courier_id", "vehicle"]], on="courier_id")
.merge(adet, on="order_id"))
d["restoran"] = d["restaurant_id"].astype(str)
X = d[["saat", "hafta_sonu", "adet", "cuisine", "city", "vehicle", "restoran"]].copy()
for c in ["cuisine", "city", "vehicle", "restoran"]:
X[c] = X[c].astype("category")
return X, d["sure_dk"]
from sklearn.model_selection import train_test_split
X, y = kurye_verisi()
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=0)
KATEGORIK = ["cuisine", "city", "vehicle", "restoran"]
from sklearn.ensemble import HistGradientBoostingRegressor
from sklearn.metrics import mean_absolute_error
print(X.shape, "| ortalama süre:", round(y.mean(), 1), "dk")
taban = mean_absolute_error(y_te, [y_tr.mean()] * len(y_te))
model = HistGradientBoostingRegressor(
categorical_features=KATEGORIK, # pandas category sütunları
learning_rate=0.1, max_iter=500, max_leaf_nodes=15,
early_stopping=True, validation_fraction=0.15, n_iter_no_change=10,
random_state=0)
model.fit(X_tr, y_tr)
print("taban MAE:", round(taban, 2), "| HGB MAE:", round(mean_absolute_error(y_te, model.predict(X_te)), 2))
print("durduğu tur:", model.n_iter_)Model ortalamaya göre hatayı azaltıyor ama mucize beklenmemeli: teslimat süresini trafik, hava ve restoranın o anki yoğunluğu da belirler ve bunlar veride yok. Erken durdurma 500 tur yerine birkaç on turda durdu; n_iter_ bunu gösterir.
Önemli ayarlar
| Parametre | Varsayılan | Etkisi |
|---|---|---|
learning_rate | 0.1 | Her ağacın katkısını küçültür. Düşür (0.05) → daha çok tur, genelde daha iyi sonuç. |
max_iter | 100 | En fazla ağaç sayısı. Erken durdurmayla birlikte üst sınır gibi düşün. |
max_leaf_nodes | 31 | Ağaç başına yaprak; modelin karmaşıklığı. Asıl ayar budur. |
max_depth | None | Derinlik sınırı; genelde max_leaf_nodes yeterli. |
min_samples_leaf | 20 | Yapraktaki en az satır; büyütmek gürültüyü ezberlemeyi azaltır. |
l2_regularization | 0 | Yaprak değerlerini küçülten ceza. |
early_stopping | "auto" | 10.000 satırdan büyük veride kendiliğinden açılır; validation_fraction ve n_iter_no_change ile birlikte çalışır. |
categorical_features | "from_dtype" (yeni sürümler) | Kategorik sütunlar; ad listesi ya da bool maske ver. Kategori sayısı max_bins'i (255) aşmamalı. |
monotonic_cst | None | "adet artınca süre azalmasın" gibi iş kuralını modele zorlar. |
XGBoost ve LightGBM karşılıkları
İş ilanlarında XGBoost ve LightGBM adları sık geçer. İkisi de aynı fikrin bağımsız, çok optimize uygulamalarıdır ve scikit-learn uyumlu sınıflar (XGBRegressor, LGBMRegressor) sunar; yani pipeline, çapraz doğrulama ve arama araçları aynen çalışır. Kavramlar birebir eşlenir, adlar değişir:
| scikit-learn HGB | XGBoost | LightGBM |
|---|---|---|
| learning_rate | learning_rate (eta) | learning_rate |
| max_iter | n_estimators | n_estimators (num_iterations) |
| max_leaf_nodes | max_leaves | num_leaves |
| max_depth | max_depth (varsayılan 6) | max_depth (varsayılan -1, sınırsız) |
| min_samples_leaf | min_child_weight (ağırlık toplamı, birebir değil) | min_child_samples |
| l2_regularization | reg_lambda | reg_lambda (lambda_l2) |
| max_bins | max_bin | max_bin |
| n_iter_no_change | early_stopping_rounds | callbacks=[early_stopping(n)] |
| categorical_features | enable_categorical=True + category dtype | category dtype ya da categorical_feature |
| (yok) | subsample, colsample_bytree | bagging_fraction, feature_fraction |
# pip install xgboost lightgbm
from xgboost import XGBRegressor
from lightgbm import LGBMRegressor, early_stopping
xgb = XGBRegressor(n_estimators=500, learning_rate=0.1, max_leaves=15, tree_method="hist",
enable_categorical=True, early_stopping_rounds=10, random_state=0)
xgb.fit(X_tr, y_tr, eval_set=[(X_te, y_te)], verbose=False) # gerçekte ayrı bir doğrulama seti kullan
print(xgb.best_iteration)
lgbm = LGBMRegressor(n_estimators=500, learning_rate=0.1, num_leaves=15, random_state=0, verbose=-1)
lgbm.fit(X_tr, y_tr, eval_set=[(X_te, y_te)], callbacks=[early_stopping(10, verbose=False)])
print(lgbm.best_iteration_)eval_set olarak test seti verildi; gerçek projede bu sızıntıdır, çünkü tur sayısını test setine bakarak seçersin. Eğitim verisinden ayrı bir doğrulama parçası ayır; HGB'nin validation_fraction ayarı bunu senin yerine yapar.Olasılık kalibrasyonu
Bir modelin 0.20 dediği başvuruların gerçekten yaklaşık %20'si temerrüde düşüyorsa model kalibredir. Lojistik regresyon genellikle iyi kalibredir; rastgele orman ve class_weight="balanced" kullanılan modeller olasılıkları çarpıtır (balanced, olasılıkları yukarı iter). Beklenen kayıp gibi olasılığın kendisinin kullanıldığı hesaplarda CalibratedClassifierCV ile kalibre et, sklearn.calibration.calibration_curve ile kontrol et.
Çok sınıflı metrikler
Üç ve daha fazla sınıfta precision/recall her sınıf için ayrı hesaplanır ve birleştirilir: average="macro" (her sınıfa eşit ağırlık; azınlık sınıflarını önemser), average="weighted" (sınıf büyüklüğüyle ağırlıklı), average="micro" (tüm kararları havuzlar; doğruluğa eşittir). classification_report üçünü birden gösterir.
Beklenen kayıp
Bankacılıkta beklenen kayıp = PD (temerrüt olasılığı) × LGD (temerrüt hâlinde kayıp oranı) × EAD (temerrüt anındaki risk tutarı). Senin sınıflandırıcın PD'yi üretir; bu yüzden olasılıkların kalibre olması doğrudan para demektir.
import numpy as np pd_ = np.array([0.02, 0.10, 0.35]) # model çıktısı lgd = 0.45 # teminata göre kayıp oranı ead = np.array([50_000, 120_000, 30_000]) # kredi tutarları (TL) beklenen = pd_ * lgd * ead print(beklenen.round(0), "toplam:", round(beklenen.sum()))