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üşteriModel: 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.
- Pozitif SHAP değeri → bu özellik başvuruyu riskli tarafa itti
- Negatif SHAP değeri → bu özellik başvuruyu güvenli tarafa itti
- 0'a yakın → bu özelliğin bu karar üzerinde etkisi az
# 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:
- hesap_durumu — Mevcut çek/vadesiz hesabın bakiyesi. Negatif bakiye, en güçlü risk sinyali. Modelin ilk baktığı yer burası.
- kredi_gecmisi — Geçmiş ödemeler düzenli mi? Gecikme kaydı olan başvurular ciddi ceza alıyor.
- sure — Kredi vadesi. Uzun vadeli krediler daha riskli görünüyor, borçlunun gelecekteki belirsizliği yüzünden.
- kredi_miktari — Yüksek tutar, gelir / teminat ile orantısızsa riski artırıyor.
- yas — Genç başvurucular daha az kredi geçmişi taşıdığından riskli bulunuyor. Bu özellik ayrımcılık riski taşıdığı için ayrıca incelenmeli.
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:
- +0.41 hesap_durumu — Negatif bakiyeli hesap. Tek başına riski en çok artıran faktör.
- +0.22 kredi_gecmisi — Geçmişte gecikme kaydı var.
- +0.14 sure — 48 aylık vade, ortalamanın üzerinde.
- −0.18 meslek — Nitelikli işçi statüsü, riski biraz düşürüyor.
- −0.09 tasarruf — Düşük de olsa birikimi var.
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.141Müş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:
- GDPR Madde 22 (2018): Tamamen otomatik kararlar için bireyin itiraz hakkı ve anlamlı açıklama talep etme hakkı var. "Algoritma öyle karar verdi" artık yeterli bir yanıt sayılmıyor.
- AB Yapay Zeka Yasası (2024, yürürlük 2026): Kredi skoru sistemleri yüksek riskli kategorisinde. Belgeleme, insan denetimi ve açıklanabilirlik yasal zorunluluk haline geliyor.
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:
- Hesaplama maliyeti. TreeExplainer ağaç modelleri için hızlıdır, ama derin ağlar için DeepExplainer veya KernelExplainer gerekir — bunlar çok daha yavaş.
- Korelasyonlu özellikler. İki özellik güçlü korelasyonlu ise SHAP katkıyı ikisine böler. Yorumlama dikkat gerektirir.
- Açıklamak ≠ doğrulamak. SHAP modelin ne yaptığını anlatır, modelin doğru olduğunu kanıtlamaz. Önyargılı veriyle önyargılı model kurulur, SHAP bu önyargıyı şeffaflaştırır ama gidermez.
- Nedensellik değil, korelasyon. "Yaş riski artırıyor" demek yaşın nedensel etkisi olduğu anlamına gelmiyor; sadece model bu kalıbı öğrenmiş.
Sonuç
Kara kutu model artık sadece bir tahmin değil, gerekçesiyle birliktebir karar. SHAP ile:
- Modelin genel davranışını (global) anlıyoruz
- Tek bir başvuru için nedenselliği (local) ortaya koyuyoruz
- Müşteriye aksiyon alınabilir geri bildirim verebiliyoruz
- Regülatörlere denetlenebilir bir karar izi sunabiliyoruz
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)