Tüm rehberler
Rehber Yapay Zeka Klasik ML

Classical ML
Scikit-learn Derinlemesine.

sklearn'in tutarlı fit/transform/predict API'sinden, pipeline inşasına, linear ve tree modellerden gradient boosting karşılaştırmasına, hiperparametre optimizasyonundan class imbalance çözümlerine. UCI Adult Income verisiyle uçtan uca ML pipeline.

00 sklearn API Konsistansı

Scikit-learn'ün en büyük gücü, her estimator'ın aynı üç metodla çalışmasıdır: fit, transform, predict.

Scikit-learn, makine öğrenmesi dünyasında estimator API adı verilen sözleşmeyle tasarlanmıştır. Bu sözleşme sayesinde, bir StandardScaler ile bir RandomForestClassifier'ı tam olarak aynı şekilde kullanabilirsiniz: veriye uydurmak için fit(X, y), dönüştürmek için transform(X), tahmin yapmak için predict(X) çağrılır. Bu konsistans, farklı modelleri hızlıca değiştirmeyi, Pipeline içinde zincirlemeyi ve GridSearchCV ile otomatik parametre aramayı mümkün kılar.

Estimator türleri üç ana kategoriye ayrılır. Transformer'lar veriyi dönüştürür (scaler, encoder, PCA); hem fit hem transform metoduna sahiplerdir ve kısayol olarak fit_transform sunarlar. Predictor'lar tahmin üretir (classifier, regressor); fit ve predict metodlarını içerirler. Pipeline'lar ise transformer zincirini ve son estimator'ı tek bir nesne altında birleştirir; aşağı akış sızıntısını (data leakage) önler.

Tüm hiperparametreler constructor'da tanımlanır ve get_params() / set_params() ile erişilebilir. Bu yapı, otomatik hiperparametre araması için temeldir. GridSearchCV, estimator üzerindeki her parametre kombinasyonunu denemek için set_params'ı çağırır ve sonuçları karşılaştırır.

api_overview.py
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
import numpy as np

# ─── Her estimator aynı API'yi paylaşır ──────────────────────
X_train = np.random.randn(100, 5)
y_train = np.random.randint(0, 2, 100)
X_test  = np.random.randn(20, 5)

# Transformer — fit ile istatistik öğren, transform ile uygula
scaler = StandardScaler()
scaler.fit(X_train)               # mean ve std hesapla
X_scaled = scaler.transform(X_train)
print(f"mean_: {scaler.mean_[:3]}")
print(f"scale_: {scaler.scale_[:3]}")

# Predictor — fit ile eğit, predict ile tahmin
clf = LogisticRegression(max_iter=200)
clf.fit(X_scaled, y_train)
preds = clf.predict(scaler.transform(X_test))
proba = clf.predict_proba(scaler.transform(X_test))

# get_params / set_params — hiperparametre yönetimi
print(clf.get_params())
clf.set_params(C=0.5, solver='saga')

# clone — aynı parametrelerle yeni nesne (fit durumu olmadan)
from sklearn.base import clone
clf_copy = clone(clf)   # coef_ gibi öğrenilmiş nitelikler yok

# check_estimator — sklearn uyumluluk doğrulaması
from sklearn.utils.estimator_checks import check_estimator
# check_estimator(MyCustomEstimator())  # kendi estimator'ınız için
İPUCU

Training set'te fit_transform, test set'te yalnızca transform kullanın. Test verisini fit'e dahil etmek data leakage'a yol açar ve model performansını gerçekçi olmayan şekilde şişirir. Pipeline bu sızıntıyı otomatik olarak önler.

01 Preprocessing Pipeline

StandardScaler, OneHotEncoder ve ColumnTransformer'ı Pipeline ile birleştirmek, hem data leakage'ı önler hem de deploy edilebilir tek bir nesne üretir.

Gerçek veri setlerinde genellikle hem sayısal hem kategorik sütunlar bulunur ve her biri farklı ön işleme gerektirir. ColumnTransformer, farklı sütun gruplarına farklı transformer'lar uygulamanıza olanak tanır. Sayısal sütunlara StandardScaler veya MinMaxScaler uygulanırken, kategorik sütunlara OneHotEncoder veya OrdinalEncoder uygulanır. remainder='passthrough' ile dokunulmayan sütunlar olduğu gibi aktarılabilir.

Pipeline nesnesi, transformer zinciri ve son estimator'ı birleştirir. Pipeline'ın kritik avantajı, cross-validation sırasında her fold'da preprocessing'in yalnızca training fold üzerinde fit edilmesidir. Bunu manuel olarak yapmak zahmetli ve hata prone'dur; Pipeline bu işi otomatik yapar. Ayrıca Pipeline, joblib.dump ile tek dosyaya serialize edilip production'da kullanılabilir.

Eksik değer işleme için SimpleImputer (mean, median, most_frequent, constant) ve daha gelişmiş IterativeImputer (MICE algoritması) mevcuttur. Feature engineering için FunctionTransformer ile özel dönüşümler, PolynomialFeatures ile etkileşim terimleri Pipeline içine eklenebilir.

pipeline.py
import pandas as pd
import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, OrdinalEncoder
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression

# ─── Örnek veri ───────────────────────────────────────────────
df = pd.DataFrame({
    'age':        [25, 32, np.nan, 45, 28],
    'income':     [40000, np.nan, 75000, 90000, 55000],
    'education':  ['HS', 'BSc', 'MSc', 'PhD', 'BSc'],
    'city':       ['İstanbul', 'Ankara', 'İzmir', 'İstanbul', 'Ankara'],
    'target':     [0, 1, 1, 1, 0]
})
X = df.drop('target', axis=1)
y = df['target']

numeric_cols     = ['age', 'income']
nominal_cols     = ['city']
ordinal_cols     = ['education']
education_order  = [['HS', 'BSc', 'MSc', 'PhD']]

# ─── Alt pipeline'lar ─────────────────────────────────────────
numeric_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler',  StandardScaler()),
])

nominal_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('ohe',     OneHotEncoder(handle_unknown='ignore', sparse_output=False)),
])

ordinal_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('ord',     OrdinalEncoder(categories=education_order)),
])

# ─── ColumnTransformer ────────────────────────────────────────
preprocessor = ColumnTransformer([
    ('num', numeric_transformer, numeric_cols),
    ('nom', nominal_transformer, nominal_cols),
    ('ord', ordinal_transformer, ordinal_cols),
], remainder='drop')

# ─── Tam Pipeline ─────────────────────────────────────────────
full_pipeline = Pipeline([
    ('prep', preprocessor),
    ('clf',  LogisticRegression(max_iter=1000)),
])

full_pipeline.fit(X, y)
print("Tahminler:", full_pipeline.predict(X))
print("Olasılıklar:", full_pipeline.predict_proba(X)[:, 1])

# ─── Pipeline adına göre parametre erişimi ────────────────────
# prep__num__scaler__with_mean, clf__C gibi çift alt çizgi ile
full_pipeline.set_params(clf__C=0.1)
print(full_pipeline.get_params().keys())
DİKKAT

Eğer ColumnTransformer'da sparse_output=True (varsayılan) kullanılıyorsa ve sonrasında numpy array bekleyen bir adım geliyorsa, ara adıma FunctionTransformer(lambda x: x.toarray()) ekleyin. sklearn 1.2+ sürümünden itibaren set_output(transform="pandas") ile DataFrame çıktısı da desteklenmektedir.

02 Linear Modeller

Ridge, Lasso ve ElasticNet, L2 ve L1 regularizasyon ile overfitting'i önler; Lasso aynı zamanda feature selection yapar.

Düzenlileştirme (regularization), modelin eğitim verisini ezberlemesini engellemek için kayıp fonksiyonuna parametre büyüklüğüne bağlı bir ceza terimi ekler. Ridge (L2), katsayıların karesini minimize ederek büyük ağırlıkları baskılar fakat hiçbirini tam sıfırlamaz. Multicollinearity durumunda güçlüdür. Lasso (L1), katsayıların mutlak değerini cezalandırır ve bazı katsayıları tam olarak sıfıra iter; böylece implicit feature selection yapar. ElasticNet, L1 ve L2'yi dengeleyen bir mix sunar ve gruplu katsayıları birlikte sıfırlamak için tercih edilir.

Regularizasyon gücü alpha parametresiyle kontrol edilir. Büyük alpha daha güçlü regularizasyon, daha basit model; küçük alpha daha az ceza, daha kompleks model anlamına gelir. LogisticRegression'da bu parametre C = 1/alpha şeklinde tanımlanır — küçük C güçlü regularizasyon demektir. En uygun değer cross-validation ile belirlenir.

linear_models.py
import numpy as np
from sklearn.linear_model import Ridge, Lasso, ElasticNet, RidgeCV, LassoCV
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# ─── Veri oluştur ─────────────────────────────────────────────
X, y = make_regression(n_samples=500, n_features=50,
                       n_informative=10, noise=25, random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2)

# ─── Ridge — L2 regularization ───────────────────────────────
ridge = Ridge(alpha=1.0)
ridge.fit(X_tr, y_tr)
rmse_ridge = mean_squared_error(y_te, ridge.predict(X_te), squared=False)
print(f"Ridge RMSE: {rmse_ridge:.2f}")
print(f"Sıfır katsayı sayısı: {(ridge.coef_ == 0).sum()}")  # 0

# ─── Lasso — L1 regularization, sparse ───────────────────────
lasso = Lasso(alpha=0.5, max_iter=5000)
lasso.fit(X_tr, y_tr)
rmse_lasso = mean_squared_error(y_te, lasso.predict(X_te), squared=False)
print(f"Lasso RMSE: {rmse_lasso:.2f}")
print(f"Sıfır katsayı sayısı: {(lasso.coef_ == 0).sum()}")  # ~40

# ─── ElasticNet — L1 + L2 mix ────────────────────────────────
enet = ElasticNet(alpha=0.5, l1_ratio=0.5, max_iter=5000)
enet.fit(X_tr, y_tr)

# ─── CV ile alpha seçimi ──────────────────────────────────────
ridge_cv = RidgeCV(alphas=np.logspace(-3, 3, 100), cv=5)
ridge_cv.fit(X_tr, y_tr)
print(f"En iyi alpha: {ridge_cv.alpha_:.4f}")

lasso_cv = LassoCV(cv=5, max_iter=10000)
lasso_cv.fit(X_tr, y_tr)
print(f"Lasso en iyi alpha: {lasso_cv.alpha_:.4f}")

# ─── Regularization path görselleştirme ───────────────────────
import matplotlib.pyplot as plt
alphas  = np.logspace(-2, 3, 50)
coefs   = [Lasso(alpha=a, max_iter=5000).fit(X_tr, y_tr).coef_ for a in alphas]
plt.figure(figsize=(10, 4))
plt.semilogx(alphas, coefs)
plt.xlabel("Alpha (Regularization Strength)")
plt.ylabel("Katsayı Değerleri")
plt.title("Lasso Regularization Path")
plt.axvline(lasso_cv.alpha_, color='red', linestyle='--', label='CV Alpha')
plt.legend()
plt.tight_layout()
plt.savefig('lasso_path.png', dpi=150)
ParametreRidgeLassoElasticNet
RegularizasyonL2 (kare)L1 (mutlak)L1 + L2
Sparse çözümHayırEvetKısmi
MulticollinearityGüçlüZayıfOrta
Feature selectionHayırEvetKısmi
Ana parametrealphaalphaalpha, l1_ratio

03 Tree Modeller

DecisionTree yorumlanabilirlik sağlar; RandomForest, bootstrap aggregating ile varyansı düşürür. Feature importance ile hangi değişkenlerin önemli olduğunu anlayabilirsiniz.

Karar ağacı, veriyi bir dizi if-else kuralıyla böler. Her düğümde bilgi kazancı (information gain) veya Gini impurity'yi maksimize eden bölünme seçilir. Ağaç derinleştikçe eğitim verisine mükemmel uyum sağlar ancak test verisinde başarısız olur — yüksek varyans problemi. max_depth, min_samples_split, min_samples_leaf parametreleri ile ağaç boyutu sınırlanabilir.

Random Forest, N adet karar ağacının bootstrap örnekleri üzerinde eğitilmesiyle ve her bölünmede rastgele bir özellik alt kümesinin kullanılmasıyla oluşur. Bu çift rastgelelik (double randomization), ağaçlar arasındaki korelasyonu kırar ve topluluk tahminini güçlendirir. Tahmin, regresyon için ortalama, sınıflandırma için çoğunluk oyu ile yapılır.

Feature importance, her özelliğin düğüm saflığı artışına ortalama katkısını ölçer. Ancak sklearn'ün varsayılan importance'ı yüksek kardinaliteli sütunlara karşı biased olabilir. Bu durumda permutation importance daha güvenilir bir alternatiftir: özellik değerleri karıştırıldığında model performansındaki düşüş ölçülür.

tree_models.py
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.inspection import permutation_importance
import pandas as pd
import numpy as np

X, y = make_classification(n_samples=1000, n_features=20,
                            n_informative=8, random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2)

# ─── Decision Tree ────────────────────────────────────────────
dt = DecisionTreeClassifier(
    max_depth=4,
    min_samples_split=20,
    min_samples_leaf=10,
    criterion='gini',           # 'entropy' da kullanılabilir
    random_state=42
)
dt.fit(X_tr, y_tr)
print(f"DT Accuracy: {dt.score(X_te, y_te):.3f}")
print("Ağaç yapısı:")
print(export_text(dt, max_depth=3))

# ─── Random Forest ────────────────────────────────────────────
rf = RandomForestClassifier(
    n_estimators=200,
    max_depth=None,             # tam derinlik — bootstrap kurtarır
    max_features='sqrt',        # sınıflandırmada sqrt(n_features)
    min_samples_leaf=5,
    n_jobs=-1,
    random_state=42
)
rf.fit(X_tr, y_tr)
print(f"RF Accuracy: {rf.score(X_te, y_te):.3f}")

# ─── Feature Importance (MDI — Mean Decrease Impurity) ───────
fi = pd.Series(rf.feature_importances_,
               index=[f"feat_{i}" for i in range(20)])
print(fi.nlargest(5))

# ─── Permutation Importance — daha güvenilir ─────────────────
perm = permutation_importance(rf, X_te, y_te, n_repeats=20,
                               n_jobs=-1, random_state=42)
perm_df = pd.DataFrame({
    'mean': perm.importances_mean,
    'std':  perm.importances_std
}).nlargest(5, 'mean')
print(perm_df)

04 Gradient Boosting

GradientBoosting, XGBoost, LightGBM ve CatBoost — artıkları düzelten ağaçları sıralı eğiterek güçlü ensemble oluşturur.

Gradient Boosting, her iterasyonda önceki modelin artıklarını (residuals) hedef alan yeni bir ağaç ekler. Ağaçlar sırayla eğitilir; her biri bir öncekinin hatalarını düzeltmeye odaklanır. Öğrenme hızı (learning_rate) her ağacın katkısını ölçekler — düşük learning rate daha fazla ağaç gerektirir ancak genellikle daha iyi genelleme sağlar.

Sklearn'ün GradientBoostingClassifier sağlamdır ancak yavaştır. XGBoost, second-order Taylor expansion ve sparse-aware algorithm ile önemli ölçüde hızlandırır. LightGBM, histogram-based algorithm ve leaf-wise büyüme stratejisiyle daha da hızlıdır. CatBoost, kategorik değişkenleri özel ordered target encoding ile işler ve kurulum gerektirmeden doğrudan kategorik kolonlarla çalışabilir.

KütüphaneHızKategorikGPUÖne Çıkan Özellik
sklearn GBYavaşManuelHayırReferans implementasyon
XGBoostHızlıManuelEvetRegularization, DART
LightGBMÇok hızlıNativeEvetHistogram, leaf-wise
CatBoostHızlıNative (ordered)EvetOrdered boosting, leakage önleme
gradient_boosting.py
from sklearn.ensemble import GradientBoostingClassifier, HistGradientBoostingClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
import xgboost as xgb
import lightgbm as lgb

X, y = make_classification(n_samples=5000, n_features=30,
                            n_informative=15, random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2)

# ─── Sklearn HistGradientBoosting (hızlı, native NaN desteği) ─
hgb = HistGradientBoostingClassifier(
    max_iter=300,
    learning_rate=0.05,
    max_depth=5,
    l2_regularization=0.1,
    early_stopping=True,
    validation_fraction=0.1,
    random_state=42
)
hgb.fit(X_tr, y_tr)
print(f"HGB Accuracy: {hgb.score(X_te, y_te):.3f}, iter: {hgb.n_iter_}")

# ─── XGBoost ──────────────────────────────────────────────────
xgb_clf = xgb.XGBClassifier(
    n_estimators=500,
    learning_rate=0.05,
    max_depth=5,
    subsample=0.8,
    colsample_bytree=0.8,
    reg_alpha=0.1,       # L1
    reg_lambda=1.0,      # L2
    early_stopping_rounds=20,
    eval_metric='auc',
    random_state=42,
    n_jobs=-1
)
xgb_clf.fit(X_tr, y_tr,
           eval_set=[(X_te, y_te)],
           verbose=False)
print(f"XGB Accuracy: {xgb_clf.score(X_te, y_te):.3f}")

# ─── LightGBM ─────────────────────────────────────────────────
lgb_clf = lgb.LGBMClassifier(
    n_estimators=500,
    learning_rate=0.05,
    num_leaves=31,       # leaf-wise büyüme kontrolü
    min_child_samples=20,
    subsample=0.8,
    colsample_bytree=0.8,
    random_state=42,
    n_jobs=-1
)
lgb_clf.fit(X_tr, y_tr,
           eval_set=[(X_te, y_te)],
           callbacks=[lgb.early_stopping(20), lgb.log_evaluation(100)])
print(f"LGB Accuracy: {lgb_clf.score(X_te, y_te):.3f}")

05 Model Değerlendirme

cross_val_score, StratifiedKFold, confusion matrix, ROC-AUC ve F1 — doğru metrik seçimi modelin gerçek performansını yansıtır.

Tek bir train/test bölünmesi, model performansının güvenilir bir tahmini değildir. K-Fold cross-validation, veriyi k parçaya böler ve her seferinde bir parçayı test olarak kullanırken geriye kalanlarla eğitim yapar. Sınıflandırma için StratifiedKFold tercih edilir; her fold'da sınıf dağılımının korunmasını sağlar. Zaman serisi verileri için TimeSeriesSplit kullanılmalıdır.

Metrik seçimi kritiktir. Accuracy, dengesiz sınıflarda yanıltıcıdır. Precision, pozitif tahminlerin ne kadarının doğru olduğunu ölçer; Recall, gerçek pozitiflerin ne kadarının yakalandığını. F1, bu ikisinin harmonik ortalamasıdır. ROC-AUC, sınıflandırma eşiğinden bağımsız olarak modelin ayrım gücünü ölçer ve dengesiz veriler için özellikle değerlidir. Hassas görevlerde (fraud, tıbbi tanı) Precision-Recall eğrisi ROC'tan daha bilgilendiricidir.

evaluation.py
from sklearn.model_selection import cross_val_score, StratifiedKFold, cross_validate
from sklearn.metrics import (confusion_matrix, classification_report,
                               roc_auc_score, roc_curve,
                               precision_recall_curve, average_precision_score)
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
import numpy as np

X, y = make_classification(n_samples=2000, n_features=20,
                            weights=[0.85, 0.15], random_state=42)

# ─── Stratified K-Fold CV ─────────────────────────────────────
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
clf = RandomForestClassifier(n_estimators=100, random_state=42)

cv_scores = cross_val_score(clf, X, y, cv=skf, scoring='roc_auc', n_jobs=-1)
print(f"ROC-AUC: {cv_scores.mean():.3f} ± {cv_scores.std():.3f}")

# ─── Çoklu metrik ─────────────────────────────────────────────
results = cross_validate(clf, X, y, cv=skf, n_jobs=-1,
    scoring=['roc_auc', 'f1', 'precision', 'recall'])
for metric in ['roc_auc', 'f1', 'precision', 'recall']:
    vals = results[f"test_{metric}"]
    print(f"{metric:12s}: {vals.mean():.3f} ± {vals.std():.3f}")

# ─── Confusion matrix + classification report ─────────────────
from sklearn.model_selection import train_test_split
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, stratify=y)
clf.fit(X_tr, y_tr)
y_pred  = clf.predict(X_te)
y_proba = clf.predict_proba(X_te)[:, 1]

cm = confusion_matrix(y_te, y_pred)
print("Confusion Matrix:\n", cm)
print(classification_report(y_te, y_pred, target_names=['Negatif', 'Pozitif']))

# ─── ROC-AUC ve Precision-Recall ─────────────────────────────
auc = roc_auc_score(y_te, y_proba)
ap  = average_precision_score(y_te, y_proba)
print(f"ROC-AUC: {auc:.3f}, Average Precision: {ap:.3f}")

fpr, tpr, _ = roc_curve(y_te, y_proba)
prec, rec, thresholds = precision_recall_curve(y_te, y_proba)
# Eşik optimizasyonu: F1 maksimum
f1_scores = 2 * (prec * rec) / (prec + rec + 1e-9)
best_thr  = thresholds[np.argmax(f1_scores)]
print(f"En iyi threshold (F1 max): {best_thr:.3f}")

06 Hiperparametre Tuning

GridSearchCV, RandomizedSearchCV ve Optuna — parametre uzayını sistematik veya Bayesian yöntemlerle araştırma.

GridSearchCV, parametre ızgarasındaki tüm kombinasyonları dener. Küçük ızgaralar için güvenilirdir ancak combinatorial explosion nedeniyle büyük uzaylarda kullanışsızdır. RandomizedSearchCV, parametre dağılımlarından rastgele örnekler alır; aynı hesaplama bütçesiyle genellikle Grid'den daha iyi sonuç verir çünkü kritik parametreler üzerinde daha fazla varyasyon dener.

Optuna, Tree-structured Parzen Estimators (TPE) Bayesian optimizasyon algoritmasını kullanır. Her deneme, önceki denemelerin sonuçlarına dayanarak umut verici bölgeleri araştırır. Erken durdurma (pruning) ile kötü performans gösteren denemeler erken sonlandırılabilir, hesaplama maliyeti dramatik şekilde azaltılabilir.

hyperparameter_tuning.py
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
from scipy.stats import randint, uniform
import optuna

X, y = make_classification(n_samples=2000, n_features=20, random_state=42)

# ─── GridSearchCV ─────────────────────────────────────────────
param_grid = {
    'n_estimators':   [100, 200, 300],
    'max_depth':       [3, 5, 7, None],
    'min_samples_leaf': [1, 5, 10],
}
grid_search = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid, cv=5, scoring='roc_auc', n_jobs=-1
)
grid_search.fit(X, y)
print(f"Grid Best: {grid_search.best_params_}, AUC: {grid_search.best_score_:.3f}")

# ─── RandomizedSearchCV ───────────────────────────────────────
param_dist = {
    'n_estimators':    randint(50, 500),
    'max_depth':        randint(2, 15),
    'min_samples_leaf': randint(1, 20),
    'max_features':     uniform(0.2, 0.8),
}
rand_search = RandomizedSearchCV(
    RandomForestClassifier(random_state=42),
    param_dist, n_iter=50, cv=5, scoring='roc_auc',
    n_jobs=-1, random_state=42
)
rand_search.fit(X, y)
print(f"Random Best: {rand_search.best_params_}, AUC: {rand_search.best_score_:.3f}")

# ─── Optuna — Bayesian Optimization ──────────────────────────
from sklearn.model_selection import cross_val_score, StratifiedKFold

def objective(trial):
    params = {
        'n_estimators':    trial.suggest_int('n_estimators', 50, 500),
        'max_depth':        trial.suggest_int('max_depth', 2, 15),
        'min_samples_leaf': trial.suggest_int('min_samples_leaf', 1, 20),
        'max_features':     trial.suggest_float('max_features', 0.2, 1.0),
    }
    clf = RandomForestClassifier(**params, n_jobs=-1, random_state=42)
    cv  = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)
    scores = cross_val_score(clf, X, y, cv=cv, scoring='roc_auc', n_jobs=1)
    return scores.mean()

optuna.logging.set_verbosity(optuna.logging.WARNING)
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=50, n_jobs=1)
print(f"Optuna Best: {study.best_params}, AUC: {study.best_value:.3f}")

07 Feature Selection

SelectKBest, RFE ve SHAP — gereksiz özellikleri elemek hem modeli basitleştirir hem overfitting'i azaltır.

Filter methods (SelectKBest), her özelliği bağımsız olarak istatistiksel bir teste göre değerlendirir: chi2 (kategorik target ile kategorik feature), f_classif (ANOVA F-test, sayısal feature) veya mutual_info_classif (doğrusal olmayan ilişkileri de yakalar). Hızlıdır ancak özellikler arası etkileşimleri görmez.

Wrapper methods (RFE — Recursive Feature Elimination), modeli tekrar tekrar eğiterek en az önemli özellikleri adım adım eler. Model performansını doğrudan ölçer ancak hesaplama maliyeti yüksektir. RFECV ile cross-validation ile en uygun özellik sayısı otomatik seçilir.

SHAP (SHapley Additive exPlanations), oyun teorisinden gelen Shapley değerlerine dayanır. Her özelliğin her tahmine olan katkısını ölçer; global önem sıralaması ve bireysel tahmin açıklama için kullanılır. SHAP, feature selection'ın ötesinde model interpretability için de vazgeçilmezdir.

feature_selection.py
from sklearn.feature_selection import (SelectKBest, f_classif,
                                          mutual_info_classif, RFE, RFECV)
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
import shap
import pandas as pd

X, y = make_classification(n_samples=1000, n_features=30,
                            n_informative=8, n_redundant=5, random_state=42)
feat_names = [f"f{i}" for i in range(30)]

# ─── SelectKBest ──────────────────────────────────────────────
selector_f = SelectKBest(f_classif, k=10)
selector_f.fit(X, y)
mask_f = selector_f.get_support()
print("ANOVA seçilen:", [feat_names[i] for i, m in enumerate(mask_f) if m])

selector_mi = SelectKBest(mutual_info_classif, k=10)
selector_mi.fit(X, y)
mask_mi = selector_mi.get_support()
print("MI seçilen:", [feat_names[i] for i, m in enumerate(mask_mi) if m])

# ─── RFE ──────────────────────────────────────────────────────
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rfe = RFE(rf, n_features_to_select=10, step=3)
rfe.fit(X, y)
print("RFE seçilen:", [feat_names[i] for i, m in enumerate(rfe.support_) if m])

# ─── RFECV — CV ile otomatik k ────────────────────────────────
rfecv = RFECV(RandomForestClassifier(n_estimators=50, random_state=42),
              step=2, cv=3, scoring='roc_auc', n_jobs=-1)
rfecv.fit(X, y)
print(f"RFECV optimum özellik sayısı: {rfecv.n_features_}")

# ─── SHAP Feature Importance ──────────────────────────────────
rf.fit(X, y)
explainer   = shap.TreeExplainer(rf)
shap_values = explainer.shap_values(X[:, ...])
# Global importance: |SHAP| ortalaması
mean_abs_shap = pd.Series(
    shap_values[1].mean(axis=0).__abs__(),  # pozitif sınıf SHAP
    index=feat_names
).sort_values(ascending=False)
print(mean_abs_shap.head(10))
# shap.summary_plot(shap_values[1], X, feature_names=feat_names)

08 Class Imbalance

SMOTE, class_weight ve threshold tuning — dengesiz sınıflarla başa çıkmanın üç temel yolu.

Gerçek dünya problemlerinin büyük bölümünde sınıf dağılımı dengesizdir: fraud %0.1, hastalık tespiti %5, arıza %1. Bu durumda accuracy yanıltıcıdır — tüm örnekleri çoğunluk sınıfına atayan model yüksek accuracy alır ama hiçbir işe yaramaz. Doğru metrikler F1-score, ROC-AUC ve özellikle Precision-Recall AUC'tur.

SMOTE (Synthetic Minority Over-sampling Technique), azınlık sınıfı örnekleri arasında interpolasyon yaparak sentetik veri noktaları üretir. imbalanced-learn kütüphanesi sklearn-uyumlu arayüz sunar. SMOTE yalnızca eğitim verisine uygulanmalıdır; Pipeline içinde imblearn.pipeline.Pipeline bu garantiyi sağlar. class_weight='balanced', her sınıfa frekansına ters orantılı ağırlık atar; ek kütüphane gerektirmez ve çoğu sklearn estimator tarafından desteklenir.

imbalance.py
from imblearn.over_sampling import SMOTE, SMOTENC
from imblearn.under_sampling import RandomUnderSampler
from imblearn.combine import SMOTETomek
from imblearn.pipeline import Pipeline as ImbPipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold, cross_validate
from sklearn.datasets import make_classification
import numpy as np

X, y = make_classification(n_samples=5000, n_features=20,
                            weights=[0.9, 0.1], random_state=42)
print(f"Sınıf 0: {(y==0).sum()}, Sınıf 1: {(y==1).sum()}")

# ─── SMOTE ────────────────────────────────────────────────────
smote = SMOTE(sampling_strategy=0.5, k_neighbors=5, random_state=42)
X_res, y_res = smote.fit_resample(X, y)
print(f"SMOTE sonrası — Sınıf 0: {(y_res==0).sum()}, Sınıf 1: {(y_res==1).sum()}")

# ─── SMOTETomek — oversample + temizleme ─────────────────────
smt = SMOTETomek(random_state=42)
X_smt, y_smt = smt.fit_resample(X, y)

# ─── class_weight='balanced' — en basit yol ───────────────────
clf_balanced = RandomForestClassifier(
    n_estimators=200, class_weight='balanced', random_state=42
)

# ─── imbalanced-learn Pipeline (SMOTE sadece train'de) ────────
pipe = ImbPipeline([
    ('smote', SMOTE(random_state=42)),
    ('clf',   RandomForestClassifier(n_estimators=200, random_state=42)),
])
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_validate(pipe, X, y, cv=cv, scoring=['f1', 'roc_auc'], n_jobs=-1)
print(f"SMOTE+RF F1:  {scores['test_f1'].mean():.3f}")
print(f"SMOTE+RF AUC: {scores['test_roc_auc'].mean():.3f}")

# ─── Threshold tuning ─────────────────────────────────────────
from sklearn.model_selection import train_test_split
from sklearn.metrics import precision_recall_curve
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, stratify=y)
clf_balanced.fit(X_tr, y_tr)
proba = clf_balanced.predict_proba(X_te)[:, 1]
prec, rec, thr = precision_recall_curve(y_te, proba)
f1  = 2 * prec * rec / (prec + rec + 1e-9)
opt = thr[np.argmax(f1[:-1])]
print(f"Varsayılan eşik (0.5) F1: {f1[np.searchsorted(thr, 0.5)]:.3f}")
print(f"Optimal eşik ({opt:.2f}) F1: {f1.max():.3f}")

09 Pratik: UCI Adult Income Pipeline

Uçtan uca bir ML pipeline: veri yükleme, preprocessing, model eğitimi, değerlendirme ve SHAP ile interpretability.

UCI Adult Income veri seti, 48.842 kişinin demografik bilgilerini ve yıllık gelirinin $50K'nın üstünde olup olmadığını içerir. 8 kategorik (workclass, education, occupation, vb.) ve 6 sayısal (age, hours-per-week, vb.) özellik mevcuttur. Sınıf dağılımı %75/%25 şeklinde hafifçe dengesizdir. Bu problem, pipeline tasarımı, kategorik encoding, class imbalance ve model interpretability kavramlarını pratikte uygulamak için idealdir.

adult_income_pipeline.py
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score, StratifiedKFold
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import classification_report, roc_auc_score
import shap, joblib

# ─── 1. Veri Yükleme ──────────────────────────────────────────
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data"
cols = ['age', 'workclass', 'fnlwgt', 'education', 'education-num',
        'marital-status', 'occupation', 'relationship', 'race',
        'sex', 'capital-gain', 'capital-loss', 'hours-per-week',
        'native-country', 'income']
df = pd.read_csv(url, header=None, names=cols,
                  skipinitialspace=True, na_values='?')

# ─── 2. Target ve Feature ayrımı ─────────────────────────────
df['income'] = (df['income'].str.strip() == '>50K').astype(int)
X = df.drop(['income', 'fnlwgt'], axis=1)  # fnlwgt kaldır
y = df['income']

num_cols  = ['age', 'education-num', 'capital-gain',
             'capital-loss', 'hours-per-week']
cat_cols  = ['workclass', 'education', 'marital-status',
             'occupation', 'relationship', 'race', 'sex', 'native-country']

X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

# ─── 3. Preprocessing ─────────────────────────────────────────
num_pipe = Pipeline([
    ('imp',   SimpleImputer(strategy='median')),
    ('scale', StandardScaler()),
])
cat_pipe = Pipeline([
    ('imp', SimpleImputer(strategy='most_frequent')),
    ('ohe', OneHotEncoder(handle_unknown='ignore', sparse_output=False)),
])
prep = ColumnTransformer([
    ('num', num_pipe, num_cols),
    ('cat', cat_pipe, cat_cols),
])

# ─── 4. Model Pipeline ────────────────────────────────────────
pipeline = Pipeline([
    ('prep', prep),
    ('clf',  GradientBoostingClassifier(
        n_estimators=300, learning_rate=0.05,
        max_depth=4, subsample=0.8, random_state=42
    )),
])

# ─── 5. Eğitim ve Değerlendirme ───────────────────────────────
pipeline.fit(X_tr, y_tr)
y_pred  = pipeline.predict(X_te)
y_proba = pipeline.predict_proba(X_te)[:, 1]
print(classification_report(y_te, y_pred, target_names=['<=50K', '>50K']))
print(f"ROC-AUC: {roc_auc_score(y_te, y_proba):.3f}")

# ─── 6. SHAP Interpretability ─────────────────────────────────
X_tr_prep = pipeline['prep'].transform(X_tr)
ohe_cats  = pipeline['prep'].named_transformers_['cat']['ohe'].get_feature_names_out(cat_cols)
feat_names = list(num_cols) + list(ohe_cats)

explainer   = shap.TreeExplainer(pipeline['clf'])
shap_values = explainer.shap_values(X_tr_prep[:500])
shap.summary_plot(shap_values, X_tr_prep[:500],
                  feature_names=feat_names, show=False)

# ─── 7. Model Kaydetme ────────────────────────────────────────
joblib.dump(pipeline, 'adult_income_pipeline.joblib')
print("Model kaydedildi: adult_income_pipeline.joblib")
SONUÇ

GradientBoosting ile UCI Adult Income üzerinde ROC-AUC ~0.928 elde edilebilir. SHAP analizi, en belirleyici özelliklerin capital-gain, relationship=Wife/Husband ve education-num olduğunu ortaya koyar. Pipeline'ın joblib.dump ile kaydedilmesi, production ortamına deployment'ı kolaylaştırır.