{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":7327,"databundleVersionId":861871,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"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","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-29T16:31:53.628006Z","iopub.execute_input":"2025-06-29T16:31:53.628161Z","iopub.status.idle":"2025-06-29T16:32:39.163963Z","shell.execute_reply.started":"2025-06-29T16:31:53.628146Z","shell.execute_reply":"2025-06-29T16:32:39.162990Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# 1. SETUP AND IMPORTS\n# ==============================================================================\n# Standard library imports\nimport os\nimport zipfile\nimport time\n\n# Third-party library imports\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\n\n# Scikit-learn for data splitting\nfrom sklearn.model_selection import train_test_split\n\n# TensorFlow and Keras for deep learning\nimport tensorflow as tf\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.applications import Xception\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping\n\nprint(\"TensorFlow Version:\", tf.__version__)\n\n# ==============================================================================\n# 2. CONFIGURATION PARAMETERS\n# ==============================================================================\n# --- Data Paths ---\n# Assumes the zip files are in the same directory as the script.\nTRAIN_ZIP_PATH = 'train.zip'\nTEST_ZIP_PATH = 'test.zip'\nLABELS_CSV_PATH = '/kaggle/input/dog-breed-identification/labels.csv'\nSAMPLE_SUBMISSION_PATH = '/kaggle/input/dog-breed-identification/sample_submission.csv'\n\n# --- Directories for Extracted Data ---\nBASE_DIR = '/kaggle/input/dog-breed-identification/'\nTRAIN_DIR = os.path.join(BASE_DIR, 'train')\nTEST_DIR = os.path.join(BASE_DIR, 'test')\n\n# --- Model & Training Hyperparameters ---\n# The Xception model was trained on 299x299 images.\nIMG_WIDTH, IMG_HEIGHT = 299, 299\nIMG_SIZE = (IMG_WIDTH, IMG_HEIGHT)\nBATCH_SIZE = 32  # Adjust based on your GPU memory\nEPOCHS_HEAD_TRAINING = 10  # Epochs for training the new classification layers\nEPOCHS_FINE_TUNING = 20  # Epochs for fine-tuning the full model\nLEARNING_RATE_HEAD = 1e-3\nLEARNING_RATE_FINE_TUNE = 1e-5\nVALIDATION_SPLIT = 0.2 # 20% of training data will be used for validation\nRANDOM_STATE = 42 # For reproducible splits\n\n# ==============================================================================\n# 3. DATA PREPARATION\n# ==============================================================================\ndef prepare_data():\n    \"\"\"\n    Extracts data from zip files, creates directories, and prepares the\n    pandas DataFrame with file paths and labels.\n    \"\"\"\n    print(\"--- Starting Data Preparation ---\")\n    start_time = time.time()\n\n    # Create base directory if it doesn't exist\n    if not os.path.exists(BASE_DIR):\n        os.makedirs(BASE_DIR)\n        print(f\"Created directory: {BASE_DIR}\")\n\n    # Unzip training data\n    if not os.path.exists(TRAIN_DIR):\n        print(f\"Extracting {TRAIN_ZIP_PATH}...\")\n        with zipfile.ZipFile(TRAIN_ZIP_PATH, 'r') as zip_ref:\n            zip_ref.extractall(BASE_DIR)\n    else:\n        print(f\"Training data already extracted at {TRAIN_DIR}\")\n\n    # Unzip test data\n    if not os.path.exists(TEST_DIR):\n        print(f\"Extracting {TEST_ZIP_PATH}...\")\n        with zipfile.ZipFile(TEST_ZIP_PATH, 'r') as zip_ref:\n            zip_ref.extractall(BASE_DIR)\n    else:\n        print(f\"Test data already extracted at {TEST_DIR}\")\n        \n    # Load labels\n    labels_df = pd.read_csv(LABELS_CSV_PATH)\n    print(f\"\\nLoaded {LABELS_CSV_PATH} with {len(labels_df)} entries.\")\n    \n    # Add image extension and full path to the dataframe\n    labels_df['id'] = labels_df['id'] + '.jpg'\n    labels_df['filepath'] = labels_df['id'].apply(lambda x: os.path.join(TRAIN_DIR, x))\n    \n    # Get the number of classes\n    num_classes = labels_df['breed'].nunique()\n    print(f\"Number of dog breeds (classes): {num_classes}\")\n    \n    # Check for missing files\n    labels_df['file_exists'] = labels_df['filepath'].apply(os.path.exists)\n    if not labels_df['file_exists'].all():\n        print(\"Warning: Some image files listed in labels.csv are missing!\")\n        labels_df = labels_df[labels_df['file_exists']]\n\n    # Split data into training and validation sets\n    train_df, val_df = train_test_split(\n        labels_df,\n        test_size=VALIDATION_SPLIT,\n        random_state=RANDOM_STATE,\n        stratify=labels_df['breed'] # Ensures balanced class distribution\n    )\n    \n    print(f\"Training set size: {len(train_df)}\")\n    print(f\"Validation set size: {len(val_df)}\")\n    \n    print(f\"--- Data Preparation Finished in {time.time() - start_time:.2f}s ---\\n\")\n    return train_df, val_df, num_classes\n\n# ==============================================================================\n# 4. DATA GENERATORS\n# ==============================================================================\ndef create_data_generators(train_df, val_df):\n    \"\"\"\n    Creates Keras ImageDataGenerators for training and validation.\n    Applies data augmentation to the training generator.\n    \"\"\"\n    print(\"--- Creating Data Generators ---\")\n    \n    # Training generator with data augmentation\n    train_datagen = ImageDataGenerator(\n        preprocessing_function=tf.keras.applications.xception.preprocess_input,\n        rotation_range=30,\n        width_shift_range=0.2,\n        height_shift_range=0.2,\n        shear_range=0.2,\n        zoom_range=0.2,\n        horizontal_flip=True,\n        fill_mode='nearest'\n    )\n    \n    # Validation generator (only rescaling, no augmentation)\n    val_datagen = ImageDataGenerator(\n        preprocessing_function=tf.keras.applications.xception.preprocess_input\n    )\n    \n    # Create generators from dataframes\n    train_generator = train_datagen.flow_from_dataframe(\n        dataframe=train_df,\n        x_col='filepath',\n        y_col='breed',\n        target_size=IMG_SIZE,\n        batch_size=BATCH_SIZE,\n        class_mode='categorical',\n        shuffle=True\n    )\n    \n    validation_generator = val_datagen.flow_from_dataframe(\n        dataframe=val_df,\n        x_col='filepath',\n        y_col='breed',\n        target_size=IMG_SIZE,\n        batch_size=BATCH_SIZE,\n        class_mode='categorical',\n        shuffle=False # No need to shuffle validation data\n    )\n    \n    print(\"--- Data Generators Created ---\\n\")\n    return train_generator, validation_generator\n\n# ==============================================================================\n# 5. MODEL BUILDING\n# ==============================================================================\ndef build_model(num_classes):\n    \"\"\"\n    Builds the classification model using transfer learning with Xception.\n    \"\"\"\n    print(\"--- Building Model ---\")\n    \n    # Load the base Xception model, pre-trained on ImageNet\n    base_model = Xception(\n        weights='imagenet',       # Load weights pre-trained on ImageNet\n        include_top=False,        # Exclude the final classification layer\n        input_shape=(IMG_WIDTH, IMG_HEIGHT, 3)\n    )\n    \n    # Freeze the base model layers to prevent them from being updated\n    # during the initial training phase.\n    base_model.trainable = False\n    \n    # Create the new model head\n    x = base_model.output\n    x = GlobalAveragePooling2D()(x) # Convert features to a single vector per image\n    x = Dense(1024, activation='relu')(x) # A fully-connected layer\n    x = Dropout(0.5)(x) # Dropout for regularization\n    predictions = Dense(num_classes, activation='softmax')(x) # The final output layer\n    \n    # Combine the base model and the new head\n    model = Model(inputs=base_model.input, outputs=predictions)\n    \n    print(\"--- Model Built Successfully ---\\n\")\n    return model, base_model\n\n# ==============================================================================\n# 6. MODEL TRAINING & FINE-TUNING\n# ==============================================================================\ndef train_model(model, base_model, train_gen, val_gen, num_classes):\n    \"\"\"\n    Trains the model in two phases:\n    1. Trains only the new head with the base model frozen.\n    2. Fine-tunes the top layers of the base model with a low learning rate.\n    \"\"\"\n    # --- Phase 1: Train the Head ---\n    print(\"--- Phase 1: Training the custom head ---\")\n    model.compile(\n        optimizer=Adam(learning_rate=LEARNING_RATE_HEAD),\n        loss='categorical_crossentropy', # Correct for multi-class classification\n        metrics=['accuracy']\n    )\n    \n    # Callbacks for Phase 1\n    # Save the best model based on validation loss\n    checkpoint_head = ModelCheckpoint('best_model_head.h5', monitor='val_loss', save_best_only=True, mode='min')\n    # Stop training if validation loss doesn't improve for 3 epochs\n    early_stop_head = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)\n\n    history_head = model.fit(\n        train_gen,\n        epochs=EPOCHS_HEAD_TRAINING,\n        validation_data=val_gen,\n        callbacks=[checkpoint_head, early_stop_head]\n    )\n\n    print(\"\\n--- Phase 2: Fine-tuning the model ---\")\n    # Unfreeze the base model to allow fine-tuning\n    base_model.trainable = True\n    \n    # We'll fine-tune from the top. A common practice is to keep the\n    # early layers (like batch normalization) frozen.\n    # For Xception, let's unfreeze the top ~30% of layers.\n    fine_tune_at = len(base_model.layers) - 40 \n    for layer in base_model.layers[:fine_tune_at]:\n        layer.trainable = False\n\n    # Re-compile the model with a very low learning rate for fine-tuning\n    model.compile(\n        optimizer=Adam(learning_rate=LEARNING_RATE_FINE_TUNE),\n        loss='categorical_crossentropy',\n        metrics=['accuracy']\n    )\n\n    # Callbacks for Phase 2\n    checkpoint_fine_tune = ModelCheckpoint('best_model_fine_tuned.h5', monitor='val_loss', save_best_only=True, mode='min')\n    early_stop_fine_tune = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)\n\n    history_fine_tune = model.fit(\n        train_gen,\n        epochs=EPOCHS_FINE_TUNING,\n        validation_data=val_gen,\n        callbacks=[checkpoint_fine_tune, early_stop_fine_tune]\n    )\n\n    print(\"--- Model Training Complete ---\\n\")\n    # The best fine-tuned model is automatically restored by EarlyStopping\n    return model\n\n# ==============================================================================\n# 7. PREDICTION AND SUBMISSION\n# ==============================================================================\ndef create_submission(model, train_generator):\n    \"\"\"\n    Generates predictions on the test set and creates the submission file.\n    \"\"\"\n    print(\"--- Generating Predictions for Submission ---\")\n    \n    # Get test file paths\n    test_files = os.listdir(TEST_DIR)\n    test_filepaths = [os.path.join(TEST_DIR, fname) for fname in test_files]\n    test_df = pd.DataFrame({'filepath': test_filepaths})\n    \n    # Create a test generator\n    test_datagen = ImageDataGenerator(\n        preprocessing_function=tf.keras.applications.xception.preprocess_input\n    )\n    \n    test_generator = test_datagen.flow_from_dataframe(\n        dataframe=test_df,\n        x_col='filepath',\n        y_col=None, # No labels for test data\n        target_size=IMG_SIZE,\n        batch_size=BATCH_SIZE,\n        class_mode=None,\n        shuffle=False # IMPORTANT: Do not shuffle test data\n    )\n    \n    # Make predictions\n    predictions = model.predict(test_generator, verbose=1)\n    \n    # Get the class labels in the correct order\n    class_indices = train_generator.class_indices\n    # Invert the dictionary to map index to label\n    labels = dict((v, k) for k, v in class_indices.items())\n    # Sort by index to get an ordered list of breed names\n    breed_columns = [labels[i] for i in range(len(labels))]\n\n    # Create the submission DataFrame\n    submission_df = pd.DataFrame(predictions, columns=breed_columns)\n    \n    # Get the image IDs from the filenames\n    test_ids = [os.path.basename(f).split('.')[0] for f in test_generator.filenames]\n    submission_df.insert(0, 'id', test_ids)\n    \n    # Ensure the columns match the sample submission format\n    sample_df = pd.read_csv(SAMPLE_SUBMISSION_PATH)\n    submission_df = submission_df[sample_df.columns]\n\n    # Save the submission file\n    submission_df.to_csv('submission.csv', index=False)\n    print(\"\\n--- Submission file 'submission.csv' created successfully! ---\")\n\n\n# ==============================================================================\n# 8. MAIN EXECUTION\n# ==============================================================================\nif __name__ == '__main__':\n    # Step 1: Prepare the data (unzip, load labels, split)\n    train_df, val_df, num_classes = prepare_data()\n    \n    # Step 2: Create data generators\n    train_gen, val_gen = create_data_generators(train_df, val_df)\n    \n    # Step 3: Build the model\n    model, base_model = build_model(num_classes)\n    \n    # Step 4: Train the model (head training + fine-tuning)\n    trained_model = train_model(model, base_model, train_gen, val_gen, num_classes)\n    \n    # Step 5: Create the submission file\n    # Note: We can also load the best saved model from disk if needed:\n    # from tensorflow.keras.models import load_model\n    # best_model = load_model('best_model_fine_tuned.h5')\n    # create_submission(best_model, train_gen)\n    create_submission(trained_model, train_gen)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-29T16:34:25.692409Z","iopub.execute_input":"2025-06-29T16:34:25.692677Z","iopub.status.idle":"2025-06-29T17:50:50.726788Z","shell.execute_reply.started":"2025-06-29T16:34:25.692660Z","shell.execute_reply":"2025-06-29T17:50:50.725709Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}