Bir model iyi tahmin ediyor olabilir, ama varsayımları tutuyor mu?
Ürettiği olasılıklar güvenilir mi?
Zamanla bozuluyor mu?
Ve en zoru: gördüğü ilişki gerçek bir neden-sonuç mu, yoksa sadece bir örüntü mü?
Bu yazı, kıdemli seviyenin ayırt edici sorularını, model tanılamadan Bayesçi çıkarıma, dağılım kaymasından nedensel çıkarıma kadar pratik Python örnekleriyle ele alıyor. Üç bölümden oluşan yazı serisinin son ve en derin halkası. Örnekler yine pandas, NumPy, scikit-learn, SciPy ve statsmodels ile.
Model Diagnostiği ve Doğrulama
İleri düzey model doğrulama; bir yapay zeka veya istatistiksel modelin ürettiği sonuçların güvenilirliğini, istikrarını ve matematiksel varsayımlara uyumunu test etme metodolojisidir.
1. Regresyon Varsayımları Nedir? / Neden Önemlidir?
Regresyon varsayımları, En Küçük Kareler (OLS) yönteminin ürettiği katsayıların sapmasız ve en küçük varyanslı (BLUE) olmasını sağlayan temel istatistiksel önkoşullardır. Bu varsayımların ihlal edilmesi modelin sayısal bir çıktı üretmesini engellemez; ancak üretilen çıktıların ve p-değerlerinin sessizce hatalı olmasına yol açar. Modelin gerçek dünyadaki güvenilirliği bu varsayımların doğrulanmasına bağlıdır.
Çoklu Bağlantı (Multicollinearity) ve VIF
Bağımsız değişkenler birbiriyle yüksek korelasyonluysa, model hangi değişkenin etkiyi taşıdığını ayırt edemez ve katsayı tahminleri kararsızlaşır — küçük veri değişimleri katsayıları savurur. VIF (Varyans Şişme Faktörü) bunu ölçer; genelde VIF > 5–10 bir uyarı işaretidir.
VIFⱼ = 1 / (1 − Rⱼ²)
# Her değişken için VIF hesabı — statsmodels
import pandas as pd
from statsmodels.stats.outliers_influence import variance_inflation_factor
import numpy as np
rng = np.random.default_rng(0)
x1 = rng.normal(size=200)
X = pd.DataFrame({
"x1": x1,
"x2": x1 * 0.95 + rng.normal(0, 0.1, 200), # x1'e çok benzer → sorun
"x3": rng.normal(size=200),
})
for i, kol in enumerate(X.columns):
vif = variance_inflation_factor(X.values, i)
print(f"{kol}: VIF = {vif:.1f}") # x1 ve x2 yüksek çıkarDeğişen Varyans ve Otokorelasyon
Heteroscedasticity (Değişen Varyanslılık), hataların varyansının sabit olmaması durumudur; Breusch-Pagan ve White testleriyle sınanır. Autocorrelation (Otokorelasyon) ise hataların birbiriyle — özellikle zaman serilerinde — ilişkili olmasıdır ve Durbin-Watson istatistiğiyle değerlendirilir (2 civarı iyidir; 0'a yakın pozitif, 4'e yakın negatif otokorelasyon).
# Breusch-Pagan ve Durbin-Watson
import statsmodels.api as sm
from statsmodels.stats.diagnostic import het_breuschpagan
from statsmodels.stats.stattools import durbin_watson
X_ = sm.add_constant(X)
model = sm.OLS(rng.normal(size=200), X_).fit()
bp = het_breuschpagan(model.resid, X_)
print(f"Breusch-Pagan p-değeri: {bp[1]:.3f}") # küçükse değişen varyans var
print(f"Durbin-Watson: {durbin_watson(model.resid):.2f}") # ~2 idealGörsel tanılama şart: Sayısal testlerin yanında artık grafikleri (residuals vs fitted) ve Q-Q plot her zaman incelenmeli. Q-Q plot noktaları düz çizgiden saparsa hatalar normal değildir; artık grafiğinde bir huni şekli görürsen değişen varyansla karşı karşıyasındır. Cook's Distance ve Leverage ise tek başına modeli çeken aşırı etkili gözlemleri yakalar.
Çoklu bağlantıyı ve aşırı öğrenmeyi dizginlemek için regularizasyon devreye girer: L2 (Ridge) katsayıları küçültür, L1 (Lasso) bazılarını tam sıfıra çekerek değişken seçimi yapar, Elastic Net ikisini birleştirir.
# Ridge, Lasso, Elastic Net — scikit-learn
from sklearn.linear_model import Ridge, Lasso, ElasticNet
y = rng.normal(size=200)
ridge = Ridge(alpha=1.0).fit(X, y)
lasso = Lasso(alpha=0.1).fit(X, y)
print("Ridge katsayıları:", ridge.coef_.round(3))
print("Lasso katsayıları:", lasso.coef_.round(3)) # bazıları 0 → seçim2. Olasılıksal Kayıplar ve Kalibrasyon
Bir sınıflandırıcının "%90 pozitif" demesi, gerçekten böyle vakaların onda dokuzunun pozitif çıktığı anlamına geliyor mu? İşte kalibrasyon sorusu budur — ve doğru cevap, kararın olasılığa dayandığı her yerde (tıp, kredi, risk) hayatidir.
Log-Loss / Cross-Entropy, modelin ürettiği olasılıkların gerçekle uyumunu ölçer ve kendinden emin ama yanlış tahminleri logaritmik olarak ağır cezalandırır — "%99 eminim" deyip yanılmak felakettir. Brier Score ise olasılık tahminlerinin ortalama kare hatasıdır; daha yumuşak ama yorumlaması sezgiseldir.
LogLoss = −mean( y·log(p) + (1−y)·log(1−p) ) · Brier = mean( (p − y)² )
# Log-Loss ve Brier Score — scikit-learn
from sklearn.metrics import log_loss, brier_score_loss
import numpy as np
y = np.array([1, 0, 1, 1, 0])
# Aynı sıralamaya sahip ama farklı özgüvende iki model
temkinli = np.array([0.7, 0.3, 0.6, 0.8, 0.4])
asiri = np.array([0.99, 0.5, 0.6, 0.9, 0.6]) # bir yanlışta çok emin
print(f"Temkinli → LogLoss {log_loss(y, temkinli):.3f} | Brier {brier_score_loss(y, temkinli):.3f}")
print(f"Aşırı özgüven → LogLoss {log_loss(y, asiri):.3f}") # daha yüksek cezaKalibrasyon kalitesini tek sayıyla özetleyen metrik ECE (Expected Calibration Error)'dir: tahminler güven aralıklarına bölünür ve her aralıkta "tahmin edilen olasılık" ile "gerçek frekans" karşılaştırılır. Kalibrasyon bozuksa Platt Scaling (sigmoid) veya Isotonic Regression ile düzeltilir.
# Kalibrasyon düzeltmesi — scikit-learn
from sklearn.calibration import CalibratedClassifierCV
# mevcut bir sınıflandırıcıyı isotonic ile kalibre et
kalibre = CalibratedClassifierCV(base_model, method="isotonic", cv=5)
kalibre.fit(X_train, y_train)
# artık predict_proba çıktıları gerçek frekanslara daha yakınÇoklu Test Düzeltmeleri
Yüzlerce hipotez test edersen, hiçbiri gerçek olmasa bile bazıları tesadüfen "anlamlı" çıkar — 100 testte %5 eşikle beklenen 5 yanlış pozitif. Düzeltmeler bu şişmeyi kontrol eder. Bonferroni, aile bazlı hata oranını (FWER) korumak için eşiği test sayısına böler (α/m) — güvenli ama katı. Holm-Bonferroni onun daha güçlü, adımsal versiyonudur. Benjamini-Hochberg (FDR) ise farklı bir felsefe izler: tüm yanlışları engellemek yerine, anlamlı bulunanlar arasındaki beklenen yanlış oranını sınırlar — genomik gibi binlerce testin yapıldığı alanlarda standarttır.
# Çoklu test düzeltmeleri — statsmodels
from statsmodels.stats.multitest import multipletests
import numpy as np
# 10 testten gelen ham p-değerleri
p_degerleri = np.array([0.001, 0.008, 0.02, 0.04, 0.05, 0.2, 0.3, 0.5, 0.7, 0.9])
for yontem in ["bonferroni", "holm", "fdr_bh"]:
red, p_duz, _, _ = multipletests(p_degerleri, alpha=0.05, method=yontem)
print(f"{yontem:12s} → anlamlı test sayısı: {red.sum()}")
# FDR (fdr_bh) genelde en fazla keşfe izin verir3. Nedensel Yanlılıklar ve Bayesian Çerçeve
Değerlendirmeyi bozan yanlılıklar
Korelasyon nedensellik değildir — ama neden değildir? Cevap çoğu zaman bir yanlılıkta gizlidir. Confounding (Karıştırıcı), hem nedeni hem sonucu etkileyen üçüncü bir değişkenin sahte bir ilişki yaratmasıdır (dondurma satışı ile boğulma vakaları — ortak neden: sıcaklık). Collider Bias (Çarpıştırıcı) ise tersini yapar: iki değişkenin ortak sonucuna koşullanmak, aralarında hiç olmayan bir ilişki uydurur. Bu yüzden "her değişkeni kontrol et" tavsiyesi tehlikelidir.
Simpson Paradoksu, alt gruplarda görülen eğilimin gruplar birleştirilince tersine dönmesidir. Covariate Shift, ilişki P(Y|X) sabitken girdi dağılımı P(X)'in değişmesidir. Selection Bias ve Survivorship (Hayatta Kalma) Yanlılığı da yalnızca "seçilmiş" veya "hayatta kalmış" gözlemlere bakmaktan doğan çarpıklıklardır.
# Simpson Paradoksu — pandas ile canlı örnek
import pandas as pd
df = pd.DataFrame({
"bolum": ["A","A","B","B"],
"cinsiyet":["K","E","K","E"],
"basvuru": [100, 400, 400, 100],
"kabul": [80, 300, 100, 20],
})
# Bölüm bazında oranlar bir hikaye anlatır...
df["oran"] = df["kabul"] / df["basvuru"]
print(df.pivot(index="bolum", columns="cinsiyet", values="oran"))
# ...toplulaştırınca tersine dönebilir
toplam = df.groupby("cinsiyet")[["basvuru","kabul"]].sum()
print((toplam["kabul"] / toplam["basvuru"]).round(2))Bayesçi çerçeve
Frekansçı istatistik "veri rastgeledir, parametre sabittir" derken; Bayesçi yaklaşım parametreyi bir olasılık dağılımıyla ele alır. Mantık üç adımdan oluşur: Prior (önsel) veriyi görmeden önceki inancın, Likelihood (olabilirlik) verinin belirli parametreler altındaki olasılığı, Posterior (sonsal) ise ikisinin birleşiminden doğan güncellenmiş inançtır.
P(θ | X) ∝ P(X | θ) · P(θ) (Sonsal ∝ Olabilirlik × Önsel)
Bayesçi Credible Interval (Güvenilir Aralık), frekansçı güven aralığından kavramsal olarak farklıdır: doğrudan "parametre %95 olasılıkla bu aralıktadır" diyebilirsin — ki insanların güven aralığından sezgisel olarak zaten beklediği yorum budur. Model seçiminde MAP (önseli hesaba katan), MLE (katmayan) ve Bayes Factor kullanılır; karmaşık modellerde sonsal dağılımdan örneklemek için MCMC yöntemlerine başvurulur.
# Basit Bayes güncellemesi: bir madeni paranın yazı olasılığı
from scipy import stats
# Beta(2,2) önseli: paranın adil olduğuna hafif inanç
a_prior, b_prior = 2, 2
# Veri: 10 atışta 8 yazı
yazi, tura = 8, 2
# Beta-Binomial eşleniği → sonsal kapalı formda
a_post, b_post = a_prior + yazi, b_prior + tura
posterior = stats.beta(a_post, b_post)
print(f"Sonsal ortalama: {posterior.mean():.3f}")
alt, ust = posterior.interval(0.95) # %95 güvenilir aralık
print(f"%95 Credible Interval: [{alt:.3f}, {ust:.3f}]")4. Dağılım Kayması (Drift) ve Mesafe Metrikleri
Bir model üretime alındıktan sonra dünya durmaz. Girdi dağılımı kayar, kullanıcı davranışı değişir, ve dün mükemmel çalışan model bugün sessizce bozulur. Drift metrikleri bu kaymayı, performans düşüşü bir felakete dönüşmeden yakalar.
KL-Divergence iki dağılım arasındaki bilgi kaybını ölçer ama asimetriktir ve payda sıfır olduğunda tanımsızlaşır. JS-Divergence onun simetrik ve [0, 1] aralığında sınırlı versiyonudur. Her ikisinin de zayıf noktası, örtüşmeyen dağılımlarda çalışmamasıdır — işte Wasserstein Distance (Earth Mover's Distance) tam burada parlar: bir dağılımı diğerine dönüştürmenin minimum "taşıma maliyetini" ölçer, örtüşme olmasa bile anlamlı bir değer üretir ve sıfıra bölme sorunu yaşamaz.
# Dağılım mesafeleri — SciPy
from scipy import stats
import numpy as np
rng = np.random.default_rng(0)
egitim = rng.normal(0, 1, 1000)
uretim = rng.normal(0.5, 1, 1000) # dağılım sağa kaymış
# Wasserstein: örtüşmeyen dağılımlarda bile stabil
w = stats.wasserstein_distance(egitim, uretim)
print(f"Wasserstein: {w:.3f}")
# İki örneklem aynı dağılımdan mı geliyor — KS testi
ks, p = stats.ks_2samp(egitim, uretim)
print(f"KS istatistiği: {ks:.3f}, p={p:.4f}")Endüstride en yaygın drift metriği PSI (Population Stability Index)'dir: PSI < 0.1 kararlı, 0.1–0.25 orta düzey kayma, > 0.25 belirgin drift anlamına gelir. Daha ileri izlemede MMD ve Energy Distance ile data drift (girdi dağılımı) ile concept drift (girdi–çıktı ilişkisi) ayrımı yapılır.
# PSI — üretim izlemenin klasik metriği
import numpy as np
def psi(beklenen, gozlenen, bins=10):
kesim = np.percentile(beklenen, np.linspace(0, 100, bins + 1))
kesim[0], kesim[-1] = -np.inf, np.inf
b = np.histogram(beklenen, kesim)[0] / len(beklenen)
g = np.histogram(gozlenen, kesim)[0] / len(gozlenen)
b, g = np.clip(b, 1e-6, None), np.clip(g, 1e-6, None)
return np.sum((g - b) * np.log(g / b))
print(f"PSI: {psi(egitim, uretim):.3f}") # >0.25 ise alarm5. Belirsizlik Kestirimi ve Conformal Prediction
Çoğu model tek bir tahmin verir ama "ne kadar eminim?" sorusunu yanıtlamaz. Conformal Prediction, bu boşluğu doldurur ve dikkat çekici bir garanti sunar: dağılımdan bağımsız (distribution-free) biçimde, sonlu örneklemde geçerli, kapsama garantili tahmin kümeleri/aralıkları üretir. En güzel yanı, herhangi bir modelin üzerine sarılabilmesidir — modeli yeniden eğitmene gerek yok.
# Basit split conformal regression — sıfırdan NumPy
import numpy as np
# model kalibrasyon kümesinde tahmin yapmış olsun
y_kalib = np.array([10, 12, 14, 16, 18, 20])
yhat_kalib = np.array([11, 11, 15, 15, 19, 19])
# uygunsuzluk skorları = mutlak hatalar
skorlar = np.abs(y_kalib - yhat_kalib)
# %90 kapsama için (1-α) kuantili
alpha = 0.1
q = np.quantile(skorlar, 1 - alpha, method="higher")
# yeni bir tahmin için aralık: ŷ ± q
yeni_tahmin = 17.0
print(f"%90 tahmin aralığı: [{yeni_tahmin - q:.1f}, {yeni_tahmin + q:.1f}]")İki tür belirsizlik: Tüm belirsizlik aynı değildir. Aleatoric belirsizlik verinin doğasındaki indirgenemez gürültüdür (aynı girdiyle farklı çıktılar) — daha fazla veri onu azaltmaz. Epistemic belirsizlik ise modelin bilgisizliğidir ve veri arttıkça azalır. Derin öğrenmede ikisi MC Dropout veya Deep Ensembles ile kestirilir; ensemble'lar genelde daha iyi kalibre olur.
6. Nedensel Çıkarım (Causal Inference)
Buraya kadar her şey örüntü tanımaydı. Nedensel çıkarım farklı bir soru sorar: "X'i değiştirirsem Y ne olur?" — sadece gözlemlediğimde değil, müdahale ettiğimde. Bu, tahminden karar vermeye geçişin eşiğidir.
Judea Pearl'ün çerçevesinde Causal DAGs (Yönlü Çevrimsiz Grafikler), değişkenler arası nedensel varsayımları grafik olarak kodlar ve hangi değişkeni kontrol etmemiz (ya da etmememiz) gerektiğini belirlememizi sağlar. do-Calculus ve do(X) müdahalesi, "X'i dışarıdan zorlarsak ne olur" sorusunu yanıtlar ve gözlemsel P(Y|X)'ten temelde farklıdır — görmek ile yapmak aynı şey değildir.
P(Y | do(X)) ≠ P(Y | X) (müdahale ≠ gözlem)
Karıştırıcıları doğru şekilde engellemek için Backdoor ve Frontdoor kriterleri kullanılır. Karşıolgusallar (Counterfactuals) ise en derin soruyu sorar: "Eğer farklı davransaydık ne olurdu?" — Pearl'ün nedensellik merdiveninde ilişki, müdahale ve karşıolgunun en üst basamağı.
Gözlemsel veriden nedensel etki çıkarmak için zengin bir metodoloji vardır: Rubin'in Potansiyel Sonuçlar (Potential Outcomes) çerçevesi, Propensity Score Matching, IPW (Ters Olasılık Ağırlıklama), Difference-in-Differences (DiD), Instrumental Variables (Araç Değişkenler) ve bireysel etkiyi kestiren Uplift Modeling / CATE.
# Propensity score fikri — scikit-learn + pandas
from sklearn.linear_model import LogisticRegression
import numpy as np, pandas as pd
# T: tedavi (0/1), X: karıştırıcılar
X = pd.DataFrame({"yas": [25,40,55,30,60,45],
"gelir": [30,50,70,35,80,55]})
T = np.array([0,1,1,0,1,0])
# eğilim skoru = tedaviye maruz kalma olasılığı P(T=1|X)
ps = LogisticRegression().fit(X, T).predict_proba(X)[:, 1]
print("Eğilim skorları:", ps.round(2))
# benzer skorlu tedavi/kontrol çiftleri eşleştirilerek etki kestirilirNedenselliğin altın kuralı: Hiçbir istatistiksel test, tek başına nedenselliği veriden çıkaramaz. Nedensel çıkarım her zaman varsayımlar gerektirir (DAG'da kodlanan) — ve sonuçlar ancak o varsayımlar kadar güçlüdür. En iyi kanıt hâlâ randomize kontrollü deneydir; gözlemsel yöntemler onun taklidini yapmaya çalışır.
Hızlı Referans: Metrik Seçim Rehberi
Dört seviye boyunca gördüğümüz araçları, sık karşılaşılan problemlere göre tek tabloda topladık:
Durum / Problem | Önerilen Metrik veya Yöntem |
|---|---|
Dengesiz sınıflandırma (tek sayı) | MCC, PR-AUC, Balanced Accuracy |
Yanlış alarm maliyeti yüksek | Precision, F-beta (β < 1) |
Kaçırma maliyeti yüksek (tıbbi) | Recall / Sensitivity, F-beta (β > 1) |
Olasılık kalibrasyonu önemli | Log-Loss, Brier Score, ECE |
Regresyon + aykırı değer var | MAE, Huber Loss, Quantile Loss |
Regresyon, büyük hata cezası | MSE / RMSE |
Model karşılaştırması (regresyon) | Adjusted R², AIC / BIC |
Dağılım kayması izleme (drift) | PSI, Wasserstein, KS, MMD |
Belirsizlik garantisi gerek | Conformal Prediction |
Çoklu hipotez testi | Benjamini-Hochberg (FDR), Bonferroni |
İki grup, normallik yok | Mann-Whitney U / Wilcoxon |
Nedensel etki (gözlemsel veri) | DAG + do-calculus, Propensity, IV |
FİNAL:
Dört seviyelik bu yolculuk, basit bir ortalamadan başlayıp nedensel çıkarımın eşiğine kadar geldi. Kıdemli seviyenin ayırt edici özelliği, artık "model doğru mu tahmin ediyor?" sorusunun ötesine geçmesidir: Varsayımları tutuyor mu? Ürettiği olasılıklar dürüst mü? Zamanla bozuluyor mu? Ve gördüğü ilişki, üzerine karar kurabileceğimiz gerçek bir neden mi?
Bu soruların hiçbirinin tek bir "en iyi metrik" cevabı yoktur. Doğru araç her zaman probleme, hatanın maliyetine ve kararın etkisine bağlıdır. İyi bir veri bilimciyi ayıran şey, formülleri ezberlemesi değil, hangi aracı ne zaman ve neden seçeceğini bilmesidir. Bu rehberi bir başvuru kaynağı olarak yanında tut; ama asıl sezgi, bu yöntemleri kendi verinde, kendi hatalarınla deneyerek gelişir. En kalıcı öğrenme, bir metriğin seni ne zaman yanılttığını bizzat gördüğünde olur.




