{"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":"none","dataSources":[{"sourceId":3362,"databundleVersionId":31148,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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)","metadata":{}},{"cell_type":"code","source":"import os, sys, glob, random, shutil, math, time, json, zipfile\nfrom pathlib import Path\n\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, classification_report\n\nprint(\"Python :\", sys.version)\nprint(\"TensorFlow :\", tf.__version__)\nprint(\"Num GPUs Available:\", len(tf.config.list_physical_devices('GPU')))\n\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); tf.random.set_seed(SEED)\n\nIN_KAGGLE = Path(\"/kaggle/input\").exists()\nif IN_KAGGLE:\n    candidates = [p for p in Path(\"/kaggle/input\").glob(\"*\") if p.is_dir()]\n    comp_root = None\n    for c in candidates:\n        if (c/\"train.zip\").exists() and (c/\"test1.zip\").exists():\n            comp_root = c; break\n    if comp_root is None:\n        raise FileNotFoundError(\"Couldn't find train.zip & test1.zip under /kaggle/input. Make sure you added the 'Dogs vs Cats' competition on the right panel.\")\n    DATA_DIR = Path(\"/kaggle/working/data/dogs-vs-cats\")\nelse:\n    DATA_DIR = Path(\"data/dogs-vs-cats\")\n    comp_root = None \n\nRAW_TRAIN_DIR = DATA_DIR / \"train\"\nRAW_TEST_DIR  = DATA_DIR / \"test1\"\nMODELS_DIR    = Path(\"models\")\nMODELS_DIR.mkdir(parents=True, exist_ok=True)\nDATA_DIR.mkdir(parents=True, exist_ok=True)\n\nCLEAR_OLD = True\n\ndef _rm_children(folder: Path):\n    \"\"\"Delete all children of a folder (but keep the folder).\"\"\"\n    if not folder.exists():\n        folder.mkdir(parents=True, exist_ok=True)\n        return\n    for p in folder.iterdir():\n        try:\n            if p.is_dir():\n                shutil.rmtree(p)\n            else:\n                p.unlink()\n        except Exception as e:\n            print(f\"[WARN] Could not remove {p}: {e}\")\n\ndef _extract(zip_path: Path, out_dir: Path, clear_old: bool = CLEAR_OLD):\n    out_dir.mkdir(parents=True, exist_ok=True)\n    if clear_old:\n        print(f\"Clearing old contents under: {out_dir}\")\n        _rm_children(out_dir)\n\n    print(f\"Extracting {zip_path.name} -> {out_dir} ...\")\n    with zipfile.ZipFile(zip_path, 'r') as zf:\n        zf.extractall(out_dir)\n\n    inner_dirs = [p for p in out_dir.iterdir() if p.is_dir()]\n    if inner_dirs and not any(out_dir.glob(\"*.jpg\")):\n        for d in inner_dirs:\n            for f in d.rglob(\"*.jpg\"):\n                dest = out_dir / f.name\n                if dest.exists():\n                    dest = out_dir / f\"{d.name}__{f.name}\"\n                f.replace(dest)\n            try: shutil.rmtree(d)\n            except Exception: pass\n\nif IN_KAGGLE:\n    _extract(comp_root / \"train.zip\", RAW_TRAIN_DIR)\n    _extract(comp_root / \"test1.zip\", RAW_TEST_DIR)\n\nif CLEAR_OLD:\n    CACHE_DIR = Path(\"cache\")\n    if CACHE_DIR.exists():\n        for f in CACHE_DIR.glob(\"*.cache\"):\n            try: f.unlink()\n            except Exception as e: print(f\"[WARN] Could not delete cache {f}: {e}\")\n\nn_train = len(list(RAW_TRAIN_DIR.glob(\"*.jpg\")))\nn_test  = len(list(RAW_TEST_DIR.glob(\"*.jpg\")))\nassert n_train > 0, f\"Beklenen klasör yok veya boş: {RAW_TRAIN_DIR}. Kaggle 'train' içeriğini buraya çıkarın.\"\nassert n_test  > 0, f\"Beklenen klasör yok veya boş: {RAW_TEST_DIR}. Kaggle 'test1' içeriğini buraya çıkarın.\"\nprint(\"Toplam eğitim görüntüsü:\", n_train)\nprint(\"Toplam test görüntüsü  :\", n_test)\n\nIMG_SIZE   = 224\nINPUT_SHAPE = (IMG_SIZE, IMG_SIZE, 3)\nBINARY = True\nBATCH_SIZE = 32\nAUTOTUNE = tf.data.AUTOTUNE","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-27T02:23:50.788371Z","iopub.execute_input":"2025-09-27T02:23:50.788705Z","iopub.status.idle":"2025-09-27T02:24:07.323945Z","shell.execute_reply.started":"2025-09-27T02:23:50.788680Z","shell.execute_reply":"2025-09-27T02:24:07.323092Z"}},"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":"all_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']\nprint(\"Örnek dosya:\", Path(all_files[0]).name, \"→ etiket:\", class_names[labels[0]])\n\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\"Eğitim: {len(train_files)}, Doğrulama: {len(val_files)}\")\n\n# Decode/resize/scale \nRESCALE = layers.Rescaling(1./255)\n\ndef load_image(path, label):\n    img = tf.io.read_file(path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, (IMG_SIZE, IMG_SIZE))\n    img = RESCALE(img)  # float32 in [0,1]\n    return img, tf.cast(label, tf.int32)\n\n# Augmentasyon \ndata_augmentation = keras.Sequential([\n    layers.RandomFlip(\"horizontal\"),\n    layers.RandomRotation(0.05),\n    layers.RandomZoom(0.1),\n    layers.Lambda(lambda x: tf.clip_by_value(x, 0.0, 1.0), name=\"clip01\")\n], name=\"data_augmentation\")\n\nopts = tf.data.Options()\nopts.experimental_deterministic = True\n\nCACHE_DIR = Path(\"cache\"); CACHE_DIR.mkdir(parents=True, exist_ok=True)\nTRAIN_CACHE = str(CACHE_DIR / \"train.cache\")\nVAL_CACHE   = str(CACHE_DIR / \"val.cache\")\n\ntrain_base = (tf.data.Dataset.from_tensor_slices((train_files, y_train))\n              .map(load_image, num_parallel_calls=AUTOTUNE)\n              .with_options(opts)\n              .cache(TRAIN_CACHE))\n\nval_ds = (tf.data.Dataset.from_tensor_slices((val_files, y_val))\n          .map(load_image, num_parallel_calls=AUTOTUNE)\n          .with_options(opts)\n          .cache(VAL_CACHE)\n          .batch(32)\n          .prefetch(AUTOTUNE))\ntrain_ds = (train_base\n            .shuffle(2048, seed=SEED, reshuffle_each_iteration=True)\n            .map(lambda x, y: (data_augmentation(x, training=True), y),\n                 num_parallel_calls=AUTOTUNE)\n            .batch(32)\n            .prefetch(AUTOTUNE))\n\nprint(\"Disk cache etkin. Cache dosyaları:\\n \", TRAIN_CACHE, \"\\n \", VAL_CACHE)\nprint(\"train batches:\", tf.data.experimental.cardinality(train_ds).numpy())\nprint(\"val batches  :\", tf.data.experimental.cardinality(val_ds).numpy())\n\npreview_ds = (train_base.take(9)\n              .map(lambda x, y: (data_augmentation(x, training=False), y))\n              .batch(9))\n\nsample_imgs, sample_labels = next(iter(preview_ds))\nimgs_np = np.clip(sample_imgs.numpy(), 0.0, 1.0)\n\nplt.figure(figsize=(8,8))\nfor i in range(imgs_np.shape[0]):\n    ax = plt.subplot(3,3,i+1)\n    plt.imshow(imgs_np[i])\n    plt.title(class_names[int(sample_labels[i])])\n    plt.axis('off')\nplt.tight_layout(); plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-27T02:24:07.325374Z","iopub.execute_input":"2025-09-27T02:24:07.325712Z","iopub.status.idle":"2025-09-27T02:24:09.162401Z","shell.execute_reply.started":"2025-09-27T02:24:07.325689Z","shell.execute_reply":"2025-09-27T02:24:09.161340Z"}},"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":"def build_simple_cnn(input_shape=INPUT_SHAPE,\n                     \n                     base_filters=32,\n                     kernel_size=3,\n                     dropout_rate=0.2,\n                     dense_units=128):\n    \"\"\"\n    Improved baseline CNN:\n    - He-normal initialization (better for ReLU)\n    - L2 kernel regularization (reduces overfitting)\n    - Batch Normalization after each conv (stabilizes/accelerates training)\n    - GlobalAveragePooling2D instead of Flatten (fewer params, CAM-friendly)\n    - Keep `last_conv` layer name for Grad‑CAM\n    \"\"\"\n    kr = keras.regularizers.l2(5e-5) # noise\n    ki = keras.initializers.HeNormal() #? standra\n\n    inputs = layers.Input(shape=input_shape)\n\n    # Block 1\n    x = layers.Conv2D(base_filters, kernel_size, padding='same', use_bias=False,\n                      kernel_initializer=ki, kernel_regularizer=kr, name='conv1')(inputs)\n    x = layers.BatchNormalization(name='bn1')(x)\n    x = layers.Activation('relu', name='relu1')(x)\n    x = layers.MaxPooling2D(name='pool1')(x)\n\n    # Block 2\n    x = layers.Conv2D(base_filters*2, kernel_size, padding='same', use_bias=False,\n                      kernel_initializer=ki, kernel_regularizer=kr, name='conv2')(x)\n    x = layers.BatchNormalization(name='bn2')(x)\n    x = layers.Activation('relu', name='relu2')(x)\n    x = layers.MaxPooling2D(name='pool2')(x)\n\n    # Block 3 (keep name for Grad‑CAM)\n    x = layers.Conv2D(base_filters*4, kernel_size, padding='same', use_bias=False,\n                      kernel_initializer=ki, kernel_regularizer=kr, name='last_conv')(x)\n    x = layers.BatchNormalization(name='bn3')(x)\n    x = layers.Activation('relu', name='relu3')(x)\n    x = layers.MaxPooling2D(name='pool3')(x)\n\n    # Head: GAP -> Dense -> Dropout\n    x = layers.GlobalAveragePooling2D(name='gap')(x)\n    x = layers.Dropout(dropout_rate, name='dropout_head')(x)\n    x = layers.Dense(dense_units, activation='relu', kernel_initializer=ki,\n                     kernel_regularizer=kr, name='dense')(x)\n    x = layers.Dropout(dropout_rate, name='dropout_out')(x)\n\n    if BINARY:\n        outputs = layers.Dense(1, activation='sigmoid', name='out')(x)\n    else:\n        outputs = layers.Dense(len(class_names), activation='softmax', name='out')(x)\n\n    model = keras.Model(inputs, outputs, name='cnn_baseline_v2')\n    return model\n\nmodel = build_simple_cnn()\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-27T02:24:09.163562Z","iopub.execute_input":"2025-09-27T02:24:09.163911Z","iopub.status.idle":"2025-09-27T02:24:09.293439Z","shell.execute_reply.started":"2025-09-27T02:24:09.163880Z","shell.execute_reply":"2025-09-27T02:24:09.292715Z"}},"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":"\nLR = 5e-4\nEPOCHS = 25\n\nloss = keras.losses.BinaryCrossentropy(label_smoothing=0.05)\n\nmodel.compile(\n    optimizer=keras.optimizers.Adam(learning_rate=LR),\n    loss=loss,\n    metrics=['accuracy', keras.metrics.AUC(name='auc')]\n)\n\ncallbacks = [\n    keras.callbacks.EarlyStopping(patience=3, restore_best_weights=True, monitor='val_loss'),\n    keras.callbacks.ReduceLROnPlateau(patience=2, factor=0.5, verbose=1),\n    keras.callbacks.ModelCheckpoint(filepath=\"models/cnn_baseline.keras\",\n                                    monitor=\"val_accuracy\", save_best_only=True),\n]\n\nfrom math import ceil\ntrain_steps = ceil(len(train_files) / BATCH_SIZE)\nval_steps   = ceil(len(val_files)   / BATCH_SIZE)\n\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS,\n    steps_per_epoch=train_steps,\n    validation_steps=val_steps,\n    callbacks=callbacks,\n    verbose=1\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-27T02:24:09.294338Z","iopub.execute_input":"2025-09-27T02:24:09.294625Z","execution_failed":"2025-09-27T02:24:12.426Z"}},"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":"\ndef plot_training_curves(history):\n    hist = history.history\n    epochs = range(1, len(hist['loss'])+1)\n\n    plt.figure(figsize=(12,4))\n    # Loss\n    plt.subplot(1,2,1)\n    plt.plot(epochs, hist['loss'], label='train')\n    plt.plot(epochs, hist['val_loss'], label='val')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.title('Loss per epoch')\n    plt.legend()\n\n    # Accuracy\n    plt.subplot(1,2,2)\n    plt.plot(epochs, hist['accuracy'], label='train')\n    plt.plot(epochs, hist['val_accuracy'], label='val')\n    plt.xlabel('Epoch')\n    plt.ylabel('Accuracy')\n    plt.title('Accuracy per epoch')\n    plt.legend()\n\n    plt.tight_layout()\n    plt.show()\n\nplot_training_curves(history)\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-27T02:24:12.426Z"}},"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).\n","metadata":{}},{"cell_type":"code","source":"y_true = []\ny_pred = []\n\nfor imgs, labels in val_ds:\n    probs = model.predict(imgs, verbose=0).ravel()\n    preds = (probs >= 0.5).astype(int)\n    y_true.extend(labels.numpy().astype(int).tolist())\n    y_pred.extend(preds.tolist())\n\ny_true = np.array(y_true)\ny_pred = np.array(y_pred)\n\nprint(classification_report(y_true, y_pred, target_names=class_names, digits=4))\n\ncm = confusion_matrix(y_true, y_pred)\nfig, ax = plt.subplots(figsize=(5,4))\nim = ax.imshow(cm, interpolation='nearest')\nax.figure.colorbar(im, ax=ax)\nax.set(xticks=np.arange(cm.shape[1]), yticks=np.arange(cm.shape[0]),\n       xticklabels=class_names, yticklabels=class_names,\n       xlabel='Predicted label', ylabel='True label', title='Confusion Matrix')\n\nthresh = cm.max() / 2.\nfor i in range(cm.shape[0]):\n    for j in range(cm.shape[1]):\n        ax.text(j, i, format(cm[i, j], 'd'),\n                ha=\"center\", va=\"center\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-27T02:24:12.426Z"}},"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\n","metadata":{}},{"cell_type":"code","source":"def get_img_array(path, size=(IMG_SIZE, IMG_SIZE)):\n    img = tf.io.read_file(path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, size)\n    img = tf.cast(img, tf.float32)/255.0\n    return img.numpy()\n\ndef make_gradcam_heatmap(img_array, model, last_conv_layer_name='last_conv'):\n    grad_model = tf.keras.models.Model(\n        [model.inputs],\n        [model.get_layer(last_conv_layer_name).output, model.output]\n    )\n\n    with tf.GradientTape() as tape:\n        conv_outputs, predictions = grad_model(tf.expand_dims(img_array, axis=0), training=False)\n        if BINARY:\n            loss = predictions[:, 0]\n        else:\n            class_idx = tf.argmax(predictions[0])\n            loss = predictions[:, class_idx]\n\n    grads = tape.gradient(loss, conv_outputs)[0]              # (H, W, C)\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1))         # (C,)\n\n    conv_outputs = conv_outputs[0]\n    heatmap = tf.reduce_sum(tf.multiply(pooled_grads, conv_outputs), axis=-1)\n\n    heatmap = tf.maximum(heatmap, 0) / (tf.reduce_max(heatmap) + 1e-8)\n    return heatmap.numpy()\n\ndef show_gradcam(image_path, model, last_conv_layer_name='last_conv'):\n    img = get_img_array(image_path, size=(IMG_SIZE, IMG_SIZE))\n    heatmap = make_gradcam_heatmap(img, model, last_conv_layer_name)\n\n    fig = plt.figure(figsize=(8,4))\n    ax1 = fig.add_subplot(1,2,1)\n    ax1.imshow(img)\n    ax1.set_title(\"Girdi Görüntü\")\n    ax1.axis('off')\n\n    ax2 = fig.add_subplot(1,2,2)\n    ax2.imshow(img)\n    ax2.imshow(heatmap, cmap='jet', alpha=0.4)\n    ax2.set_title(\"Grad‑CAM Isı Haritası\")\n    ax2.axis('off')\n\n    plt.tight_layout()\n    plt.show()\n\nexample_path = val_files[89]\nprint(\"Örnek:\", Path(example_path).name)\nshow_gradcam(example_path, model, last_conv_layer_name='last_conv')","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-27T02:24:12.426Z"}},"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":{"execution_failed":"2025-09-27T02:24:12.426Z"}},"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":{"execution_failed":"2025-09-27T02:24:12.426Z"}},"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":"from sklearn.metrics import (\n    accuracy_score, precision_recall_fscore_support,\n    roc_auc_score\n)\n\ndef collect_preds(model, dataset, num_classes=None, max_batches=None):\n    \"\"\"\n    dataset .repeat() ile sonsuzsa max_batches=val_steps verin.\n    \"\"\"\n    y_true_all, y_prob_all = [], []\n    for i, (xb, yb) in enumerate(dataset):\n        probs = model.predict(xb, verbose=0)\n        y_true_all.append(yb.numpy() if hasattr(yb, \"numpy\") else yb)\n        y_prob_all.append(probs)\n        if max_batches is not None and (i + 1) >= max_batches:\n            break\n\n    y_true = np.concatenate([np.asarray(t).reshape(-1) for t in y_true_all])\n    y_prob = np.concatenate(y_prob_all)\n\n    # Sınıf sayısını/çıkış biçimini çıkar\n    if num_classes is None:\n        n_out = 1 if y_prob.ndim == 1 else y_prob.shape[-1]\n        num_classes = 2 if n_out == 1 else n_out\n\n    # Sınıf tahmini\n    if num_classes == 2:\n        p1 = y_prob.reshape(-1) if y_prob.ndim > 1 else y_prob\n        y_pred = (p1 >= 0.5).astype(int)\n    else:\n        y_pred = np.argmax(y_prob, axis=1)\n\n    return y_true, y_pred, y_prob, num_classes\n\n# Eğer val_ds .repeat() kullanıyorsa ve daha önce val_steps hesapladıysanız, aşağıdaki çağrıda max_batches=val_steps kullanın.\ntry:\n    y_true, y_pred, y_prob, NUM_CLASSES = collect_preds(model, val_ds, max_batches=val_steps)\nexcept NameError:\n    # val_steps yoksa normal topla (val_ds sonsuz olmamalı)\n    y_true, y_pred, y_prob, NUM_CLASSES = collect_preds(model, val_ds)\n\n# Metrikler\nacc = accuracy_score(y_true, y_pred)\nprec, rec, f1, _ = precision_recall_fscore_support(\n    y_true, y_pred, average=\"macro\", zero_division=0\n)\n\nauc_val = None\nif NUM_CLASSES == 2:\n    try:\n        p1 = y_prob.reshape(-1) if y_prob.ndim > 1 else y_prob\n        auc_val = roc_auc_score(y_true, p1)\n    except Exception:\n        auc_val = None\n\nprint(\"\\n=========== BAŞARI ÇIKTISI ===========\")\nprint(f\"F1-macro: {f1:.4f}\")\nprint(f\"Accuracy: {acc:.4f}\")\nprint(f\"Precision (macro): {prec:.4f}\")\nprint(f\"Recall (macro): {rec:.4f}\")\nif auc_val is not None:\n    print(f\"AUC: {auc_val:.4f}\")\nprint(\"======================================\\n\")\n\nprint(\"— Ayrıntılı Rapor —\")\nprint(classification_report(y_true, y_pred, digits=4))\n\nprint(\"— Confusion Matrix —\")\nprint(confusion_matrix(y_true, y_pred))","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-27T02:24:12.426Z"}},"outputs":[],"execution_count":null}]}