Sz
Sıfır Gecikme
veri bilimi · türkçe
Hakkımda🗺️ Haritam
Tümü
Ana sayfavaka çalışması

Kredi başvurun neden reddedildi? SHAP ile kara kutuyu aç

German Credit Dataset · 15 dakika okuma · Python + XGBoost + SHAP

Bankaya kredi başvurusu yaptın, sistem saniyeler içinde red dedi. Müşteri temsilcisi "algoritmamız öyle karar verdi" dedi ve kapandı. Peki algoritmaya itiraz edebilir misin? Model neye baktı? Ne yaptıysan onaylanırdın?

Bu sorular artık salt teknik değil, yasal. GDPR Madde 22 ve AB Yapay Zeka Yasası, yüksek riskli otomatik kararlar için açıklanabilirlik zorunlu kılıyor. Bu yazıda XGBoost ile bir kredi riski modeli kuracak, ardından SHAP ile her kararın arkasındaki mantığı açık edeceğiz.

Veri seti: German Credit

UCI Machine Learning Repository'nin klasiklerinden. 1000 başvuru, her biri 20 özellik (yaş, istihdam süresi, kredi miktarı, hesap bakiyesi…) ve nihai etiket:iyi müşteri mi, kötü müşteri mi.

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
import xgboost as xgb
import shap

# Veri setini çek
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/german.data"
sutunlar = [
    "hesap_durumu", "sure", "kredi_gecmisi", "amac", "kredi_miktari",
    "tasarruf", "istihdam_suresi", "taksit_orani", "medeni_durum",
    "diger_borclar", "ikamet_suresi", "mulk", "yas", "diger_taksitler",
    "konut", "mevcut_krediler", "meslek", "bakmakla_yukumlu", "telefon",
    "yabanci_isci", "etiket"
]
df = pd.read_csv(url, sep=" ", header=None, names=sutunlar)
df["etiket"] = df["etiket"].map({1: 0, 2: 1})  # 1=iyi→0, 2=kötü→1
print(df.shape)  # (1000, 21)
print(df["etiket"].value_counts())
# 0    700   ← iyi müşteri
# 1    300   ← riskli müşteri

Model: XGBoost ile kredi riski tahmini

Kategorik sütunları encode edip hızlı bir XGBoost modeli kuruyoruz. Amacımız mükemmel bir model değil; SHAP ile açıklayabileceğimiz, gerçekçi tahminler üreten bir model.

# Kategorik sütunları encode et
le = LabelEncoder()
kategorik = df.select_dtypes(include="object").columns
for col in kategorik:
    df[col] = le.fit_transform(df[col])

X = df.drop("etiket", axis=1)
y = df["etiket"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

model = xgb.XGBClassifier(
    n_estimators=200,
    max_depth=4,
    learning_rate=0.05,
    subsample=0.8,
    random_state=42,
    eval_metric="logloss"
)
model.fit(X_train, y_train)

from sklearn.metrics import roc_auc_score, classification_report
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1]
print(f"AUC: {roc_auc_score(y_test, y_prob):.3f}")   # ~0.79
print(classification_report(y_test, y_pred))

AUC 0.79, production için yeterli bir başlangıç. Asıl ilginç kısım şimdi başlıyor: bu model ne öğrendi?

SHAP nedir?

SHAP (SHapley Additive exPlanations), oyun teorisinden gelen Shapley değerlerini makine öğrenmesine uyarlar. Temel fikir şu: her özelliğin modelin tahinine katkısını adil biçimde hesapla.

Bir maçtaki gol bonusunu takım oyuncularına adil dağıtmak gibi düşün. Kim hangi pozisyonda ne kadar katkı sağladı? SHAP da bunu yapar — ama modelin tahmin değeri üzerinden.

# SHAP explainer oluştur
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)

# shap_values.shape == (200, 20)
# Her satır bir başvuru, her sütun bir özellik
print(shap_values.shape)  # (200, 20)

Global açıklama: hangi özellik en çok etkiliyor?

İlk sorumuz: modelin genelinde hangi özellikler kararları domine ediyor? Summary plot bunu SHAP değerlerinin mutlak ortalamasıyla gösterir.

import matplotlib.pyplot as plt

# Beeswarm (summary) plot — her nokta bir başvuru
shap.summary_plot(shap_values, X_test, plot_type="bar", max_display=12)
plt.tight_layout()
plt.savefig("global_shap.png", dpi=150, bbox_inches="tight")

Tipik çıktıda öne çıkan özellikler:

Local açıklama: bu başvuru neden reddedildi?

Asıl güç burada. Test setinden 42. indeksteki başvurucuyu alalım — model onu %81 olasılıkla riskli sınıflandırdı.

# Tek bir başvuru için waterfall plot
idx = 42
print(f"Tahmin: %{model.predict_proba(X_test)[idx, 1] * 100:.0f} riskli")

shap.plots.waterfall(
    shap.Explanation(
        values=shap_values[idx],
        base_values=explainer.expected_value,
        data=X_test.iloc[idx],
        feature_names=X_test.columns.tolist()
    )
)
plt.savefig("waterfall_42.png", dpi=150, bbox_inches="tight")

Waterfall plot şunu gösterir: modelin başlangıç noktası(base value, ~%30 risk) üzerine her özellik nasıl etki birikirdi, sonunda %81'e nasıl ulaştı?

42. başvurucu için tipik bir senaryo:

Force plot: görsel özet

Waterfall'ın alternatifi olan force plot, aynı bilgiyi yatay olarak gösterir. Bir web sayfasında interaktif HTML olarak da sunulabilir.

# HTML olarak kaydet (Jupyter dışında da çalışır)
shap.initjs()
force_plot = shap.force_plot(
    explainer.expected_value,
    shap_values[idx],
    X_test.iloc[idx],
    feature_names=X_test.columns.tolist()
)
shap.save_html("force_plot.html", force_plot)

# Toplu analiz: tüm test seti için
force_all = shap.force_plot(
    explainer.expected_value,
    shap_values,
    X_test
)
shap.save_html("force_all.html", force_all)

Aksiyon: "Ne yaptıysan onaylanırdın?"

Müşteriye gerçekten faydalı olan çıktı bu. SHAP değerleri sayesinde hangi faktörlerin değiştirilebilir, hangilerinin sabit olduğunu ayırt edebilirsin.

def aksiyon_onerisi(idx, shap_vals, X_test, ust_n=3):
    """Başvurucunun riskini artıran değiştirilebilir faktörleri döndür."""
    degistirilebilir = ["sure", "kredi_miktari", "taksit_orani",
                        "tasarruf", "hesap_durumu"]

    # SHAP değeri pozitif = riski artıran özellikler
    etkiler = pd.Series(shap_vals[idx], index=X_test.columns)
    risk_artiranlar = (
        etkiler[etkiler > 0]
        .filter(items=degistirilebilir)
        .sort_values(ascending=False)
        .head(ust_n)
    )
    return risk_artiranlar

oneri = aksiyon_onerisi(42, shap_values, X_test)
for ozellik, etki in oneri.items():
    mevcut = X_test.iloc[42][ozellik]
    print(f"• {ozellik}: mevcut değer {mevcut:.0f}, risk katkısı +{etki:.3f}")

# Çıktı:
# • hesap_durumu: mevcut değer 1, risk katkısı +0.412
# • sure: mevcut değer 48, risk katkısı +0.219
# • kredi_miktari: mevcut değer 7882, risk katkısı +0.141

Müşteri temsilcisi artık şunu söyleyebilir: "Hesap bakiyenizi pozitife taşırsanız ve kredi vadesini 48 aydan 24 aya indirirseniz onay olasılığınız önemli ölçüde artıyor."

Yasal zorunluluk: GDPR ve AB AI Yasası

Teknik açıklanabilirlik artık seçimlik değil. İki temel düzenleme var:

Türkiye'de KVKK kapsamında benzer bir trend başladı. Büyük fintech şirketleri artık XAI (Explainable AI) ekipleri kuruyor.

SHAP'ın sınırları

Her araç gibi SHAP da mükemmel değil:

Sonuç

Kara kutu model artık sadece bir tahmin değil, gerekçesiyle birliktebir karar. SHAP ile:

Finans dışında da geçerli: hastalık tahmini, işe alım, sigorta primlendirme — yüksek riskli otomatik kararların geçtiği her yerde SHAP, model geliştirmenin ayrılmaz parçası haline geliyor.

# Kurulum
pip install xgboost shap scikit-learn pandas matplotlib

# Hızlı başlangıç
import shap
shap.initjs()  # Jupyter'da interaktif grafikler için

Kaynaklar: SHAP dokümantasyonu · German Credit Dataset (UCI) · Lundberg & Lee, 2017 (SHAP orijinal makale)

Bunları da beğenebilirsin

vaka

Müşteri kaybı tahmini: kim gidecek, kime yatırım yapmalısın?

18 dakika

vaka

İzmir kira piyasası: 5.841 ilan, bir analist gözüyle

12 dakika

vaka

Süper Lig'de xG analizi: gol mü şans mı?

12 dakika

Faydalı bulduysan paylaş

X'te paylaşLinkedIn'de paylaş

💬 Yorumlar