{"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"},"accelerator":"GPU","colab":{"collapsed_sections":["LOzvAnrFoGXt","_761dLeYbh6E"],"gpuType":"T4","provenance":[]},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":7327,"databundleVersionId":861871,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## **Dog Breed Identification**","metadata":{"id":"LOzvAnrFoGXt"}},{"cell_type":"markdown","source":"In this project, we will be building a deep learning model that classifies dog breeds from images. It’s a multi-class image classification task involving **120 distinct breeds** and **over 20,000 total images (train + test).**\n\n---\n\n### **Problem**\n\nThe main challenge is to **identify the breed of a dog given only its image**. Since many breeds look visually similar - think of a Labrador vs. a Golden Retriever, so the model needs to learn very fine-grained visual patterns to make accurate predictions.\n\nThis makes it an excellent example of how computer vision can tackle real-world image recognition problems that are often difficult for humans to distinguish at a glance.\n\n---\n\n### **Data**\n\nWe are using **[Dog Breed Identification](https://www.kaggle.com/competitions/dog-breed-identification)** datasset. The dataset includes:\n\n* A **training set** of over 10,000 labeled dog images, each associated with one of 120 breeds.\n* A **testing set** of over 10,000 images without labels - these are used to evaluate model predictions.\n* A **CSV file (`labels.csv`)** that maps each training image ID to its corresponding breed.\n\nThis dataset provides a strong foundation for applying modern deep learning techniques to solve a practical and visually complex classification problem.","metadata":{"id":"HZ8TgKLRoGFI"}},{"cell_type":"markdown","source":"---","metadata":{"id":"p4TLtKyb5w2Z"}},{"cell_type":"markdown","source":"## **Imports**\n\nBefore diving into data exploration or model building, we will start by importing all the necessary libraries. These include tools for **data handling**, **image processing**, **deep learning model development**, and **performance evaluation**.\n\nSetting up these imports ensures that our environment is ready and that we have everything we need to move seamlessly through the next stages of the project.\n","metadata":{"id":"YD8c9jPfkL8Q"}},{"cell_type":"code","source":"# For filtering warnings\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\n# Standard Python Libraries\nimport os\nimport datetime\n\n# Data Handling & Visualisation\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n# Machine Learning\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import train_test_split\n\n# Deep Learning\nimport tensorflow as tf\nfrom tensorflow.keras.applications import MobileNetV2, EfficientNetB3","metadata":{"id":"BL2-EmqGw6CX","outputId":"d708a8f3-585b-4b12-e27c-08e5e3a489d7","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:19:45.601007Z","iopub.execute_input":"2025-11-13T12:19:45.601309Z","iopub.status.idle":"2025-11-13T12:19:49.583973Z","shell.execute_reply.started":"2025-11-13T12:19:45.601270Z","shell.execute_reply":"2025-11-13T12:19:49.583333Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"9IC93G1jGaCz"}},{"cell_type":"markdown","source":"## **Project Configuration**\n\nBefore we start processing data or training our model, it’s useful to define all the key configurations in one place. This provides a clear foundation and makes the project easier to manage.\n\n* **Image parameters:** Defining input size, number of channels, and other image-related settings to be used throughout preprocessing and model training.\n* **Training constants:** Setting batch size, number of epochs, learning rate, and other hyperparameters for model training.\n* **Directory paths:** Organizing references to training and test folders, label files, and other resources to make data loading and management straightforward.\n\nBy centralizing these settings, we keep the project **organized, flexible, and easy to update**, which is especially helpful when experimenting with different models or hyperparameters.\n","metadata":{"id":"Z6WuLfkC7KtA"}},{"cell_type":"code","source":"# Configurations\nconfig = {\n\n    # Data\n    \"CLASS_INT_LABELS\": None,\n    \"CLASS_NAMES\": None,\n    \"TARGET_COLUMN\": \"encoded_breed\",\n    \"FEATURE_COLUMN\": \"image_path\",\n\n    # File paths\n    \"DATA_DIR\": \"/kaggle/input/dog-breed-identification/\",\n    \"SAVE_MODEL_TO\": \"/kaggle/working/saved-models/\",\n    \"PROJECT_NAME\": \"dob-breed-identification-classifier\",\n\n    # Split ratio\n    \"TRAIN_RATIO\": 0.80,\n    \"RANDOM_STATE\": 7,\n    \"N_SAMPLES_TO_USE\": 3000,\n\n    # Model Architecture\n    \"MOBILE_NET_IMG_SIZE\": 224,\n    \"EFFICIENT_NET_IMG_SIZE\": 300,\n    \"N_CHANNELS\": 3,\n    \"ACTIVATION_FUNC\": \"relu\",\n    \"DROPOUT_RATE_1\": 0.3,\n    \"DROPOUT_RATE_2\": 0.5,\n    \"DENSE_UNITS\": 256,\n    \"L2_REGULARISATION\": 0.01,\n    \"OUTPUT_ACTIVATION_FUNC\": \"softmax\",\n\n    # Training Parameters\n    \"BATCH_SIZE\": 32,\n    \"BUFFER_SIZE\": 500,\n    \"LEARNING_RATE\": 0.001,\n    \"N_EPOCHS\": 100,\n    \"LABEL_SMOOTHING\": 0.15,\n\n    # Callback Parameters\n    \"METRIC_TO_MONITOR\": \"val_loss\",\n    \"ES_PATIENCE\": 5,\n    \"LR_PATIENCE\": 2,\n    \"MIN_DELTA\": 0.001,\n    \"LR_FACTOR\": 0.5,\n    \"MIN_LR\": 1e-7,\n    \"RESTORE_BEST_WEIGHTS\": True\n\n}","metadata":{"id":"FN-GgVV48E72","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:19:49.584732Z","iopub.execute_input":"2025-11-13T12:19:49.585138Z","iopub.status.idle":"2025-11-13T12:19:49.590546Z","shell.execute_reply.started":"2025-11-13T12:19:49.585121Z","shell.execute_reply":"2025-11-13T12:19:49.589709Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"rRFuDC227Rny"}},{"cell_type":"markdown","source":"## **Loading & Exploring the Dataset**\n\nWith our environment and configurations ready, the next step is to **load the dataset and explore its structure**. This helps us understand what we are working with and highlights any potential issues before moving on to modeling.\n\nKey steps include:\n\n* **Load the label file:** Read the CSV that maps each image ID to its corresponding dog breed - this will serve as the foundation for our training labels.\n* **Preview the data:** Take a quick look at few images to understand the format, label distribution, and image IDs.\n* **Check for missing or duplicate entries:** Identify any inconsistencies or gaps in the data that might need attention before training.\n* **Inspect sample images:** Display a few images with their labels to get a feel for the variety and complexity of the dataset.\n* **Convert labels to numeric format:** Use `LabelEncoder` from scikit-learn to transform string breed names into integer values that the model can work with.\n\nBy the end of this step, we will have a **clear understanding of the dataset** and properly formatted labels, setting the stage for smooth preprocessing and effective model training.","metadata":{"id":"Ui-PJvOkDBDo"}},{"cell_type":"code","source":"# Function to load data from a csv file\ndef load_data(filename, config):\n    \"\"\"\n    Reads a CSV file and generates full file paths for training images.\n\n    Parameters:\n    - filename: Name of the CSV file.\n    - config: Configuration dictionary.\n\n    Returns:\n    - DataFrame with image file paths.\n    \"\"\"\n\n    # Full path to the CSV file\n    filepath = config.get(\"DATA_DIR\", \"\") + filename\n\n    # Reading data from the file\n    df = pd.read_csv(filepath)\n\n    # Generating full file path for each training image\n    df[config.get(\"FEATURE_COLUMN\", \"\")] = df[\"id\"].apply(\n        lambda id: os.path.join(config.get(\"DATA_DIR\", \"\"), \"train\", f\"{id}.jpg\")\n    )\n\n    return df","metadata":{"id":"UPErZHKCTxij","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:19:49.591445Z","iopub.execute_input":"2025-11-13T12:19:49.592046Z","iopub.status.idle":"2025-11-13T12:19:49.609338Z","shell.execute_reply.started":"2025-11-13T12:19:49.592021Z","shell.execute_reply":"2025-11-13T12:19:49.608681Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Loading data from the file: \"labels.csv\"\nlabels = load_data(\"labels.csv\", config)\nlabels.head()","metadata":{"id":"3zY-3GVOUIMW","outputId":"5948387f-b269-49f2-eeb6-d5355e428b9b","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:19:49.610345Z","iopub.execute_input":"2025-11-13T12:19:49.610512Z","iopub.status.idle":"2025-11-13T12:19:49.656199Z","shell.execute_reply.started":"2025-11-13T12:19:49.610497Z","shell.execute_reply":"2025-11-13T12:19:49.655326Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Checking for duplicate values in the dataset\nlabels.duplicated().sum()","metadata":{"id":"sK4TPaQnVzHD","outputId":"04ceeeff-dad6-4ea6-b8aa-be5129834021","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:19:49.657072Z","iopub.execute_input":"2025-11-13T12:19:49.657696Z","iopub.status.idle":"2025-11-13T12:19:49.669081Z","shell.execute_reply.started":"2025-11-13T12:19:49.657656Z","shell.execute_reply":"2025-11-13T12:19:49.668404Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Checking the top 10 dog breeds\nlabels[\"breed\"].value_counts().nlargest(10)","metadata":{"id":"X7jiDfTVV1ju","outputId":"2f8de4cf-938e-4a7f-de65-8a95ada29656","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:19:49.671625Z","iopub.execute_input":"2025-11-13T12:19:49.672117Z","iopub.status.idle":"2025-11-13T12:19:49.680391Z","shell.execute_reply.started":"2025-11-13T12:19:49.672099Z","shell.execute_reply":"2025-11-13T12:19:49.679589Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Checking the bottom 10 dog breeds\nlabels[\"breed\"].value_counts().nsmallest(10)","metadata":{"id":"y88TVOvw7NRh","outputId":"2a65fb81-ba2b-46d9-cc39-de2c9f8896a6","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:19:49.681126Z","iopub.execute_input":"2025-11-13T12:19:49.681423Z","iopub.status.idle":"2025-11-13T12:19:49.699945Z","shell.execute_reply.started":"2025-11-13T12:19:49.681394Z","shell.execute_reply":"2025-11-13T12:19:49.699127Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Checking what is the average number of images for each class\nlabels[\"breed\"].value_counts().median()","metadata":{"id":"Y8_iaLPA7NOp","outputId":"2a084edd-7612-4cc0-c3b3-e0bc1cc714c3","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:19:49.701139Z","iopub.execute_input":"2025-11-13T12:19:49.701415Z","iopub.status.idle":"2025-11-13T12:19:49.714458Z","shell.execute_reply.started":"2025-11-13T12:19:49.701385Z","shell.execute_reply":"2025-11-13T12:19:49.713805Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Checking if the number of image_path matches the number of actual image files\nif len(os.listdir(os.path.join(config.get(\"DATA_DIR\", \"\"), \"train\"))) == len(labels[\"image_path\"]):\n    print(\"File check passed: Number of filenames matches the number of image files. Good to proceed.\")\nelse:\n    print(\"File check failed: Mismatch between filenames and image files. Please verify.\")","metadata":{"id":"wmc1LSQIWJ1o","outputId":"955d6c6c-ca94-46fd-e0d6-c0a26390dff9","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:19:49.715247Z","iopub.execute_input":"2025-11-13T12:19:49.715515Z","iopub.status.idle":"2025-11-13T12:19:49.732599Z","shell.execute_reply.started":"2025-11-13T12:19:49.715488Z","shell.execute_reply":"2025-11-13T12:19:49.731853Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"fWy5GvFoYBGX"}},{"cell_type":"markdown","source":"### **Visualizing Sample Images**\n\nBefore diving into preprocessing and modeling, it’s helpful to look at a few sample images. This gives us an intuitive sense of the data, highlights the variety in poses, backgrounds, and lighting, and ensures the dataset is ready for training.","metadata":{"id":"CPfFzjPSYDfm"}},{"cell_type":"code","source":"# Setting figure size\nplt.figure(figsize = (12, 5))\nplt.imshow(\n    # Reading an image (normalized)\n    plt.imread(labels.loc[9000, \"image_path\"]) / 255.0\n)\nplt.title(\" \".join(labels.loc[9000, \"breed\"].split(\"_\")).title())\nplt.axis(\"off\");","metadata":{"id":"p9bZ1ib2XBoB","outputId":"3d8468a2-da57-4144-f751-5c7a66edef7d","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:19:49.733450Z","iopub.execute_input":"2025-11-13T12:19:49.733789Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"SZ7uhgnRX_1S"}},{"cell_type":"markdown","source":"### **Encoding Dog Breeds**\n\nBefore training the model, we need to **convert the dog breed names into numerical labels**. This encoding transforms the string breed names into a format the model can work with efficiently, allowing it to learn to classify each breed correctly.","metadata":{"id":"r135tJfLYNOf"}},{"cell_type":"code","source":"# Creating an instance of LabelEncoder\nencoder = LabelEncoder()","metadata":{"id":"nv9EpWRwYPen","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Encoding dog breeds\nlabels[config.get(\"TARGET_COLUMN\", \"\")] = encoder.fit_transform(labels[\"breed\"])\nlabels.head()","metadata":{"id":"OK0Wg5CEYc38","outputId":"c0fb9a45-1fd1-4e7d-b3bc-becbd90f6535","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Setting \"CLASS_NAMES\", and \"CLASS_INT_LABELS\"\nconfig[\"CLASS_INT_LABELS\"] = labels[\"encoded_breed\"].sort_values().unique()\nconfig[\"CLASS_NAMES\"] = encoder.inverse_transform(config.get(\"CLASS_INT_LABELS\", []))","metadata":{"id":"AApvx1HCZqHD","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"XlZdi2kQ7M4x"}},{"cell_type":"markdown","source":"## **Data Preprocessing**\n\nBefore feeding images into our model, we need to **preprocess them so they are in the right format**. We will create a function to handle this step-by-step, ensuring consistency across all images.\n\nThe preprocessing function will:\n\n* **Accept an image file path:** Load images directly from their file locations.\n* **Read the image file:** Use TensorFlow utilities to read the raw image data.\n* **Decode to a tensor:** Convert the JPEG image into a 3D tensor representing height, width, and color channels.\n* **Resize to a fixed shape:** Standardize all images, as deep learning models generally require consistent input dimensions.\n* **Return the processed tensor:** Output a ready-to-use image tensor for training or evaluation.\n\nThis preprocessing ensures that all images have a **consistent size**, which helps the model learn efficiently and improves overall performance.\n","metadata":{"id":"NSEdS62CeBJd"}},{"cell_type":"code","source":"# Function to preprocess images\ndef preprocess_images(filepath, img_size):\n    \"\"\"\n    Reads and preprocesses an image based on the model requirements.\n\n    Parameters:\n    - filepath: Path to the image file.\n    - img_size: Target size to resize the image, e.g., (224, 224) or (300, 300).\n\n    Returns:\n    - Preprocessed image tensor.\n    \"\"\"\n\n    # Reading image\n    img = tf.io.read_file(filepath)\n\n    # Decoding JPEG image\n    img = tf.image.decode_jpeg(img, channels=config.get(\"N_CHANNELS\", 3))\n\n    # Resizing image\n    img = tf.image.resize(img, [img_size, img_size])\n\n    return img","metadata":{"id":"vTazzvAtsVgH","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to preprocess image and convert label to one-hot encoding\ndef get_processed_image_and_label(image_path, label, img_size, total_classes=len(config.get(\"CLASS_NAMES\", []))):\n    \"\"\"\n    Loads and preprocesses an image, and converts its label to one-hot encoding.\n\n    Parameters:\n    - image_path: Path to the image file.\n    - label: Integer label for the image.\n    - img_size: Target size to resize the image, e.g., (224, 224) or (300, 300).\n    - total_classes: Total number of classes for one-hot encoding.\n\n    Returns:\n    - Tuple of (preprocessed image tensor, one-hot encoded label).\n    \"\"\"\n\n    # Preprocessing the image\n    image = preprocess_images(image_path, img_size)\n\n    # Converting label to one-hot encoding\n    label = tf.one_hot(label, depth = total_classes)\n    return (image, label)","metadata":{"id":"pENIcuPGg-wb","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"vHTqB-Mam3Ld"}},{"cell_type":"markdown","source":"## **Creating the Dataset**\n\nWorking with large image datasets requires **efficient organization** to avoid memory issues and ensure smooth model training. We will set up a structured data pipeline that prepares the images and labels in batches for training.\n\nKey steps in creating the dataset include:\n\n* **Batching data:** Split the dataset into smaller groups (e.g., batches of 32 images) to manage memory efficiently and allow the model to update weights more frequently.\n* **Create (image, label) pairs:** Pair each image with its corresponding label so the model can learn the correct classifications during training.\n* **Define a dataset function:** Build a function that takes image paths and labels, loads and preprocesses each image, and pairs it with its label.\n* **Batch the dataset:** Organize the data into batches of a fixed size for consistent memory usage and training efficiency.\n* **Shuffle training data:** Randomly shuffle batches during training to help the model learn more generalized features and reduce overfitting.\n* **Prepare training and validation sets:** Apply this process to both the training and validation splits to ensure uniform preprocessing and batching.\n\nThis structured approach creates a **memory-efficient, well-organized data pipeline**, enabling the model to train effectively on manageable chunks of data.\n","metadata":{"id":"Ad2BuojFFxvQ"}},{"cell_type":"code","source":"# Splitting data into X and y\nX = labels[config.get(\"FEATURE_COLUMN\", \"\")]\ny = labels[config.get(\"TARGET_COLUMN\", \"\")]","metadata":{"id":"xF3MdpfUcKlV","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to create a TensorFlow dataset for training, validation, or testing\ndef create_dataset(X, y=None, model_name=None, config=config, dataset_type=\"train\", shuffle=True):\n    \"\"\"\n    Creates a TensorFlow dataset for training, validation, or testing.\n\n    Parameters:\n    - X: List or array of image file paths.\n    - y: Labels corresponding to X.\n    - model_name: Name of the model to load (e.g., 'EfficientNetB0', 'MobileNetV2').\n    - config: Configuration dictionary.\n    - dataset_type: One of \"train\", \"val\", or \"test\".\n    - shuffle: Whether to shuffle the dataset (only applies to training).\n\n    Returns:\n    - A tf.data.Dataset object.\n    \"\"\"\n\n    if \"efficientnet\" in model_name.lower():\n        # Input shape for EfficientNetV2\n        img_size = config.get(\"EFFICIENT_NET_IMG_SIZE\", 300)\n\n    elif \"mobilenet\" in model_name.lower():\n        # Input shape for MobileNetV2\n        img_size = config.get(\"MOBILE_NET_IMG_SIZE\", 224)\n    \n    # Training & Validation Dataset\n    if dataset_type.lower() in [\"train\", \"training\", \"val\", \"validation\"] and y is not None:\n        dataset = tf.data.Dataset.from_tensor_slices((X, y))\n        dataset = dataset.map(\n            lambda img_path, label: get_processed_image_and_label(img_path, label, img_size),\n            num_parallel_calls = tf.data.AUTOTUNE,\n            deterministic=True\n        )\n\n        # For training dataset\n        if shuffle and dataset_type.lower() in [\"train\", \"training\"]:\n            dataset = dataset.shuffle(\n                buffer_size = config.get(\"BUFFER_SIZE\", 500),\n                reshuffle_each_iteration = True,\n                seed = config.get(\"RANDOM_STATE\", 7)\n            )\n\n    # Testing Dataset\n    elif dataset_type.lower() in [\"test\", \"testing\"] and y is None:\n        dataset = tf.data.Dataset.from_tensor_slices(X)\n        dataset = dataset.map(\n            lambda img_path: preprocess_images(img_path, img_size),\n            num_parallel_calls = tf.data.AUTOTUNE,\n            deterministic=True\n        )\n\n    else:\n        raise ValueError(\n            \"Invalid dataset_type or label configuration. Please verify your inputs.\"\n        )\n\n    # Batching, Caching and Prefetching\n    dataset = dataset.batch(batch_size = config.get(\"BATCH_SIZE\", 32)).prefetch(tf.data.AUTOTUNE)\n\n    return dataset","metadata":{"id":"Mb7MOpsZi6ir","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:41:41.675281Z","iopub.execute_input":"2025-11-13T14:41:41.675926Z","iopub.status.idle":"2025-11-13T14:41:41.683277Z","shell.execute_reply.started":"2025-11-13T14:41:41.675898Z","shell.execute_reply":"2025-11-13T14:41:41.682411Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to prepare training and validation datasets\ndef prepare_train_val_datasets(X, y, model_name, config=config):\n    \"\"\"\n    Splits the data into training and validation sets, then creates TensorFlow datasets for model training and evaluation.\n\n    Parameters:\n    - X: Array of image file paths.\n    - y: Array of corresponding labels for each image.\n    - model_name: Name of the model to load (e.g., 'EfficientNetB0', 'MobileNetV2').\n    - config: Configuration dictionary.\n\n    Returns:\n    - tuple: (train_dataset, val_dataset).\n    \"\"\"\n\n    # Train-Validation split\n    X_train, X_val, y_train, y_val = train_test_split(\n        X,\n        y,\n        train_size = config.get(\"TRAIN_RATIO\", 0.80),\n        random_state = config.get(\"RANDOM_STATE\", 7),\n        stratify=y  # To ensure the class distribution\n    )\n\n    # Printing dataset sizes\n    print(f\"# Dataset sizes:\")\n    print(f\"1. {'Training':<12}: {X_train.shape, y_train.shape}\")\n    print(f\"2. {'Validation':<12}: {X_val.shape, y_val.shape}\")\n\n    # Creating TensorFlow dataset for the training set with optional shuffling\n    train_dataset = create_dataset(\n        X = X_train,\n        y = y_train,\n        model_name = model_name,\n        config = config,\n        dataset_type = \"train\",\n        shuffle = True\n    )\n\n    # Create TensorFlow dataset for the validation set without shuffling\n    val_dataset = create_dataset(\n        X = X_val,\n        y = y_val,\n        model_name = model_name,\n        config = config,\n        dataset_type = \"val\",\n        shuffle = False\n    )\n\n    return train_dataset, val_dataset\n","metadata":{"id":"4Uqj6IXQgkYY","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"encxNbQm7KoP"}},{"cell_type":"markdown","source":"## **Defining Model Architecture**\n\nWith the data prepared and configurations set, the next step is to **define our deep learning model architecture**. We will leverage **transfer learning** by building on a pretrained base model, which helps accelerate training and improve performance on this fine-grained classification task.\n\nKey steps include:\n\n* **Load the base model:** Set up a pretrained model, such as `MobileNetV2` or `EfficientNetB3`, excluding its top layers so we can add a custom classification head.\n* **Add custom layers:** Build additional layers - like global pooling, fully connected (dense) layers, and dropout - to tailor the model specifically for dog breed classification.\n* **Wrap in reusable functions:** Organize these steps into modular functions for cleaner, more flexible code that can be easily updated or extended later.\n\nBy structuring the model this way, we create a **scalable and adaptable architecture**, ready for training on our dataset.\n","metadata":{"id":"Q_cEFfqp7Klp"}},{"cell_type":"code","source":"# Function to load the base model\ndef load_base_model(model_name, input_shape, config=config):\n\n    \"\"\"\n    Load a pre-trained base model without the top classification layer.\n\n    Parameters:\n    - model_name: Name of the model to load (e.g., 'EfficientNetB0', 'MobileNetV2').\n    - input_shape: The shape of input images.\n    - config: Configuration dictionary.\n\n    Returns:\n    - tf.keras.Model: Base model ready for fine-tuning.\n    \"\"\"\n\n    # Output shape\n    output_shape = len(config.get(\"CLASS_NAMES\", []))\n\n    # MobileNetV2\n    if \"mobilenet\" in model_name.lower():\n        base_model = MobileNetV2(\n            input_shape = input_shape,\n            include_top = False,\n            weights = \"imagenet\"\n        )\n\n    # EfficientNetB3\n    elif \"efficientnet\" in model_name.lower():\n        base_model = EfficientNetB3(\n            input_shape = input_shape,\n            include_top = False,\n            weights = \"imagenet\"\n        )\n\n    else:\n        # Raise error if model name is not correct\n        raise ValueError(f\"Unknown model name: {model_name.lower()}. Expected one of ('mobilenet', or 'efficientnet'.\")\n\n    # Freezing the base model layers\n    base_model.trainable = False\n\n    return base_model","metadata":{"id":"vYefGFpao4Ec","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to create and compile a model\ndef create_model(base_model, input_shape, config=config):\n    \"\"\"\n    Creates and compiles a model using a base model.\n\n    Parameters:\n    - base_model: The base pre-trained model.\n    - input_shape: The shape of input images.\n    - config: Configuration dictionary.\n\n    Returns:\n    - tf.keras.Model: Compiled model ready for training.\n    \"\"\"\n    print(f\"Building model with base from: {base_model.name}\")\n\n    # Number of output units\n    output_units = len(config.get(\"CLASS_NAMES\", []))\n\n    # Input Layer\n    input = tf.keras.layers.Input(shape = input_shape)\n\n    # Data augmentation\n    x = tf.keras.layers.RandomFlip(\"horizontal\", seed= config.get(\"RANDOM_STATE\", 7))(input, training = True)     # Random horizontal flip\n    x = tf.keras.layers.RandomRotation(0.15, seed=config.get(\"RANDOM_STATE\", 7))(x, training = True)              # Random rotation\n    x = tf.keras.layers.RandomZoom(0.15, seed=config.get(\"RANDOM_STATE\", 7))(x, training = True)                  # Random zoom\n    x = tf.keras.layers.RandomContrast(0.15, seed=config.get(\"RANDOM_STATE\", 7))(x, training = True)              # Random contrast\n    x = tf.keras.layers.RandomBrightness(0.15, seed=config.get(\"RANDOM_STATE\", 7))(x, training = True)            # Random brightness\n\n\n    # Rescale pixel values if the model is not EfficientNet\n    if \"efficientnet\" not in base_model.name.lower():\n        x = tf.keras.layers.Rescaling(1./255)(x)\n\n    # Pre-trained base model\n    x = base_model(x, training = False)\n\n    # Global Average Pooling Layer\n    x = tf.keras.layers.GlobalAveragePooling2D()(x)\n\n    # Dropout Layer\n    x = tf.keras.layers.Dropout(\n        rate = config.get(\"DROPOUT_RATE_1\", 0.3),\n        seed = config.get(\"RANDOM_STATE\", 7)\n    )(x)\n\n    # Dense Layer\n    x = tf.keras.layers.Dense(\n        units = config.get(\"DENSE_UNITS\", 256) // 2,\n        activation = config.get(\"ACTIVATION_FUNC\", \"relu\"),\n        kernel_regularizer = tf.keras.regularizers.l2(config.get(\"L2_REGULARISATION\", 0.01))\n    )(x)\n\n    # Dropout Layer\n    x = tf.keras.layers.Dropout(\n        rate = config.get(\"DROPOUT_RATE_2\", 0.5),\n        seed = config.get(\"RANDOM_STATE\", 7)\n    )(x)\n\n    # Output Layer\n    output = tf.keras.layers.Dense(\n        units = output_units,\n        activation = \"softmax\",\n    )(x)\n\n    # Creating model\n    model = tf.keras.Model(\n        inputs = input,\n        outputs = output,\n        name = f\"{base_model.name}_dog_classifier\"\n    )\n\n    # Compiling the model\n    model.compile(\n        optimizer = tf.keras.optimizers.Adam(\n            learning_rate = config.get(\"LEARNING_RATE\", 0.001)\n        ),\n        loss = tf.keras.losses.CategoricalCrossentropy(\n            label_smoothing = config.get(\"LABEL_SMOOTHING\", 0.15)\n        ),\n        metrics=[\n            \"accuracy\",\n            tf.keras.metrics.TopKCategoricalAccuracy(k = 3, name = \"top_3_accuracy\"),\n            tf.keras.metrics.TopKCategoricalAccuracy(k = 5, name = \"top_5_accuracy\")\n        ]\n    )\n\n    return model","metadata":{"id":"WSBhZT1oHp7P","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"oy0RgazRmcvH"}},{"cell_type":"markdown","source":"## **Creating Model Callbacks**\n\nTo make training more **efficient and effective**, we will use **callbacks** - special functions that monitor the model during training and adjust its behavior automatically.\n\nHere we will set up two key callbacks:\n\n* **Early Stopping:** Stops training if the model’s performance stops improving, helping prevent overfitting and saving unnecessary training time.\n* **Reduce Learning Rate on Plateau:** Lowers the learning rate when the model’s performance metric stagnates for several epochs, allowing the model to continue improving.\n\nThese callbacks help the model **train smarter**, ensuring we get the best results without wasting resources or overfitting.","metadata":{"id":"cf-g_TFzBZxC"}},{"cell_type":"code","source":"# EarlyStopping callback\nearly_stopping = tf.keras.callbacks.EarlyStopping(\n    monitor = config.get(\"METRIC_TO_MONITOR\", \"val_loss\"),\n    min_delta = config.get(\"MIN_DELTA\", 0.005),\n    patience = config.get(\"ES_PATIENCE\", 5),\n    verbose = 1,\n    restore_best_weights = config.get(\"RESTORE_BEST_WEIGHTS\", True)\n)","metadata":{"id":"4qcrX8PYCZS7","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ReduceLROnPlateau callback\nreduce_lr = tf.keras.callbacks.ReduceLROnPlateau(\n    monitor = config.get(\"METRIC_TO_MONITOR\", \"val_loss\"),\n    factor = config.get(\"LR_FACTOR\", 0.5),\n    patience = config.get(\"LR_PATIENCE\", 2),\n    verbose = 1,\n    min_delta = config.get(\"MIN_DELTA\", 0.005),\n    min_lr = config.get(\"MIN_LR\", 1e-7),\n)","metadata":{"id":"R00o_WkzCOLo","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"vuz4lgBbCOG8"}},{"cell_type":"markdown","source":"## **Model Training (Subset of Data)**\n\nBefore committing to the full dataset, we will **start by training on a smaller subset of 3,000 images**.\n\nThis allows us to quickly **verify that the training pipeline is working correctly**, including data loading, preprocessing, and model updates, before scaling up to the full dataset. It’s a practical way to catch issues early and ensure a smooth training process on the complete data.\n","metadata":{"id":"jiT4tQkYDp7y"}},{"cell_type":"code","source":"# Function to train the model\ndef train_model(base_model, training_dataset, num_epochs, validation_dataset, callbacks):\n    \"\"\"\n    Creates and trains a model using the provided datasets and callbacks.\n\n    Parameters:\n    - base_model: The base pre-trained model.\n    - training_dataset: Dataset for training the model.\n    - num_epochs: Number of epochs to train.\n    - validation_dataset: Dataset for validation during training.\n    - callbacks: List of callbacks to use during training.\n\n    Returns:\n    - model: The trained model and training history.\n    \"\"\"\n\n    if \"efficientnet\" in base_model.lower():\n        # Input shape for EfficientNetV2\n        input_shape = (\n            config.get(\"EFFICIENT_NET_IMG_SIZE\", 300),\n            config.get(\"EFFICIENT_NET_IMG_SIZE\", 300),\n            config.get(\"N_CHANNELS\", 3)\n        )\n\n    elif \"mobilenet\" in base_model.lower():\n        # Input shape for MobileNetV2\n        input_shape = (\n            config.get(\"MOBILE_NET_IMG_SIZE\", 224),\n            config.get(\"MOBILE_NET_IMG_SIZE\", 224),\n            config.get(\"N_CHANNELS\", 3)\n        )\n\n\n    # Loading the base model and creating the full model\n    base_model = load_base_model(base_model, input_shape)\n    model = create_model(base_model, input_shape)\n\n    # Training the model\n    history = model.fit(\n        training_dataset,\n        epochs = num_epochs,\n        validation_data = validation_dataset,\n        callbacks = callbacks,\n        verbose=1,\n    )\n\n    # Return the trained model\n    return model, history","metadata":{"id":"2zIwlyllE8_T","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"60_0UM7Df644"}},{"cell_type":"markdown","source":"### **Training MobileNetV2 Model**\n\nWe will begin by **training the `MobileNetV2` model on a small subset of the data**.\n\nThis step helps us **evaluate how well the model performs** on a manageable set before scaling up to the full dataset, allowing us to identify any potential issues early and fine-tune our training setup if needed.","metadata":{"id":"RB0ocowXf62k"}},{"cell_type":"code","source":"# Creating training and validation datasets for MobileNetV2 model\ntrain_dataset_mbnetv2, val_dataset_mbnetv2 = prepare_train_val_datasets(\n    X = X[:config.get(\"N_SAMPLES_TO_USE\", 3000)],\n    y = y[:config.get(\"N_SAMPLES_TO_USE\", 3000)],\n    model_name = \"MobileNetV2\"\n)","metadata":{"id":"eSI6sCpLg2lv","outputId":"c38d7b1f-0d1d-4ddc-8924-d674bef393cd","trusted":true,"execution":{"iopub.status.idle":"2025-11-13T12:19:51.716086Z","shell.execute_reply.started":"2025-11-13T12:19:50.310841Z","shell.execute_reply":"2025-11-13T12:19:51.715144Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training MobileNetV2 model\nmbnetv2_model, mbnetv2_model_history = train_model(\n    base_model = \"MobileNetV2\",\n    training_dataset = train_dataset_mbnetv2,\n    num_epochs = config.get(\"N_EPOCHS\", 100),\n    validation_dataset = val_dataset_mbnetv2,\n    callbacks = [early_stopping, reduce_lr]\n)","metadata":{"id":"4mmRi43OoAu5","outputId":"2703e221-866d-427a-fef6-f8d7609c9662","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:19:51.717053Z","iopub.execute_input":"2025-11-13T12:19:51.717697Z","iopub.status.idle":"2025-11-13T12:25:09.379705Z","shell.execute_reply.started":"2025-11-13T12:19:51.717669Z","shell.execute_reply":"2025-11-13T12:25:09.379107Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"d0R6d8mJfzkq"}},{"cell_type":"markdown","source":"### **Training EfficientNetB3 Model**\n\nNext, we will **train the `EfficientNetB3` model on the same subset of data**.\n\nThis allows us to **compare its performance with `MobileNetV2`** and determine which model is better suited for the full dataset before committing to longer training sessions.","metadata":{"id":"FYfze458f065"}},{"cell_type":"code","source":"# Creating training and validation datasets for EfficientNetB3 model\ntrain_dataset_efnetb3, val_dataset_efnetb3 = prepare_train_val_datasets(\n    X = X[:config.get(\"N_SAMPLES_TO_USE\", 3000)],\n    y = y[:config.get(\"N_SAMPLES_TO_USE\", 3000)],\n    model_name = \"EfficientNetB3\"\n)","metadata":{"id":"xgZaaITnhQFR","outputId":"69e6ec1b-98c1-431b-a0e8-103e6f19f851","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:25:09.380606Z","iopub.execute_input":"2025-11-13T12:25:09.380829Z","iopub.status.idle":"2025-11-13T12:25:09.440709Z","shell.execute_reply.started":"2025-11-13T12:25:09.380811Z","shell.execute_reply":"2025-11-13T12:25:09.440098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training EfficientNetB3 model\nefnetb3_model, efnetb3_model_history = train_model(\n    base_model = \"EfficientNetB3\",\n    training_dataset = train_dataset_efnetb3,\n    num_epochs = config.get(\"N_EPOCHS\", 100),\n    validation_dataset = val_dataset_efnetb3,\n    callbacks = [early_stopping, reduce_lr]\n)","metadata":{"id":"DWxZCMiC8pt1","outputId":"7d5e3e48-c21e-4325-f2df-e72fb6cffa15","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:25:09.441435Z","iopub.execute_input":"2025-11-13T12:25:09.441657Z","iopub.status.idle":"2025-11-13T12:51:20.491482Z","shell.execute_reply.started":"2025-11-13T12:25:09.441640Z","shell.execute_reply":"2025-11-13T12:51:20.490841Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"776ABQbQ6Jq7"}},{"cell_type":"markdown","source":"## **Making Predictions**\n\nWith our model trained and ready, the next step is to **use it to make predictions on new images**.\n\nThis involves feeding unseen images through the model to obtain **breed probability scores**, allowing us to see how well the model can classify dogs it hasn’t seen before.\n","metadata":{"id":"KEXgxasioBek"}},{"cell_type":"code","source":"# Function to get the predicted label from probabilities\ndef get_predicted_class_label(prediction_probabilities, config = config):\n    \"\"\"\n    Converts prediction probabilities into their corresponding class label.\n\n    Parameters:\n    - prediction_probabilities: Array of prediction probabilities.\n    - config: Configuration dictionary.\n\n    Returns:\n    - str: The formatted label corresponding to the highest prediction probability.\n    \"\"\"\n\n    # Index of the highest probability\n    predicted_index = prediction_probabilities.argmax()\n\n    # Label associated with the predicted index\n    label = \" \".join((config.get(\"CLASS_NAMES\", [])[predicted_index]).split(\"_\")).title()\n\n    return label","metadata":{"id":"dSy-Y1_vOlXk","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:51:46.149826Z","iopub.execute_input":"2025-11-13T12:51:46.150457Z","iopub.status.idle":"2025-11-13T12:51:46.155032Z","shell.execute_reply.started":"2025-11-13T12:51:46.150431Z","shell.execute_reply":"2025-11-13T12:51:46.154093Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Making predictions on the validation dataset\nmbnetv2_predictions = mbnetv2_model.predict(val_dataset_mbnetv2)\nefnetb3_predictions = efnetb3_model.predict(val_dataset_efnetb3)","metadata":{"id":"sxQzOJg5RQI9","outputId":"fd7ec5cd-7e48-443a-a94e-7731aee9f55f","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:51:47.614459Z","iopub.execute_input":"2025-11-13T12:51:47.615195Z","iopub.status.idle":"2025-11-13T12:52:03.548041Z","shell.execute_reply.started":"2025-11-13T12:51:47.615171Z","shell.execute_reply":"2025-11-13T12:52:03.547444Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"tz_z4gFe5ysW"}},{"cell_type":"markdown","source":"## **Unbatching Validation Data for Evaluation**\n\nTo gain a **clearer view of model performance on individual images**, we will convert the validation data from batches into separate samples.\n\nThis allows us to **evaluate predictions and metrics at the image level**, making it easier to spot patterns, strengths, or areas where the model may struggle.","metadata":{"id":"p3UPBZG7T7pO"}},{"cell_type":"code","source":"# Function to unbatch the data\ndef unbatchify(data):\n    \"\"\"\n    Unbatches a dataset and extracts images and label names.\n\n    Parameters:\n    - data: A batched TensorFlow dataset containing image and label pairs.\n\n    Returns:\n    - Tuple of two lists:\n        - images: List of unbatched image tensors.\n        - labels: List of label strings.\n    \"\"\"\n\n    # Lists to store the images and labels\n    images = []\n    labels = []\n\n    # Looping through the images and labels\n    for image, label in data.unbatch():\n\n        # Appending the data\n        images.append(image)\n        labels.append(\" \".join(config.get(\"CLASS_NAMES\", [])[np.argmax(label)].split(\"_\")).title())\n\n    return images, labels","metadata":{"id":"OcXT3rmHT65l","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:52:05.828010Z","iopub.execute_input":"2025-11-13T12:52:05.828294Z","iopub.status.idle":"2025-11-13T12:52:05.833165Z","shell.execute_reply.started":"2025-11-13T12:52:05.828274Z","shell.execute_reply":"2025-11-13T12:52:05.832271Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Separating images and labels\nimages_mbnetv2, labels_mbnetv2 = unbatchify(val_dataset_mbnetv2)\nimages_efnetb3, labels_efnetb3 = unbatchify(val_dataset_efnetb3)","metadata":{"id":"Gz3uXDdFoBN5","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:52:07.802801Z","iopub.execute_input":"2025-11-13T12:52:07.803484Z","iopub.status.idle":"2025-11-13T12:52:09.091243Z","shell.execute_reply.started":"2025-11-13T12:52:07.803462Z","shell.execute_reply":"2025-11-13T12:52:09.090655Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"NpJtd4Nw6qCi"}},{"cell_type":"markdown","source":"## **Visualizing Model Predictions**\n\nTo better understand how our model is making predictions, we will **create visualizations that combine images with prediction data**. This helps us see not only what the model predicts, but also **how confident it is in those predictions** and whether it gets them right.\n\nThe visualizations will include:\n\n* **Image with predicted and true label:** Display a selected image along with its predicted breed, the true breed, and the model’s confidence in the prediction.\n* **Top-n prediction probabilities:** Show a bar chart of the top-n most likely breeds predicted by the model, giving insight into its uncertainty even if the top prediction is incorrect.\n* **Grid of multiple predictions:** Display a grid of images with predicted labels, confidence scores, and top-n predictions to get a broader view of the model’s behavior.\n\nThese visualizations allow us to **assess the model’s performance on individual samples, spot common misclassifications, and understand confidence levels across different breeds**, providing valuable insights into its strengths and limitations.\n","metadata":{"id":"fnnxe8TvWCjq"}},{"cell_type":"code","source":"# Function to plot the image along with predicted and true labels\ndef plot_image_with_prediction(prediction_probs, images, true_labels, sample_index=7):\n    \"\"\"\n    Plot a single image along with its predicted and true labels.\n\n    Parameters:\n    - prediction_probabilities: Array of prediction probabilities for all images.\n    - images: List of image tensors.\n    - true_labels: List of true class label strings.\n    - sample_index: Index of the sample to plot.\n\n    Returns: None\n    \"\"\"\n\n    # Extracting prediction, image, and true label for the specified index\n    sample_prediction = prediction_probs[sample_index]\n    sample_image = images[sample_index]\n    sample_true_label = true_labels[sample_index]\n\n    # Getting predicted class label\n    predicted_label = get_predicted_class_label(sample_prediction)\n\n    # Normalising the sample image if pixel values are not in [0,1] range\n    if np.max(sample_image) > 1:\n        sample_image = sample_image/255.0\n\n    # Setting title color\n    title_color = \"green\" if predicted_label == sample_true_label else \"red\"\n\n    # Plotting the image\n    plt.imshow(sample_image)\n    plt.axis(\"off\")\n\n    # Prediction confidence\n    prediction_confidence = np.max(sample_prediction) * 100\n\n    # Set the plot title\n    plt.title(\n        f\"Predicted: {predicted_label} \\nTrue: {sample_true_label} \\nConfidence:{prediction_confidence:.2f}%\",\n        color = title_color,\n        fontsize = 12\n    )\n","metadata":{"id":"wsQESqNKWCKZ","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:52:11.508958Z","iopub.execute_input":"2025-11-13T12:52:11.509597Z","iopub.status.idle":"2025-11-13T12:52:11.515106Z","shell.execute_reply.started":"2025-11-13T12:52:11.509568Z","shell.execute_reply":"2025-11-13T12:52:11.514194Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to plot the top 10 highest prediction probabilties along with the true label\ndef plot_top_prediction_confidences(prediction_probs, true_labels, n, sample_index=7):\n    \"\"\"\n    Plots the top 10 predicted class probabilities for a given sample index, highlighting the true label in green.\n\n    Parameters:\n    - prediction_probs: Array of prediction probabilities (batch_size x num_classes).\n    - true_labels: List or array of true class labels.\n    - n: Number of top predictions to display.\n    - sample_index: Index of the sample to plot.\n\n    Returns: None\n    \"\"\"\n\n    # Extracting prediction probabilities and true label for the specified sample\n    sample_prediction = prediction_probs[sample_index]\n    sample_true_label = true_labels[sample_index]\n\n    # Getting indices of the top 10 predicted class probabilities\n    top_n_indices = sample_prediction.argsort()[-n:][::-1]\n    top_n_probabilities = sample_prediction[top_n_indices]\n    top_n_class_names = [\" \".join(label.split(\"_\")).title() for label in config.get(\"CLASS_NAMES\", [])[top_n_indices]]\n\n    # Plotting the top 10 class probabilities as a bar chart\n    bars = plt.bar(range(len(top_n_indices)), top_n_probabilities, color = \"grey\")\n    plt.xticks(range(len(top_n_class_names)), labels = top_n_class_names, rotation = \"vertical\")\n\n    # Highlighting the bar for the true label\n    if sample_true_label in top_n_class_names:\n        true_label_index = top_n_class_names.index(sample_true_label)\n        bars[true_label_index].set_color(\"green\")\n","metadata":{"id":"KTxylUtOarJK","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:52:13.669108Z","iopub.execute_input":"2025-11-13T12:52:13.669386Z","iopub.status.idle":"2025-11-13T12:52:13.675550Z","shell.execute_reply.started":"2025-11-13T12:52:13.669366Z","shell.execute_reply":"2025-11-13T12:52:13.674580Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to display a grid of images with predicted labels and their top prediction confidence score.\ndef display_prediction_grid(n, prediction_probs, images, true_labels, start_index=0, num_rows=3, num_cols=2):\n    \"\"\"\n    Displays a grid of images with their predicted labels and top prediction confidences.\n\n    Parameters:\n    - n: Number of top predictions to display.\n    - prediction_probs: Array of prediction probabilities.\n    - images: List of image tensors.\n    - true_labels: List of true label strings.\n    - start_index: Index to start displaying from.\n    - num_rows: Number of rows in the grid.\n    - num_cols: Number of columns in the grid.\n\n    Returns: None\n    \"\"\"\n\n    # Total number of images to display\n    num_images = num_rows * num_cols\n\n    # Setting the overall figure size\n    plt.figure(figsize=(12 * num_cols, 5 * num_rows))\n\n    # Looping through the images\n    for i in range(num_images):\n        sample_index = start_index + i\n\n        # Plotting the image with its predicted and true label\n        plt.subplot(num_rows, num_cols * 2, 2 * i + 1)\n        plot_image_with_prediction(prediction_probs, images, true_labels, sample_index)\n\n        # Plotting the bar chart showing top 10 prediction confidences\n        plt.subplot(num_rows, num_cols * 2, 2 * i + 2)\n        plot_top_prediction_confidences(prediction_probs, true_labels, n, sample_index)\n\n    # Adjusting layout to prevent overlap\n    plt.tight_layout()\n","metadata":{"id":"Ot4a0gJJhbv3","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:55:33.414094Z","iopub.execute_input":"2025-11-13T12:55:33.414405Z","iopub.status.idle":"2025-11-13T12:55:33.420110Z","shell.execute_reply.started":"2025-11-13T12:55:33.414383Z","shell.execute_reply":"2025-11-13T12:55:33.419178Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Showing prediction grid of MobileNetV2 model\ndisplay_prediction_grid(\n    7,\n    mbnetv2_predictions,\n    images_mbnetv2,\n    labels_mbnetv2,\n    start_index = 0,\n    num_rows = 3,\n    num_cols = 3\n)","metadata":{"id":"Fjbz3DrDm3wC","outputId":"3f76c85c-02f7-4131-edbe-0f36a3fcdbe3","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:55:40.449709Z","iopub.execute_input":"2025-11-13T12:55:40.449992Z","iopub.status.idle":"2025-11-13T12:55:42.774371Z","shell.execute_reply.started":"2025-11-13T12:55:40.449970Z","shell.execute_reply":"2025-11-13T12:55:42.773234Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Showing prediction grid of EfficientNetB3 model\ndisplay_prediction_grid(\n    7,\n    efnetb3_predictions,\n    images_efnetb3,\n    labels_efnetb3,\n    start_index = 0,\n    num_rows = 3,\n    num_cols = 3\n)","metadata":{"id":"mqQkFMkDhD-A","outputId":"19928069-715c-4708-92fc-a4cd4d55b5e3","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:55:50.368974Z","iopub.execute_input":"2025-11-13T12:55:50.369268Z","iopub.status.idle":"2025-11-13T12:55:52.687065Z","shell.execute_reply.started":"2025-11-13T12:55:50.369246Z","shell.execute_reply":"2025-11-13T12:55:52.685931Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"u0Vqj7hXayWc"}},{"cell_type":"markdown","source":"## **Saving and Loading a Trained Model**\n\nNext, we will set up functions to **save our trained model and load it back later**. This ensures that we can preserve the model’s weights and architecture, making it easy to reuse or share without retraining.\n\nKey steps include:\n\n* **Saving the model:** Store the model with a filename that includes a timestamp and optional suffix, keeping multiple versions organized and easy to identify.\n* **Loading the model:** Reload a previously saved model to restore it to its exact trained state, allowing us to make predictions or continue training without losing progress.\n\nThis approach makes the workflow **efficient, reproducible, and ready for deployment or further experimentation**.","metadata":{"id":"_ElWE5EtnS9O"}},{"cell_type":"code","source":"# Function to save the trained model\ndef save_model_with_timestamp(model, suffix=None, base_dir=config.get(\"SAVE_MODEL_TO\", \"\")):\n    \"\"\"\n    Saves a trained model to a timestamped file in the specified directory.\n\n    Parameters:\n    - model: Trained model to save.\n    - suffix: Optional suffix to add to the filename.\n    - base_dir: Base directory to save the model.\n\n    Returns:\n    - str: Full filepath of the saved model.\n    \"\"\"\n\n    # If base_dir is not specified\n    if base_dir is None:\n        base_dir = \"/kaggle/working/\"\n\n    # Creating base directory if it doesn't exist\n    os.makedirs(base_dir, exist_ok=True)\n\n    # Creating filename with current timestamp and optional suffix\n    timestamp = datetime.datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n    filename = f\"model-{timestamp}\"\n\n    # Adding suffix, if provided\n    if suffix:\n        filename += f\"-{suffix}\"\n\n    # Creating model filepath\n    filename += \".keras\"\n    model_filepath = os.path.join(base_dir , filename)\n\n    # Saving the model\n    print(f\"Saving model to: {model_filepath}...\")\n    model.save(model_filepath)\n\n    return model_filepath","metadata":{"id":"X8nViPbzrCs3","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:56:02.794840Z","iopub.execute_input":"2025-11-13T12:56:02.795367Z","iopub.status.idle":"2025-11-13T12:56:02.800586Z","shell.execute_reply.started":"2025-11-13T12:56:02.795336Z","shell.execute_reply":"2025-11-13T12:56:02.799640Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to load a saved Keras model\ndef load_model_from_filepath(model_filepath, mode = True):\n    \"\"\"\n    Loads a trained Keras model from the given file path.\n\n    Parameters:\n    - model_filepath: Path to the saved model file.\n    - mode: Specifies the 'safe_mode' parameter when loading the model.\n\n    Returns:\n        tf.keras.Model: Loaded Keras model.\n    \"\"\"\n    print(f\"Loading saved model from: {model_filepath}\")\n    model = tf.keras.models.load_model(model_filepath, safe_mode = mode)\n    return model\n","metadata":{"id":"BdLHg42lsryK","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:56:03.150471Z","iopub.execute_input":"2025-11-13T12:56:03.150782Z","iopub.status.idle":"2025-11-13T12:56:03.155251Z","shell.execute_reply.started":"2025-11-13T12:56:03.150759Z","shell.execute_reply":"2025-11-13T12:56:03.154381Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{"id":"go7kPvwctmu4"}},{"cell_type":"markdown","source":"## **Model Training With 10K Images**\n\nNow, we will train the `EfficientNetB3` model on the **full training dataset**, which contains approximately 10,000 images because it showed higher validation accuracy compared to `MobileNetV3`.\n\nThe main steps in this process are:\n\n* **Prepare the datasets:** Create the complete training and validation datasets with all images and labels, applying the necessary preprocessing steps to ensure consistency.\n* **Train the model:** Train the `EfficientNetB3` architecture on the full dataset for a set number of epochs. We will use callbacks like **early stopping** and **learning rate reduction** to improve performance and prevent overfitting.\n* **Save the trained model:** Once training is complete, save the model with a **timestamped filename and descriptive suffix**, keeping track of different versions easily.\n* **Load the saved model:** Demonstrate how to reload the model into memory, ready for evaluation, predictions, or further experimentation.\n\nBy training on the full dataset, we can **maximize accuracy while maintaining an efficient, reproducible workflow**, preparing the model for real-world use or deployment.\n","metadata":{"id":"LFR3JMTPtmsM"}},{"cell_type":"code","source":"# Creating full training and validation datasets\ntrain_dataset_full, val_dataset_full = prepare_train_val_datasets(\n    X = X,\n    y = y,\n    model_name = \"EfficientNetB3\"\n)","metadata":{"id":"UbksoqqUtmp3","outputId":"96702ab2-af28-48c6-81a4-1af121ca2aff","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:56:08.461041Z","iopub.execute_input":"2025-11-13T12:56:08.461958Z","iopub.status.idle":"2025-11-13T12:56:08.523952Z","shell.execute_reply.started":"2025-11-13T12:56:08.461932Z","shell.execute_reply":"2025-11-13T12:56:08.522990Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training EfficientNetB3 model on full dataset\nmodel, model_history = train_model(\n    base_model = \"EfficientNetB3\",\n    training_dataset = train_dataset_full,\n    num_epochs = config.get(\"N_EPOCHS\", 100),\n    validation_dataset = val_dataset_full,\n    callbacks = [early_stopping, reduce_lr]\n)","metadata":{"id":"2FoNpBHtAiEh","outputId":"cc2f439f-7b60-4cf0-9bbd-4eed1d5c90ac","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T12:56:09.730357Z","iopub.execute_input":"2025-11-13T12:56:09.730681Z","iopub.status.idle":"2025-11-13T14:20:51.462275Z","shell.execute_reply.started":"2025-11-13T12:56:09.730658Z","shell.execute_reply":"2025-11-13T14:20:51.461576Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Making prediction\npredictions = model.predict(val_dataset_full)","metadata":{"id":"xxYWld8v57RF","outputId":"40fa85c5-fbeb-45ca-de82-4e3eb1a7f001","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:23:05.540852Z","iopub.execute_input":"2025-11-13T14:23:05.541160Z","iopub.status.idle":"2025-11-13T14:23:23.797688Z","shell.execute_reply.started":"2025-11-13T14:23:05.541137Z","shell.execute_reply":"2025-11-13T14:23:23.797033Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Separating images and labels\nimages, labels = unbatchify(val_dataset_full)","metadata":{"id":"AxGXvntS6G4S","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:23:23.799030Z","iopub.execute_input":"2025-11-13T14:23:23.799327Z","iopub.status.idle":"2025-11-13T14:23:26.367280Z","shell.execute_reply.started":"2025-11-13T14:23:23.799307Z","shell.execute_reply":"2025-11-13T14:23:26.366700Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Showing prediction grid\ndisplay_prediction_grid(\n    7,\n    predictions,\n    images,\n    labels,\n    start_index = 77,\n    num_rows = 3,\n    num_cols = 3\n)","metadata":{"id":"y9PCxriV58BW","outputId":"0980945c-e574-42b5-dfd3-204ee42f33a1","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:23:39.385174Z","iopub.execute_input":"2025-11-13T14:23:39.385429Z","iopub.status.idle":"2025-11-13T14:23:41.658728Z","shell.execute_reply.started":"2025-11-13T14:23:39.385410Z","shell.execute_reply":"2025-11-13T14:23:41.657424Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Saving the fully trained model\nsuffix = \"full-efficientnetb3-adam\"\nsaved_model_filepath = save_model_with_timestamp(model, suffix = suffix)","metadata":{"id":"uzjll_jgKJOC","outputId":"e15b8ed8-3bb2-4aea-bff9-cee888eec0dd","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:23:35.307696Z","iopub.execute_input":"2025-11-13T14:23:35.308290Z","iopub.status.idle":"2025-11-13T14:23:37.377551Z","shell.execute_reply.started":"2025-11-13T14:23:35.308267Z","shell.execute_reply":"2025-11-13T14:23:37.376948Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Loading saved model\nloaded_model = load_model_from_filepath(saved_model_filepath, mode = True)","metadata":{"id":"9Reow2RaCNlF","outputId":"23e17a86-1b5d-4e32-a751-197f3b9176a3","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:23:48.725866Z","iopub.execute_input":"2025-11-13T14:23:48.726442Z","iopub.status.idle":"2025-11-13T14:23:50.704058Z","shell.execute_reply.started":"2025-11-13T14:23:48.726415Z","shell.execute_reply":"2025-11-13T14:23:50.703425Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Making prediction using the loaded model\nloaded_model_predictions = loaded_model.predict(val_dataset_full)","metadata":{"id":"iUoC3ruV3Vsz","outputId":"96e0c1b4-7874-4343-ff92-1fbea6b6ece1","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:23:50.705261Z","iopub.execute_input":"2025-11-13T14:23:50.705457Z","iopub.status.idle":"2025-11-13T14:24:08.746797Z","shell.execute_reply.started":"2025-11-13T14:23:50.705441Z","shell.execute_reply":"2025-11-13T14:24:08.746009Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Showing first 6 predictions of EfficientNetB3 model\ndisplay_prediction_grid(\n    7,\n    efnetb3_predictions,\n    images_efnetb3,\n    labels_efnetb3,\n    start_index = 0,\n    num_rows = 3,\n    num_cols = 2\n)","metadata":{"id":"1mXITQZ5B5JB","outputId":"d44f324c-54c6-4d36-9d3c-1daaa76d8598","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:24:08.748300Z","iopub.execute_input":"2025-11-13T14:24:08.748752Z","iopub.status.idle":"2025-11-13T14:24:11.517865Z","shell.execute_reply.started":"2025-11-13T14:24:08.748725Z","shell.execute_reply":"2025-11-13T14:24:11.516879Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---","metadata":{}},{"cell_type":"markdown","source":"## **Making Predictions On Test Data**\n\nAfter training and validating our model, we now want to **evaluate it on unseen test images** and prepare the predictions for submission. This step ensures we can measure real-world performance and use the results for downstream tasks.\n\nThe main steps in this process are:\n\n* **Prepare the test dataset:** Load all images from the test directory and create a dataset compatible with our model. We disable shuffling to maintain the original order of files.\n* **Visualize sample images:** Display a subset of test images in a grid to verify they are loaded and preprocessed correctly.\n* **Make predictions:** Use the trained `EfficientNetB3` model to predict class probabilities for all test images.\n* **Organize predictions:** Extract image IDs and create a DataFrame that pairs each ID with its predicted probabilities for easy reference.\n* **Save predictions to CSV:** Export the DataFrame to a `.csv` file for submission.","metadata":{}},{"cell_type":"code","source":"# Path to the test images directory\ntest_dataset_path = os.path.join(config.get(\"DATA_DIR\", \"\"), \"test\")\n\n# List of all image file paths in the test directory\ntest_filenames = [os.path.join(test_dataset_path, fname) for fname in os.listdir(test_dataset_path)]\nprint(\"Sample test files:\", *test_filenames[:5], sep = \"\\n\")","metadata":{"id":"f-K1EwoKB5G8","outputId":"2b0a3207-b091-4ecf-9e27-8df16c876ecd","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:38:59.810122Z","iopub.execute_input":"2025-11-13T14:38:59.810718Z","iopub.status.idle":"2025-11-13T14:38:59.827845Z","shell.execute_reply.started":"2025-11-13T14:38:59.810691Z","shell.execute_reply":"2025-11-13T14:38:59.826980Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Creating test dataset\ntest_dataset = create_dataset(\n    X = test_filenames,\n    model_name = \"efficientnet\",\n    config = config,\n    dataset_type = \"test\",\n    shuffle = False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:41:48.883396Z","iopub.execute_input":"2025-11-13T14:41:48.884179Z","iopub.status.idle":"2025-11-13T14:41:48.930837Z","shell.execute_reply.started":"2025-11-13T14:41:48.884148Z","shell.execute_reply":"2025-11-13T14:41:48.929985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Displaying images from test dataset\n\n# Setting figure size\nplt.figure(figsize=(15, 15))\n\nfor img in test_dataset.take(1):\n\n    # Looping through the images\n    for i in range(25):\n        \n        # Plotting image in a 5x5 grid\n        plt.subplot(5, 5, i + 1)\n        plt.imshow(img[i] / 255.0)\n\n        # Hiding axes and adjust layout\n        plt.axis(\"off\")\n        plt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:52:48.224350Z","iopub.execute_input":"2025-11-13T14:52:48.224694Z","iopub.status.idle":"2025-11-13T14:52:50.874784Z","shell.execute_reply.started":"2025-11-13T14:52:48.224668Z","shell.execute_reply":"2025-11-13T14:52:50.872534Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Making predictions on test data\ntest_predictions = loaded_model.predict(test_dataset, verbose=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:53:06.027713Z","iopub.execute_input":"2025-11-13T14:53:06.028370Z","iopub.status.idle":"2025-11-13T14:54:01.835899Z","shell.execute_reply.started":"2025-11-13T14:53:06.028332Z","shell.execute_reply":"2025-11-13T14:54:01.835037Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Extracting test image IDs\ntest_ids = [os.path.splitext(os.path.basename(path))[0] for path in test_filenames]\nprint(\"Sample test ids:\", *test_ids[:5], sep = \"\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:55:21.147734Z","iopub.execute_input":"2025-11-13T14:55:21.148011Z","iopub.status.idle":"2025-11-13T14:55:21.164506Z","shell.execute_reply.started":"2025-11-13T14:55:21.147993Z","shell.execute_reply":"2025-11-13T14:55:21.163820Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Creating a DataFrame with image IDs and prediction probabilities\npreds_df = pd.DataFrame(test_predictions, columns = config.get(\"CLASS_NAMES\", []))\npreds_df.insert(0, \"id\", test_ids)\npreds_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T14:55:32.383854Z","iopub.execute_input":"2025-11-13T14:55:32.384538Z","iopub.status.idle":"2025-11-13T14:55:32.412428Z","shell.execute_reply.started":"2025-11-13T14:55:32.384480Z","shell.execute_reply":"2025-11-13T14:55:32.411486Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Saving predictions to a CSV file \noutput_csv_path = \"/kaggle/working/submission.csv\"\npreds_df.to_csv(output_csv_path, index=False)\nprint(f\"Submission file saved to: {output_csv_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T15:16:34.271047Z","iopub.execute_input":"2025-11-13T15:16:34.271346Z","iopub.status.idle":"2025-11-13T15:16:35.602834Z","shell.execute_reply.started":"2025-11-13T15:16:34.271322Z","shell.execute_reply":"2025-11-13T15:16:35.601900Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}