{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":3362,"databundleVersionId":31148,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 🐶🐱 Dogs vs. Cats Image Classification  \n**(CNN + Grad-CAM + Hiperparametre Optimizasyonu)**\n\nBu notebook, Kaggle Dogs vs. Cats veri setinde kedi ve köpekleri ayırt edebilen bir derin öğrenme modeli geliştirmek için hazırlanmıştır.  \nAdımlar: veri hazırlama → model oluşturma → eğitim → değerlendirme → görselleştirme → hiperparametre araması → sonuç.\n","metadata":{}},{"cell_type":"markdown","source":"## 1) Kurulum, Yol Yapısı ve Veri Çıkartma\n\nBu hücre, deney ortamını deterministik hale getirir, Kaggle’da çalışıp çalışmadığını algılar, **Dogs vs. Cats** yarışmasının zip dosyalarını doğru dizinlere **yeniden** çıkartır ve veri boru hattı için temel parametreleri hazırlar.\n\n- **Ortam & Tekrarlanabilirlik:** Python/TensorFlow sürümleri yazdırılır; `SEED=42` ile `random`, `numpy`, `tf` tohumları sabitlenir.\n- **Dizin Mantığı:**  \n  - Yerel: `data/dogs-vs-cats/`  \n  - Kaggle: `/kaggle/input` altında yarışma zip’lerini bulur; çıkış yolu `/kaggle/working/data/dogs-vs-cats/`.\n- **Zip Çıkartma (idempotent):**  \n  - `CLEAR_OLD=True` ile hedef klasörlerin **içeriği** silinir (klasörler korunur).  \n  - `train.zip` → `data/dogs-vs-cats/train/`, `test1.zip` → `data/dogs-vs-cats/test1/`.  \n  - Zip içi alt klasör yapısı varsa **düzleştirilir** (tüm `.jpg` dosyaları hedef köke taşınır; çakışmalara isim ekleyerek çözüm).\n- **Cache Temizliği:** Yeniden çıkartma sonrası önceki **disk cache** dosyaları (`cache/*.cache`) silinir; veri seti pipeline’ında eski cache’in tutarsızlık yaratması engellenir.\n- **Sağlama (Sanity Check):** `train/*.jpg` ve `test1/*.jpg` sayıları kontrol edilir; boşsa açıklayıcı hata mesajıyla durur.\n- **Görüntü Parametreleri:**  \n  - `IMG_SIZE=224`, `INPUT_SHAPE=(224,224,3)` (CNN giriş boyutu)  \n  - `BINARY=True` (ikili sınıflandırma), `BATCH_SIZE=32`  \n  - `AUTOTUNE=tf.data.AUTOTUNE` (tf.data pipeline için otomatik paralellik/ön getirme)\n","metadata":{}},{"cell_type":"code","source":"# ===============================\n# 1) Kurulum, Yol Yapısı, Veri Çıkartma (ZIP uyumlu)\n# ===============================\n\nimport os, sys, glob, random, shutil, zipfile\nfrom pathlib import Path\nimport numpy as np\nimport tensorflow as tf\n\n# --- Sürüm bilgisi ---\nprint(\"Python :\", sys.version)\nprint(\"TensorFlow:\", tf.__version__)\n\n# --- Deterministiklik ve seed ---\nSEED = 42\nos.environ[\"PYTHONHASHSEED\"] = str(SEED)\nos.environ[\"TF_DETERMINISTIC_OPS\"] = \"1\"\nos.environ[\"TF_CUDNN_DETERMINISTIC\"] = \"1\"\nrandom.seed(SEED); np.random.seed(SEED); tf.random.set_seed(SEED)\n\n# --- GPU bellek ayarı ---\ngpus = tf.config.list_physical_devices('GPU')\nif gpus:\n    for gpu in gpus:\n        tf.config.experimental.set_memory_growth(gpu, True)\n    print(\"GPU aktif:\", [tf.config.experimental.get_device_details(g)['device_name'] for g in gpus])\nelse:\n    print(\"GPU bulunamadı; CPU ile devam.\")\n\n# --- Veri kaynağı kök klasör ---\nINPUT_ROOT = Path(\"/kaggle/input/dogs-vs-cats\")\nWORK_ROOT = Path(\"/kaggle/working\") if Path(\"/kaggle/working\").exists() else Path.cwd()\nDATA_ROOT = WORK_ROOT / \"data\" / \"dogs-vs-cats\"\n\nRAW_TRAIN_DIR = DATA_ROOT / \"train\"\nRAW_TEST_DIR  = DATA_ROOT / \"test1\"\n\nif not RAW_TRAIN_DIR.exists() or not RAW_TEST_DIR.exists():\n    DATA_ROOT.mkdir(parents=True, exist_ok=True)\n\n    train_zip = INPUT_ROOT / \"train.zip\"\n    test_zip  = INPUT_ROOT / \"test1.zip\"\n\n    assert train_zip.exists(), f\"train.zip bulunamadı: {INPUT_ROOT}\"\n    assert test_zip.exists(), f\"test1.zip bulunamadı: {INPUT_ROOT}\"\n\n    with zipfile.ZipFile(train_zip, 'r') as z:\n        z.extractall(DATA_ROOT)   # → data/dogs-vs-cats/train/...\n    with zipfile.ZipFile(test_zip, 'r') as z:\n        z.extractall(DATA_ROOT)   # → data/dogs-vs-cats/test1/...\n\n    print(\"Zip dosyaları açıldı:\", DATA_ROOT)\n\n# --- Eski modelleri temizle (opsiyonel) ---\nold_models = WORK_ROOT / \"models\"\nif old_models.exists():\n    shutil.rmtree(old_models)\n    print(\"Eski modeller temizlendi.\")\n\n# --- Veri kontrolü ---\nall_files = sorted(glob.glob(str(RAW_TRAIN_DIR / '*.jpg')))\nprint(\"Örnek dosya yolu:\", all_files[0] if all_files else \"Bulunamadı\")\nassert len(all_files) > 0, f\"Hiç .jpg bulunamadı: {RAW_TRAIN_DIR}\"\n\nlabels = np.array([1 if Path(f).name.startswith('dog') else 0 for f in all_files])\nprint(f\"Toplam: {len(labels)}  |  Cats: {(labels==0).sum()}  Dogs: {(labels==1).sum()}\")\nprint(\"Örnek dosya:\", Path(all_files[0]).name)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T09:31:44.485220Z","iopub.execute_input":"2025-10-03T09:31:44.486069Z","iopub.status.idle":"2025-10-03T09:31:58.950786Z","shell.execute_reply.started":"2025-10-03T09:31:44.486035Z","shell.execute_reply":"2025-10-03T09:31:58.950147Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2) Veri Hazırlama Pipeline’ı (etiket, ayrım, ön-işleme, augmentasyon, cache)\n\n**Ne yapıyoruz?** Eğitim görsellerinden etiketleri **dosya adından** çıkarıyoruz, veriyi **stratified** şekilde eğitim/doğrulama olarak ayırıyoruz, görüntüleri **decode→resize→normalize** akışına sokuyoruz, **augmentation** uyguluyoruz ve I/O maliyetini azaltmak için **disk tabanlı cache** kullanıyoruz. Son olarak, küçük bir **önizleme** ile boru hattını doğruluyoruz.\n\n- **Etiket çıkarma:**  \n  `cat.* → 0`, `dog.* → 1`. Dosya adının prefix’ine bakarak `labels` dizisi üretilir; sınıf isimleri `['cat', 'dog']`.\n\n- **Eğitim/Doğrulama ayrımı (stratify):**  \n  `train_test_split(..., test_size=0.2, stratify=labels, random_state=SEED)` ile sınıf oranları korunur; deney tekrarları için tohum sabittir.\n\n- **Ön-işleme (`load_image`):**  \n  JPEG okuma → RGB’ye sabitleme (`channels=3`) → `IMG_SIZE×IMG_SIZE` yeniden boyutlandırma → `layers.Rescaling(1./255)` ile `[0,1]` aralığına ölçekleme.  \n  Çıkış: `(float32 image, int32 label)` — modele doğrudan beslenebilir.\n\n- **Augmentasyon:**  \n  `RandomFlip(\"horizontal\") + RandomRotation(0.05) + RandomZoom(0.1)` ile veri çeşitliliği; sonda `clip [0,1]` (görselleştirme/uyarıları temiz tutmak için).  \n  Not: Augmentasyon **cache’ten sonra** uygulanır; decode/resize tekrar hesaplanmaz, yalnızca stochastic transformlar yapılır.\n\n- **Deterministik seçenek:**  \n  `tf.data.Options().experimental_deterministic = True` → aynı tohumla aynı örnek sırası (tekrarlanabilirlik).\n\n- **Disk tabanlı cache:**  \n  `train.cache` ve `val.cache` dosyalarına **decode/resize/normalize** sonrası ara tensörler yazılır.  \n  Fayda: CPU I/O ve tekrar decode maliyetleri azalır; özellikle RAM düşükken eğitimi hızlandırır/istikrarlı kılar.\n\n- **tf.data akışları:**  \n  - `train_base = from_tensor_slices → map(load_image) → cache(TRAIN_CACHE)`  \n  - `train_ds = train_base → shuffle(2048, seed) → map(augmentation, training=True) → batch(32) → prefetch(AUTOTUNE)`  \n  - `val_ds   = from_tensor_slices → map(load_image) → cache(VAL_CACHE) → batch(32) → prefetch(AUTOTUNE)`  \n  Bu sıralama **doğru yerlerde** shuffle/augment/batch uygulandığını garanti eder.\n\n- **Kardinalite & günlük:**  \n  `tf.data.experimental.cardinality` ile batch sayıları yazdırılır; cache dosya yolları log’lanır (izlenebilirlik).\n\n- **Önizleme (sanity check):**  \n  `train_base.take(9)` ile 9 örnek çekilir, `augmentation(training=False)` ile görselleştirilir.  \n  Amaç: boru hattının beklenen biçimde çalıştığını *gözle* doğrulamak.","metadata":{}},{"cell_type":"code","source":"# ===============================\n# 2) Veri Hazırlama Pipeline’ı\n# ===============================\n\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\n\nIMG_SIZE = (224, 224)\nBATCH_SIZE = 32\n\n# --- 2.1 Etiket çıkarma ---\nall_files = sorted(glob.glob(str(RAW_TRAIN_DIR / '*.jpg')))\nlabels = np.array([1 if Path(f).name.startswith('dog') else 0 for f in all_files], dtype=np.int32)\nclass_names = ['cat','dog']\n\nprint(f\"Eğitim datası toplam: {len(all_files)} | Cats: {(labels==0).sum()} | Dogs: {(labels==1).sum()}\")\n\n# --- 2.2 Stratified split (80/20) ---\ntrain_files, val_files, y_train, y_val = train_test_split(\n    all_files, labels, test_size=0.2, random_state=SEED, stratify=labels\n)\nprint(f\"Train: {len(train_files)} | Val: {len(val_files)}\")\n\n# --- 2.3 Ön işleme fonksiyonu ---\ndef decode_img(filename, label):\n    img = tf.io.read_file(filename)\n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, IMG_SIZE)\n    img = tf.cast(img, tf.float32) / 255.0\n    return img, label\n\n# --- 2.4 Augmentasyon ---\ndata_augmentation = tf.keras.Sequential([\n    tf.keras.layers.RandomFlip(\"horizontal\"),\n    tf.keras.layers.RandomRotation(0.05),\n    tf.keras.layers.RandomZoom(0.1),\n    tf.keras.layers.RandomTranslation(0.1, 0.1),\n    tf.keras.layers.RandomContrast(0.1),\n])\n\ndef augment_img(img, label):\n    return data_augmentation(img, training=True), label\n\n# --- 2.5 tf.data pipeline ---\ndef make_dataset(files, labels, augment=False, shuffle=False):\n    ds = tf.data.Dataset.from_tensor_slices((files, labels))\n    ds = ds.map(lambda f,l: decode_img(f,l), num_parallel_calls=tf.data.AUTOTUNE)\n    if augment:\n        ds = ds.map(augment_img, num_parallel_calls=tf.data.AUTOTUNE)\n    if shuffle:\n        ds = ds.shuffle(1000, seed=SEED)\n    ds = ds.batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n    return ds\n\ntrain_ds = make_dataset(train_files, y_train, augment=True, shuffle=True)\nval_ds   = make_dataset(val_files, y_val, augment=False, shuffle=False)\n\n# --- 2.6 Kontrol için birkaç görsel gösterelim ---\nsample_imgs, sample_labels = next(iter(train_ds))\nplt.figure(figsize=(10,10))\nfor i in range(9):\n    plt.subplot(3,3,i+1)\n    plt.imshow(sample_imgs[i].numpy())\n    plt.title(class_names[sample_labels[i].numpy()])\n    plt.axis(\"off\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T09:33:31.595030Z","iopub.execute_input":"2025-10-03T09:33:31.595551Z","iopub.status.idle":"2025-10-03T09:33:53.025669Z","shell.execute_reply.started":"2025-10-03T09:33:31.595526Z","shell.execute_reply":"2025-10-03T09:33:53.024980Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3) Basit CNN — Mimari ve Tasarım Gerekçeleri\n\n**Ne yapıyor?** ReLU için uygun **HeNormal** başlatma, **L2** cezası, her konv sonrası **BatchNorm**, son konv bloğundan sonra **GlobalAveragePooling (GAP)** ve isimlendirilmiş **last_conv** katmanı ile **Grad-CAM** uyumlu, hafif ama düzenli bir temel CNN kuruyor.\n\n- **Giriş / Hiperparametreler:**  \n  `input_shape=(224,224,3)`; kapasiteyi `base_filters` (32→64→128), `kernel_size`, `dropout_rate` ve `dense_units` ile ayarlayabiliyoruz.\n\n- **Ağırlık başlatma ve düzenlileştirme:**  \n  - `HeNormal`: ReLU etkinleştirmeleriyle daha iyi gradyan akışı.  \n  - `kernel_regularizer=L2(5e-5)`: Ağırlıkları cezalandırarak **overfitting**’i azaltır.  \n  - Konv katmanlarında `use_bias=False`: Hemen ardından **BatchNorm** olduğu için gereksiz bias parametresini kaldırır.\n\n- **Konvolüsyon blokları (3 blok):**  \n  Her blok: `Conv2D → BatchNorm → ReLU → MaxPool`. Filtreler 1×, 2×, 4× ölçeklenir.  \n  - **last_conv** adı verilen son konv katmanı, **Grad-CAM** için hedef katman olarak kullanılacak şekilde **bilinçli isimlendirildi**.\n\n- **Özellikten sınıfa geçiş (Head):**  \n  - **GAP (`GlobalAveragePooling2D`)**: `Flatten` yerine kullanılarak parametre sayısını düşürür, **CAM/Grad-CAM** ile uyumluluk sağlar ve aşırı öğrenmeyi azaltır.  \n  - **Dropout → Dense(ReLU) → Dropout**: Tam bağlantılı kısımda rastgele devre dışı bırakma ile düzenlileştirme.\n\n- **Çıkış katmanı:**  \n  - `BINARY=True` ise `Dense(1, sigmoid)` (ikili sınıflandırma, dogs-vs-cats).  \n  - Aksi halde `Dense(num_classes, softmax)` (çok sınıflı senaryolar).\n\n- **Model adı ve özet:**  \n  Model `cnn_baseline_v2` olarak adlandırılır; `model.summary()` ile katman boyutları ve parametre sayısı doğrulanır.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras import layers, models, regularizers\n\ndef build_cnn(input_shape=(224,224,3), num_classes=1, dropout_rate=0.3, l2_reg=1e-4):\n    inputs = layers.Input(shape=input_shape)\n\n    # Blok 1\n    x = layers.Conv2D(32, (3,3), padding='same', use_bias=False,\n                      kernel_regularizer=regularizers.l2(l2_reg))(inputs)\n    x = layers.BatchNormalization()(x)\n    x = layers.ReLU()(x)\n    x = layers.MaxPooling2D()(x)\n\n    # Blok 2\n    x = layers.Conv2D(64, (3,3), padding='same', use_bias=False,\n                      kernel_regularizer=regularizers.l2(l2_reg))(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.ReLU()(x)\n    x = layers.MaxPooling2D()(x)\n\n    # Blok 3\n    x = layers.Conv2D(128, (3,3), padding='same', use_bias=False,\n                      kernel_regularizer=regularizers.l2(l2_reg))(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.ReLU()(x)\n    x = layers.MaxPooling2D()(x)\n\n    # Fully connected\n    x = layers.Flatten()(x)\n    x = layers.Dropout(dropout_rate)(x)\n    x = layers.Dense(128, activation='relu')(x)\n    x = layers.Dropout(dropout_rate)(x)\n\n    outputs = layers.Dense(1, activation='sigmoid')(x)\n\n    model = models.Model(inputs, outputs, name=\"baseline_cnn_bn\")\n    return model\n\nmodel = build_cnn()\nmodel.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T09:44:20.007320Z","iopub.execute_input":"2025-10-03T09:44:20.007620Z","iopub.status.idle":"2025-10-03T09:44:20.927775Z","shell.execute_reply.started":"2025-10-03T09:44:20.007597Z","shell.execute_reply":"2025-10-03T09:44:20.927094Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4) Eğitim Stratejisi — Derleme (compile), Callback’ler ve Fit\n\n**Özet:** Model, **Adam** ile `LR=5e-4` başlangıç öğrenme oranında eğitiliyor; kayıp fonksiyonu **BinaryCrossentropy** (etiket yumuşatma `0.05`), metrikler **accuracy** ve **AUC**. Eğitim akışı **EarlyStopping**, **ReduceLROnPlateau** ve **ModelCheckpoint** ile izlenip iyileştiriliyor.\n\n- **Kayıp (loss) ve etiket yumuşatma:**  \n  `BinaryCrossentropy(label_smoothing=0.05)` pozitif/negatif hedefleri tam 0/1 yerine %5 esnetir. Gürültülü/yanılabilir etiketlerde aşırı güveni azaltır, genellemeyi iyileştirebilir.\n\n- **Optimizasyon ve öğrenme oranı:**  \n  `Adam(lr=5e-4)` iyi bir başlangıçtır. **ReduceLROnPlateau** doğrulama metriği iyileşmediğinde LR’ı **×0.5** düşürerek yerel plato/oynaklıklarda daha iyi yakınsamayı hedefler.\n\n- **Metrikler:**  \n  - `accuracy`: doğru sınıflama oranı.  \n  - `AUC`: eşik-bağımsız ayırım gücü; sınıf dengesizliğine karşı daha stabildir.\n\n- **Callback’ler:**  \n  - **EarlyStopping** (`patience=3`, `monitor='val_loss'`, `restore_best_weights=True`): Doğrulama kaybı 3 epoch iyileşmezse durur ve **en iyi val_loss**’a ait ağırlıkları geri yükler (overfitting’i sınırlar, süreyi kısaltır).  \n  - **ReduceLROnPlateau** (`patience=2`, `factor=0.5`): 2 epoch gelişme yoksa LR’ı yarıya indirir, daha ince adımlarla aramaya devam eder.  \n  - **ModelCheckpoint** (`monitor='val_accuracy'`, `save_best_only=True`): En iyi **val_accuracy** görüldüğünde modeli `models/cnn_baseline.keras` olarak kaydeder.  \n    > **Not:** EarlyStopping **val_loss**’u, Checkpoint ise **val_accuracy**’yi izliyor. Tek bir hedefe (örn. **val_auc** ya da **val_loss**) hizalamak raporlama tutarlılığını artırır.\n\n- **Adım sayıları (steps):**  \n  `steps_per_epoch = ceil(len(train_files)/BATCH_SIZE)` ve `validation_steps` ile her epoch’ta tüm verinin bir kez görülmesi garanti edilir. (Pipeline’ında `.repeat()` yoksa bu hesaplama isabetlidir.)\n\n- **Eğitim çağrısı:**  \n  `model.fit(train_ds, validation_data=val_ds, epochs=25, ...)` tarihçeyi `history` içine yazar. Eğitim sonrası `history.history` ile **loss/accuracy/AUC** eğrileri çizilebilir ve en iyi epoch tespit edilebilir.","metadata":{}},{"cell_type":"code","source":"# ===============================\n# 4) Eğitim Stratejisi — Compile, Callback, Fit\n# ===============================\n\nfrom tensorflow import keras\n\n# --- Derleme ---\nloss_fn = keras.losses.BinaryCrossentropy(label_smoothing=0.0)   # smoothing kapalı\noptimizer = keras.optimizers.Adam(learning_rate=3e-4)\n\nmodel.compile(\n    optimizer=optimizer,\n    loss=loss_fn,\n    metrics=[\n        'accuracy',\n        keras.metrics.AUC(name=\"auc\"),\n        keras.metrics.Precision(name=\"precision\"),\n        keras.metrics.Recall(name=\"recall\")\n    ]\n)\n\n# --- Callback'ler ---\ncallbacks = [\n    keras.callbacks.EarlyStopping(\n        patience=6, restore_best_weights=True, monitor=\"val_loss\"\n    ),\n    keras.callbacks.ReduceLROnPlateau(\n        patience=3, factor=0.5, min_lr=1e-6, monitor=\"val_loss\", verbose=1\n    ),\n    keras.callbacks.ModelCheckpoint(\n        filepath=\"cnn_best.keras\",\n        monitor=\"val_auc\",\n        mode=\"max\",\n        save_best_only=True,\n        verbose=1\n    )\n]\n\n# --- Eğitim ---\nEPOCHS = 30\n\n# Köpek recall'unu artırmak için class_weight\nclass_weight = {0: 1.0, 1: 1.3}\n\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS,\n    callbacks=callbacks,\n    class_weight=class_weight,\n    verbose=1\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T09:46:26.192208Z","iopub.execute_input":"2025-10-03T09:46:26.192829Z","iopub.status.idle":"2025-10-03T13:03:25.185271Z","shell.execute_reply.started":"2025-10-03T09:46:26.192805Z","shell.execute_reply":"2025-10-03T13:03:25.184664Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5) Eğitim Eğrileri — Loss & Accuracy Yorumlama\n\n**Ne gösterir?** `history.history` içinden **eğitim** ve **doğrulama** için epoch bazında iki eğri çizer:\n- **Loss per epoch:** Öğrenmenin istikrarlı ilerleyip ilerlemediğini; **overfitting**’i (train loss ↓, val loss ↑) ya da **underfitting**’i (her iki loss da yüksek seyir) görürüz.\n- **Accuracy per epoch:** Sınıflama başarısının genel trendini izleriz; train–val farkı büyüyorsa aşırı uyum belirtisi olabilir.\n\n**Nasıl okunur?**\n- Sağlıklı bir eğitimde **train loss** ve **val loss** birlikte azalır; epoch ilerledikçe aradaki fark **makul** kalır.\n- **EarlyStopping** etkinse, model ağırlıkları en iyi **val_loss** epoch’una geri yüklenir; grafikte en düşük **val_loss** noktası kritik referanstır.\n- **Accuracy** grafiğinde train–val uçurumu büyürse, **daha fazla düzenlileştirme** (Dropout↑, L2↑, augmentation↑) veya **LR azaltımı** gerekebilir.\n","metadata":{}},{"cell_type":"code","source":"# ===============================\n# 5A) Eğitim Eğrileri – Görselleştirme\n# ===============================\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n# 1) History'yi DataFrame'e çevir\nassert 'history' in globals(), \"history bulunamadı. Önce model.fit(...) çalışmış olmalı.\"\nhist = pd.DataFrame(history.history)\nhist.index = np.arange(1, len(hist) + 1)  # epoch 1'den başlasın\nprint(\"Eğitim geçmişi sütunları:\", list(hist.columns))\ndisplay(hist.tail())\n\n# 2) Yardımcı çizim fonksiyonu\ndef plot_metric(df, metric, val_metric=None, title=None, best_on='auto'):\n    if metric not in df.columns:\n        return\n    plt.figure(figsize=(7,4))\n    plt.plot(df.index, df[metric].values, label=metric)\n    if val_metric and val_metric in df.columns:\n        plt.plot(df.index, df[val_metric].values, label=val_metric)\n        # En iyi epoch (val_metric'e göre) – loss ise min, diğerleri max\n        if best_on == 'auto':\n            best_on = 'min' if 'loss' in val_metric else 'max'\n        if best_on == 'min':\n            best_epoch = int(np.argmin(df[val_metric].values) + 1)\n        else:\n            best_epoch = int(np.argmax(df[val_metric].values) + 1)\n        plt.axvline(best_epoch, linestyle='--', alpha=0.5)\n        plt.text(best_epoch + 0.1, plt.ylim()[0], f\"best@{best_epoch}\", rotation=90, va='bottom')\n    plt.title(title or metric)\n    plt.xlabel(\"Epoch\"); plt.ylabel(metric)\n    plt.grid(True, alpha=0.25); plt.legend()\n    plt.show()\n\n# 3) Çizimler\nplot_metric(hist, 'loss', 'val_loss', 'Loss (train vs val)', best_on='min')\nplot_metric(hist, 'accuracy', 'val_accuracy', 'Accuracy (train vs val)', best_on='max')\n\nif 'precision' in hist.columns or 'val_precision' in hist.columns:\n    plot_metric(hist, 'precision', 'val_precision', 'Precision (train vs val)', best_on='max')\n\nif 'recall' in hist.columns or 'val_recall' in hist.columns:\n    plot_metric(hist, 'recall', 'val_recall', 'Recall (train vs val)', best_on='max')\n\nif 'auc' in hist.columns or 'val_auc' in hist.columns:\n    plot_metric(hist, 'auc', 'val_auc', 'AUC (train vs val)', best_on='max')\n\n# Learning rate tarihçesi bazı TF versiyonlarında 'learning_rate' bazılarında 'lr' olarak geçer\nlr_key = 'learning_rate' if 'learning_rate' in hist.columns else ('lr' if 'lr' in hist.columns else None)\nif lr_key is not None:\n    plot_metric(hist, lr_key, None, 'Learning Rate', best_on='max')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T13:17:20.169899Z","iopub.execute_input":"2025-10-03T13:17:20.170167Z","iopub.status.idle":"2025-10-03T13:17:21.194806Z","shell.execute_reply.started":"2025-10-03T13:17:20.170147Z","shell.execute_reply":"2025-10-03T13:17:21.194134Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 5B) Otomatik Yorum – Hızlı Tanı & Öneriler\n# ===============================\nimport numpy as np\n\ndef safe_get(col, default=None):\n    return hist[col].values if col in hist.columns else default\n\nloss = safe_get('loss'); val_loss = safe_get('val_loss')\nacc  = safe_get('accuracy'); val_acc = safe_get('val_accuracy')\nprec = safe_get('precision'); vprec = safe_get('val_precision')\nrec  = safe_get('recall');    vrec  = safe_get('val_recall')\nauc  = safe_get('auc');       vauc  = safe_get('val_auc')\n\nn = len(hist)\nsuggestions = []\n\n# 1) En iyi epoch'lar\nbest_loss_epoch = int(np.argmin(val_loss) + 1) if val_loss is not None else None\nbest_auc_epoch  = int(np.argmax(vauc) + 1)     if vauc is not None else None\n\nprint(\"— En iyi epoch bilgisi —\")\nif best_loss_epoch: print(f\"  • Min val_loss epoch: {best_loss_epoch} (val_loss={val_loss[best_loss_epoch-1]:.4f})\")\nif best_auc_epoch:  print(f\"  • Max val_auc  epoch: {best_auc_epoch}  (val_auc ={vauc[best_auc_epoch-1]:.4f})\")\n\n# 2) Genelizasyon farkı (gap)\nif loss is not None and val_loss is not None:\n    gap_final = val_loss[-1] - loss[-1]\n    gap_best  = val_loss[best_loss_epoch-1] - loss[best_loss_epoch-1]\n    print(f\"\\n— Genelizasyon farkı (val_loss - loss) —\")\n    print(f\"  • Final epoch gap: {gap_final:+.4f}\")\n    print(f\"  • Best-loss epoch gap: {gap_best:+.4f}\")\n\n    # Heuristik: Overfit/Underfit\n    # Overfit sinyali: val_loss son 3 epokta artarken train_loss düşmeye devam ediyorsa\n    if n >= 4:\n        tr_drop = loss[-4] - loss[-1]   # son 3 epokta train düşüşü\n        val_rise = val_loss[-1] - val_loss[-4]  # son 3 epokta val artışı\n        if tr_drop > 0.02 and val_rise > 0.02:\n            suggestions.append(\"Overfit sinyali: Son epoklarda train loss düşerken val loss artıyor. Patience/ES iyi; Dropout/L2 biraz artırılabilir veya augmentasyon hafifçe güçlendirilebilir.\")\n    # Underfit sinyali: hem train hem val loss yüksek ve araları çok yakın\n    if val_loss[-1] > 0.60 and loss[-1] > 0.60 and abs(gap_final) < 0.05:\n        suggestions.append(\"Underfit sinyali: Train/Val loss yüksek ve birbirine çok yakın. Kapasite artır (daha fazla filtre/blok) veya transfer learning dene.\")\n\n# 3) Precision–Recall dengesi\nif vprec is not None and vrec is not None:\n    diff = float(vrec[-1] - vprec[-1])\n    print(f\"\\n— PR Dengesi —\\n  • Val recall - precision (final): {diff:+.3f}\")\n    if diff > 0.15:\n        suggestions.append(\"Recall ≫ Precision: Eşik (threshold) çok düşük veya class_weight yüksek. Eğitim sonrası threshold kalibrasyonu yap; gerekiyorsa class_weight'i 1.3 → 1.15-1.25 aralığına çek.\")\n    elif diff < -0.15:\n        suggestions.append(\"Precision ≫ Recall: Model fazla tutucu. Threshold'u düşür veya augmentasyonu çeşitlendir; class_weight ile dog sınıfını biraz ağırlıklandır.\")\n\n# 4) AUC trendi\nif vauc is not None and n >= 5:\n    # son 5 epokta gelişim\n    delta_auc = vauc[-1] - vauc[max(-5, -n)]\n    print(f\"\\n— AUC Trend —\\n  • Son 5 epok delta (val_auc): {delta_auc:+.4f}\")\n    if delta_auc < 0.005:\n        suggestions.append(\"Val AUC son epoklarda plato yapmış görünüyor. LR yarıya düşürme (ReduceLROnPlateau), augmentasyon hafifletme veya daha büyük batch ile tekrar dene.\")\n\n# 5) LR kaydı varsa raporla\nlr_key = 'learning_rate' if 'learning_rate' in hist.columns else ('lr' if 'lr' in hist.columns else None)\nif lr_key:\n    lr_last = float(hist[lr_key].iloc[-1])\n    print(f\"\\n— Learning Rate —\\n  • Son LR: {lr_last:.2e}\")\n\n# 6) Son önerileri yazdır\nprint(\"\\n— Öneriler —\")\nif suggestions:\n    for s in suggestions:\n        print(\"  •\", s)\nelse:\n    print(\"  • Eğilimler sağlıklı görünüyor. Bir sonraki adım: Eşik (threshold) kalibrasyonu ve karışıklık matrisi ile doğrulama.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T13:19:21.887333Z","iopub.execute_input":"2025-10-03T13:19:21.887908Z","iopub.status.idle":"2025-10-03T13:19:21.899583Z","shell.execute_reply.started":"2025-10-03T13:19:21.887887Z","shell.execute_reply":"2025-10-03T13:19:21.899052Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6) Doğrulama Değerlendirmesi — Sınıflandırma Raporu & Karışıklık Matrisi\n\n**Ne yapıyoruz?** `val_ds` üzerinde modelin olasılık çıkışlarını alıp (`probs`), **eşik=0.5** ile sınıfa çeviriyoruz (`preds`). Ardından **precision/recall/F1**’ı içeren `classification_report` ve hata türlerini görselleyen **confusion matrix** üretiyoruz.\n\n- **Tahmin akışı:**\n  - `model.predict(imgs)` → `probs ∈ [0,1]` (sigmoid çıkışı).  \n  - `preds = (probs ≥ 0.5)` → ikili sınıf etiketi.  \n  - `y_true` gerçek etiketler; `y_pred` model tahminleri.\n\n- **Sınıflandırma raporu (`classification_report`):**  \n  - **precision (kesinlik):** Pozitif dediğinin ne kadarının doğru olduğu.  \n  - **recall (duyarlılık):** Gerçek pozitiflerin ne kadarını yakaladığın.  \n  - **F1:** Precision & recall’ın harmonik ortalaması (dengesiz veri için faydalı).  \n  - **support:** Her sınıftaki örnek sayısı (raporu okurken sınıf dengesiyle birlikte düşün).\n\n- **Karışıklık matrisi (Confusion Matrix):**  \n  - Köşegen elemanlar: **doğru** sınıflamalar.  \n  - Üst sağ: Gerçekte **cat**, tahmin **dog** (**false positive** for dog).  \n  - Alt sol: Gerçekte **dog**, tahmin **cat** (**false negative** for dog).","metadata":{}},{"cell_type":"code","source":"# ===============================\n# 6) Doğrulama Değerlendirmesi — Rapor & Karışıklık Matrisi\n# ===============================\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import classification_report, confusion_matrix, f1_score, roc_auc_score\n\n# 1) En iyi modeli yükle ve doğrulama için olasılıkları al\nbest = keras.models.load_model(\"cnn_best.keras\")\nval_probs = best.predict(val_ds, verbose=0).ravel()  # p(dog)\n\n# 2) Eşik (threshold) kalibrasyonu: F1 ve dog-recall için en iyi eşik\nts = np.linspace(0.20, 0.80, 121)\n\ndef dog_recall_for_threshold(t):\n    y_hat = (val_probs >= t).astype(int)\n    cm = confusion_matrix(y_val, y_hat, labels=[0,1])\n    tp = cm[1,1]; fn = cm[1,0]\n    return tp / (tp + fn + 1e-12)\n\nscores_f1  = [(t, f1_score(y_val, (val_probs >= t).astype(int))) for t in ts]\nscores_rec = [(t, dog_recall_for_threshold(t)) for t in ts]\n\nbest_t_f1  = max(scores_f1,  key=lambda x: x[1])[0]\nbest_t_rec = max(scores_rec, key=lambda x: x[1])[0]\n\n# Kullanılacak eşiği seç (hedefine göre). İstersen manuel override yap.\nUSE_OBJECTIVE = \"f1\"   # \"f1\" veya \"dog_recall\"\nOVERRIDE_T = None      # Örn: 0.47 verirsen bu kullanılır\n\nuse_t = (OVERRIDE_T if OVERRIDE_T is not None \n         else (best_t_f1 if USE_OBJECTIVE==\"f1\" else best_t_rec))\n\nprint(f\"Seçilen eşik: {use_t:.3f}  |  En iyi F1 eşiği: {best_t_f1:.3f}  |  En iyi dog-recall eşiği: {best_t_rec:.3f}\")\nprint(\"ROC AUC (val):\", roc_auc_score(y_val, val_probs))\n\n# 3) Sınıflandırma raporu\ny_pred = (val_probs >= use_t).astype(int)\nreport = classification_report(y_val, y_pred, target_names=class_names, digits=4)\nprint(\"\\n=== Sınıflandırma Raporu (val) ===\")\nprint(report)\n\n# 4) Karışıklık matrisi (ham ve normalize)\ncm = confusion_matrix(y_val, y_pred, labels=[0,1])\ncm_norm = cm.astype(np.float64) / cm.sum(axis=1, keepdims=True)\n\ndef plot_confusion_matrix(cm_, labels, normalize=False, title=\"Confusion Matrix\"):\n    plt.figure(figsize=(5,4))\n    im = plt.imshow(cm_, interpolation='nearest')\n    plt.title(title)\n    plt.colorbar(im, fraction=0.046, pad=0.04)\n    tick_marks = np.arange(len(labels))\n    plt.xticks(tick_marks, labels); plt.yticks(tick_marks, labels)\n    fmt = \".2f\" if normalize else \"d\"\n    thresh = cm_.max() / 2. if cm_.size > 0 else 0.5\n    for i in range(cm_.shape[0]):\n        for j in range(cm_.shape[1]):\n            plt.text(j, i, format(cm_[i, j], fmt),\n                     ha=\"center\", va=\"center\")\n    plt.ylabel('Gerçek Sınıf'); plt.xlabel('Tahmin Sınıf')\n    plt.tight_layout()\n    plt.show()\n\nplot_confusion_matrix(cm,      class_names, normalize=False, title=\"CM (Ham Sayılar)\")\nplot_confusion_matrix(cm_norm, class_names, normalize=True,  title=\"CM (Normalize, satır %)\")\n\n# 5) Özet metrikler (özellikle DOG sınıfı için)\ntp, fp = cm[1,1], cm[0,1]\nfn, tn = cm[1,0], cm[0,0]\ndog_precision = tp / (tp + fp + 1e-12)\ndog_recall    = tp / (tp + fn + 1e-12)\n\nprint(f\"\\nDOG precision: {dog_precision:.4f} | DOG recall: {dog_recall:.4f}\")\nprint(f\"Accuracy: {(tn+tp)/cm.sum():.4f}\")\n\n# 6) (Opsiyonel) Rapor ve CM’i diske kaydet\nwith open(\"val_classification_report.txt\", \"w\") as f:\n    f.write(f\"Threshold: {use_t:.3f}\\n\")\n    f.write(f\"ROC AUC: {roc_auc_score(y_val, val_probs):.4f}\\n\\n\")\n    f.write(report)\n\nplt.imsave(\"val_cm_counts.png\", cm, format='png')\nplt.imsave(\"val_cm_norm.png\", cm_norm, format='png')\nprint(\"\\nKaydedildi: val_classification_report.txt, val_cm_counts.png, val_cm_norm.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T13:21:52.906418Z","iopub.execute_input":"2025-10-03T13:21:52.907111Z","iopub.status.idle":"2025-10-03T13:21:58.208579Z","shell.execute_reply.started":"2025-10-03T13:21:52.907089Z","shell.execute_reply":"2025-10-03T13:21:58.207906Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7) Açıklanabilirlik — Grad-CAM (son konv katmanı: `last_conv`)\n\n**Amaç:** Modelin kararı verirken görüntünün **hangi bölgelerine** odaklandığını ısı haritası ile göstermek. Bu uygulama, mimaride bilinçli olarak isimlendirdiğimiz `last_conv` katmanını hedef alır ve **GAP** ile uyumludur.\n\n**Nasıl çalışır?**\n1. `grad_model`: Girdi → (`last_conv` aktivasyonları, `model.output`) ikilisini döndüren yardımcı bir model kurulur.\n2. `GradientTape`: İlgili sınıfın çıkış skoru (ikili görevde `predictions[:,0]`) ile `last_conv` aktivasyonları arasındaki **gradyan** alınır.\n3. **Kanal ağırlıkları:** Gradyanlar uzamsal ortalama ile kanallara indirgenir → `pooled_grads (C,)`.\n4. **Sınıf aktivasyon haritası:** `conv_outputs` kanalları, ağırlıklarla çarpılıp toplanır → **ısı haritası**.\n5. **Normalize & ReLU:** Negatif değerler sıfırlanır; [0,1] aralığına normalize edilir ve orijinal görüntü üstüne `alpha≈0.4` ile bindirilir.\n\n**Fonksiyonlar:**\n- `get_img_array(path)`: Görüntüyü okur, `IMG_SIZE×IMG_SIZE`’a yeniden boyutlandırır, `[0,1]`’e ölçekler.\n- `make_gradcam_heatmap(img_array, model, last_conv_layer_name)`: Yukarıdaki akışla ısı haritasını üretir (NumPy).\n- `show_gradcam(image_path, model, last_conv_layer_name)`: Girdi ve bindirilmiş ısı haritasını yan yana çizer.\n","metadata":{}},{"cell_type":"code","source":"# ===============================\n# 7) Açıklanabilirlik — Grad-CAM (son konv: last_conv)\n# ===============================\nimport numpy as np\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nfrom tensorflow.keras import layers, Model\nfrom tensorflow import keras\nimport matplotlib.cm as cm\n\n# 1) En iyi modeli yükle\nbest = keras.models.load_model(\"cnn_best.keras\")\n\n# 2) Son konv katmanını bul (öncelik: 'last_conv', yoksa en sondaki Conv2D)\ndef find_last_conv_layer_name(model, preferred_name='last_conv'):\n    # Önce ismen ara\n    try:\n        layer = model.get_layer(preferred_name)\n        if isinstance(layer, layers.Conv2D):\n            return preferred_name\n    except Exception:\n        pass\n    # Bulamazsa, sondan başlayarak ilk Conv2D'yi getir\n    for layer in reversed(model.layers):\n        if isinstance(layer, layers.Conv2D):\n            return layer.name\n    raise ValueError(\"Modelde Conv2D katmanı bulunamadı.\")\n\nlast_conv_name = find_last_conv_layer_name(best, 'last_conv')\nprint(\"Grad-CAM için kullanılacak son konv katmanı:\", last_conv_name)\n\n# 3) Grad-CAM ısı haritasını üret\ndef make_gradcam_heatmap(img_tensor, model, conv_layer_name, pred_index=None):\n    \"\"\"\n    img_tensor: (H, W, 3) float32 [0,1]\n    model: tf.keras.Model\n    conv_layer_name: son konv katman adı\n    pred_index: çok sınıflı ise hedef sınıf; binary ise None (pozitif sınıf/dog)\n    \"\"\"\n    grad_model = Model(\n        inputs=model.inputs,\n        outputs=[model.get_layer(conv_layer_name).output, model.output]\n    )\n\n    img_batch = tf.expand_dims(img_tensor, 0)  # (1,H,W,3)\n\n    with tf.GradientTape() as tape:\n        conv_outputs, predictions = grad_model(img_batch, training=False)\n        # Hedef skor\n        if predictions.shape[-1] == 1:\n            # Binary: dog olasılığı (pozitif sınıf)\n            class_channel = predictions[:, 0]\n        else:\n            # Çok sınıflı ise tahmin edilen sınıfı seç (veya pred_index parametresi)\n            if pred_index is None:\n                pred_index = tf.argmax(predictions[0])\n            class_channel = predictions[:, pred_index]\n\n    # Gradyanlar\n    grads = tape.gradient(class_channel, conv_outputs)        # (1,h,w,c)\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))      # (c,)\n\n    # Kanal ağırlıkları ile feature map'leri tart\n    conv_outputs = conv_outputs[0]                            # (h,w,c)\n    heatmap = tf.reduce_sum(conv_outputs * pooled_grads, axis=-1)  # (h,w)\n\n    # ReLU ve normalize\n    heatmap = tf.maximum(heatmap, 0) / (tf.reduce_max(heatmap) + 1e-12)\n    return heatmap.numpy(), float(predictions.numpy().squeeze())\n\n# 4) Isı haritasını orijinal görsel üzerine bind et\ndef overlay_heatmap(img_tensor, heatmap, alpha=0.35, cmap='jet'):\n    \"\"\"\n    img_tensor: (H,W,3) float32 [0,1]\n    heatmap: (h,w) float32 [0,1]\n    \"\"\"\n    H, W = img_tensor.shape[:2]\n    heatmap_resized = tf.image.resize(heatmap[..., np.newaxis], (H, W)).numpy().squeeze()\n    colormap = cm.get_cmap(cmap)\n    heatmap_color = colormap(heatmap_resized)[..., :3]  # RGBA->RGB\n    overlay = heatmap_color * alpha + img_tensor * (1 - alpha)\n    overlay = np.clip(overlay, 0, 1)\n    return heatmap_resized, overlay\n\n# 5) Eşik (threshold): önceki adımdan 'use_t' varsa onu kullan, yoksa 0.50\nuse_t = globals().get('use_t', 0.50)\nprint(f\"Kullanılan eşik (threshold): {use_t:.3f}\")\n\n# 6) Val setinden birkaç örnekle Grad-CAM görselleştir\nn_show = 6  # kaç görsel göstermek istersin\nshown = 0\nplt.figure(figsize=(12, 4 * ((n_show + 2)//3)))\n\nfor batch_imgs, batch_labels in val_ds:\n    bs = batch_imgs.shape[0]\n    for i in range(bs):\n        if shown >= n_show:\n            break\n\n        img = batch_imgs[i].numpy()        # [0,1]\n        true_label = int(batch_labels[i].numpy())\n        heatmap, prob = make_gradcam_heatmap(img, best, last_conv_name)\n        _, overlay = overlay_heatmap(img, heatmap, alpha=0.35, cmap='jet')\n\n        pred_label = int(prob >= use_t)\n        title = f\"y_true={true_label} | p(dog)={prob:.3f} | y_pred={pred_label}\"\n\n        # 3 sütun: Orijinal – Heatmap – Overlay\n        col = (shown % 3)\n        row = (shown // 3)\n        # Orijinal\n        plt.subplot(((n_show + 2)//3), 3, shown + 1)\n        plt.imshow(img)\n        plt.title(title)\n        plt.axis('off')\n\n        # Heatmap (tek başına)\n        plt.figure(figsize=(12, 4 * ((n_show + 2)//3))) if False else None  # tek fig içinde kal\n        # Aynı subplotta göstermek için küçük hile: alt satırdaki iki plot'u kombine edeceğiz\n        # (Basit tutmak adına 3 görseli ardışık satırlarda göstereceğiz)\n        # -> Pratikte tek satırda 3 görsel istenirse ayrı grid kurulabilir.\n\n        # Heatmap görseli\n        plt.subplot(((n_show + 2)//3), 3, min(shown + 2, n_show))\n        plt.imshow(heatmap, cmap='jet')\n        plt.title(\"Grad-CAM Heatmap\")\n        plt.axis('off')\n\n        # Overlay görseli\n        plt.subplot(((n_show + 2)//3), 3, min(shown + 3, n_show))\n        plt.imshow(overlay)\n        plt.title(\"Overlay\")\n        plt.axis('off')\n\n        shown += 3  # 3 görsel birden yer kapladığı için sayacı 3 artırıyoruz\n\n    if shown >= n_show:\n        break\n\nplt.tight_layout()\nplt.show()\n\n# 7) (Opsiyonel) Yanlış sınıflanan örneklerden bir batch seçip sadece onları görselleştir\nmis_examples = []\nfor batch_imgs, batch_labels in val_ds.take(5):  # ilk 5 batch'i tara (isteğe bağlı büyüt)\n    probs = best.predict(batch_imgs, verbose=0).ravel()\n    preds = (probs >= use_t).astype(int)\n    y_true = batch_labels.numpy().astype(int)\n    mis_mask = preds != y_true\n    for i in np.where(mis_mask)[0]:\n        mis_examples.append((batch_imgs[i].numpy(), y_true[i], float(probs[i])))\n    if len(mis_examples) >= 6:\n        break\n\nif mis_examples:\n    plt.figure(figsize=(12, 8))\n    for k, (img, y_true, p) in enumerate(mis_examples[:6]):\n        heatmap, prob = make_gradcam_heatmap(img, best, last_conv_name)\n        _, overlay = overlay_heatmap(img, heatmap, alpha=0.35)\n        plt.subplot(2, 3, k+1)\n        plt.imshow(overlay)\n        plt.title(f\"YANLIŞ: y={y_true}, p(dog)={p:.3f}\")\n        plt.axis('off')\n    plt.suptitle(\"Yanlış Sınıflanan Örnekler — Grad-CAM Overlay\", y=1.02, fontsize=14)\n    plt.tight_layout()\n    plt.show()\nelse:\n    print(\"İlk taranan batch'lerde yanlış sınıflanan örnek bulunamadı (harika!).\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T13:29:37.475896Z","iopub.execute_input":"2025-10-03T13:29:37.476189Z","iopub.status.idle":"2025-10-03T13:29:40.656896Z","shell.execute_reply.started":"2025-10-03T13:29:37.476168Z","shell.execute_reply":"2025-10-03T13:29:40.655886Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 8) Transfer Learning (MobileNetV2) (Mini Kullanım)\n\n**Amaç:** Önceden ImageNet üzerinde eğitilmiş **MobileNetV2** gövdesinin genelleme gücünden yararlanıp, üstüne hafif bir sınıflandırıcı başlık (GAP → Dense → Dropout → Sigmoid) ekleyerek daha hızlı ve daha iyi başlangıç performansı elde etmek.\n\n**Kurgu:**\n- `USE_TL=False/True` anahtarı ile transfer learning yolu aç/kapat.\n- `include_top=False` + **dondurulmuş** (`base.trainable=False`) MobileNetV2 gövdesi.\n- Girişe `layers.Resizing(224,224)`; gövdeden sonra `GlobalAveragePooling2D` → `Dropout(0.3)` → `Dense(128, relu)` → `Dropout(0.3)` → `Dense(1, sigmoid)`.\n\n**Eğitim ayarları:**\n- `optimizer=Adam(1e-3)`, `loss='binary_crossentropy'`, `metrics=['accuracy']`.\n- `EarlyStopping(patience=2, restore_best_weights=True)` ve\n  `ModelCheckpoint(..., monitor='val_accuracy', save_best_only=True)`.\n- 8 epoch’luk kısa bir **ısınma eğitimi** (frozen gövde ile).\n\n**Not:** Ufak bir örnekle gösterilmiştir\n","metadata":{}},{"cell_type":"code","source":"USE_TL = True\n\nif USE_TL:\n    IMG_TL = 224\n    tl_input = layers.Input(shape=(IMG_TL, IMG_TL, 3))\n    base = keras.applications.MobileNetV2(\n        input_tensor=tl_input, include_top=False, weights='imagenet')\n    base.trainable = False\n\n    tl_model = keras.Sequential([\n        layers.Resizing(IMG_TL, IMG_TL),\n        base,\n        layers.GlobalAveragePooling2D(),\n        layers.Dropout(0.3),\n        layers.Dense(128, activation='relu'),\n        layers.Dropout(0.3),\n        layers.Dense(1, activation='sigmoid')\n    ], name=\"mobilenetv2_transfer\")\n\n    tl_model.compile(optimizer=keras.optimizers.Adam(1e-3),\n                     loss='binary_crossentropy', metrics=['accuracy'])\n\n    tl_history = tl_model.fit(\n        train_ds,\n        validation_data=val_ds,\n        epochs=8,\n        callbacks=[\n            keras.callbacks.EarlyStopping(patience=2, restore_best_weights=True),\n            keras.callbacks.ModelCheckpoint(str(MODELS_DIR/'mobilenetv2_tl.h5'),\n                                            monitor='val_accuracy', save_best_only=True)\n        ]\n    )\n\n    plot_training_curves(tl_history)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T13:50:43.592610Z","iopub.execute_input":"2025-10-03T13:50:43.592900Z","iopub.status.idle":"2025-10-03T13:50:43.621135Z","shell.execute_reply.started":"2025-10-03T13:50:43.592882Z","shell.execute_reply":"2025-10-03T13:50:43.620187Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9) Hiperparametre Optimizasyonu\nFarklı yapılandırmalar denenerek en iyi model seçilir.  \nAmaç: performansı artırmak.\n\n- Parametreler: filtre sayısı, kernel boyutu, dropout, dense units, learning rate  \n- Yöntem: Keras Tuner Hyperband  \n- Sonuç: doğrulama metriklerinde artış sağlayan en iyi yapı\n\nKerasTuner Hyperband kullanarak build_simple_cnn için otomatik arama yapar.\n\n- Hız için küçük bir alt-küme ile (take) arama yapılır; en iyi hiperparametreler bulunduğunda tam veride yeniden eğitebilirsiniz.\n- CPU ortamında süreci kısaltmak için max_epochs=6, EarlyStopping(patience=2) ve azaltılmış steps_per_epoch/validation_steps kullanılmıştır.","metadata":{}},{"cell_type":"code","source":"import keras_tuner as kt\n\ndef build_model(hp):\n    base_filters = hp.Choice(\"base_filters\", [16, 32, 64])\n    kernel_size  = hp.Choice(\"kernel_size\",  [3, 5])\n    dropout_rate = hp.Choice(\"dropout_rate\", [0.3, 0.5])\n    dense_units  = hp.Choice(\"dense_units\",  [64, 128, 256])\n    lr           = hp.Choice(\"lr\",           [1e-3, 5e-4, 1e-4])\n\n    m = build_simple_cnn(base_filters=base_filters,\n                         kernel_size=kernel_size,\n                         dropout_rate=dropout_rate,\n                         dense_units=dense_units)\n    m.compile(optimizer=keras.optimizers.Adam(lr),\n              loss='binary_crossentropy', metrics=['accuracy'])\n    return m\n\nHPO_TRAIN_SAMPLES = 800\nHPO_VAL_SAMPLES   = 200\nHPO_BATCH_SIZE    = 32\n\ntr_small = (tf.data.Dataset.from_tensor_slices((train_files, y_train))\n            .shuffle(2048, seed=SEED)\n            .map(load_image, num_parallel_calls=AUTOTUNE)\n            .map(lambda x,y: (data_augmentation(x, training=True), y), num_parallel_calls=AUTOTUNE)\n            .take(HPO_TRAIN_SAMPLES)\n            .batch(HPO_BATCH_SIZE)\n            .repeat()\n            .prefetch(AUTOTUNE))\n\nva_small = (tf.data.Dataset.from_tensor_slices((val_files, y_val))\n            .map(load_image, num_parallel_calls=AUTOTUNE)\n            .take(HPO_VAL_SAMPLES)\n            .batch(HPO_BATCH_SIZE)\n            .repeat()\n            .prefetch(AUTOTUNE))\n\nSTEPS_PER_EPOCH    = HPO_TRAIN_SAMPLES // HPO_BATCH_SIZE\nVAL_STEPS_PER_EPOCH = HPO_VAL_SAMPLES // HPO_BATCH_SIZE\n\ntuner = kt.Hyperband(\n    build_model,\n    objective='val_accuracy',\n    max_epochs=6,        \n    factor=3,\n    directory=str(MODELS_DIR / 'kt'),\n    project_name='dogs_vs_cats'\n)\n\nearly = keras.callbacks.EarlyStopping(patience=2, restore_best_weights=True)\nprint(f\"[KT] Arama başlıyor… steps/epoch={STEPS_PER_EPOCH}, val_steps={VAL_STEPS_PER_EPOCH}\")\ntuner.search(\n    tr_small,\n    validation_data=va_small,\n    steps_per_epoch=STEPS_PER_EPOCH,\n    validation_steps=VAL_STEPS_PER_EPOCH,\n    epochs=6,\n    callbacks=[early],\n    verbose=1\n)\n\nbest_hp = tuner.get_best_hyperparameters(1)[0]\nbest_cfg = {k: best_hp.get(k) for k in [\"base_filters\",\"kernel_size\",\"dropout_rate\",\"dense_units\",\"lr\"]}\nprint(\"[KT] En iyi hiperparametreler:\", best_cfg)\n\n(MODELS_DIR / 'kt').mkdir(parents=True, exist_ok=True)\nwith open(MODELS_DIR / 'kt' / 'best_hparams.json', 'w') as f:\n    json.dump(best_cfg, f, indent=2)\nprint(f\"[KT] Hiperparametreler kaydedildi: {MODELS_DIR / 'kt' / 'best_hparams.json'}\")\n\nbest_model = tuner.hypermodel.build(best_hp)\nbest_model.summary()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T13:54:28.740504Z","iopub.execute_input":"2025-10-03T13:54:28.741121Z","iopub.status.idle":"2025-10-03T13:54:28.800535Z","shell.execute_reply.started":"2025-10-03T13:54:28.741099Z","shell.execute_reply":"2025-10-03T13:54:28.799670Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 10) Doğrulama Metrikleri — Tek Noktadan Toplama (thresh=0.5) ve Raporlama\n\n**Amaç:** `val_ds` üzerinde **tek fonksiyon** ile gerçek etiketler (`y_true`), model olasılıkları (`y_prob`) ve sınıf tahminleri (`y_pred`) toplanır; ardından **Accuracy, Precision/Recall/F1 (macro)** ve **AUC** hesaplanır. Karışıklık matrisi ve ayrıntılı sınıf raporu da yazdırılır.\n\n**Toplama mantığı (`collect_preds`)**\n- **repeat senaryoları:** Dataset `.repeat()` ile sonsuz ise, `max_batches=val_steps` verilerek **tam bir epoch** uzunluğunda veri çekilir. Aksi halde döngü durmaz.\n- **çıktı biçimi:**  \n  - İkili durumda (sigmoid çıkış) `y_prob` tek boyutlu/tek sütunlu olabilir → `p1 = y_prob.reshape(-1)` ile düzleştirilir.  \n  - Çok sınıflıda (softmax) `y_pred = argmax(y_prob, axis=1)`.\n- **eşik:** İkili sınıflamada varsayılan **0.5** eşiği ile `y_pred` üretilir.\n\n**Metrikler**\n- `Accuracy`: genel doğruluk.  \n- `Precision/Recall/F1 (macro)`: Sınıf başına metriklerin ortalaması; **sınıf dengesizliğine** duyarlıdır (her sınıfa eşit ağırlık verir).  \n- `AUC` (yalnızca ikili): `roc_auc_score(y_true, p1)`; **eşik-bağımsız** ayırım gücünü gösterir.\n\n**Raporlar**\n- `classification_report(digits=4)`: sınıf-bazlı P/R/F1 ve macro/weighted özetleri.  \n- `confusion_matrix`: TP/FP/FN/TN dağılımını **somut** gösterir; eşik ve augmentation kararlarını yönlendirir.\n\n**Dikkat / İpuçları**\n- **repeat + steps hizası:** `.repeat()` kullanıyorsan `max_batches=val_steps` **zorunlu**; aksi halde birden fazla epoch’luk veri toplanır ve metrikler şişer.  \n- **eşik ayarı:** 0.5 her zaman optimal değildir. Hedef metrik (örn. **F1-macro** veya **Youden J**) maksimize edilerek **en iyi eşik** bulunabilir.  \n- **AUC ve kalibrasyon:** AUC yüksek ama F1 düşükse, sınıf eşiğini yeniden ayarla; olasılık kalibrasyonu (Platt/Isotonic) gerekli olabilir.  \n- **dtype/şekil:** `y_true` ve `y_pred` vektörlerinin **aynı uzunlukta** ve **1D** olduğundan emin olun (reshape/astype adımları bu yüzden var).\n","metadata":{}},{"cell_type":"code","source":"# ===============================\n# 10) Doğrulama Metrikleri — Tek Nokta (thresh=0.50) & Raporlama\n# ===============================\nimport numpy as np, pandas as pd, matplotlib.pyplot as plt\nfrom pathlib import Path\nfrom tensorflow import keras\nfrom sklearn.metrics import (\n    accuracy_score, precision_score, recall_score, f1_score,\n    roc_auc_score, average_precision_score, log_loss,\n    confusion_matrix, classification_report\n)\n\n# 1) Modeli seç: varsa mbv2'yi, yoksa baseline CNN'i yükle\nMODEL_CANDIDATES = [\"mbv2_best.keras\", \"cnn_best.keras\", \"best_model.keras\"]\nmodel_path = next((m for m in MODEL_CANDIDATES if Path(m).exists()), None)\nassert model_path is not None, \"Değerlendirilecek model bulunamadı (mbv2_best.keras/cnn_best.keras).\"\nmodel = keras.models.load_model(model_path)\nprint(f\"Değerlendirilen model: {model_path}\")\n\n# 2) Val olasılıkları ve sabit eşik (0.50)\nval_probs = model.predict(val_ds, verbose=0).ravel().astype(np.float64)  # p(dog)\nTHRESH = 0.50\ny_pred = (val_probs >= THRESH).astype(int)\n\n# 3) Temel metrikler\nacc  = accuracy_score(y_val, y_pred)\nprec = precision_score(y_val, y_pred, zero_division=0)\nrec  = recall_score(y_val, y_pred, zero_division=0)            # dog recall\nf1   = f1_score(y_val, y_pred, zero_division=0)\nroc  = roc_auc_score(y_val, val_probs)                          # ROC-AUC (threshold bağımsız)\nprc  = average_precision_score(y_val, val_probs)                # PR-AUC (AP)\nbrier = float(np.mean((val_probs - y_val)**2))\nlloss = log_loss(y_val, np.clip(val_probs, 1e-7, 1-1e-7))\n\ncm = confusion_matrix(y_val, y_pred, labels=[0,1])\ntn, fp, fn, tp = cm.ravel()\nspecificity = tn / (tn + fp + 1e-12)\nbalanced_acc = 0.5 * (rec + specificity)\n\n# 4) Özet tablo\nsummary = pd.DataFrame({\n    \"threshold\":   [THRESH],\n    \"accuracy\":    [acc],\n    \"precision\":   [prec],\n    \"recall_dog\":  [rec],\n    \"f1\":          [f1],\n    \"specificity\": [specificity],\n    \"balanced_acc\":[balanced_acc],\n    \"roc_auc\":     [roc],\n    \"pr_auc\":      [prc],\n    \"brier\":       [brier],\n    \"log_loss\":    [lloss],\n    \"tp\":[tp], \"fp\":[fp], \"fn\":[fn], \"tn\":[tn],\n    \"model\":[model_path]\n})\nprint(\"\\n— Tek Nokta (thresh=0.50) Özet —\")\ndisplay(summary.round(4))\n\n# 5) Sınıflandırma raporu\nprint(\"\\n=== Sınıflandırma Raporu (thresh=0.50) ===\")\nprint(classification_report(y_val, y_pred, target_names=class_names, digits=4))\n\n# 6) Karışıklık matrisleri (ham & normalize)\ndef plot_conf_mat(M, labels, normalize=False, title=\"Confusion Matrix\"):\n    mat = (M.astype(np.float64) / M.sum(axis=1, keepdims=True)) if normalize else M\n    plt.figure(figsize=(5,4))\n    im = plt.imshow(mat, interpolation='nearest')\n    plt.title(title); plt.colorbar(im, fraction=0.046, pad=0.04)\n    ticks = np.arange(len(labels))\n    plt.xticks(ticks, labels); plt.yticks(ticks, labels)\n    fmt = \".2f\" if normalize else \"d\"\n    for i in range(mat.shape[0]):\n        for j in range(mat.shape[1]):\n            plt.text(j, i, format(mat[i, j], fmt), ha=\"center\", va=\"center\")\n    plt.ylabel(\"Gerçek\"); plt.xlabel(\"Tahmin\"); plt.tight_layout(); plt.show()\n\nplot_conf_mat(cm, class_names, normalize=False, title=\"CM (Ham)\")\nplot_conf_mat(cm, class_names, normalize=True,  title=\"CM (Normalize, satır %)\")\n\n# 7) Kayıt\nsummary.to_csv(\"val_metrics_thresh_0.50.csv\", index=False)\nsummary.to_json(\"val_metrics_thresh_0.50.json\", orient=\"records\", indent=2)\nwith open(\"val_report_thresh_0.50.txt\",\"w\") as f:\n    f.write(f\"Model: {model_path}\\nThreshold: {THRESH:.2f}\\n\")\n    f.write(f\"ROC AUC: {roc:.4f} | PR AUC: {prc:.4f} | Brier: {brier:.6f} | LogLoss: {lloss:.6f}\\n\\n\")\n    f.write(classification_report(y_val, y_pred, target_names=class_names, digits=4))\n\nprint(\"\\nKaydedildi: val_metrics_thresh_0.50.csv, val_metrics_thresh_0.50.json, val_report_thresh_0.50.txt\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T14:07:42.155794Z","iopub.execute_input":"2025-10-03T14:07:42.156134Z","iopub.status.idle":"2025-10-03T14:07:47.175583Z","shell.execute_reply.started":"2025-10-03T14:07:42.156113Z","shell.execute_reply":"2025-10-03T14:07:47.175041Z"}},"outputs":[],"execution_count":null}]}