{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.x"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"c11b735b","cell_type":"markdown","source":"# Cats vs Dogs Classification using a Traditional Neural Network (MLP)\n\nThis notebook classifies **cats vs dogs** using a **traditional fully connected neural network**, not a CNN.\n\nModel:\n\n`64×64×3 image → Rescaling → Flatten → Dense(128) → Dense(64) → Dropout → Dense(1)`\n\n**No `Conv2D`, `MaxPooling2D`, or VGG16 is used.**\n","metadata":{}},{"id":"30668164","cell_type":"code","source":"# 1. Import libraries\nimport os\nimport zipfile\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\n\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras import Sequential\nfrom tensorflow.keras.layers import Input, Rescaling, Flatten, Dense, Dropout\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator, load_img\n\nprint(\"TensorFlow version:\", tf.__version__)\nprint(\"GPU available:\", len(tf.config.list_physical_devices(\"GPU\")) > 0)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"b02a2ea3","cell_type":"markdown","source":"## 2. Prepare the Kaggle Dogs vs Cats dataset\n\nThis keeps the same Kaggle dataset used by the original notebook: `dogs-vs-cats`.\n","metadata":{}},{"id":"e2509682","cell_type":"code","source":"# Paths\nDATASET_DIR = \"/kaggle/input/dogs-vs-cats\"\nWORK_DIR = \"/kaggle/working/dogs-vs-cats\"\n\nos.makedirs(WORK_DIR, exist_ok=True)\n\ntrain_dir = os.path.join(WORK_DIR, \"train\")\ntest_dir = os.path.join(WORK_DIR, \"test1\")\n\n# Extract only if not already extracted\nif not os.path.exists(train_dir):\n    with zipfile.ZipFile(os.path.join(DATASET_DIR, \"train.zip\"), \"r\") as z:\n        z.extractall(WORK_DIR)\n\nif not os.path.exists(test_dir):\n    with zipfile.ZipFile(os.path.join(DATASET_DIR, \"test1.zip\"), \"r\") as z:\n        z.extractall(WORK_DIR)\n\nprint(\"Train folder:\", train_dir)\nprint(\"Test folder :\", test_dir)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"0a4228cd","cell_type":"code","source":"# 3. Build DataFrames containing image paths and labels\n\ndef list_image_paths(directory):\n    return [\n        os.path.join(directory, filename)\n        for filename in os.listdir(directory)\n        if filename.lower().endswith((\".jpg\", \".jpeg\", \".png\"))\n    ]\n\ntrain = pd.DataFrame({\"filepath\": list_image_paths(train_dir)})\n\n# Filenames are cat.0.jpg, dog.0.jpg, ...\ntrain[\"truth_label\"] = np.where(\n    train[\"filepath\"].str.contains(\"dog\", case=False),\n    \"dog\",\n    \"cat\"\n)\n\ntest = pd.DataFrame({\"filepath\": list_image_paths(test_dir)})\n\nprint(\"Training images:\", len(train))\nprint(\"Kaggle test images:\", len(test))\ntrain.head()\n","metadata":{},"outputs":[],"execution_count":null},{"id":"b5c47c99","cell_type":"code","source":"# 4. Split the labeled data into training and validation sets\n\nX_train, X_val = train_test_split(\n    train,\n    test_size=0.20,\n    random_state=42,\n    stratify=train[\"truth_label\"]\n)\n\nprint(\"Training set  :\", X_train.shape)\nprint(\"Validation set:\", X_val.shape)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"f7329c1f","cell_type":"markdown","source":"## 5. Create image generators\n\nImages are resized to **64×64**.\n\nWe use `class_mode=\"binary\"` because the model has one sigmoid output:\n- class 0 → cat\n- class 1 → dog\n\nPixel normalization is performed **inside the neural network** by the `Rescaling(1./255)` layer.\n","metadata":{}},{"id":"103fa4af","cell_type":"code","source":"IMG_SIZE = (64, 64)\nBATCH_SIZE = 128\n\n# No CNN-specific preprocessing is needed.\n# Keep the input pixels in 0..255; the model will rescale them to 0..1.\ntrain_datagen = ImageDataGenerator()\nval_datagen = ImageDataGenerator()\n\ntraining_set = train_datagen.flow_from_dataframe(\n    dataframe=X_train,\n    x_col=\"filepath\",\n    y_col=\"truth_label\",\n    class_mode=\"binary\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    seed=42\n)\n\nvalidation_set = val_datagen.flow_from_dataframe(\n    dataframe=X_val,\n    x_col=\"filepath\",\n    y_col=\"truth_label\",\n    class_mode=\"binary\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    shuffle=False\n)\n\nprint(\"Class mapping:\", training_set.class_indices)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"2d790226","cell_type":"markdown","source":"## 6. Display a few training images","metadata":{}},{"id":"299a4cee","cell_type":"code","source":"images, labels = next(training_set)\n\nplt.figure(figsize=(10, 8))\n\nindex_to_class = {v: k for k, v in training_set.class_indices.items()}\n\nfor i in range(12):\n    plt.subplot(3, 4, i + 1)\n    plt.imshow(images[i].astype(\"uint8\"))\n    plt.title(index_to_class[int(labels[i])])\n    plt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()\n","metadata":{},"outputs":[],"execution_count":null},{"id":"8a5e8e0c","cell_type":"markdown","source":"## 7. Traditional Neural Network model\n\nThe image is flattened into a vector:\n\n`64 × 64 × 3 = 12,288 input values`\n\nThe model then uses only fully connected (`Dense`) layers.\n","metadata":{}},{"id":"9d4486ac","cell_type":"code","source":"# Traditional NN / MLP -- NO CNN\nmodel = Sequential([\n    Input(shape=(64, 64, 3)),\n\n    # Normalize pixel values: 0..255 -> 0..1\n    Rescaling(1.0 / 255),\n\n    # 64 x 64 x 3 -> 12,288 values\n    Flatten(),\n\n    # Fully connected neural network\n    Dense(128, activation=\"relu\"),\n    Dense(64, activation=\"relu\"),\n\n    Dropout(0.2),\n\n    # Binary classification: cat vs dog\n    Dense(1, activation=\"sigmoid\")\n])\n\nmodel.summary()\n","metadata":{},"outputs":[],"execution_count":null},{"id":"a719fe07","cell_type":"code","source":"# 8. Compile the model\nmodel.compile(\n    optimizer=\"adam\",\n    loss=\"binary_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"3b62f96b","cell_type":"markdown","source":"## 9. Train the model","metadata":{}},{"id":"70532d0a","cell_type":"code","source":"EPOCHS = 10\n\nhistory = model.fit(\n    training_set,\n    validation_data=validation_set,\n    epochs=EPOCHS\n)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"cb3d1e32","cell_type":"markdown","source":"## 10. Evaluate the model","metadata":{}},{"id":"4e08dabe","cell_type":"code","source":"val_loss, val_accuracy = model.evaluate(validation_set, verbose=1)\n\nprint(f\"Validation loss    : {val_loss:.4f}\")\nprint(f\"Validation accuracy: {val_accuracy:.4f}\")\n","metadata":{},"outputs":[],"execution_count":null},{"id":"31590b6b","cell_type":"markdown","source":"## 11. Plot learning curves","metadata":{}},{"id":"a02e4612","cell_type":"code","source":"plt.figure(figsize=(8, 5))\nplt.plot(history.history[\"accuracy\"], label=\"Train Accuracy\")\nplt.plot(history.history[\"val_accuracy\"], label=\"Validation Accuracy\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.title(\"Accuracy\")\nplt.legend()\nplt.grid(alpha=0.3)\nplt.show()\n","metadata":{},"outputs":[],"execution_count":null},{"id":"701eb0f2","cell_type":"code","source":"plt.figure(figsize=(8, 5))\nplt.plot(history.history[\"loss\"], label=\"Train Loss\")\nplt.plot(history.history[\"val_loss\"], label=\"Validation Loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Loss\")\nplt.legend()\nplt.grid(alpha=0.3)\nplt.show()\n","metadata":{},"outputs":[],"execution_count":null},{"id":"ea23ae74","cell_type":"markdown","source":"## 12. Predict unlabeled Kaggle test images","metadata":{}},{"id":"02c257f0","cell_type":"code","source":"test_generator = val_datagen.flow_from_dataframe(\n    dataframe=test,\n    x_col=\"filepath\",\n    y_col=None,\n    class_mode=None,\n    target_size=IMG_SIZE,\n    batch_size=32,\n    shuffle=False\n)\n\n# Sigmoid returns P(class=1)\ntest_probabilities = model.predict(test_generator, verbose=1).ravel()\n\ntest[\"probability_dog\"] = test_probabilities\n\n# Convert probabilities to class indices using threshold 0.5\npredicted_indices = (test_probabilities >= 0.5).astype(int)\n\nindex_to_class = {v: k for k, v in training_set.class_indices.items()}\ntest[\"prediction\"] = [index_to_class[i] for i in predicted_indices]\n\ntest.head()\n","metadata":{},"outputs":[],"execution_count":null},{"id":"f3010fe3","cell_type":"code","source":"# Show sample predictions\nsample_test = test.sample(20, random_state=42).reset_index(drop=True)\n\nplt.figure(figsize=(15, 12))\n\nfor i in range(len(sample_test)):\n    plt.subplot(4, 5, i + 1)\n\n    image = load_img(\n        sample_test.loc[i, \"filepath\"],\n        target_size=IMG_SIZE\n    )\n\n    plt.imshow(image)\n    plt.axis(\"off\")\n\n    pred = sample_test.loc[i, \"prediction\"]\n    prob_dog = sample_test.loc[i, \"probability_dog\"]\n\n    plt.title(f\"{pred}\\nP(dog)={prob_dog:.2f}\")\n\nplt.tight_layout()\nplt.show()\n","metadata":{},"outputs":[],"execution_count":null},{"id":"bf96d1e2","cell_type":"markdown","source":"## Conclusion\n\nThis is a **traditional neural network (MLP)** for image classification:\n\n- `Flatten` converts the image into a 12,288-dimensional vector.\n- `Dense(128)` and `Dense(64)` learn the classification function.\n- `Dense(1, sigmoid)` predicts cat or dog.\n- There are **no convolution or pooling layers**.\n\nBecause spatial relationships between neighboring pixels are lost after `Flatten`, this model will normally perform worse than a CNN. That makes it useful as a simple baseline before introducing CNNs.\n","metadata":{}},{"id":"54fb666e","cell_type":"markdown","source":"## 13. Test a random image\n\nThis cell randomly selects one image from the Kaggle test set, runs the trained model, prints the result, and displays the image.\n","metadata":{}},{"id":"3e795e91","cell_type":"code","source":"# Randomly choose one test image\nrandom_row = test.sample(1).iloc[0]\nimage_path = random_row[\"filepath\"]\n\n# Load and resize image\nimg = tf.keras.utils.load_img(\n    image_path,\n    target_size=IMG_SIZE\n)\n\nimg_array = tf.keras.utils.img_to_array(img)\n\n# Add batch dimension: (64,64,3) -> (1,64,64,3)\nimg_batch = tf.expand_dims(img_array, axis=0)\n\n# Predict\nprediction = model.predict(img_batch, verbose=0)[0][0]\n\n# Convert probability to class\npredicted_class = \"dog\" if prediction >= 0.5 else \"cat\"\n\nprint(\"Test image:\", image_path)\nprint(f\"Probability of DOG: {prediction:.4f}\")\nprint(f\"Probability of CAT: {1 - prediction:.4f}\")\nprint(\"Prediction:\", predicted_class.upper())\n\n# Display image\nplt.figure(figsize=(4, 4))\nplt.imshow(img)\nplt.title(\n    f\"Prediction: {predicted_class.upper()}\\n\"\n    f\"P(dog) = {prediction:.3f}\"\n)\nplt.axis(\"off\")\nplt.show()\n","metadata":{},"outputs":[],"execution_count":null}]}