Bir modeli kurmak işin kolay kısmıdır; asıl zorluk, onun gerçekten işe yarayıp yaramadığını dürüstçe ölçmektir. Yanlış metrik, kötü bir modeli iyi gibi gösterebilir ya da tam tersi. Bu yazıda regresyon hatalarını, sıralama tabanlı AUC metriklerini ve iki grubu karşılaştırırken başvurduğumuz hipotez testlerini — hepsini pratik Python örnekleriyle — ele alıyoruz.
Junior seviyedeki tanımlayıcı istatistik ve karışıklık matrisi temelinin üzerine inşa ediyoruz. Örnekler yine pandas, Polars, NumPy, scikit-learn ve yeri geldiğinde SciPy ile.
1. Regresyon Hata Metrikleri
Regresyonda model, sürekli bir sayıyı tahmin eder — ve "ne kadar yanıldı?" sorusunun birden fazla yanıtı vardır. Hangi metriği seçtiğin, hangi tür hataya ne kadar önem verdiğini belirler.
MAE, MSE ve RMSE:
MAE (Ortalama Mutlak Hata), hataların mutlak değerlerinin ortalamasıdır ve aykırı değerlere dayanıklıdır — her hataya eşit ağırlık verir. MSE (Ortalama Kare Hata) ise hataları karesel olarak cezalandırdığı için büyük sapmaları çok daha ağır biçimde vurgular; türevlenebilir olması nedeniyle optimizasyonda tercih edilir. RMSE, MSE'nin kareköküdür ve hedef değişkenle aynı birimde olduğu için yorumlaması en kolay olanıdır.
MAE = mean(|y − ŷ|) · MSE = mean((y − ŷ)²) · RMSE = √MSE
# Üç metriği scikit-learn ile hesaplama
from sklearn.metrics import mean_absolute_error, mean_squared_error
import numpy as np
y_gercek = np.array([100, 150, 200, 250, 300])
y_tahmin = np.array([110, 140, 210, 240, 500]) # son tahmin çok kötü
mae = mean_absolute_error(y_gercek, y_tahmin)
mse = mean_squared_error(y_gercek, y_tahmin)
rmse = np.sqrt(mse)
print(f"MAE : {mae:.1f}") # 48.0 → tek kötü tahmini yumuşatır
print(f"MSE : {mse:.1f}") # büyük, çünkü 200'lük hata karesel büyür
print(f"RMSE: {rmse:.1f}") # hedefle aynı birimde, yorumlanabilirMAE mi RMSE mi? Aykırı hatalar senin için özellikle maliyetliyse (bir büyük sapma, on küçük sapmadan kötüyse) RMSE'yi seç. Tüm hatalar eşit derecede önemliyse ve veride gürültülü aykırılıklar varsa MAE daha adil bir resim verir.
Yüzdesel ve dayanıklı alternatifler
MAPE (Ortalama Mutlak Yüzde Hata) hatayı yüzde cinsinden sunar, bu yüzden ölçekten bağımsız ve iş birimlerine anlatması kolaydır — ama gerçek değer sıfıra yaklaştığında patlar. SMAPE bu sorunu paydaya gerçek ve tahmin değerlerinin ortalamasını koyarak yumuşatır ve simetri sağlar. Huber Loss ise iki dünyanın en iyisini birleştirir: küçük hatalarda karesel (MSE gibi), δ eşiğinden büyük hatalarda doğrusal (MAE gibi) davranarak aykırı değerlere dayanıklılık kazandırır.
# MAPE ve Huber loss — NumPy ile sıfırdan
import numpy as np
def mape(y, yhat):
return np.mean(np.abs((y - yhat) / y)) * 100
def huber(y, yhat, delta=1.0):
hata = y - yhat
kucuk = np.abs(hata) <= delta
karesel = 0.5 * hata**2
dogrusal = delta * (np.abs(hata) - 0.5 * delta)
return np.mean(np.where(kucuk, karesel, dogrusal))
y = np.array([100, 150, 200, 250])
yhat = np.array([110, 140, 210, 400])
print(f"MAPE : {mape(y, yhat):.1f}%")
print(f"Huber: {huber(y, yhat, delta=10):.1f}")Son olarak iki özel amaçlı kayıp: RMSLE (Kök Ortalama Kare Log Hata) logaritmik ölçekte çalıştığı için mutlak farkları değil oransal farkları önemser ve düşük tahminleri (olması gerekenin altında kalmayı) daha çok cezalandırır. Quantile / Pinball Loss ise ortalamayı değil belirli bir yüzdelik dilimi (örneğin %90) tahmin etmeye yarar — talep planlaması gibi "yeterince stok bulundurmak, az bulundurmaktan daha önemli" olan senaryolarda çok kullanışlıdır.
# Quantile (Pinball) loss — %90'lık dilim için asimetrik ceza
import numpy as np
def pinball(y, yhat, q=0.9):
hata = y - yhat
return np.mean(np.maximum(q * hata, (q - 1) * hata))
# q=0.9 → düşük tahmin (stok yetersizliği) daha ağır cezalanır
print(pinball(np.array([50]), np.array([40]), q=0.9)) # düşük kaldı → yüksek ceza
print(pinball(np.array([50]), np.array([60]), q=0.9)) # fazla tahmin → düşük ceza2. Model Açıklayıcılığı ve AUC Metrikleri
R² ve Adjusted R²: model ne kadarını açıklıyor?
R² (Belirlilik Katsayısı), modelin hedef değişkendeki toplam varyansın ne kadarını açıkladığını gösterir — 1'e yakınsa iyi, 0 ise ortalama kadar, negatifse ortalamadan bile kötü demektir. Ancak bir tuzağı vardır: modele anlamsız bir değişken eklesen bile R² asla düşmez. Adjusted R² bu sorunu, değişken sayısını ve serbestlik derecesini cezalandırarak çözer; işe yaramayan bir değişken eklendiğinde düşer. Model karşılaştırırken bu yüzden çok daha güvenilirdir. Aynı dengeleme fikrini AIC ve BIC bilgi kriterleri de farklı bir açıdan sunar (düşük değer daha iyi).
R² = 1 − (SS_res / SS_tot) · Adjusted R² = 1 − (1 − R²)·(n − 1)/(n − p − 1)
# R² ve elle Adjusted R² hesabı
from sklearn.metrics import r2_score
import numpy as np
y = np.array([3, 5, 7, 9, 11, 13])
y_pred = np.array([2.8, 5.2, 6.9, 9.1, 10.8, 13.2])
r2 = r2_score(y, y_pred)
n, p = len(y), 1 # n gözlem, p bağımsız değişken
adj_r2 = 1 - (1 - r2) * (n - 1) / (n - p - 1)
print(f"R² : {r2:.3f}")
print(f"Adjusted R² : {adj_r2:.3f}")Sıralama gücü: ROC-AUC ve PR-AUC
Sınıflandırmada model genelde bir olasılık üretir; eşiği nereye koyduğun sonucu değiştirir. ROC eğrisi, tüm olası eşiklerde TPR (Recall) ile FPR (yanlış pozitif oranı) arasındaki ödünleşmeyi çizer. ROC-AUC, bu eğrinin altındaki alandır ve eşikten bağımsız "ayrım gücünü" tek sayıda özetler (0.5 rastgele, 1.0 mükemmel).
Ama kritik bir uyarı var: veri aşırı dengesizse (örneğin pozitifler %1) ROC-AUC yanıltıcı derecede iyimser olabilir. Böyle durumlarda PR-AUC (Precision-Recall eğrisi altındaki alan) çok daha bilgilendiricidir, çünkü doğrudan pozitif sınıfın yakalanmasına odaklanır.
# ROC-AUC ve PR-AUC — dengesiz veride farkı görün
from sklearn.metrics import roc_auc_score, average_precision_score
import numpy as np
# %10 pozitif olan dengesiz bir örnek
y_gercek = np.array([0]*90 + [1]*10)
y_skor = np.concatenate([
np.random.uniform(0, 0.6, 90), # negatiflerin skorları
np.random.uniform(0.4, 1.0, 10), # pozitiflerin skorları
])
print(f"ROC-AUC : {roc_auc_score(y_gercek, y_skor):.3f}")
print(f"PR-AUC : {average_precision_score(y_gercek, y_skor):.3f}")
# Gini katsayısı doğrudan AUC'den türetilir
gini = 2 * roc_auc_score(y_gercek, y_skor) - 1
print(f"Gini : {gini:.3f}")Kredi skorlama gibi alanlarda Gini katsayısı (Gini = 2·AUC − 1) sık kullanılır. Modelin rastgeleye kıyasla kazanımını ölçmek için ise Lift & Gain eğrileri ve pozitif–negatif dağılımların maksimum ayrımını gösteren Kolmogorov-Smirnov (KS) istatistiği yaygındır.
3. Derin Sınıflandırma Metrikleri
F-beta Score, F1'in ayarlanabilir bir genellemesidir: β > 1 olduğunda Recall'a, β < 1 olduğunda Precision'a ağırlık verir. Böylece "hangisi daha önemli?" kararını metriğe gömebilirsin.
Fβ = (1 + β²) · (Precision · Recall) / (β² · Precision + Recall)
MCC (Matthews Korelasyon Katsayısı), karışıklık matrisinin dört hücresini de (TP, FP, TN, FN) hesaba katar ve [−1, +1] aralığında bir değer üretir. Dengesiz sınıflarda tek bir sayıyla model kalitesini özetlemek için en güvenilir metrik odur — accuracy'nin aksine, bir hücreyi ihmal edip seni kandıramaz. Cohen's Kappa ise gözlemlenen uyumdan şansa dayalı uyumu çıkararak "tesadüfen ne kadar tutturulurdu?" sorusunu düzeltir.
# F-beta, MCC ve Cohen's Kappa — scikit-learn
from sklearn.metrics import fbeta_score, matthews_corrcoef, cohen_kappa_score
y_gercek = [1, 0, 1, 1, 0, 1, 0, 0, 1, 0]
y_tahmin = [1, 0, 0, 1, 0, 1, 1, 0, 1, 0]
print(f"F2 (Recall ağırlıklı): {fbeta_score(y_gercek, y_tahmin, beta=2):.3f}")
print(f"F0.5 (Prec. ağırlıklı): {fbeta_score(y_gercek, y_tahmin, beta=0.5):.3f}")
print(f"MCC : {matthews_corrcoef(y_gercek, y_tahmin):.3f}")
print(f"Kappa : {cohen_kappa_score(y_gercek, y_tahmin):.3f}")Neden MCC? 99 negatif, 1 pozitif olan bir veride "hepsi negatif" diyen model %99 accuracy alır ama MCC'si 0 çıkar — çünkü MCC modelin hiçbir pozitifi yakalamadığını görür. Dengesiz problemde tek sayı raporlaman gerekiyorsa MCC en dürüst seçimdir.
4. Hipotez Testleri ve p-değeri
İki grubu (A/B testinde iki tasarım, iki ilaç, iki model) karşılaştırırken gördüğün fark gerçek mi, yoksa şans eseri mi? Hipotez testleri tam da bunu yanıtlar.
p-değeri, sıfır hipotezi (gruplar arasında fark yok) doğruyken, gözlediğin kadar veya daha uç bir sonucun ortaya çıkma olasılığıdır. Küçük p (genelde < 0.05) sıfır hipotezine karşı kanıttır. Ama dikkat: p-değeri etki büyüklüğü değildir — farkın var olup olmadığını söyler, ne kadar büyük olduğunu değil.
En yaygın yanlış anlama: p-değeri "sıfır hipotezinin doğru olma olasılığı" değildir. Ayrıca büyük örneklemde çok küçük, pratikte önemsiz farklar bile "istatistiksel olarak anlamlı" çıkabilir. Bu yüzden p-değerini her zaman etki büyüklüğüyle birlikte rapor et.
Hangi test, ne zaman?
Test | Kullanım amacı | Türü |
|---|---|---|
Student's t-test | İki grubun ortalaması eşit mi (eşit varyans varsayımı) | Parametrik |
Welch's t-test | İki grup ortalaması (varyanslar eşit değilken; güvenli varsayılan) | Parametrik |
Paired t-test | Eşleştirilmiş / tekrarlı ölçümler (önce–sonra) | Parametrik |
ANOVA (F-testi) | Üç ve daha fazla grubun ortalamalarını karşılaştırma | Parametrik |
Mann-Whitney U | İki bağımsız grup (normallik varsayımı yokken) | Non-parametrik |
Wilcoxon Signed-Rank | Eşleştirilmiş iki grup (normallik varsayımı yokken) | Non-parametrik |
Kruskal-Wallis | ANOVA'nın non-parametrik karşılığı (3+ grup) | Non-parametrik |
Chi-Square (χ²) | Kategorik değişkenlerin bağımsızlığı / uyum iyiliği | Non-parametrik |
SciPy bu testlerin neredeyse hepsini tek satırda sunar scipy
# İki grubu karşılaştırma — parametrik ve non-parametrik
from scipy import stats
import numpy as np
rng = np.random.default_rng(0)
grup_A = rng.normal(100, 15, 50) # kontrol
grup_B = rng.normal(108, 15, 50) # deney (biraz yüksek)
# Welch's t-test — varyans eşitliği varsaymaz, güvenli varsayılan
t, p = stats.ttest_ind(grup_A, grup_B, equal_var=False)
print(f"Welch t-test → t={t:.2f}, p={p:.4f}")
# Normallik yoksa non-parametrik karşılığı
u, p_mw = stats.mannwhitneyu(grup_A, grup_B)
print(f"Mann-Whitney U → p={p_mw:.4f}")
# 3+ grup için ANOVA
grup_C = rng.normal(115, 15, 50)
f, p_anova = stats.f_oneway(grup_A, grup_B, grup_C)
print(f"ANOVA → F={f:.2f}, p={p_anova:.4f}")Anlamlılık yetmez: etki büyüklüğü ve güç
p-değeri "fark var mı?" der; Cohen's d (Etki Büyüklüğü) ise "fark ne kadar büyük?" sorusunu yanıtlar ve örneklem büyüklüğünden bağımsızdır. İkisini birlikte raporlamak iyi bir alışkanlıktır. Güç Analizi (Statistical Power) ise deney öncesinde devreye girer: gerçek bir etkiyi yakalayabilmek için kaç gözleme ihtiyacın olduğunu (1 − β) hesaplamanı sağlar.
# Cohen's d — iki grup arasındaki standartlaştırılmış fark
import numpy as np
def cohens_d(a, b):
na, nb = len(a), len(b)
# birleştirilmiş (pooled) standart sapma
s = np.sqrt(((na-1)*a.var(ddof=1) + (nb-1)*b.var(ddof=1)) / (na+nb-2))
return (a.mean() - b.mean()) / s
d = cohens_d(grup_B, grup_A)
print(f"Cohen's d: {d:.2f}") # ~0.2 küçük, ~0.5 orta, ~0.8 büyük etki# Güç analizi — gerekli örneklem büyüklüğü (statsmodels)
from statsmodels.stats.power import TTestIndPower
analiz = TTestIndPower()
n = analiz.solve_power(effect_size=0.5, alpha=0.05, power=0.8)
print(f"Grup başına gereken örneklem: {n:.0f}") # orta etki için ~64KAPANIŞ
Bu katman, bir modeli yargılama repertuvarını genişletir. Regresyonda MAE'den Huber'a kadar her metrik, hatanın farklı bir yüzünü öne çıkarır. R² ve Adjusted R² açıklayıcılığı, ROC-AUC ile PR-AUC ise sıralama gücünü — özellikle dengesiz veride farklı hikayeler anlatarak — ölçer. MCC gibi metrikler tek sayıda dürüst bir özet sunarken, hipotez testleri gördüğün farkın gerçek mi tesadüf mü olduğunu ayırt etmeni sağlar.
Altın kural değişmiyor: doğru metrik, modelinden değil probleminden doğar. Hatanın maliyeti, sınıfların dengesi ve kararın etkisi — metriği bunlar seçer. Bir sonraki katmanda regresyon varsayımları, olasılıksal kayıplar ve Bayesçi çıkarım gibi daha derin araçlara geçeceğiz.
Örnekleri kendi veri kümene uyarlayıp iki metriğin ne zaman ayrıştığını (örneğin ROC-AUC yüksekken PR-AUC'nin düşük kalması) gözlemlemeni öneririm; bu, teoriyi sezgiye çeviren en hızlı yoldur.




