{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.13.15","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport random\nimport zipfile\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport tensorflow as tf\n\nfrom pathlib import Path\nfrom PIL import Image\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import (\n    accuracy_score,\n    precision_score,\n    recall_score,\n    f1_score,\n    confusion_matrix,\n    classification_report\n)\n\n# Reproducibility\nSEED = 42\n\nos.environ[\"PYTHONHASHSEED\"] = str(SEED)\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\nprint(\"TensorFlow version:\", tf.__version__)\nprint(\"Keras version:\", tf.keras.__version__)\nprint(\"GPU available:\", tf.config.list_physical_devices(\"GPU\"))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:48:47.569754Z","iopub.execute_input":"2026-10-14T17:48:47.570482Z","iopub.status.idle":"2026-10-14T17:48:53.644619Z","shell.execute_reply.started":"2026-10-14T17:48:47.570451Z","shell.execute_reply":"2026-10-14T17:48:53.643899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"INPUT_DIR = Path(\"/kaggle/input\")\n\nprint(\"Available Kaggle input folders:\")\n\nfor item in INPUT_DIR.iterdir():\n    print(item.name)\n\nprint(\"\\nSearching for dataset archives...\")\n\nfor file in INPUT_DIR.rglob(\"*.zip\"):\n    print(file)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:49:30.557622Z","iopub.execute_input":"2026-10-14T17:49:30.558314Z","iopub.status.idle":"2026-10-14T17:49:30.565416Z","shell.execute_reply.started":"2026-10-14T17:49:30.558288Z","shell.execute_reply":"2026-10-14T17:49:30.564623Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"zip_files = list(INPUT_DIR.rglob(\"train.zip\"))\n\nif not zip_files:\n    raise FileNotFoundError(\n        \"train.zip not found. Check that the Dogs vs. Cats \"\n        \"competition dataset is attached to this notebook.\"\n    )\n\nTRAIN_ZIP = zip_files[0]\nDATA_DIR = Path(\"/kaggle/working/dogs_vs_cats\")\nTRAIN_DIR = DATA_DIR / \"train\"\n\nDATA_DIR.mkdir(parents=True, exist_ok=True)\n\nprint(\"Training archive:\", TRAIN_ZIP)\nprint(\"Working directory:\", DATA_DIR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:49:45.51556Z","iopub.execute_input":"2026-10-14T17:49:45.515941Z","iopub.status.idle":"2026-10-14T17:49:45.524425Z","shell.execute_reply.started":"2026-10-14T17:49:45.515917Z","shell.execute_reply":"2026-10-14T17:49:45.523683Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if not TRAIN_DIR.exists() or not any(TRAIN_DIR.iterdir()):\n    TRAIN_DIR.mkdir(parents=True, exist_ok=True)\n\n    with zipfile.ZipFile(TRAIN_ZIP, \"r\") as zip_ref:\n        zip_ref.extractall(TRAIN_DIR)\n\nprint(\"Extraction complete.\")\nprint(\"Training directory:\", TRAIN_DIR)\n\n# Locate the actual image directory, accommodating\n# archives that contain an extra nested folder.\nsample_images = list(TRAIN_DIR.rglob(\"cat.*.jpg\"))\n\nif not sample_images:\n    sample_images = list(TRAIN_DIR.rglob(\"dog.*.jpg\"))\n\nif not sample_images:\n    raise FileNotFoundError(\n        \"No cat/dog images found after extraction.\"\n    )\n\nIMAGE_DIR = sample_images[0].parent\n\nprint(\"Image directory:\", IMAGE_DIR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:50:11.145557Z","iopub.execute_input":"2026-10-14T17:50:11.145968Z","iopub.status.idle":"2026-10-14T17:50:18.652407Z","shell.execute_reply.started":"2026-10-14T17:50:11.145943Z","shell.execute_reply":"2026-10-14T17:50:18.651543Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_paths = sorted(\n    list(IMAGE_DIR.glob(\"cat.*.jpg\")) +\n    list(IMAGE_DIR.glob(\"dog.*.jpg\"))\n)\n\n# Labels: 0 = cat, 1 = dog\nlabels = np.array([\n    0 if path.name.startswith(\"cat.\") else 1\n    for path in image_paths\n])\n\nprint(\"Total images:\", len(image_paths))\nprint(\"Number of cats:\", np.sum(labels == 0))\nprint(\"Number of dogs:\", np.sum(labels == 1))\nprint(\"Class names: 0 = Cat, 1 = Dog\")\n\nif len(image_paths) == 0:\n    raise ValueError(\"No images found. Check IMAGE_DIR.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:51:21.553497Z","iopub.execute_input":"2026-10-14T17:51:21.553904Z","iopub.status.idle":"2026-10-14T17:51:21.895911Z","shell.execute_reply.started":"2026-10-14T17:51:21.553878Z","shell.execute_reply":"2026-10-14T17:51:21.895016Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_counts = pd.Series(labels).map({\n    0: \"Cat\",\n    1: \"Dog\"\n}).value_counts()\n\nplt.figure(figsize=(7, 5))\nsns.barplot(x=class_counts.index, y=class_counts.values)\n\nplt.title(\"Distribution of Cat and Dog Images\")\nplt.xlabel(\"Class\")\nplt.ylabel(\"Number of Images\")\nplt.tight_layout()\nplt.show()\n\nprint(class_counts)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:51:38.298865Z","iopub.execute_input":"2026-10-14T17:51:38.299612Z","iopub.status.idle":"2026-10-14T17:51:38.566087Z","shell.execute_reply.started":"2026-10-14T17:51:38.299583Z","shell.execute_reply":"2026-10-14T17:51:38.565272Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_size = min(10, len(image_paths))\n\nprint(\"Dimensions of sample images:\\n\")\n\nfor path in image_paths[:sample_size]:\n    with Image.open(path) as img:\n        print(path.name, \"->\", img.size, img.mode)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:51:52.225436Z","iopub.execute_input":"2026-10-14T17:51:52.226203Z","iopub.status.idle":"2026-10-14T17:51:52.233601Z","shell.execute_reply.started":"2026-10-14T17:51:52.226173Z","shell.execute_reply":"2026-10-14T17:51:52.232708Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(12, 8))\n\nsample_indices = np.random.choice(\n    len(image_paths),\n    size=min(12, len(image_paths)),\n    replace=False\n)\n\nfor plot_idx, data_idx in enumerate(sample_indices):\n    with Image.open(image_paths[data_idx]) as img:\n        img = img.convert(\"RGB\")\n\n        plt.subplot(3, 4, plot_idx + 1)\n        plt.imshow(img)\n        plt.title(\n            \"Cat\" if labels[data_idx] == 0 else \"Dog\"\n        )\n        plt.axis(\"off\")\n\nplt.suptitle(\"Sample Images from the Dogs vs. Cats Dataset\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:52:18.586518Z","iopub.execute_input":"2026-10-14T17:52:18.586918Z","iopub.status.idle":"2026-10-14T17:52:19.844049Z","shell.execute_reply.started":"2026-10-14T17:52:18.586891Z","shell.execute_reply":"2026-10-14T17:52:19.842997Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X_train_paths, X_val_paths, y_train, y_val = train_test_split(\n    np.array([str(p) for p in image_paths]),\n    labels,\n    test_size=0.20,\n    random_state=SEED,\n    stratify=labels\n)\n\nprint(\"Training images:\", len(X_train_paths))\nprint(\"Validation images:\", len(X_val_paths))\n\nprint(\"\\nTraining class distribution:\")\nprint(pd.Series(y_train).map({\n    0: \"Cat\", 1: \"Dog\"\n}).value_counts())\n\nprint(\"\\nValidation class distribution:\")\nprint(pd.Series(y_val).map({\n    0: \"Cat\", 1: \"Dog\"\n}).value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:52:34.171312Z","iopub.execute_input":"2026-10-14T17:52:34.17161Z","iopub.status.idle":"2026-10-14T17:52:34.222355Z","shell.execute_reply.started":"2026-10-14T17:52:34.171572Z","shell.execute_reply":"2026-10-14T17:52:34.221694Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_SIZE = (128, 128)\nBATCH_SIZE = 32\nEPOCHS = 15\n\nAUTOTUNE = tf.data.AUTOTUNE\n\nprint(\"Image size:\", IMG_SIZE)\nprint(\"Batch size:\", BATCH_SIZE)\nprint(\"Maximum epochs:\", EPOCHS)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:53:05.590503Z","iopub.execute_input":"2026-10-14T17:53:05.59102Z","iopub.status.idle":"2026-10-14T17:53:05.596372Z","shell.execute_reply.started":"2026-10-14T17:53:05.590994Z","shell.execute_reply":"2026-10-14T17:53:05.59548Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_image(path, label):\n    image = tf.io.read_file(path)\n\n    image = tf.io.decode_jpeg(\n        image,\n        channels=3\n    )\n\n    image = tf.image.resize(\n        image,\n        IMG_SIZE\n    )\n\n    # Normalize pixel values from [0, 255] to [0, 1]\n    image = tf.cast(image, tf.float32) / 255.0\n\n    label = tf.cast(label, tf.float32)\n\n    return image, label\n\n\ntrain_ds = tf.data.Dataset.from_tensor_slices(\n    (X_train_paths, y_train)\n)\n\ntrain_ds = train_ds.shuffle(\n    buffer_size=len(X_train_paths),\n    seed=SEED,\n    reshuffle_each_iteration=True\n)\n\ntrain_ds = train_ds.map(\n    load_image,\n    num_parallel_calls=AUTOTUNE\n)\n\ntrain_ds = train_ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n\n\nval_ds = tf.data.Dataset.from_tensor_slices(\n    (X_val_paths, y_val)\n)\n\nval_ds = val_ds.map(\n    load_image,\n    num_parallel_calls=AUTOTUNE\n)\n\nval_ds = val_ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n\nprint(\"Training and validation pipelines created.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:53:19.240929Z","iopub.execute_input":"2026-10-14T17:53:19.241704Z","iopub.status.idle":"2026-10-14T17:53:21.729179Z","shell.execute_reply.started":"2026-10-14T17:53:19.241676Z","shell.execute_reply":"2026-10-14T17:53:21.72841Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for images, batch_labels in train_ds.take(1):\n    print(\"Image batch shape:\", images.shape)\n    print(\"Label batch shape:\", batch_labels.shape)\n    print(\"Minimum pixel value:\", tf.reduce_min(images).numpy())\n    print(\"Maximum pixel value:\", tf.reduce_max(images).numpy())\n\n    plt.figure(figsize=(10, 6))\n\n    for i in range(min(8, images.shape[0])):\n        plt.subplot(2, 4, i + 1)\n\n        plt.imshow(images[i].numpy())\n        plt.title(\n            \"Dog\" if int(batch_labels[i].numpy()) == 1\n            else \"Cat\"\n        )\n        plt.axis(\"off\")\n\n    plt.suptitle(\"Resized and Normalized Training Images\")\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:53:47.306428Z","iopub.execute_input":"2026-10-14T17:53:47.306885Z","iopub.status.idle":"2026-10-14T17:53:48.106094Z","shell.execute_reply.started":"2026-10-14T17:53:47.306858Z","shell.execute_reply":"2026-10-14T17:53:48.105202Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras import layers, models, regularizers\n\ntf.keras.backend.clear_session()\ntf.random.set_seed(SEED)\n\nmodel = models.Sequential([\n    layers.Input(shape=(128, 128, 3)),\n\n    # Block 1\n    layers.Conv2D(\n        32, (3, 3),\n        activation=\"relu\",\n        padding=\"same\"\n    ),\n    layers.MaxPooling2D((2, 2)),\n\n    # Block 2\n    layers.Conv2D(\n        64, (3, 3),\n        activation=\"relu\",\n        padding=\"same\"\n    ),\n    layers.MaxPooling2D((2, 2)),\n\n    # Block 3\n    layers.Conv2D(\n        128, (3, 3),\n        activation=\"relu\",\n        padding=\"same\",\n        kernel_regularizer=regularizers.l2(0.0001)\n    ),\n    layers.MaxPooling2D((2, 2)),\n\n    # Block 4\n    layers.Conv2D(\n        256, (3, 3),\n        activation=\"relu\",\n        padding=\"same\"\n    ),\n    layers.MaxPooling2D((2, 2)),\n\n    # Classification head\n    layers.Flatten(),\n\n    layers.Dense(128, activation=\"relu\"),\n    layers.Dropout(0.5),\n\n    layers.Dense(1, activation=\"sigmoid\")\n])\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:54:07.941007Z","iopub.execute_input":"2026-10-14T17:54:07.941502Z","iopub.status.idle":"2026-10-14T17:54:09.202667Z","shell.execute_reply.started":"2026-10-14T17:54:07.941474Z","shell.execute_reply":"2026-10-14T17:54:09.202023Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.compile(\n    optimizer=tf.keras.optimizers.Adam(\n        learning_rate=0.001\n    ),\n    loss=\"binary_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n\nprint(\"CNN compiled successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:54:23.603568Z","iopub.execute_input":"2026-10-14T17:54:23.604097Z","iopub.status.idle":"2026-10-14T17:54:23.619053Z","shell.execute_reply.started":"2026-10-14T17:54:23.604072Z","shell.execute_reply":"2026-10-14T17:54:23.618115Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"checkpoint_path = \"/kaggle/working/best_cat_dog_cnn.keras\"\n\ncallbacks = [\n    tf.keras.callbacks.EarlyStopping(\n        monitor=\"val_loss\",\n        patience=3,\n        restore_best_weights=True,\n        verbose=1\n    ),\n\n    tf.keras.callbacks.ModelCheckpoint(\n        filepath=checkpoint_path,\n        monitor=\"val_loss\",\n        save_best_only=True,\n        verbose=1\n    ),\n\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.5,\n        patience=2,\n        min_lr=1e-6,\n        verbose=1\n    )\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:54:59.762537Z","iopub.execute_input":"2026-10-14T17:54:59.763296Z","iopub.status.idle":"2026-10-14T17:54:59.767605Z","shell.execute_reply.started":"2026-10-14T17:54:59.763269Z","shell.execute_reply":"2026-10-14T17:54:59.766803Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS,\n    callbacks=callbacks,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:55:17.929978Z","iopub.execute_input":"2026-10-14T17:55:17.930331Z","iopub.status.idle":"2026-10-14T17:58:18.426022Z","shell.execute_reply.started":"2026-10-14T17:55:17.930306Z","shell.execute_reply":"2026-10-14T17:58:18.425366Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8, 5))\n\nplt.plot(\n    history.history[\"accuracy\"],\n    label=\"Training Accuracy\"\n)\n\nplt.plot(\n    history.history[\"val_accuracy\"],\n    label=\"Validation Accuracy\"\n)\n\nplt.title(\"CNN Training vs. Validation Accuracy\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.legend()\nplt.grid(True)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T17:59:24.89121Z","iopub.execute_input":"2026-10-14T17:59:24.891582Z","iopub.status.idle":"2026-10-14T17:59:25.072756Z","shell.execute_reply.started":"2026-10-14T17:59:24.891558Z","shell.execute_reply":"2026-10-14T17:59:25.071704Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8, 5))\n\nplt.plot(\n    history.history[\"loss\"],\n    label=\"Training Loss\"\n)\n\nplt.plot(\n    history.history[\"val_loss\"],\n    label=\"Validation Loss\"\n)\n\nplt.title(\"CNN Training vs. Validation Loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Binary Cross-Entropy Loss\")\nplt.legend()\nplt.grid(True)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:00:40.490576Z","iopub.execute_input":"2026-10-14T18:00:40.491122Z","iopub.status.idle":"2026-10-14T18:00:40.657661Z","shell.execute_reply.started":"2026-10-14T18:00:40.491091Z","shell.execute_reply":"2026-10-14T18:00:40.656939Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_acc = history.history[\"accuracy\"]\nval_acc = history.history[\"val_accuracy\"]\n\ntrain_loss = history.history[\"loss\"]\nval_loss = history.history[\"val_loss\"]\n\nprint(\"Final recorded training accuracy:\", round(train_acc[-1], 4))\nprint(\"Final recorded validation accuracy:\", round(val_acc[-1], 4))\nprint(\"Final recorded training loss:\", round(train_loss[-1], 4))\nprint(\"Final recorded validation loss:\", round(val_loss[-1], 4))\n\naccuracy_gap = train_acc[-1] - val_acc[-1]\n\nprint(\"\\nLearning-curve interpretation:\")\n\nif accuracy_gap > 0.10 and val_loss[-1] > min(val_loss):\n    print(\"Possible overfitting: training accuracy is higher,\")\n    print(\"and validation loss has increased from its minimum.\")\nelif train_acc[-1] < 0.65 and val_acc[-1] < 0.65:\n    print(\"Possible underfitting: both accuracies remain low.\")\nelse:\n    print(\"Inspect both curves to determine generalization.\")\n    print(\"A small accuracy gap and stable validation loss\")\n    print(\"suggest better generalization.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:01:07.332159Z","iopub.execute_input":"2026-10-14T18:01:07.332574Z","iopub.status.idle":"2026-10-14T18:01:07.339413Z","shell.execute_reply.started":"2026-10-14T18:01:07.332549Z","shell.execute_reply":"2026-10-14T18:01:07.338618Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Validation dataset is not shuffled, so prediction order\n# matches the original y_val and X_val_paths order.\n\ny_prob = model.predict(val_ds, verbose=1).ravel()\n\n# Probability >= 0.5 means Dog (1); otherwise Cat (0)\ny_pred = (y_prob >= 0.5).astype(int)\ny_true = y_val.astype(int)\n\nprint(\"Number of validation predictions:\", len(y_pred))\nprint(\"Number of actual validation labels:\", len(y_true))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:01:34.684601Z","iopub.execute_input":"2026-10-14T18:01:34.685223Z","iopub.status.idle":"2026-10-14T18:01:37.351718Z","shell.execute_reply.started":"2026-10-14T18:01:34.68519Z","shell.execute_reply":"2026-10-14T18:01:37.350883Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"accuracy = accuracy_score(y_true, y_pred)\n\nprecision = precision_score(\n    y_true, y_pred, zero_division=0\n)\n\nrecall = recall_score(\n    y_true, y_pred, zero_division=0\n)\n\nf1 = f1_score(\n    y_true, y_pred, zero_division=0\n)\n\nresults = pd.DataFrame({\n    \"Metric\": [\n        \"Accuracy\",\n        \"Precision\",\n        \"Recall\",\n        \"F1-score\"\n    ],\n    \"Value\": [\n        accuracy,\n        precision,\n        recall,\n        f1\n    ]\n})\n\nprint(results.to_string(index=False))\n\nprint(\"\\nClassification report:\\n\")\n\nprint(classification_report(\n    y_true,\n    y_pred,\n    labels=[0, 1],\n    target_names=[\"Cat\", \"Dog\"],\n    zero_division=0\n))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:02:57.693689Z","iopub.execute_input":"2026-10-14T18:02:57.694361Z","iopub.status.idle":"2026-10-14T18:02:57.725336Z","shell.execute_reply.started":"2026-10-14T18:02:57.694331Z","shell.execute_reply":"2026-10-14T18:02:57.72451Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cm = confusion_matrix(\n    y_true,\n    y_pred,\n    labels=[0, 1]\n)\n\nplt.figure(figsize=(6, 5))\n\nsns.heatmap(\n    cm,\n    annot=True,\n    fmt=\"d\",\n    cmap=\"Blues\",\n    xticklabels=[\"Cat\", \"Dog\"],\n    yticklabels=[\"Cat\", \"Dog\"]\n)\n\nplt.title(\"Confusion Matrix — Cat vs. Dog CNN\")\nplt.xlabel(\"Predicted Class\")\nplt.ylabel(\"Actual Class\")\nplt.tight_layout()\nplt.show()\n\ntn, fp, fn, tp = cm.ravel()\n\nprint(\"True Negatives (Cat predicted as Cat):\", tn)\nprint(\"False Positives (Cat predicted as Dog):\", fp)\nprint(\"False Negatives (Dog predicted as Cat):\", fn)\nprint(\"True Positives (Dog predicted as Dog):\", tp)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:03:16.025235Z","iopub.execute_input":"2026-10-14T18:03:16.025674Z","iopub.status.idle":"2026-10-14T18:03:16.192568Z","shell.execute_reply.started":"2026-10-14T18:03:16.025646Z","shell.execute_reply":"2026-10-14T18:03:16.191892Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"report = classification_report(\n    y_true,\n    y_pred,\n    labels=[0, 1],\n    target_names=[\"Cat\", \"Dog\"],\n    output_dict=True,\n    zero_division=0\n)\n\nclass_metrics = pd.DataFrame(report).T.loc[\n    [\"Cat\", \"Dog\"],\n    [\"precision\", \"recall\", \"f1-score\"]\n]\n\nclass_metrics.plot(\n    kind=\"bar\",\n    figsize=(8, 5),\n    ylim=(0, 1)\n)\n\nplt.title(\"Class-wise Precision, Recall and F1-score\")\nplt.xlabel(\"Class\")\nplt.ylabel(\"Score\")\nplt.xticks(rotation=0)\nplt.legend(loc=\"lower right\")\nplt.grid(axis=\"y\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:03:55.984941Z","iopub.execute_input":"2026-10-14T18:03:55.985529Z","iopub.status.idle":"2026-10-14T18:03:56.208898Z","shell.execute_reply.started":"2026-10-14T18:03:55.985501Z","shell.execute_reply":"2026-10-14T18:03:56.208259Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"results.to_csv(\n    \"/kaggle/working/validation_metrics.csv\",\n    index=False\n)\n\nprint(\"Metrics saved to /kaggle/working/validation_metrics.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:04:16.982682Z","iopub.execute_input":"2026-10-14T18:04:16.983264Z","iopub.status.idle":"2026-10-14T18:04:16.996561Z","shell.execute_reply.started":"2026-10-14T18:04:16.983233Z","shell.execute_reply":"2026-10-14T18:04:16.995667Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"error_indices = np.where(y_true != y_pred)[0]\n\nprint(\"Total incorrect predictions:\", len(error_indices))\n\nif len(error_indices) == 0:\n    print(\"No misclassified validation images were found.\")\nelse:\n    n_errors = min(10, len(error_indices))\n\n    plt.figure(figsize=(15, 10))\n\n    for i, idx in enumerate(error_indices[:n_errors]):\n        with Image.open(X_val_paths[idx]) as img:\n            img = img.convert(\"RGB\")\n\n            plt.subplot(2, 5, i + 1)\n            plt.imshow(img)\n\n            actual = \"Dog\" if y_true[idx] == 1 else \"Cat\"\n            predicted = \"Dog\" if y_pred[idx] == 1 else \"Cat\"\n\n            plt.title(\n                f\"Actual: {actual}\\n\"\n                f\"Predicted: {predicted}\\n\"\n                f\"Dog probability: {y_prob[idx]:.3f}\",\n                fontsize=9\n            )\n\n            plt.axis(\"off\")\n\n    plt.suptitle(\"Misclassified Validation Images\")\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:04:35.795816Z","iopub.execute_input":"2026-10-14T18:04:35.796256Z","iopub.status.idle":"2026-10-14T18:04:36.737443Z","shell.execute_reply.started":"2026-10-14T18:04:35.796229Z","shell.execute_reply":"2026-10-14T18:04:36.736394Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"error_rows = []\n\nfor idx in error_indices:\n    error_rows.append({\n        \"Image\": Path(X_val_paths[idx]).name,\n        \"Actual Class\": \"Dog\" if y_true[idx] == 1 else \"Cat\",\n        \"Predicted Class\": \"Dog\" if y_pred[idx] == 1 else \"Cat\",\n        \"Dog Probability\": round(float(y_prob[idx]), 4),\n        \"Confidence in Prediction\": round(\n            float(max(y_prob[idx], 1 - y_prob[idx])), 4\n        )\n    })\n\nerror_df = pd.DataFrame(error_rows)\n\ndisplay(error_df.head(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:05:07.956264Z","iopub.execute_input":"2026-10-14T18:05:07.957022Z","iopub.status.idle":"2026-10-14T18:05:07.982453Z","shell.execute_reply.started":"2026-10-14T18:05:07.956994Z","shell.execute_reply":"2026-10-14T18:05:07.98165Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_predictions = min(5, len(X_val_paths))\n\nprediction_indices = np.random.default_rng(SEED).choice(\n    len(X_val_paths),\n    size=num_predictions,\n    replace=False\n)\n\nplt.figure(figsize=(15, 7))\n\nfor plot_idx, idx in enumerate(prediction_indices):\n    # Load and preprocess one image\n    with Image.open(X_val_paths[idx]) as img:\n        img = img.convert(\"RGB\").resize(IMG_SIZE)\n        image_array = np.asarray(img, dtype=np.float32) / 255.0\n\n    # Add batch dimension: (128, 128, 3) -> (1, 128, 128, 3)\n    image_batch = np.expand_dims(image_array, axis=0)\n\n    # Predict probability of Dog\n    dog_probability = float(\n        model.predict(image_batch, verbose=0)[0][0]\n    )\n\n    predicted_label = 1 if dog_probability >= 0.5 else 0\n\n    actual_class = \"Dog\" if y_val[idx] == 1 else \"Cat\"\n    predicted_class = \"Dog\" if predicted_label == 1 else \"Cat\"\n\n    confidence = (\n        dog_probability if predicted_label == 1\n        else 1 - dog_probability\n    )\n\n    plt.subplot(1, 5, plot_idx + 1)\n    plt.imshow(image_array)\n\n    plt.title(\n        f\"Actual: {actual_class}\\n\"\n        f\"Predicted: {predicted_class}\\n\"\n        f\"Confidence: {confidence:.2%}\",\n        fontsize=9\n    )\n\n    plt.axis(\"off\")\n\n    print(f\"Image: {Path(X_val_paths[idx]).name}\")\n    print(f\"  Actual class: {actual_class}\")\n    print(f\"  Predicted class: {predicted_class}\")\n    print(f\"  Dog probability: {dog_probability:.4f}\")\n    print(f\"  Prediction confidence: {confidence:.2%}\")\n    print()\n\nplt.suptitle(\"Five Individual Cat/Dog Predictions\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:05:31.558983Z","iopub.execute_input":"2026-10-14T18:05:31.559777Z","iopub.status.idle":"2026-10-14T18:05:33.202573Z","shell.execute_reply.started":"2026-10-14T18:05:31.559749Z","shell.execute_reply":"2026-10-14T18:05:33.201738Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"FINAL_MODEL_PATH = \"/kaggle/working/final_cat_dog_cnn.keras\"\n\nmodel.save(FINAL_MODEL_PATH)\n\nprint(\"Final model saved to:\", FINAL_MODEL_PATH)\nprint(\"Best checkpoint saved to:\", checkpoint_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:05:57.455189Z","iopub.execute_input":"2026-10-14T18:05:57.455799Z","iopub.status.idle":"2026-10-14T18:05:57.58813Z","shell.execute_reply.started":"2026-10-14T18:05:57.45577Z","shell.execute_reply":"2026-10-14T18:05:57.587366Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"final_results = pd.DataFrame({\n    \"Item\": [\n        \"Dataset images\",\n        \"Training images\",\n        \"Validation images\",\n        \"Image resolution\",\n        \"CNN optimizer\",\n        \"Loss function\",\n        \"Maximum epochs\",\n        \"Epochs completed\",\n        \"Batch size\",\n        \"Validation accuracy\",\n        \"Validation precision (Dog)\",\n        \"Validation recall (Dog)\",\n        \"Validation F1-score (Dog)\",\n        \"Misclassified images\"\n    ],\n    \"Result\": [\n        len(image_paths),\n        len(X_train_paths),\n        len(X_val_paths),\n        \"128 x 128 x 3\",\n        \"Adam\",\n        \"Binary cross-entropy\",\n        EPOCHS,\n        len(history.history[\"loss\"]),\n        BATCH_SIZE,\n        f\"{accuracy:.4f}\",\n        f\"{precision:.4f}\",\n        f\"{recall:.4f}\",\n        f\"{f1:.4f}\",\n        len(error_indices)\n    ]\n})\n\ndisplay(final_results)\n\nfinal_results.to_csv(\n    \"/kaggle/working/final_results_summary.csv\",\n    index=False\n)\n\nprint(\"Final results summary saved.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-14T18:06:24.242693Z","iopub.execute_input":"2026-10-14T18:06:24.242963Z","iopub.status.idle":"2026-10-14T18:06:24.255609Z","shell.execute_reply.started":"2026-10-14T18:06:24.242942Z","shell.execute_reply":"2026-10-14T18:06:24.254755Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}