{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# **CATS VS DOGS CLASSIFICATION:**\nIn this project, we use a Convolutional Neural Network (CNN) to automatically identify whether an image contains a cat or a dog. The notebook covers data preparation, model training, performance evaluation, and testing on new images.","metadata":{}},{"cell_type":"markdown","source":"# Step 1: Import Libraries","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport os\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom sklearn.metrics import confusion_matrix, classification_report\n\n# GPU check\nprint(\"TensorFlow Version:\", tf.__version__)\ngpus = tf.config.list_physical_devices('GPU')\nif gpus:\n    print(f\"✅ GPU Available: {gpus}\")\nelse:\n    print(\"⚠️ No GPU — Kaggle Settings > Accelerator > GPU T4 x2\")\n\nprint(\"\\n✅ All libraries imported!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:11.991054Z","iopub.execute_input":"2026-06-19T00:55:11.991671Z","iopub.status.idle":"2026-06-19T00:55:11.999512Z","shell.execute_reply.started":"2026-06-19T00:55:11.991601Z","shell.execute_reply":"2026-06-19T00:55:11.998305Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 3: Load Dataset","metadata":{}},{"cell_type":"code","source":"base_dir = '/kaggle/input/competitions/dogs-vs-cats-redux-kernels-edition'\nprint(os.listdir(base_dir))  ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:12.001301Z","iopub.execute_input":"2026-06-19T00:55:12.001978Z","iopub.status.idle":"2026-06-19T00:55:12.019308Z","shell.execute_reply.started":"2026-06-19T00:55:12.00194Z","shell.execute_reply":"2026-06-19T00:55:12.018362Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 4: extract zip files","metadata":{}},{"cell_type":"code","source":"import os\nimport zipfile\n\nbase_dir = '/kaggle/input/competitions/dogs-vs-cats-redux-kernels-edition'\n\nprint(\"Files in dataset:\")\nprint(os.listdir(base_dir))\n\nextract_dir = '/kaggle/working/cats_dogs_data'\nos.makedirs(extract_dir, exist_ok=True)\n\nprint(\"\\n📦 Extracting train.zip...\")\nwith zipfile.ZipFile(f'{base_dir}/train.zip', 'r') as z:\n    z.extractall(extract_dir)\n    print(f\"✅ Train extracted!\")\n\nprint(\"\\n📦 Extracting test.zip...\")\nwith zipfile.ZipFile(f'{base_dir}/test.zip', 'r') as z:\n    z.extractall(extract_dir)\n    print(f\"✅ Test extracted!\")\n\nprint(\"\\nExtracted structure:\")\nfor item in os.listdir(extract_dir):\n    path = os.path.join(extract_dir, item)\n    if os.path.isdir(path):\n        print(f\"  📁 {item}/ → {len(os.listdir(path))} files\")\n    else:\n        print(f\"  📄 {item}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:12.020552Z","iopub.execute_input":"2026-06-19T00:55:12.020997Z","iopub.status.idle":"2026-06-19T00:55:23.911756Z","shell.execute_reply.started":"2026-06-19T00:55:12.020959Z","shell.execute_reply":"2026-06-19T00:55:23.910857Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" ","metadata":{}},{"cell_type":"code","source":"import os\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\n\ntrain_dir = '/kaggle/working/cats_dogs_data/train'\n\nall_files = os.listdir(train_dir)\n\ncat_files = [f for f in all_files if f.startswith('cat.')]\ndog_files = [f for f in all_files if f.startswith('dog.')]\n\nprint(\"=\" * 45)\nprint(\"📊 DATASET OVERVIEW\")\nprint(\"=\" * 45)\nprint(f\"Total files  : {len(all_files)}\")\nprint(f\"Cat images   : {len(cat_files)}\")\nprint(f\"Dog images   : {len(dog_files)}\")\nprint(f\"\\nSample cat files : {cat_files[:4]}\")\nprint(f\"Sample dog files : {dog_files[:4]}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:23.913544Z","iopub.execute_input":"2026-06-19T00:55:23.913888Z","iopub.status.idle":"2026-06-19T00:55:23.943999Z","shell.execute_reply.started":"2026-06-19T00:55:23.913863Z","shell.execute_reply":"2026-06-19T00:55:23.943118Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 4.5: Sample Images Visualize","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(2, 8, figsize=(20, 6))\nfig.suptitle('🐱 Cats vs 🐶 Dogs — Sample Images',\n             fontsize=16, fontweight='bold')\n\nfor j in range(8):\n  \n    cat_path = os.path.join(train_dir, cat_files[j])\n    img = mpimg.imread(cat_path)\n    axes[0, j].imshow(img)\n    axes[0, j].axis('off')\n    if j == 0:\n        axes[0, j].set_title('🐱 CAT', fontsize=11,\n                              fontweight='bold', color='orange')\n\n    dog_path = os.path.join(train_dir, dog_files[j])\n    img = mpimg.imread(dog_path)\n    axes[1, j].imshow(img)\n    axes[1, j].axis('off')\n    if j == 0:\n        axes[1, j].set_title('🐶 DOG', fontsize=11,\n                              fontweight='bold', color='royalblue')\n\nplt.tight_layout()\nplt.show()\n\nsample = mpimg.imread(os.path.join(train_dir, cat_files[0]))\nprint(f\"Sample image shape  : {sample.shape}\")\nprint(f\"Pixel range         : {sample.min()} to {sample.max()}\")\nprint(f\"Color channels      : RGB (3 channels)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:23.94525Z","iopub.execute_input":"2026-06-19T00:55:23.945632Z","iopub.status.idle":"2026-06-19T00:55:25.030519Z","shell.execute_reply.started":"2026-06-19T00:55:23.945586Z","shell.execute_reply":"2026-06-19T00:55:25.029477Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 5: Dataset Exploration (EDA)","metadata":{}},{"cell_type":"code","source":"import seaborn as sns\n\nfig, axes = plt.subplots(1, 2, figsize=(12, 5))\nfig.suptitle('Class Distribution', fontsize=14, fontweight='bold')\n\nlabels  = ['Cat 🐱', 'Dog 🐶']\ncounts  = [len(cat_files), len(dog_files)]\ncolors  = ['#FF6B6B', '#4ECDC4']\n\n# Bar chart\nbars = axes[0].bar(labels, counts, color=colors,\n                   edgecolor='black', width=0.4)\naxes[0].set_title('Image Count per Class', fontweight='bold')\naxes[0].set_ylabel('Number of Images')\naxes[0].set_ylim([0, max(counts) + 1000])\nfor bar, val in zip(bars, counts):\n    axes[0].text(bar.get_x() + bar.get_width()/2,\n                 bar.get_height() + 100,\n                 str(val), ha='center', fontweight='bold', fontsize=12)\n\n# Pie chart\naxes[1].pie(counts, labels=labels, autopct='%1.1f%%',\n            colors=colors, startangle=90)\naxes[1].set_title('Class Proportion', fontweight='bold')\n\nplt.tight_layout()\nplt.show()\n\nprint(f\"Cat images : {len(cat_files)}\")\nprint(f\"Dog images : {len(dog_files)}\")\nprint(f\"Balanced   : {'✅ Yes' if abs(len(cat_files)-len(dog_files)) < 100 else '⚠️ No'}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:25.031715Z","iopub.execute_input":"2026-06-19T00:55:25.031969Z","iopub.status.idle":"2026-06-19T00:55:25.244832Z","shell.execute_reply.started":"2026-06-19T00:55:25.031946Z","shell.execute_reply":"2026-06-19T00:55:25.243819Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 6: Data preprocessing","metadata":{}},{"cell_type":"code","source":"import shutil\nimport random\n\norganized_dir = '/kaggle/working/cats_dogs_split'\n\n# to make Folders \nfor split in ['train', 'val', 'test']:\n    for cls in ['cat', 'dog']:\n        os.makedirs(f'{organized_dir}/{split}/{cls}', exist_ok=True)\n\ndef split_and_copy(file_list, class_name, src_dir,\n                   train_r=0.70, val_r=0.15):\n    random.seed(42)\n    random.shuffle(file_list)\n\n    n         = len(file_list)\n    train_end = int(n * train_r)\n    val_end   = int(n * (train_r + val_r))\n\n    splits = {\n        'train' : file_list[:train_end],\n        'val'   : file_list[train_end:val_end],\n        'test'  : file_list[val_end:]\n    }\n\n    for split_name, files in splits.items():\n        dst = f'{organized_dir}/{split_name}/{class_name}'\n        for f in files:\n            shutil.copy(os.path.join(src_dir, f),\n                        os.path.join(dst, f))\n        print(f\"  {split_name:6s} / {class_name:4s} : {len(files)} images\")\n\nprint(\"📁 Splitting Cat images...\")\nsplit_and_copy(cat_files, 'cat', train_dir)\n\nprint(\"\\n📁 Splitting Dog images...\")\nsplit_and_copy(dog_files, 'dog', train_dir)\n\nprint(\"\\n✅ Done! Final structure:\")\nfor split in ['train', 'val', 'test']:\n    c = len(os.listdir(f'{organized_dir}/{split}/cat'))\n    d = len(os.listdir(f'{organized_dir}/{split}/dog'))\n    print(f\"  {split:6s} → Cat: {c:5d} | Dog: {d:5d} | Total: {c+d}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:25.246974Z","iopub.execute_input":"2026-06-19T00:55:25.247893Z","iopub.status.idle":"2026-06-19T00:55:29.632824Z","shell.execute_reply.started":"2026-06-19T00:55:25.247852Z","shell.execute_reply":"2026-06-19T00:55:29.631731Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 7: Data augmentation","metadata":{}},{"cell_type":"code","source":"IMG_SIZE   = (128, 128)\nBATCH_SIZE = 32\n\ntrain_datagen = ImageDataGenerator(\n    rescale=1./255,            \n    rotation_range=20,         \n    width_shift_range=0.2,     \n    height_shift_range=0.2,    \n    shear_range=0.2,           \n    zoom_range=0.2,            \n    horizontal_flip=True,      \n    fill_mode='nearest'     \n)\n\nval_test_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_gen = train_datagen.flow_from_directory(\n    f'{organized_dir}/train',\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='binary',\n    shuffle=True\n)\n\nval_gen = val_test_datagen.flow_from_directory(\n    f'{organized_dir}/val',\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='binary',\n    shuffle=False\n)\n\ntest_gen = val_test_datagen.flow_from_directory(\n    f'{organized_dir}/test',\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='binary',\n    shuffle=False\n)\n\nprint(f\"\\n✅ Generators ready!\")\nprint(f\"Class mapping : {train_gen.class_indices}\")\nprint(f\"Train batches : {len(train_gen)}\")\nprint(f\"Val batches   : {len(val_gen)}\")\nprint(f\"Test batches  : {len(test_gen)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:29.634132Z","iopub.execute_input":"2026-06-19T00:55:29.634511Z","iopub.status.idle":"2026-06-19T00:55:29.905877Z","shell.execute_reply.started":"2026-06-19T00:55:29.634466Z","shell.execute_reply":"2026-06-19T00:55:29.90491Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.preprocessing import image as keras_image\nsample_path = f'{organized_dir}/train/cat/{os.listdir(organized_dir+\"/train/cat\")[0]}'\nimg = keras_image.load_img(sample_path, target_size=(128, 128))\nimg_array = keras_image.img_to_array(img)\nimg_array = img_array.reshape((1,) + img_array.shape)\n\naug_gen = ImageDataGenerator(\n    rotation_range=20,\n    width_shift_range=0.2,\n    height_shift_range=0.2,\n    zoom_range=0.2,\n    horizontal_flip=True,\n    fill_mode='nearest'\n)\n\nfig, axes = plt.subplots(2, 5, figsize=(18, 7))\nfig.suptitle('Data Augmentation — Same Cat, Different Variations! 🐱',\n             fontsize=14, fontweight='bold')\n\n# Original\naxes[0, 0].imshow(img)\naxes[0, 0].set_title('Original', fontweight='bold', color='green')\naxes[0, 0].axis('off')\n\naug_iter = aug_gen.flow(img_array, batch_size=1)\nfor i in range(1, 10):\n    row, col = divmod(i, 5)\n    aug_img = next(aug_iter)[0].astype('uint8')\n    axes[row, col].imshow(aug_img)\n    axes[row, col].set_title(f'Augmented {i}', fontsize=9)\n    axes[row, col].axis('off')\n\nplt.tight_layout()\nplt.show()\n\nprint(\"👆 Same cat image — 9 different augmented versions!\")\nprint(\"Model in sab variations ko 'different' images samjhega\")\nprint(\"= Better generalization!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:29.907024Z","iopub.execute_input":"2026-06-19T00:55:29.908207Z","iopub.status.idle":"2026-06-19T00:55:31.021029Z","shell.execute_reply.started":"2026-06-19T00:55:29.908171Z","shell.execute_reply":"2026-06-19T00:55:31.019614Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 8: Build CNN Model Architecture","metadata":{}},{"cell_type":"code","source":"def build_cats_dogs_cnn():\n    model = models.Sequential(name=\"CatsDogs_CNN\")\n\n    model.add(layers.Conv2D(32, (3,3), activation='relu',\n                            padding='same', input_shape=(128, 128, 3)))\n    model.add(layers.BatchNormalization())\n    model.add(layers.Conv2D(32, (3,3), activation='relu', padding='same'))\n    model.add(layers.BatchNormalization())\n    model.add(layers.MaxPooling2D(2, 2))\n    model.add(layers.Dropout(0.25))\n\n    model.add(layers.Conv2D(64, (3,3), activation='relu', padding='same'))\n    model.add(layers.BatchNormalization())\n    model.add(layers.Conv2D(64, (3,3), activation='relu', padding='same'))\n    model.add(layers.BatchNormalization())\n    model.add(layers.MaxPooling2D(2, 2))\n    model.add(layers.Dropout(0.25))\n\n    model.add(layers.Conv2D(128, (3,3), activation='relu', padding='same'))\n    model.add(layers.BatchNormalization())\n    model.add(layers.Conv2D(128, (3,3), activation='relu', padding='same'))\n    model.add(layers.BatchNormalization())\n    model.add(layers.MaxPooling2D(2, 2))\n    model.add(layers.Dropout(0.4))\n\n    model.add(layers.Conv2D(256, (3,3), activation='relu', padding='same'))\n    model.add(layers.BatchNormalization())\n    model.add(layers.MaxPooling2D(2, 2))\n    model.add(layers.Dropout(0.4))\n\n    model.add(layers.Flatten())\n    model.add(layers.Dense(512, activation='relu'))\n    model.add(layers.BatchNormalization())\n    model.add(layers.Dropout(0.5))\n    model.add(layers.Dense(128, activation='relu'))\n    model.add(layers.Dropout(0.3))\n\n    model.add(layers.Dense(1, activation='sigmoid'))\n\n    return model\n\nmodel = build_cats_dogs_cnn()\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:31.023735Z","iopub.execute_input":"2026-06-19T00:55:31.024037Z","iopub.status.idle":"2026-06-19T00:55:31.4218Z","shell.execute_reply.started":"2026-06-19T00:55:31.024013Z","shell.execute_reply":"2026-06-19T00:55:31.421124Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.compile(\n    optimizer=keras.optimizers.Adam(learning_rate=0.001),\n    loss='binary_crossentropy',      \n    metrics=['accuracy']\n)\n\nprint(\"✅ Model compiled!\")\nprint(f\"\\nOptimizer : Adam (lr=0.001)\")\nprint(f\"Loss      : binary_crossentropy  ← binary classification!\")\nprint(f\"Output    : sigmoid → single probability\")\nprint(f\"\\nTotal parameters : {model.count_params():,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:31.422814Z","iopub.execute_input":"2026-06-19T00:55:31.42316Z","iopub.status.idle":"2026-06-19T00:55:31.434152Z","shell.execute_reply.started":"2026-06-19T00:55:31.423135Z","shell.execute_reply":"2026-06-19T00:55:31.433208Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 9: Model Training with Callbacks","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nimport pickle\n\nearly_stop = EarlyStopping(\n    monitor='val_loss',\n    patience=8,\n    restore_best_weights=True,\n    verbose=1\n)\n\nreduce_lr = ReduceLROnPlateau(\n    monitor='val_loss',\n    factor=0.5,\n    patience=4,\n    min_lr=1e-7,\n    verbose=1\n)\n\ncheckpoint = ModelCheckpoint(\n    '/kaggle/working/best_catdog_model.keras',\n    monitor='val_accuracy',\n    save_best_only=True,\n    verbose=1\n)\n\nprint(\"🚀 Training started...\")\n\nhistory = model.fit(\n    train_gen,\n    epochs=30,\n    validation_data=val_gen,\n    callbacks=[early_stop, reduce_lr, checkpoint],\n    verbose=1\n)\n\nprint(f\"\\n✅ Training complete!\")\n\n# ── IMPORTANT: History saved!\nmodel.save('/kaggle/working/catdog_scratch_model.keras')\n\nwith open('/kaggle/working/history.pkl', 'wb') as f:\n    pickle.dump(history.history, f)\n\nprint(\"💾 Model AND History both saved successfully!\")\nprint(f\"Best val accuracy : {max(history.history['val_accuracy']):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T00:55:31.435336Z","iopub.execute_input":"2026-06-19T00:55:31.436008Z","iopub.status.idle":"2026-06-19T01:48:08.11332Z","shell.execute_reply.started":"2026-06-19T00:55:31.435971Z","shell.execute_reply":"2026-06-19T01:48:08.112406Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 10: Training History Plot","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(1, 2, figsize=(16, 6))\nfig.suptitle('Training History — Cats vs Dogs CNN',\n             fontsize=15, fontweight='bold')\n\nep = range(1, len(history.history['accuracy']) + 1)\n\n# Accuracy\naxes[0].plot(ep, history.history['accuracy'],\n             label='Train Accuracy', color='royalblue', linewidth=2.5)\naxes[0].plot(ep, history.history['val_accuracy'],\n             label='Val Accuracy', color='coral',\n             linewidth=2.5, linestyle='--')\naxes[0].set_title('Model Accuracy', fontsize=13, fontweight='bold')\naxes[0].set_xlabel('Epoch')\naxes[0].set_ylabel('Accuracy')\naxes[0].legend()\naxes[0].grid(True, alpha=0.3)\naxes[0].axhline(y=max(history.history['val_accuracy']),\n                color='green', linestyle=':', alpha=0.7,\n                label=f\"Best val: {max(history.history['val_accuracy']):.3f}\")\naxes[0].legend()\n\n# Loss\naxes[1].plot(ep, history.history['loss'],\n             label='Train Loss', color='royalblue', linewidth=2.5)\naxes[1].plot(ep, history.history['val_loss'],\n             label='Val Loss', color='coral',\n             linewidth=2.5, linestyle='--')\naxes[1].set_title('Model Loss', fontsize=13, fontweight='bold')\naxes[1].set_xlabel('Epoch')\naxes[1].set_ylabel('Loss')\naxes[1].legend()\naxes[1].grid(True, alpha=0.3)\n\nplt.tight_layout()\nplt.show()\n\nprint(f\"Final Train Accuracy : {history.history['accuracy'][-1]:.4f}\")\nprint(f\"Final Val Accuracy   : {history.history['val_accuracy'][-1]:.4f}\")\nprint(f\"Best Val Accuracy    : {max(history.history['val_accuracy']):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T01:48:08.114553Z","iopub.execute_input":"2026-06-19T01:48:08.114913Z","iopub.status.idle":"2026-06-19T01:48:08.551906Z","shell.execute_reply.started":"2026-06-19T01:48:08.114888Z","shell.execute_reply":"2026-06-19T01:48:08.550831Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 11: Model Evaluation on Test Set","metadata":{}},{"cell_type":"code","source":"test_loss, test_accuracy = model.evaluate(test_gen, verbose=0)\n\nprint(\"=\" * 45)\nprint(\"🎯 FINAL TEST SET RESULTS\")\nprint(\"=\" * 45)\nprint(f\"Test Loss     : {test_loss:.4f}\")\nprint(f\"Test Accuracy : {test_accuracy*100:.2f}%\")\n\nprint(\"\\n⏳ Generating predictions...\")\ntest_gen.reset()\ny_pred_probs = model.predict(test_gen, verbose=0)\ny_pred = (y_pred_probs > 0.5).astype(int).flatten()\ny_true = test_gen.classes\n\nprint(f\"\\nTotal test images : {len(y_true)}\")\nprint(f\"Correct           : {(y_pred == y_true).sum()}\")\nprint(f\"Wrong             : {(y_pred != y_true).sum()}\")\n\nclass_labels = ['Cat 🐱', 'Dog 🐶']\nprint(f\"\\nClass mapping: {test_gen.class_indices}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T01:48:08.553312Z","iopub.execute_input":"2026-06-19T01:48:08.553732Z","iopub.status.idle":"2026-06-19T01:48:21.773134Z","shell.execute_reply.started":"2026-06-19T01:48:08.553692Z","shell.execute_reply":"2026-06-19T01:48:21.772039Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"confusion matrix","metadata":{}},{"cell_type":"code","source":"cm = confusion_matrix(y_true, y_pred)\n\nfig, axes = plt.subplots(1, 2, figsize=(16, 6))\nfig.suptitle('Model Evaluation — Cats vs Dogs',\n             fontsize=14, fontweight='bold')\n\n# Confusion Matrix\nsns.heatmap(\n    cm,\n    annot=True,\n    fmt='d',\n    cmap='RdYlGn',\n    xticklabels=['Cat 🐱', 'Dog 🐶'],\n    yticklabels=['Cat 🐱', 'Dog 🐶'],\n    ax=axes[0],\n    linewidths=2,\n    annot_kws={'size': 16, 'weight': 'bold'}\n)\naxes[0].set_title('Confusion Matrix', fontsize=13, fontweight='bold')\naxes[0].set_xlabel('Predicted', fontsize=12)\naxes[0].set_ylabel('Actual', fontsize=12)\n\ntn, fp, fn, tp = cm.ravel()\ncat_accuracy = tn / (tn + fp) * 100\ndog_accuracy = tp / (tp + fn) * 100\n\nbars = axes[1].bar(\n    ['Cat 🐱', 'Dog 🐶'],\n    [cat_accuracy, dog_accuracy],\n    color=['#FF6B6B', '#4ECDC4'],\n    edgecolor='black',\n    width=0.4\n)\naxes[1].set_title('Per-Class Accuracy', fontsize=13, fontweight='bold')\naxes[1].set_ylabel('Accuracy (%)')\naxes[1].set_ylim([0, 110])\nfor bar, val in zip(bars, [cat_accuracy, dog_accuracy]):\n    axes[1].text(\n        bar.get_x() + bar.get_width()/2,\n        bar.get_height() + 1,\n        f'{val:.1f}%',\n        ha='center', fontweight='bold', fontsize=13\n    )\n\nplt.tight_layout()\nplt.show()\n\nprint(\"\\n📊 CLASSIFICATION REPORT\")\nprint(\"=\" * 50)\nprint(classification_report(y_true, y_pred,\n                            target_names=['Cat', 'Dog']))\n\nprint(f\"\\n📊 Confusion Matrix Breakdown:\")\nprint(f\"  True Cats  (correct) : {tn}\")\nprint(f\"  True Dogs  (correct) : {tp}\")\nprint(f\"  Cats → Dogs (wrong)  : {fp}\")\nprint(f\"  Dogs → Cats (wrong)  : {fn}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T01:48:21.775042Z","iopub.execute_input":"2026-06-19T01:48:21.775616Z","iopub.status.idle":"2026-06-19T01:48:22.159563Z","shell.execute_reply.started":"2026-06-19T01:48:21.775591Z","shell.execute_reply":"2026-06-19T01:48:22.158588Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_gen.reset()\nbatch_images, batch_labels = next(test_gen)\nbatch_preds = model.predict(batch_images, verbose=0).flatten()\n\nfig, axes = plt.subplots(4, 8, figsize=(22, 12))\nfig.suptitle('🐱🐶 Sample Predictions — Green=Correct, Red=Wrong',\n             fontsize=14, fontweight='bold')\n\nfor i in range(32):\n    row, col = divmod(i, 8)\n    ax = axes[row, col]\n\n    ax.imshow(batch_images[i])\n    ax.axis('off')\n\n    pred_prob   = batch_preds[i]\n    pred_class  = 1 if pred_prob > 0.5 else 0\n    true_class  = int(batch_labels[i])\n    confidence  = pred_prob if pred_class == 1 else 1 - pred_prob\n    is_correct  = pred_class == true_class\n\n    label = 'Dog🐶' if pred_class == 1 else 'Cat🐱'\n    color = 'green' if is_correct else 'red'\n\n    ax.set_title(\n        f\"{label}\\n{confidence*100:.0f}%\",\n        fontsize=8,\n        color=color,\n        fontweight='bold'\n    )\n\nplt.tight_layout()\nplt.show()\n\ncorrect = sum(1 for i in range(len(batch_preds))\n              if (int(batch_preds[i] > 0.5)) == int(batch_labels[i]))\nprint(f\"In this batch: {correct}/32 correct ({correct/32*100:.0f}%)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T01:48:22.160709Z","iopub.execute_input":"2026-06-19T01:48:22.161036Z","iopub.status.idle":"2026-06-19T01:48:26.198273Z","shell.execute_reply.started":"2026-06-19T01:48:22.161011Z","shell.execute_reply":"2026-06-19T01:48:26.196983Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 12: Transfer Learning with MobileNetV2","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import MobileNetV2\n\nbase_model = MobileNetV2(\n    input_shape=(128, 128, 3),\n    include_top=False,\n    weights='imagenet'\n)\nbase_model.trainable = False\n\ninputs  = layers.Input(shape=(128, 128, 3))\nx       = base_model(inputs, training=False)\nx       = layers.GlobalAveragePooling2D()(x)\nx       = layers.Dense(256, activation='relu')(x)\nx       = layers.BatchNormalization()(x)\nx       = layers.Dropout(0.5)(x)\nx       = layers.Dense(64, activation='relu')(x)\nx       = layers.Dropout(0.3)(x)\n\noutputs = layers.Dense(1, activation='sigmoid')(x)\n\nmodel_transfer = models.Model(inputs, outputs,\n                              name=\"MobileNetV2_CatsDogs\")\n\nmodel_transfer.compile(\n    optimizer=keras.optimizers.Adam(learning_rate=0.001),\n    loss='binary_crossentropy',\n    metrics=['accuracy']\n)\n\nprint(f\"Total params     : {model_transfer.count_params():,}\")\ntrainable = sum([tf.size(w).numpy()\n                 for w in model_transfer.trainable_weights])\nprint(f\"Trainable params : {trainable:,}\")\n\n# Train\nhistory_transfer = model_transfer.fit(\n    train_gen,\n    epochs=15,\n    validation_data=val_gen,\n    callbacks=[\n        EarlyStopping(monitor='val_loss', patience=5,\n                      restore_best_weights=True),\n        ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3)\n    ],\n    verbose=1\n)\n\nloss_t, acc_t = model_transfer.evaluate(test_gen, verbose=0)\nprint(f\"\\n✅ Transfer Model Test Accuracy: {acc_t*100:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T01:48:26.19946Z","iopub.execute_input":"2026-06-19T01:48:26.199897Z","iopub.status.idle":"2026-06-19T02:14:02.014214Z","shell.execute_reply.started":"2026-06-19T01:48:26.199855Z","shell.execute_reply":"2026-06-19T02:14:02.013347Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 13: Summary Dashboard","metadata":{}},{"cell_type":"code","source":"loss_scratch, acc_scratch = model.evaluate(test_gen, verbose=0)\ntest_gen.reset()\nloss_transfer, acc_transfer = model_transfer.evaluate(test_gen, verbose=0)\n\nfig = plt.figure(figsize=(20, 14))\nfig.suptitle('🐱🐶 Cats vs Dogs — Complete Project Summary Dashboard',\n             fontsize=16, fontweight='bold', y=0.98)\n\nax1 = fig.add_subplot(2, 3, 1)\nmodels_names = ['CNN\\n(Scratch)', 'MobileNetV2\\n(Transfer)']\naccuracies   = [acc_scratch * 100, acc_transfer * 100]\ncolors_bar   = ['#4ECDC4', '#FF6B6B']\nbars = ax1.bar(models_names, accuracies, color=colors_bar,\n               edgecolor='black', width=0.4)\nax1.set_title('Model Accuracy Comparison', fontweight='bold')\nax1.set_ylabel('Test Accuracy (%)')\nax1.set_ylim([0, 105])\nfor bar, val in zip(bars, accuracies):\n    ax1.text(bar.get_x() + bar.get_width()/2,\n             bar.get_height() + 0.5,\n             f'{val:.2f}%', ha='center', fontweight='bold', fontsize=12)\n\nax2 = fig.add_subplot(2, 3, 2)\nep = range(1, len(history.history['accuracy']) + 1)\nax2.plot(ep, history.history['accuracy'],\n         label='Train', color='royalblue', linewidth=2)\nax2.plot(ep, history.history['val_accuracy'],\n         label='Val', color='coral', linewidth=2, linestyle='--')\nax2.set_title('Scratch CNN — Training Curves', fontweight='bold')\nax2.set_xlabel('Epoch')\nax2.set_ylabel('Accuracy')\nax2.legend()\nax2.grid(True, alpha=0.3)\n\nax3 = fig.add_subplot(2, 3, 3)\nep2 = range(1, len(history_transfer.history['accuracy']) + 1)\nax3.plot(ep2, history_transfer.history['accuracy'],\n         label='Train', color='purple', linewidth=2)\nax3.plot(ep2, history_transfer.history['val_accuracy'],\n         label='Val', color='orange', linewidth=2, linestyle='--')\nax3.set_title('Transfer Learning — Training Curves', fontweight='bold')\nax3.set_xlabel('Epoch')\nax3.set_ylabel('Accuracy')\nax3.legend()\nax3.grid(True, alpha=0.3)\n\nax4 = fig.add_subplot(2, 3, 4)\ntest_gen.reset()\ny_pred_t = (model_transfer.predict(test_gen, verbose=0) > 0.5).astype(int).flatten()\ncm_t = confusion_matrix(y_true, y_pred_t)\nsns.heatmap(cm_t, annot=True, fmt='d', cmap='Blues',\n            xticklabels=['Cat', 'Dog'],\n            yticklabels=['Cat', 'Dog'], ax=ax4,\n            annot_kws={'size': 14, 'weight': 'bold'})\nax4.set_title('Transfer Model — Confusion Matrix', fontweight='bold')\nax4.set_xlabel('Predicted')\nax4.set_ylabel('Actual')\n\nax5 = fig.add_subplot(2, 3, 5)\nax5.axis('off')\nstats_text = f\"\"\"\n📊  PROJECT STATISTICS\n{'─'*35}\nDataset        : Cats vs Dogs\nTotal Images   : ~25,000\nTrain/Val/Test : 70% / 15% / 15%\nImage Size     : 128×128 RGB\nClasses        : 2 (Binary)\n\n🧠  MODEL COMPARISON\n{'─'*35}\nScratch CNN Accuracy  : {acc_scratch*100:.2f}%\nTransfer Learning     : {acc_transfer*100:.2f}%\nImprovement           : +{(acc_transfer-acc_scratch)*100:.2f}%\n\n⚙️  KEY DIFFERENCES vs Fashion\n{'─'*35}\nLoss Function  : binary_crossentropy\nOutput Neuron  : 1 (sigmoid)\nData Loading   : ImageDataGenerator\nAugmentation   : YES (real images)\nColor          : RGB (3 channels)\n\"\"\"\nax5.text(0.05, 0.95, stats_text, transform=ax5.transAxes,\n         fontsize=10, verticalalignment='top',\n         fontfamily='monospace',\n         bbox=dict(boxstyle='round', facecolor='lightyellow',\n                   alpha=0.8))\n\nax6 = fig.add_subplot(2, 3, 6)\nax6.axis('off')\nconcepts = f\"\"\"\n🔑  KEY CONCEPTS USED\n{'─'*35}\n✅ Convolutional Neural Network\n✅ Binary Classification\n✅ sigmoid activation (output)\n✅ binary_crossentropy loss\n✅ Data Augmentation\n✅ ImageDataGenerator\n✅ Transfer Learning (MobileNetV2)\n✅ Early Stopping\n✅ ReduceLROnPlateau\n✅ Confusion Matrix\n✅ Batch Normalization\n✅ Dropout (regularization)\n\n📌  BINARY vs MULTI-CLASS\n{'─'*35}\n2 classes  → sigmoid + binary_CE\n3+ classes → softmax + sparse_CE\n\"\"\"\nax6.text(0.05, 0.95, concepts, transform=ax6.transAxes,\n         fontsize=10, verticalalignment='top',\n         fontfamily='monospace',\n         bbox=dict(boxstyle='round', facecolor='lightcyan',\n                   alpha=0.8))\n\nplt.tight_layout()\nplt.show()\n\nprint(\"\\n✅ Project Complete!\")\nmodel.save('/kaggle/working/catdog_scratch_model.keras')\nmodel_transfer.save('/kaggle/working/catdog_transfer_model.keras')\nprint(\"💾 Both models saved!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T02:14:02.015481Z","iopub.execute_input":"2026-06-19T02:14:02.016191Z","iopub.status.idle":"2026-06-19T02:14:30.740612Z","shell.execute_reply.started":"2026-06-19T02:14:02.016164Z","shell.execute_reply":"2026-06-19T02:14:30.739695Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#  Custom Image Prediction","metadata":{}},{"cell_type":"code","source":"from IPython.display import display\nimport ipywidgets as widgets\n\nupload = widgets.FileUpload(accept='image/*', multiple=False)\ndisplay(upload)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T02:14:30.741853Z","iopub.execute_input":"2026-06-19T02:14:30.742976Z","iopub.status.idle":"2026-06-19T02:14:30.75275Z","shell.execute_reply.started":"2026-06-19T02:14:30.742942Z","shell.execute_reply":"2026-06-19T02:14:30.751837Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfrom PIL import Image\nimport io\n\nuploaded_file = list(upload.value)[0]\nimage = Image.open(io.BytesIO(uploaded_file['content']))\n\nimage = image.resize((128, 128))\nimg_array = np.array(image) / 255.0\nimg_array = np.expand_dims(img_array, axis=0)\n\nprediction = model.predict(img_array)\n\nif prediction[0][0] > 0.5:\n    print(\"🐶 IT'S A DOG!\")\nelse:\n    print(\"🐱 IT'S A CAT!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T02:28:15.397065Z","iopub.execute_input":"2026-06-19T02:28:15.397902Z","iopub.status.idle":"2026-06-19T02:28:15.508605Z","shell.execute_reply.started":"2026-06-19T02:28:15.397871Z","shell.execute_reply":"2026-06-19T02:28:15.507712Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(\"📁 Files in /kaggle/working:\")\nfor f in os.listdir('/kaggle/working'):\n    path = os.path.join('/kaggle/working', f)\n    size = os.path.getsize(path) / (1024*1024) \n    print(f\"  {f}  ({size:.1f} MB)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-19T02:26:34.586724Z","iopub.execute_input":"2026-06-19T02:26:34.587065Z","iopub.status.idle":"2026-06-19T02:26:34.594324Z","shell.execute_reply.started":"2026-06-19T02:26:34.587041Z","shell.execute_reply":"2026-06-19T02:26:34.593067Z"}},"outputs":[],"execution_count":null}]}