{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-08-31T13:54:23.540699Z","iopub.execute_input":"2026-08-31T13:54:23.541597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"GPUs:\", tf.config.list_physical_devices(\"GPU\"))\n\n\n# ============================================================\n# PATHS\n# ============================================================\n\nBASE_PATH = \"/kaggle/input/competitions/dog-breed-identification\"\n\nTRAIN_DIR = os.path.join(BASE_PATH, \"train\")\nTEST_DIR = os.path.join(BASE_PATH, \"test\")\n\nLABELS_PATH = os.path.join(BASE_PATH, \"labels.csv\")\nSAMPLE_PATH = os.path.join(BASE_PATH, \"sample_submission.csv\")\n\n\n# ============================================================\n# LOAD CSV FILES\n# ============================================================\n\nlabels_df = pd.read_csv(LABELS_PATH)\nsample_df = pd.read_csv(SAMPLE_PATH)\n\nprint(\"Labels shape:\", labels_df.shape)\nprint(\"Sample shape:\", sample_df.shape)\n\nprint(\"\\nLabels:\")\nprint(labels_df.head())\n\nprint(\"\\nSample:\")\nprint(sample_df.head())\n\n\n# ============================================================\n# BREED LIST\n# ============================================================\n\nbreed_names = sorted(labels_df[\"breed\"].unique())\n\nNUM_CLASSES = len(breed_names)\n\nprint(\"\\nNumber of breeds:\", NUM_CLASSES)\n\n\n# ============================================================\n# CREATE ID -> INTEGER LABEL MAPPING\n# ============================================================\n\nbreed_to_number = {\n    breed: index\n    for index, breed in enumerate(breed_names)\n}\n\n\nlabels_df[\"label\"] = labels_df[\"breed\"].map(\n    breed_to_number\n)\n\n\n# ============================================================\n# CREATE IMAGE PATHS\n# ============================================================\n\nlabels_df[\"path\"] = labels_df[\"id\"].apply(\n    lambda x: os.path.join(TRAIN_DIR, x + \".jpg\")\n)\n\ntest_ids = sample_df[\"id\"].astype(str).values\n\ntest_paths = np.array([\n    os.path.join(TEST_DIR, image_id + \".jpg\")\n    for image_id in test_ids\n])\n\n\n# Check files\n\nprint(\"\\nTraining images:\", len(labels_df))\nprint(\"Test images:\", len(test_paths))\n\nprint(\"\\nExample training path:\")\nprint(labels_df[\"path\"].iloc[0])\n\nprint(\"\\nExample test path:\")\nprint(test_paths[0])\n\n\n# ============================================================\n# CHECK MISSING FILES\n# ============================================================\n\nmissing_train = sum(\n    not os.path.exists(p)\n    for p in labels_df[\"path\"]\n)\n\nmissing_test = sum(\n    not os.path.exists(p)\n    for p in test_paths\n)\n\nprint(\"\\nMissing training images:\", missing_train)\nprint(\"Missing test images:\", missing_test)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T19:26:56.198346Z","iopub.execute_input":"2026-08-31T19:26:56.198616Z","iopub.status.idle":"2026-08-31T19:27:52.145999Z","shell.execute_reply.started":"2026-08-31T19:26:56.198587Z","shell.execute_reply":"2026-08-31T19:27:52.145251Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# SETTINGS\n# ============================================================\n\nIMAGE_SIZE = 160\nBATCH_SIZE = 64\nVALIDATION_RATIO = 0.20\nSEED = 42\n\nAUTOTUNE = tf.data.AUTOTUNE\n\n\n# ============================================================\n# STRATIFIED TRAIN / VALIDATION SPLIT\n# ============================================================\n\ntrain_parts = []\nvalid_parts = []\n\nrng = np.random.default_rng(SEED)\n\nfor breed in breed_names:\n\n    breed_rows = labels_df[\n        labels_df[\"breed\"] == breed\n    ].copy()\n\n    indices = np.arange(len(breed_rows))\n\n    rng.shuffle(indices)\n\n    split_point = int(\n        len(indices) * (1 - VALIDATION_RATIO)\n    )\n\n    train_indices = indices[:split_point]\n    valid_indices = indices[split_point:]\n\n    train_parts.append(\n        breed_rows.iloc[train_indices]\n    )\n\n    valid_parts.append(\n        breed_rows.iloc[valid_indices]\n    )\n\n\ntrain_df = pd.concat(\n    train_parts,\n    ignore_index=True\n)\n\nvalid_df = pd.concat(\n    valid_parts,\n    ignore_index=True\n)\n\n\nprint(\"Training images:\", len(train_df))\nprint(\"Validation images:\", len(valid_df))\n\n\n# ============================================================\n# IMAGE LOADING FUNCTION\n# ============================================================\n\ndef load_training_image(path, label):\n\n    image = tf.io.read_file(path)\n\n    image = tf.image.decode_jpeg(\n        image,\n        channels=3\n    )\n\n    image = tf.image.resize(\n        image,\n        [IMAGE_SIZE, IMAGE_SIZE]\n    )\n\n    image = tf.cast(\n        image,\n        tf.float32\n    ) / 255.0\n\n    return image, label\n\n\n# ============================================================\n# TRAIN DATASET\n# ============================================================\n\ntrain_paths = train_df[\"path\"].values\ntrain_labels = train_df[\"label\"].values.astype(np.int32)\n\nvalid_paths = valid_df[\"path\"].values\nvalid_labels = valid_df[\"label\"].values.astype(np.int32)\n\n\ntrain_ds = tf.data.Dataset.from_tensor_slices(\n    (train_paths, train_labels)\n)\n\ntrain_ds = train_ds.shuffle(\n    len(train_paths),\n    seed=SEED,\n    reshuffle_each_iteration=True\n)\n\ntrain_ds = train_ds.map(\n    load_training_image,\n    num_parallel_calls=AUTOTUNE\n)\n\ntrain_ds = train_ds.batch(\n    BATCH_SIZE\n)\n\ntrain_ds = train_ds.prefetch(\n    AUTOTUNE\n)\n\n\n# ============================================================\n# VALIDATION DATASET\n# ============================================================\n\nvalid_ds = tf.data.Dataset.from_tensor_slices(\n    (valid_paths, valid_labels)\n)\n\nvalid_ds = valid_ds.map(\n    load_training_image,\n    num_parallel_calls=AUTOTUNE\n)\n\nvalid_ds = valid_ds.batch(\n    BATCH_SIZE\n)\n\nvalid_ds = valid_ds.prefetch(\n    AUTOTUNE\n)\n\n\nprint(\"\\nDatasets ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T19:28:05.678463Z","iopub.execute_input":"2026-08-31T19:28:05.679196Z","iopub.status.idle":"2026-08-31T19:28:08.058088Z","shell.execute_reply.started":"2026-08-31T19:28:05.679166Z","shell.execute_reply":"2026-08-31T19:28:08.057454Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# DATA AUGMENTATION\n# ============================================================\n\naugmentation = tf.keras.Sequential(\n    [\n        tf.keras.layers.RandomFlip(\n            \"horizontal\"\n        ),\n\n        tf.keras.layers.RandomRotation(\n            0.08\n        ),\n\n        tf.keras.layers.RandomZoom(\n            0.10\n        ),\n\n        tf.keras.layers.RandomContrast(\n            0.10\n        )\n    ],\n    name=\"dog_augmentation\"\n)\n\n\n# ============================================================\n# CUSTOM CNN\n# ============================================================\n\nmodel = tf.keras.Sequential(\n    [\n\n        tf.keras.layers.Input(\n            shape=(IMAGE_SIZE, IMAGE_SIZE, 3)\n        ),\n\n        augmentation,\n\n\n        # ----------------------------------------------------\n        # BLOCK 1\n        # ----------------------------------------------------\n\n        tf.keras.layers.Conv2D(\n            32,\n            kernel_size=3,\n            padding=\"same\",\n            activation=\"relu\"\n        ),\n\n        tf.keras.layers.BatchNormalization(),\n\n        tf.keras.layers.Conv2D(\n            32,\n            kernel_size=3,\n            padding=\"same\",\n            activation=\"relu\"\n        ),\n\n        tf.keras.layers.MaxPooling2D(\n            pool_size=2\n        ),\n\n\n        # ----------------------------------------------------\n        # BLOCK 2\n        # ----------------------------------------------------\n\n        tf.keras.layers.Conv2D(\n            64,\n            kernel_size=3,\n            padding=\"same\",\n            activation=\"relu\"\n        ),\n\n        tf.keras.layers.BatchNormalization(),\n\n        tf.keras.layers.Conv2D(\n            64,\n            kernel_size=3,\n            padding=\"same\",\n            activation=\"relu\"\n        ),\n\n        tf.keras.layers.MaxPooling2D(\n            pool_size=2\n        ),\n\n\n        # ----------------------------------------------------\n        # BLOCK 3\n        # ----------------------------------------------------\n\n        tf.keras.layers.Conv2D(\n            128,\n            kernel_size=3,\n            padding=\"same\",\n            activation=\"relu\"\n        ),\n\n        tf.keras.layers.BatchNormalization(),\n\n        tf.keras.layers.Conv2D(\n            128,\n            kernel_size=3,\n            padding=\"same\",\n            activation=\"relu\"\n        ),\n\n        tf.keras.layers.MaxPooling2D(\n            pool_size=2\n        ),\n\n\n        # ----------------------------------------------------\n        # BLOCK 4\n        # ----------------------------------------------------\n\n        tf.keras.layers.Conv2D(\n            256,\n            kernel_size=3,\n            padding=\"same\",\n            activation=\"relu\"\n        ),\n\n        tf.keras.layers.BatchNormalization(),\n\n        tf.keras.layers.MaxPooling2D(\n            pool_size=2\n        ),\n\n\n        # ----------------------------------------------------\n        # CLASSIFIER\n        # ----------------------------------------------------\n\n        tf.keras.layers.GlobalAveragePooling2D(),\n\n        tf.keras.layers.Dense(\n            256,\n            activation=\"relu\"\n        ),\n\n        tf.keras.layers.Dropout(\n            0.35\n        ),\n\n        tf.keras.layers.Dense(\n            NUM_CLASSES,\n            activation=\"softmax\"\n        )\n    ],\n    name=\"custom_dog_cnn\"\n)\n\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T19:28:10.959476Z","iopub.execute_input":"2026-08-31T19:28:10.960347Z","iopub.status.idle":"2026-08-31T19:28:11.925446Z","shell.execute_reply.started":"2026-08-31T19:28:10.960317Z","shell.execute_reply":"2026-08-31T19:28:11.924924Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# COMPILE MODEL\n# ============================================================\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(\n        learning_rate=0.001\n    ),\n\n    loss=tf.keras.losses.SparseCategoricalCrossentropy(),\n\n    metrics=[\n        \"accuracy\"\n    ]\n)\n\n\n# ============================================================\n# CALLBACKS\n# ============================================================\n\ncallbacks = [\n\n    tf.keras.callbacks.ModelCheckpoint(\n        filepath=\"/kaggle/working/best_dog_cnn.keras\",\n        monitor=\"val_accuracy\",\n        mode=\"max\",\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\n    tf.keras.callbacks.EarlyStopping(\n        monitor=\"val_accuracy\",\n        mode=\"max\",\n        patience=4,\n        restore_best_weights=True,\n        verbose=1\n    )\n]\n\n\nprint(\"Model compiled.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T19:28:15.804022Z","iopub.execute_input":"2026-08-31T19:28:15.804278Z","iopub.status.idle":"2026-08-31T19:28:15.820108Z","shell.execute_reply.started":"2026-08-31T19:28:15.804257Z","shell.execute_reply":"2026-08-31T19:28:15.819153Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 8\n\ncallbacks = [\n\n    tf.keras.callbacks.ModelCheckpoint(\n        \"/kaggle/working/best_dog_cnn.keras\",\n        monitor=\"val_accuracy\",\n        mode=\"max\",\n        save_best_only=True,\n        verbose=1\n    ),\n\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.4,\n        patience=2,\n        min_lr=1e-6,\n        verbose=1\n    ),\n\n    tf.keras.callbacks.EarlyStopping(\n        monitor=\"val_accuracy\",\n        mode=\"max\",\n        patience=3,\n        restore_best_weights=True,\n        verbose=1\n    )\n]\n\nhistory = model.fit(\n    train_ds,\n    validation_data=valid_ds,\n    epochs=EPOCHS,\n    callbacks=callbacks\n)\n\nbest_epoch = np.argmax(\n    history.history[\"val_accuracy\"]\n) + 1\n\nbest_accuracy = max(\n    history.history[\"val_accuracy\"]\n)\n\nprint(\"\\n==============================\")\nprint(\"BEST EPOCH:\", best_epoch)\nprint(\"BEST VALIDATION ACCURACY:\", best_accuracy)\nprint(\"==============================\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T19:28:19.166215Z","iopub.execute_input":"2026-08-31T19:28:19.166724Z","iopub.status.idle":"2026-08-31T19:32:26.708841Z","shell.execute_reply.started":"2026-08-31T19:28:19.166684Z","shell.execute_reply":"2026-08-31T19:32:26.708031Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# TEST IMAGE LOADING\n# ============================================================\n\ndef load_test_image(path):\n\n    image = tf.io.read_file(path)\n\n    image = tf.image.decode_jpeg(\n        image,\n        channels=3\n    )\n\n    image = tf.image.resize(\n        image,\n        [IMAGE_SIZE, IMAGE_SIZE]\n    )\n\n    image = tf.cast(\n        image,\n        tf.float32\n    ) / 255.0\n\n    return image\n\n\n# ============================================================\n# TEST DATASET\n# ============================================================\n\ntest_ds = tf.data.Dataset.from_tensor_slices(\n    test_paths\n)\n\ntest_ds = test_ds.map(\n    load_test_image,\n    num_parallel_calls=AUTOTUNE\n)\n\ntest_ds = test_ds.batch(\n    BATCH_SIZE\n)\n\ntest_ds = test_ds.prefetch(\n    AUTOTUNE\n)\n\n\n# ============================================================\n# LOAD BEST MODEL\n# ============================================================\n\nbest_model = tf.keras.models.load_model(\n    \"/kaggle/working/best_dog_cnn.keras\"\n)\n\n\n# ============================================================\n# PREDICTIONS\n# ============================================================\n\nprint(\"Generating predictions...\")\n\npredictions = best_model.predict(\n    test_ds,\n    verbose=1\n)\n\nprint(\n    \"Prediction shape:\",\n    predictions.shape\n)\n\n\n# ============================================================\n# SAFETY CHECK\n# ============================================================\n\nprint(\n    \"Number of model classes:\",\n    predictions.shape[1]\n)\n\nprint(\n    \"Number of breed names:\",\n    len(breed_names)\n)\n\n\nassert predictions.shape[1] == len(breed_names)\n\n\n# ============================================================\n# NORMALIZE PROBABILITIES\n# ============================================================\n\npredictions = (\n    predictions /\n    predictions.sum(\n        axis=1,\n        keepdims=True\n    )\n)\n\n\n# ============================================================\n# CREATE SUBMISSION\n# ============================================================\n\nsubmission = pd.DataFrame(\n    predictions,\n    columns=breed_names\n)\n\n\nsubmission.insert(\n    0,\n    \"id\",\n    test_ids\n)\n\n\n# ============================================================\n# FINAL CHECKS\n# ============================================================\n\nprint(\"\\nSubmission shape:\", submission.shape)\n\nprint(\n    \"Expected rows:\",\n    len(test_ids)\n)\n\nprint(\n    \"Expected columns:\",\n    NUM_CLASSES + 1\n)\n\n\nassert submission.shape == (\n    len(test_ids),\n    NUM_CLASSES + 1\n)\n\n\n# Every row should sum approximately to 1\n\nrow_probability_sums = submission[\n    breed_names\n].sum(axis=1)\n\nprint(\n    \"\\nProbability sum of first 5 rows:\"\n)\n\nprint(\n    row_probability_sums.head()\n)\n\n\n# ============================================================\n# SAVE FILE\n# ============================================================\n\nsubmission_path = (\n    \"/kaggle/working/submission.csv\"\n)\n\nsubmission.to_csv(\n    submission_path,\n    index=False\n)\n\n\nprint(\"\\n====================================\")\nprint(\"SUBMISSION CREATED\")\nprint(\"====================================\")\nprint(\n    \"Location:\",\n    submission_path\n)\nprint(\n    \"Shape:\",\n    submission.shape\n)\nprint(\"====================================\")\n\nprint(\"\\nFirst 5 rows:\")\nprint(submission.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T19:32:43.424615Z","iopub.execute_input":"2026-08-31T19:32:43.425314Z","iopub.status.idle":"2026-08-31T19:33:02.005130Z","shell.execute_reply.started":"2026-08-31T19:32:43.425283Z","shell.execute_reply":"2026-08-31T19:33:02.004320Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}