{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":22962,"databundleVersionId":3171193,"sourceType":"competition"},{"sourceId":3398941,"sourceType":"datasetVersion","datasetId":2040833}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false},"papermill":{"default_parameters":{},"duration":41.006696,"end_time":"2025-08-03T05:59:48.024918","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-08-03T05:59:07.018222","version":"2.5.0"},"widgets":{"application/vnd.jupyter.widget-state+json":{"state":{"0242f0ca0559484daa5f53e208b4940d":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"0ad43bed980645c0847df078c41bfa0e":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_8668b357f7ab4259bdd3f60f9d8cea89","max":18,"min":0,"orientation":"horizontal","style":"IPY_MODEL_8933328305d4487da91eca0b59f929d9","value":18}},"0b53c5206eb34f6689cc32900b345a31":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"0d8b8e8413f049c7b1f71e9d63016f2f":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"14a5c260b4e142b79661775e9ab2f2e3":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_0b53c5206eb34f6689cc32900b345a31","placeholder":"​","style":"IPY_MODEL_8f4503703d6c48d095e387d9c761bebc","value":"Epoch 2: 100%"}},"2be52247723d4bfc92b0113b03262625":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":"inline-flex","flex":null,"flex_flow":"row wrap","grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":"100%"}},"3f1e58790231494ea54ca1c1dbae1c22":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_e61efd293cd84ff6bc853b34f8e9860c","placeholder":"​","style":"IPY_MODEL_0242f0ca0559484daa5f53e208b4940d","value":"Testing DataLoader 0: 100%"}},"4776a80a5d3c4f1dae8c032fd298aed9":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":"2","flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"4ddfb0468dc84f36b157b401dab0b040":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"7a9d7263f1f1439ba8edb9ea26923257":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_3f1e58790231494ea54ca1c1dbae1c22","IPY_MODEL_0ad43bed980645c0847df078c41bfa0e","IPY_MODEL_f9d0430c993e44598bc43694a8dae3ce"],"layout":"IPY_MODEL_2be52247723d4bfc92b0113b03262625"}},"7cb0b283f32a46529d5d345211178ca4":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":"inline-flex","flex":null,"flex_flow":"row wrap","grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":"100%"}},"8668b357f7ab4259bdd3f60f9d8cea89":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":"2","flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"8933328305d4487da91eca0b59f929d9":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"8f4503703d6c48d095e387d9c761bebc":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"a9b18ee47ae84012bf67a69167983661":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_f39e84f54d614d64b8c7529b009af830","placeholder":"​","style":"IPY_MODEL_4ddfb0468dc84f36b157b401dab0b040","value":" 70/70 [00:40&lt;00:00,  1.72it/s, v_num=0]"}},"d0d8a7dee29b439084621db10efc20e1":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_14a5c260b4e142b79661775e9ab2f2e3","IPY_MODEL_f693cca8123c4f17830fe2b749632ad6","IPY_MODEL_a9b18ee47ae84012bf67a69167983661"],"layout":"IPY_MODEL_7cb0b283f32a46529d5d345211178ca4"}},"d5eecd7849aa4202b2c5717233b5dd81":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"e61efd293cd84ff6bc853b34f8e9860c":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"f39e84f54d614d64b8c7529b009af830":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"f693cca8123c4f17830fe2b749632ad6":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_4776a80a5d3c4f1dae8c032fd298aed9","max":70,"min":0,"orientation":"horizontal","style":"IPY_MODEL_f6eb65a6c7aa4cf8882db6dc8fa3a295","value":70}},"f6eb65a6c7aa4cf8882db6dc8fa3a295":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"f9d0430c993e44598bc43694a8dae3ce":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_d5eecd7849aa4202b2c5717233b5dd81","placeholder":"​","style":"IPY_MODEL_0d8b8e8413f049c7b1f71e9d63016f2f","value":" 18/18 [00:07&lt;00:00,  2.44it/s]"}}},"version_major":2,"version_minor":0}}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"","metadata":{"papermill":{"duration":0.007357,"end_time":"2025-08-03T05:59:11.600526","exception":false,"start_time":"2025-08-03T05:59:11.593169","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Whale Identification with CNN Training**\n\nhttps://www.kaggle.com/competitions/happy-whale-and-dolphin (2022)","metadata":{"papermill":{"duration":0.005626,"end_time":"2025-08-03T05:59:11.612345","exception":false,"start_time":"2025-08-03T05:59:11.606719","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"---\n\n## **Introduction**\n\nThis project focuses on individual whale and dolphin identification using deep learning.\nBy leveraging **EfficientNetB0** as a transfer learning backbone and a memory-efficient data pipeline, the system is designed to handle large image datasets without exhausting memory resources. The goal is to train a robust image classifier that can distinguish between many different individuals, even when the number of samples per class is limited. This is particularly useful for wildlife monitoring and conservation, where datasets are often imbalanced and memory constraints are significant.\n\n---\n\n## **Pipeline Overview**\n\n1. **Dataset Loading & Balancing**\n\n   * Read metadata from a CSV file containing image paths and individual IDs.\n   * Remove classes with too few images to ensure reliable training.\n   * Limit the maximum number of samples per class to balance the dataset.\n\n2. **Memory-Efficient Data Generator**\n\n   * Load images on-the-fly from disk instead of storing all in memory.\n   * Preprocess using EfficientNet's input normalization.\n   * Optionally apply image augmentation (rotation, shifting, zooming, flipping) to improve generalization.\n\n3. **Model Construction**\n\n   * Use **EfficientNetB0** pretrained on ImageNet as the feature extractor.\n   * Initially freeze the backbone layers to focus on training the custom classification head.\n   * Add a global average pooling layer, dropout for regularization, and a final dense layer with softmax activation.\n\n4. **Training Process**\n\n   * **Stage 1 – Feature Extraction:** Train only the classification head while the backbone remains frozen.\n   * **Stage 2 – Fine-Tuning:** Unfreeze the top layers of the backbone and continue training with a reduced learning rate.\n   * Use callbacks such as ModelCheckpoint, EarlyStopping, and ReduceLROnPlateau for training stability.\n\n5. **Evaluation & Visualization**\n\n   * Evaluate on a validation set using accuracy and top-5 accuracy.\n   * Plot accuracy and loss curves for both the initial training and fine-tuning stages to monitor progress.\n\n---\n\n","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport gc\nfrom tensorflow.keras.applications import EfficientNetB0\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.preprocessing.image import load_img, img_to_array\nfrom tensorflow.keras.applications.efficientnet import preprocess_input\nfrom tensorflow.keras.utils import to_categorical\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\nfrom tensorflow.keras import mixed_precision\n\n# Enable mixed precision training\npolicy = mixed_precision.Policy('mixed_float16')\nmixed_precision.set_global_policy(policy)\n\n# 1. Memory-Optimized Data Loading\ndef load_balanced_dataset(csv_path, img_dir, max_samples_per_class=15, min_samples_per_class=5):\n    \"\"\"Load and balance the dataset without loading images into memory\"\"\"\n    df = pd.read_csv(csv_path)\n    \n    print(f\"Original dataset: {len(df)} images\")\n    print(f\"Original individuals: {df['individual_id'].nunique()}\")\n    \n    # Filter classes with too few samples\n    class_counts = df['individual_id'].value_counts()\n    valid_classes = class_counts[class_counts >= min_samples_per_class].index\n    df = df[df['individual_id'].isin(valid_classes)]\n    \n    # Balance the dataset\n    balanced_dfs = []\n    for class_id in df['individual_id'].unique():\n        class_df = df[df['individual_id'] == class_id]\n        if len(class_df) > max_samples_per_class:\n            class_df = class_df.sample(n=max_samples_per_class, random_state=42)\n        balanced_dfs.append(class_df)\n    \n    return pd.concat(balanced_dfs, ignore_index=True)\n\n# 2. Efficient Data Generator\nclass WhaleDataGenerator(tf.keras.utils.Sequence):\n    \"\"\"Memory-efficient data generator\"\"\"\n    def __init__(self, df, img_dir, target_size=(224, 224), batch_size=32, shuffle=True, augment=False):\n        self.df = df.copy()\n        self.img_dir = img_dir\n        self.target_size = target_size\n        self.batch_size = batch_size\n        self.shuffle = shuffle\n        self.augment = augment\n        self.class_indices = {cls: idx for idx, cls in enumerate(df['individual_id'].unique())}\n        self.num_classes = len(self.class_indices)\n        self.on_epoch_end()\n        \n        # Create augmentation generator if needed\n        if self.augment:\n            self.augmenter = tf.keras.preprocessing.image.ImageDataGenerator(\n                rotation_range=20,\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    def __len__(self):\n        return int(np.ceil(len(self.df) / self.batch_size))\n\n    def __getitem__(self, idx):\n        batch_df = self.df.iloc[idx * self.batch_size:(idx + 1) * self.batch_size]\n        batch_images = []\n        batch_labels = []\n        \n        for _, row in batch_df.iterrows():\n            try:\n                img_path = os.path.join(self.img_dir, row['image'])\n                img = load_img(img_path, target_size=self.target_size)\n                img = img_to_array(img)\n                img = preprocess_input(img)\n                \n                batch_images.append(img)\n                batch_labels.append(self.class_indices[row['individual_id']])\n            except Exception as e:\n                print(f\"Error loading {img_path}: {e}\")\n                continue\n        \n        X = np.array(batch_images, dtype=np.float32)\n        y = to_categorical(batch_labels, num_classes=self.num_classes)\n        \n        if self.augment:\n            X = self.augmenter.flow(X, batch_size=len(X), shuffle=False).__next__()\n            \n        return X, y\n\n    def on_epoch_end(self):\n        if self.shuffle:\n            self.df = self.df.sample(frac=1).reset_index(drop=True)\n\n# 3. Model Creation\ndef create_whale_model(input_shape=(224, 224, 3), num_classes=1000):\n    \"\"\"Create EfficientNet model with custom head\"\"\"\n    base_model = EfficientNetB0(\n        weights='imagenet',\n        include_top=False,\n        input_shape=input_shape,\n        pooling=None\n    )\n    \n    # Freeze base model layers initially\n    for layer in base_model.layers:\n        layer.trainable = False\n    \n    # Custom head\n    x = base_model.output\n    x = GlobalAveragePooling2D()(x)\n    x = Dropout(0.3)(x)\n    predictions = Dense(num_classes, activation='softmax', dtype='float32')(x)\n    \n    model = Model(inputs=base_model.input, outputs=predictions)\n    return model, base_model\n\n# 4. Training Pipeline\ndef train_whale_model():\n    # Configuration\n    CSV_PATH = '/kaggle/input/happy-whale-and-dolphin/train.csv'\n    IMG_DIR = '/kaggle/input/happywhale-cropped-removebackground-v1/removedBackground_train_images'\n    EPOCHS_INITIAL = 3\n    EPOCHS_FINETUNE = 3\n    BATCH_SIZE = 16  # Reduced from 32\n    MAX_SAMPLES_PER_CLASS = 20  # Limit samples per whale/dolphin\n    MIN_SAMPLES_PER_CLASS = 5   # Minimum samples required\n    TEST_SIZE = 0.2\n    IMG_SIZE = (224, 224)\n    \n    # Enable garbage collection\n    gc.enable()\n    \n    # 1. Load and prepare metadata\n    print(\"Loading and balancing dataset...\")\n    df = load_balanced_dataset(\n        csv_path=CSV_PATH,\n        img_dir=IMG_DIR,\n        max_samples_per_class=MAX_SAMPLES_PER_CLASS,\n        min_samples_per_class=MIN_SAMPLES_PER_CLASS\n    )\n    \n    # Encode labels\n    label_encoder = LabelEncoder()\n    df['individual_id_encoded'] = label_encoder.fit_transform(df['individual_id'])\n    num_classes = len(label_encoder.classes_)\n    \n    # Split data\n    train_df, val_df = train_test_split(\n        df,\n        test_size=TEST_SIZE,\n        stratify=df['individual_id_encoded'],\n        random_state=42\n    )\n    \n    print(\"\\nDataset Summary:\")\n    print(f\"Total images: {len(df)}\")\n    print(f\"Classes: {num_classes}\")\n    print(f\"Training set: {len(train_df)} samples\")\n    print(f\"Validation set: {len(val_df)} samples\")\n    \n    # 2. Create data generators\n    train_gen = WhaleDataGenerator(\n        train_df, \n        IMG_DIR, \n        target_size=IMG_SIZE, \n        batch_size=BATCH_SIZE, \n        shuffle=True,\n        augment=True\n    )\n    \n    val_gen = WhaleDataGenerator(\n        val_df, \n        IMG_DIR, \n        target_size=IMG_SIZE, \n        batch_size=BATCH_SIZE, \n        shuffle=False,\n        augment=False\n    )\n    \n    # 3. Create and compile model\n    print(\"\\nCreating model...\")\n    model, base_model = create_whale_model(input_shape=(*IMG_SIZE, 3), num_classes=num_classes)\n    \n    # Callbacks\n    callbacks = [\n        ModelCheckpoint(\n            'best_model.h5',\n            monitor='val_accuracy',\n            save_best_only=True,\n            mode='max',\n            verbose=1\n        ),\n        EarlyStopping(\n            monitor='val_loss',\n            patience=5,\n            restore_best_weights=True,\n            verbose=1\n        ),\n        ReduceLROnPlateau(\n            monitor='val_loss',\n            factor=0.2,\n            patience=3,\n            min_lr=1e-6,\n            verbose=1\n        ),\n        tf.keras.callbacks.TerminateOnNaN()\n    ]\n    \n    # Initial training (feature extraction)\n    print(\"\\nStarting initial training...\")\n    model.compile(\n        optimizer=Adam(learning_rate=1e-3),\n        loss='categorical_crossentropy',\n        metrics=['accuracy', 'top_k_categorical_accuracy']\n    )\n    \n    history = model.fit(\n        train_gen,\n        epochs=EPOCHS_INITIAL,\n        validation_data=val_gen,\n        callbacks=callbacks,\n        verbose=1,\n    )\n    \n    # Fine-tuning (unfreeze some layers)\n    print(\"\\nStarting fine-tuning...\")\n    for layer in base_model.layers[-20:]:\n        layer.trainable = True\n    \n    model.compile(\n        optimizer=Adam(learning_rate=1e-4),\n        loss='categorical_crossentropy',\n        metrics=['accuracy', 'top_k_categorical_accuracy']\n    )\n    \n    history_finetune = model.fit(\n        train_gen,\n        epochs=EPOCHS_FINETUNE,\n        initial_epoch=history.epoch[-1],\n        validation_data=val_gen,\n        callbacks=callbacks,\n        verbose=1,\n    )\n    \n    # Evaluation\n    print(\"\\nEvaluating model...\")\n    results = model.evaluate(val_gen)\n    print(f\"Validation Loss: {results[0]:.4f}\")\n    print(f\"Validation Accuracy: {results[1]:.4f}\")\n    print(f\"Validation Top-5 Accuracy: {results[2]:.4f}\")\n    \n    # Plot training history\n    plot_training_history(history, history_finetune)\n    \n    return model, label_encoder, history, history_finetune\n\n# 5. Visualization\ndef plot_training_history(history, history_finetune=None):\n    \"\"\"Plot training and validation metrics\"\"\"\n    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n    \n    # Accuracy plot\n    ax1.plot(history.history['accuracy'], label='Train Accuracy')\n    ax1.plot(history.history['val_accuracy'], label='Val Accuracy')\n    if history_finetune:\n        offset = len(history.history['accuracy'])\n        ax1.plot(range(offset, offset + len(history_finetune.history['accuracy'])), \n                history_finetune.history['accuracy'], label='Finetune Train Accuracy')\n        ax1.plot(range(offset, offset + len(history_finetune.history['val_accuracy'])), \n                history_finetune.history['val_accuracy'], label='Finetune Val Accuracy')\n    ax1.set_title('Model Accuracy')\n    ax1.set_xlabel('Epoch')\n    ax1.set_ylabel('Accuracy')\n    ax1.legend()\n    \n    # Loss plot\n    ax2.plot(history.history['loss'], label='Train Loss')\n    ax2.plot(history.history['val_loss'], label='Val Loss')\n    if history_finetune:\n        offset = len(history.history['loss'])\n        ax2.plot(range(offset, offset + len(history_finetune.history['loss'])), \n                history_finetune.history['loss'], label='Finetune Train Loss')\n        ax2.plot(range(offset, offset + len(history_finetune.history['val_loss'])), \n                history_finetune.history['val_loss'], label='Finetune Val Loss')\n    ax2.set_title('Model Loss')\n    ax2.set_xlabel('Epoch')\n    ax2.set_ylabel('Loss')\n    ax2.legend()\n    \n    plt.tight_layout()\n    plt.show()\n\nif __name__ == \"__main__\":\n    model, label_encoder, history, history_finetune = train_whale_model()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"https://www.kaggle.com/code/stpeteishii/fe-whale-identification-with-cnn-training (private)","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}