{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":"nvidiaTeslaT4","dataSources":[{"sourceId":45867,"databundleVersionId":6924515,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":41239.72278,"end_time":"2025-04-26T02:21:40.075351","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-04-25T14:54:20.352571","version":"2.4.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# UBC Ocean JPEG Dataset Pipeline with DenseNet-121","metadata":{}},{"cell_type":"code","source":"\n# 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    print(len(filenames))\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":{"execution":{"iopub.execute_input":"2025-04-25T14:54:23.846373Z","iopub.status.busy":"2025-04-25T14:54:23.845582Z","iopub.status.idle":"2025-04-25T14:54:26.058269Z","shell.execute_reply":"2025-04-25T14:54:26.057282Z"},"papermill":{"duration":2.220463,"end_time":"2025-04-25T14:54:26.060331","exception":false,"start_time":"2025-04-25T14:54:23.839868","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Package Imports and Environment Setup**\n\nImporting essential libraries for image processing, deep learning, and data visualization.","metadata":{"papermill":{"duration":0.003982,"end_time":"2025-04-25T14:54:26.068619","exception":false,"start_time":"2025-04-25T14:54:26.064637","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# Install specific package versions for compatibility\n%pip install numpy==1.22.4\n%pip install --upgrade scipy","metadata":{"execution":{"iopub.execute_input":"2025-04-25T14:54:26.078599Z","iopub.status.busy":"2025-04-25T14:54:26.077741Z","iopub.status.idle":"2025-04-25T15:02:21.358736Z","shell.execute_reply":"2025-04-25T15:02:21.357841Z"},"papermill":{"duration":475.28871,"end_time":"2025-04-25T15:02:21.361532","exception":false,"start_time":"2025-04-25T14:54:26.072822","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CORE LIBRARIES\nimport os\nimport random\nimport warnings\nfrom pathlib import Path\nfrom tqdm import tqdm\n\n\n# DATA PROCESSING & SCIENTIFIC COMPUTING\nimport numpy as np\nimport pandas as pd\nimport cv2\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.ensemble import IsolationForest\n\n\n# DEEP LEARNING & TENSORFLOW\nimport tensorflow as tf\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.applications.vgg16 import preprocess_input\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.models import Sequential, Model\nfrom tensorflow.keras.layers import (\n    Conv2D, MaxPooling2D, Flatten, Dense, Dropout, \n    BatchNormalization, ReLU, Concatenate, AvgPool2D, \n    GlobalAveragePooling2D, Input\n)\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping\nfrom tensorflow.keras import backend as K\n\n\n# VISUALIZATION\nimport matplotlib.pyplot as plt\n\n\n# CONFIGURATION\nwarnings.filterwarnings('ignore')\n\n# Set random seeds for reproducibility\nnp.random.seed(42)\ntf.random.set_seed(42)\nrandom.seed(42)\n\nprint(f\"✅ TensorFlow version: {tf.__version__}\")\nprint(f\"✅ GPU Available: {len(tf.config.list_physical_devices('GPU')) > 0}\")\nprint(f\"✅ Environment setup complete!\")\n","metadata":{"execution":{"iopub.execute_input":"2025-04-25T15:02:21.373386Z","iopub.status.busy":"2025-04-25T15:02:21.372644Z","iopub.status.idle":"2025-04-25T15:02:33.661485Z","shell.execute_reply":"2025-04-25T15:02:33.660548Z"},"papermill":{"duration":12.296778,"end_time":"2025-04-25T15:02:33.663799","exception":false,"start_time":"2025-04-25T15:02:21.367021","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Configuration and Constants**\n\nDefining global variables, paths, and processing parameters","metadata":{"papermill":{"duration":0.004588,"end_time":"2025-04-25T15:02:33.673322","exception":false,"start_time":"2025-04-25T15:02:33.668734","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# ENVIRONMENT CONFIGURATION\n# Set environment variable to handle large histopathological images\nos.environ['OPENCV_IO_MAX_IMAGE_PIXELS'] = str(pow(2, 40))\n\n\n# DATASET PATHS\ncompetition_dataset_directory = Path('/kaggle/input/UBC-OCEAN')\n\n\n# IMAGE PROCESSING PARAMETERS\nPROCESSED_IMAGE_SIZE = 224     # Target image size for DenseNet-121\nJPEG_QUALITY = 80              # Compression quality for processed images\n\n\n# TRAINING PARAMETERS\nVALIDATION_SPLIT = 0.2         # 20% for validation\nRANDOM_STATE = 42              # For reproducible results\nTARGET_TRAINING_SAMPLES = 30000 # Desired dataset size\nMAX_OUTLIER_PERCENT = 0.05     # Maximum 5% outliers to remove\n\nprint(f\"🎯 Target image size: {PROCESSED_IMAGE_SIZE}x{PROCESSED_IMAGE_SIZE}\")\nprint(f\"📊 Validation split: {VALIDATION_SPLIT*100}%\")\nprint(f\"🔢 Random state: {RANDOM_STATE}\")\n","metadata":{"execution":{"iopub.execute_input":"2025-04-25T15:02:33.684112Z","iopub.status.busy":"2025-04-25T15:02:33.68362Z","iopub.status.idle":"2025-04-25T15:02:33.688264Z","shell.execute_reply":"2025-04-25T15:02:33.687464Z"},"papermill":{"duration":0.011816,"end_time":"2025-04-25T15:02:33.689811","exception":false,"start_time":"2025-04-25T15:02:33.677995","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Dataset Loading and Directory Setup**\n\nLoading the dataset and creating processing directories for processed images.","metadata":{"papermill":{"duration":0.004341,"end_time":"2025-04-25T15:02:33.698822","exception":false,"start_time":"2025-04-25T15:02:33.694481","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# LOAD DATASET FILES\nprint(\"📖 Loading dataset files...\")\ndf_train = pd.read_csv(competition_dataset_directory / 'train.csv')\ndf_test = pd.read_csv(competition_dataset_directory / 'test.csv')\n\nprint(f\"✅ Training samples: {len(df_train):,}\")\nprint(f\"✅ Test samples: {len(df_test):,}\")\nprint(f\"✅ Label distribution:\")\nprint(df_train['label'].value_counts())\n\n# CREATE PROCESSING DIRECTORIES\nprint(\"\\n📁 Creating directories for processed images...\")\ntrain_processed_dir = Path('./train_processed_images')\ntest_processed_dir = Path('./test_processed_images')\ntrain_processed_dir.mkdir(exist_ok=True, parents=True)   \ntest_processed_dir.mkdir(exist_ok=True, parents=True)\n\nprint(f\"✅ Training directory: {train_processed_dir}\")\nprint(f\"✅ Test directory: {test_processed_dir}\")\n","metadata":{"execution":{"iopub.execute_input":"2025-04-25T15:02:33.709232Z","iopub.status.busy":"2025-04-25T15:02:33.708815Z","iopub.status.idle":"2025-04-25T15:02:33.72791Z","shell.execute_reply":"2025-04-25T15:02:33.727105Z"},"papermill":{"duration":0.026006,"end_time":"2025-04-25T15:02:33.729594","exception":false,"start_time":"2025-04-25T15:02:33.703588","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Utility Functions for Image Processing**\n\nDefining essential functions for handling large histopathological images with multiple processing strategies.","metadata":{"papermill":{"duration":0.004434,"end_time":"2025-04-25T15:02:33.738904","exception":false,"start_time":"2025-04-25T15:02:33.73447","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# Install OpenSlide for handling whole slide images (WSI)\n%pip install openslide-python","metadata":{"execution":{"iopub.execute_input":"2025-04-25T15:02:33.749313Z","iopub.status.busy":"2025-04-25T15:02:33.748818Z","iopub.status.idle":"2025-04-25T15:03:14.238188Z","shell.execute_reply":"2025-04-25T15:03:14.237279Z"},"papermill":{"duration":40.496683,"end_time":"2025-04-25T15:03:14.240413","exception":false,"start_time":"2025-04-25T15:02:33.74373","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def resize_with_aspect_ratio(image, target_size):\n    \"\"\"\n    Resize image to target size while preserving aspect ratio\n    \n    Parameters\n    ----------\n    image: numpy.ndarray of shape (height, width, 3)\n        Image array\n    target_size: int\n        Desired size for both dimensions\n        \n    Returns\n    -------\n    resized_image: numpy.ndarray of shape (target_size, target_size, 3)\n        Resized and padded image array\n    \"\"\"\n    height, width = image.shape[:2]\n    \n    # Calculate scaling factor to preserve aspect ratio\n    scale = min(target_size / height, target_size / width)\n    \n    # Calculate new dimensions\n    new_height = int(height * scale)\n    new_width = int(width * scale)\n    \n    # Resize image\n    resized = cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_AREA)\n    \n    # Create a black canvas of target size\n    canvas = np.zeros((target_size, target_size, 3), dtype=np.uint8)\n     # Calculate offsets to center the image\n    y_offset = (target_size - new_height) // 2\n    x_offset = (target_size - new_width) // 2\n    \n    # Place the resized image on the canvas\n    canvas[y_offset:y_offset+new_height, x_offset:x_offset+new_width] = resized\n    \n    return canvas\n","metadata":{"execution":{"iopub.execute_input":"2025-04-25T15:03:14.252447Z","iopub.status.busy":"2025-04-25T15:03:14.251605Z","iopub.status.idle":"2025-04-25T15:03:14.258856Z","shell.execute_reply":"2025-04-25T15:03:14.258012Z"},"papermill":{"duration":0.015032,"end_time":"2025-04-25T15:03:14.260626","exception":false,"start_time":"2025-04-25T15:03:14.245594","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def process_by_tiles(raw_image_path, processed_image_path, tile_size=1024):\n    \"\"\"\n    Process very large images by tiling them and taking the center tile\n    \n    Parameters\n    ----------\n    raw_image_path: str\n        Path to the input image\n    processed_image_path: str\n        Path to save the processed image\n    tile_size: int\n        Size of tiles to extract\n    \"\"\"\n    try:\n        from PIL import Image\n        img = Image.open(raw_image_path)\n        width, height = img.size\n        \n        # Take center tile\n        center_x, center_y = width // 2, height // 2\n        left = max(0, center_x - tile_size // 2)\n        top = max(0, center_y - tile_size // 2)\n        right = min(width, left + tile_size)\n        bottom = min(height, top + tile_size)\n        \n        # Crop and resize\n        cropped = img.crop((left, top, right, bottom))\n        resized = cropped.resize((PROCESSED_IMAGE_SIZE, PROCESSED_IMAGE_SIZE), Image.LANCZOS)\n        resized.save(processed_image_path, quality=JPEG_QUALITY)\n        \n    except Exception as e:\n        print(f\"Tiling process failed: {e}\")\n        raise e","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def process_large_images(df, source_dir, target_dir, is_train=True):\n    processed_paths = []\n    \n    for idx, row in tqdm(df.iterrows(), total=df.shape[0], desc=\"Processing images\"):\n        image_id = row[\"image_id\"]\n        raw_image_path = str(source_dir / f'{image_id}.png')\n        processed_image_path = str(target_dir / f'{image_id}.jpg')\n\n        if os.path.exists(processed_image_path):\n            processed_paths.append(processed_image_path)\n            continue\n\n        # Check file extension to determine processing method\n        file_extension = Path(raw_image_path).suffix.lower()\n        \n        # 1. For WSI formats, try OpenSlide first\n        if file_extension in ['.svs', '.ndpi', '.tiff', '.tif', '.vms', '.vmu', '.scn', '.mrxs', '.bif']:\n            try:\n                import openslide\n                from PIL import Image\n\n                slide = openslide.OpenSlide(raw_image_path)\n                width, height = slide.dimensions\n                scale = min(PROCESSED_IMAGE_SIZE / height, PROCESSED_IMAGE_SIZE / width)\n                new_w, new_h = int(width * scale), int(height * scale)\n\n                thumbnail = slide.get_thumbnail((new_w, new_h))\n\n                # Center on canvas\n                result = Image.new(\"RGB\", (PROCESSED_IMAGE_SIZE, PROCESSED_IMAGE_SIZE), (0, 0, 0))\n                result.paste(thumbnail, ((PROCESSED_IMAGE_SIZE - new_w) // 2, (PROCESSED_IMAGE_SIZE - new_h) // 2))\n                result.save(processed_image_path, quality=JPEG_QUALITY)\n                processed_paths.append(processed_image_path)\n                continue\n            except Exception as e:\n                print(f\"OpenSlide failed for {raw_image_path}: {e}\")\n\n        # 2. For standard image formats (PNG, JPG, etc.), use PIL as primary method\n        try:\n            from PIL import Image\n            import warnings\n            warnings.simplefilter('ignore', Image.DecompressionBombWarning)\n            Image.MAX_IMAGE_PIXELS = None\n\n            img = Image.open(raw_image_path)\n            # Convert to RGB if necessary\n            if img.mode != 'RGB':\n                img = img.convert('RGB')\n                \n            width, height = img.size\n            scale = min(PROCESSED_IMAGE_SIZE / height, PROCESSED_IMAGE_SIZE / width)\n            new_w, new_h = int(width * scale), int(height * scale)\n            img_resized = img.resize((new_w, new_h), Image.LANCZOS)\n\n            result = Image.new(\"RGB\", (PROCESSED_IMAGE_SIZE, PROCESSED_IMAGE_SIZE), (0, 0, 0))\n            result.paste(img_resized, ((PROCESSED_IMAGE_SIZE - new_w) // 2, (PROCESSED_IMAGE_SIZE - new_h) // 2))\n            result.save(processed_image_path, quality=JPEG_QUALITY)\n            processed_paths.append(processed_image_path)\n            continue\n        except Exception as e:\n            print(f\"PIL failed for {raw_image_path}: {e}\")\n\n        # 3. Fallback to OpenCV\n        try:\n            import cv2\n            image = cv2.imread(raw_image_path)\n            if image is not None:\n                image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n                resized = resize_with_aspect_ratio(image, PROCESSED_IMAGE_SIZE)\n                resized = cv2.cvtColor(resized, cv2.COLOR_RGB2BGR)\n                cv2.imwrite(processed_image_path, resized, [int(cv2.IMWRITE_JPEG_QUALITY), JPEG_QUALITY])\n                processed_paths.append(processed_image_path)\n                continue\n        except Exception as e:\n            print(f\"OpenCV failed for {raw_image_path}: {e}\")\n\n        # 4. Last resort: tiling\n        try:\n            process_by_tiles(raw_image_path, processed_image_path)\n            processed_paths.append(processed_image_path)\n        except Exception as e:\n            print(f\"Tiling failed for {raw_image_path}: {e}\")\n            # Create black placeholder\n            try:\n                blank = np.zeros((PROCESSED_IMAGE_SIZE, PROCESSED_IMAGE_SIZE, 3), dtype=np.uint8)\n                cv2.imwrite(processed_image_path, blank)\n                processed_paths.append(processed_image_path)\n                print(f\"Created black placeholder for {image_id}\")\n            except:\n                pass\n\n    return processed_paths","metadata":{"papermill":{"duration":0.019354,"end_time":"2025-04-25T15:03:14.284491","exception":false,"start_time":"2025-04-25T15:03:14.265137","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Image Processing Pipeline**\n\nProcessing training and test images with different approaches and creating label mappings for model training.","metadata":{"papermill":{"duration":0.004886,"end_time":"2025-04-25T15:03:14.29433","exception":false,"start_time":"2025-04-25T15:03:14.289444","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# Process training and test images\ntrain_image_paths = process_large_images(df_train, competition_dataset_directory / 'train_images', train_processed_dir, is_train=True)\ntest_image_paths = process_large_images(df_test, competition_dataset_directory / 'test_images', test_processed_dir, is_train=False)\n\n\n# Create a label mapping\nlabel_mapping = {label: idx for idx, label in enumerate(df_train['label'].unique())}\nnum_classes = len(label_mapping)\nprint(f\"Number of classes: {num_classes}\")\nprint(f\"Label mapping: {label_mapping}\")\n\n# Map labels to numeric values\ndf_train['label_idx'] = df_train['label'].map(label_mapping)\n","metadata":{"execution":{"iopub.status.busy":"2025-05-27T07:13:22.781467Z","iopub.execute_input":"2025-05-27T07:13:22.781767Z","iopub.status.idle":"2025-05-27T07:13:22.89627Z","shell.execute_reply.started":"2025-05-27T07:13:22.781742Z","shell.execute_reply":"2025-05-27T07:13:22.894592Z"},"papermill":{"duration":40697.817265,"end_time":"2025-04-26T02:21:32.116641","exception":false,"start_time":"2025-04-25T15:03:14.299376","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def extract_image_features(image_path):\n    \"\"\"\n    Extract basic features from an image for outlier detection\n    \n    Parameters\n    ----------\n    image_path: str\n        Path to the image file\n        \n    Returns\n    -------\n    features: numpy.ndarray\n        Array of extracted features or None if extraction fails\n    \"\"\"\n    try:\n        img = cv2.imread(image_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n        # Extract color statistics for each channel\n        features = []\n        for c in range(3):\n            channel = img[:,:,c]\n            features.extend([\n                np.mean(channel),       # Mean\n                np.std(channel),        # Standard deviation\n                np.percentile(channel, 5),  # 5th percentile\n                np.percentile(channel, 95), # 95th percentile\n            ])\n        \n        # Add texture features (using basic edge detection)\n        gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n        edges = cv2.Canny(gray, 100, 200)\n        features.append(np.mean(edges))\n        features.append(np.std(edges))\n        \n        # Add basic shape features\n        features.append(np.sum(edges > 0) / (edges.shape[0] * edges.shape[1]))  # Edge density\n        \n        return np.array(features)\n        \n    except Exception as e:\n        print(f\"Error extracting features from {image_path}: {e}\")\n        return None\n","metadata":{"execution":{"iopub.execute_input":"2025-04-26T02:21:32.218796Z","iopub.status.busy":"2025-04-26T02:21:32.218529Z","iopub.status.idle":"2025-04-26T02:21:32.224813Z","shell.execute_reply":"2025-04-26T02:21:32.2241Z"},"papermill":{"duration":0.056979,"end_time":"2025-04-26T02:21:32.226437","exception":false,"start_time":"2025-04-26T02:21:32.169458","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Outlier Detection**\n\nApplying Isolation Forest to identify and remove problematic images that might negatively impact training.","metadata":{"papermill":{"duration":0.048099,"end_time":"2025-04-26T02:21:32.334355","exception":false,"start_time":"2025-04-26T02:21:32.286256","status":"completed"},"tags":[]}},{"cell_type":"code","source":"#Extract features for outlier detection\nprint(\"Extracting features for outlier detection...\")\nfeatures_list = []\nvalid_image_indices = []\n\nfor i, path in enumerate(tqdm(train_image_paths, desc=\"Extracting features\")):\n    features = extract_image_features(path)\n    if features is not None:\n        features_list.append(features)\n        valid_image_indices.append(i)\n\n# Convert list to numpy array\nX_features = np.array(features_list)\n# Apply Isolation Forest for outlier detection\nprint(\"Detecting outliers using Isolation Forest...\")\ncontamination = min(MAX_OUTLIER_PERCENT, 0.05)  # Max 5% outliers\noutlier_detector = IsolationForest(contamination=contamination, random_state=RANDOM_STATE)\noutlier_predictions = outlier_detector.fit_predict(X_features)\n\n# Filter out outliers\ninlier_indices = [valid_image_indices[i] for i, pred in enumerate(outlier_predictions) if pred == 1]\noutlier_indices = [valid_image_indices[i] for i, pred in enumerate(outlier_predictions) if pred == -1]\n\nprint(f\"Detected {len(outlier_indices)} outliers out of {len(valid_image_indices)} images ({len(outlier_indices)/len(valid_image_indices)*100:.2f}%)\")\n\n# Create filtered DataFrame\ndf_train_filtered = df_train.iloc[inlier_indices].reset_index(drop=True)\nprint(f\"After outlier removal: {len(df_train_filtered)} training samples\")\n","metadata":{"execution":{"iopub.execute_input":"2025-04-26T02:21:32.431649Z","iopub.status.busy":"2025-04-26T02:21:32.431372Z","iopub.status.idle":"2025-04-26T02:21:36.144344Z","shell.execute_reply":"2025-04-26T02:21:36.143584Z"},"papermill":{"duration":3.763435,"end_time":"2025-04-26T02:21:36.146016","exception":false,"start_time":"2025-04-26T02:21:32.382581","status":"completed"},"tags":[]},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **DenseNet-121**\n\n### **Architecture Overview**\nImplementing the complete DenseNet-121 architecture following https://www.kaggle.com/code/iamtapendu/introduction-to-densenet-121.\n","metadata":{}},{"cell_type":"code","source":"# DenseNet-121 Implementation following the tutorial\n\ndef bottleneck_layer(x, filters, strides=1):\n    \"\"\"Create bottleneck layer for DenseNet\"\"\"\n    skip_connection = x\n    # BN-ReLU-Conv(1×1)-BN-ReLU-Conv(3×3)\n    x = BatchNormalization()(x)\n    x = ReLU()(x)\n    x = Conv2D(4*filters, kernel_size=1, strides=strides, padding='same')(x)\n    \n    x = BatchNormalization()(x)\n    x = ReLU()(x)\n    x = Conv2D(filters, kernel_size=3, strides=strides, padding='same')(x)\n    \n    x = Concatenate()([x, skip_connection])\n    return x\n\ndef transition_layer(x):\n    \"\"\"Create transition layer for DenseNet\"\"\"\n    x = BatchNormalization()(x)\n    x = ReLU()(x)\n    x = Conv2D(K.int_shape(x)[-1]//2, kernel_size=1, strides=1, padding='same')(x)\n    x = AvgPool2D(2, strides=2, padding='same')(x)\n    return x\n\ndef dense_block(x, repetition=1, growth_rate=32):\n    \"\"\"Create dense block with multiple bottleneck layers\"\"\"\n    for _ in range(repetition):\n        x = bottleneck_layer(x, growth_rate)\n    return x\n\ndef densenet121(input_shape, num_classes, growth_rate=32):\n    \"\"\"Create DenseNet-121 model\"\"\"\n    # Input layer\n    inputs = Input(shape=input_shape)\n    \n    # Initial layer\n    x = BatchNormalization()(inputs)\n    x = ReLU()(x)\n    x = Conv2D(64, kernel_size=7, strides=2, padding='same')(x)\n    \n    # Pooling layer\n    x = MaxPooling2D(3, strides=2, padding='same')(x)\n    \n    # First dense and transition layer (6 layers)\n    x = dense_block(x, 6, growth_rate)\n    x = transition_layer(x)\n    \n    # Second dense and transition layer (12 layers)\n    x = dense_block(x, 12, growth_rate)\n    x = transition_layer(x)\n    \n    # Third dense and transition layer (24 layers)\n    x = dense_block(x, 24, growth_rate)\n    x = transition_layer(x)\n    \n    # Last dense layer (16 layers)\n    x = dense_block(x, 16, growth_rate)\n    \n    # Global average pooling layer\n    x = GlobalAveragePooling2D()(x)\n    \n    # Output layer\n    outputs = Dense(num_classes, activation='softmax')(x)\n    \n    model = Model(inputs, outputs)\n    return model","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Helper function to load an image\ndef load_image(image_path):\n    \"\"\"Load and return RGB image\"\"\"\n    img = cv2.imread(image_path)\n    if img is None:\n        raise ValueError(f\"Could not load image: {image_path}\")\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    return img\n\n# Improved data generator with proper augmentation\nclass ImprovedDataGenerator(tf.keras.utils.Sequence):\n    def __init__(self, dataframe, image_dir, batch_size, datagen, \n                 target_size=(224, 224), shuffle=True, num_classes=None):\n        self.dataframe = dataframe.reset_index(drop=True)\n        self.image_dir = Path(image_dir)\n        self.batch_size = batch_size\n        self.datagen = datagen\n        self.target_size = target_size\n        self.shuffle = shuffle\n        self.num_classes = num_classes\n        self.indexes = np.arange(len(self.dataframe))\n        self.on_epoch_end()\n        \n    def __len__(self):\n        return int(np.ceil(len(self.dataframe) / self.batch_size))\n    \n    def on_epoch_end(self):\n        if self.shuffle:\n            np.random.shuffle(self.indexes)\n            \n    def __getitem__(self, index):\n        # Generate indexes for this batch\n        start_idx = index * self.batch_size\n        end_idx = min((index + 1) * self.batch_size, len(self.dataframe))\n        batch_indexes = self.indexes[start_idx:end_idx]\n        \n        # Initialize batch arrays\n        batch_size_actual = len(batch_indexes)\n        X = np.empty((batch_size_actual, *self.target_size, 3), dtype=np.float32)\n        y = np.empty((batch_size_actual, self.num_classes), dtype=np.float32)\n        \n        # Generate data\n        for i, idx in enumerate(batch_indexes):\n            # Get image path\n            image_id = self.dataframe.loc[idx, 'image_id']\n            img_path = self.image_dir / f'{image_id}.jpg'\n            \n            try:\n                # Load image\n                img = load_image(str(img_path))\n                \n                # Ensure correct size\n                if img.shape[:2] != self.target_size:\n                    img = cv2.resize(img, self.target_size, interpolation=cv2.INTER_AREA)\n                \n                # Apply augmentation\n                img = img.astype(np.float32)\n                if hasattr(self.datagen, 'random_transform'):\n                    img = self.datagen.random_transform(img)\n                \n                # Apply preprocessing\n                if hasattr(self.datagen, 'preprocessing_function') and self.datagen.preprocessing_function:\n                    img = self.datagen.preprocessing_function(img)\n                else:\n                    img = img / 255.0  # Default normalization\n                    \n                X[i] = img\n                \n                # Get label\n                label_idx = self.dataframe.loc[idx, 'label_idx']\n                y[i] = to_categorical(label_idx, self.num_classes)\n                \n            except Exception as e:\n                print(f\"Error loading image {img_path}: {e}\")\n                # Create black image as fallback\n                X[i] = np.zeros((*self.target_size, 3), dtype=np.float32)\n                label_idx = self.dataframe.loc[idx, 'label_idx']\n                y[i] = to_categorical(label_idx, self.num_classes)\n        \n        return X, y","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Data Splitting Strategy**\n\nSplitting the filtered dataset into training and validation sets with stratification to maintain class balance.","metadata":{}},{"cell_type":"code","source":"# Split into training and validation sets\ntrain_df, val_df = train_test_split(\n    df_train_filtered, \n    test_size=VALIDATION_SPLIT, \n    random_state=RANDOM_STATE,\n    stratify=df_train_filtered['label_idx']\n)\n\nprint(f\"Training samples: {len(train_df)}, Validation samples: {len(val_df)}\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Data Augmentation Configuration**\n","metadata":{}},{"cell_type":"code","source":"# Define augmentation for training\ntrain_datagen = ImageDataGenerator(\n    preprocessing_function=preprocess_input,\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    vertical_flip=True,\n    brightness_range=[0.8, 1.2],\n    fill_mode='constant',\n    cval=0\n)\n\n# Minimal augmentation for validation\nval_datagen = ImageDataGenerator(\n    preprocessing_function=preprocess_input\n)","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create improved data generators\nbatch_size = 32\n\n# Training generator with augmentation\ntrain_generator = ImprovedDataGenerator(\n    train_df,\n    train_processed_dir,\n    batch_size=batch_size,\n    datagen=train_datagen,\n    target_size=(PROCESSED_IMAGE_SIZE, PROCESSED_IMAGE_SIZE),\n    shuffle=True,\n    num_classes=num_classes\n)\n\n# Validation generator without augmentation\nval_generator = ImprovedDataGenerator(\n    val_df,\n    train_processed_dir,\n    batch_size=batch_size,\n    datagen=val_datagen,\n    target_size=(PROCESSED_IMAGE_SIZE, PROCESSED_IMAGE_SIZE),\n    shuffle=False,\n    num_classes=num_classes\n)\n\nprint(f\"Training batches: {len(train_generator)}\")\nprint(f\"Validation batches: {len(val_generator)}\")\nprint(f\"Training samples: {len(train_df)}\")\nprint(f\"Validation samples: {len(val_df)}\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Print dataset statistics\nprint(f\"\\n=== Dataset Statistics ===\")\nprint(f\"Number of classes: {num_classes}\")\nprint(f\"Label mapping: {label_mapping}\")\nprint(f\"Training samples: {len(train_df)}\")\nprint(f\"Validation samples: {len(val_df)}\")\nprint(f\"Training steps per epoch: {len(train_generator)}\")\nprint(f\"Validation steps: {len(val_generator)}\")\nprint(f\"Batch size: {batch_size}\")\nprint(f\"Image size: {PROCESSED_IMAGE_SIZE}x{PROCESSED_IMAGE_SIZE}\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Model Creation and Training Pipeline**\n\n### **Training Strategy**\nCreating and training the DenseNet-121 model with optimal hyperparameters and monitoring strategies.\n\n**Model Configuration:**\n- Input size: 224×224×3 \n- Growth rate: 32 \n- Dense block configuration: [6, 12, 24, 16] layers\n\n**Training Setup:**\n- Adam optimizer with adaptive learning rate\n- Categorical crossentropy loss for multi-class classification\n- Model checkpointing for best validation accuracy\n- Early stopping to prevent overfitting","metadata":{}},{"cell_type":"code","source":"# Create DenseNet-121 model\nprint(\"Creating DenseNet-121 model...\")\nmodel = densenet121(\n    input_shape=(PROCESSED_IMAGE_SIZE, PROCESSED_IMAGE_SIZE, 3),\n    num_classes=num_classes,\n    growth_rate=32\n)\n\n# Compile the model\nmodel.compile(\n    optimizer=Adam(learning_rate=0.001, epsilon=0.05),\n    loss='categorical_crossentropy',\n    metrics=['accuracy']\n)\n\n# Display model summary\nprint(f\"\\nModel Parameters: {model.count_params():,}\")\nmodel.summary()","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define callbacks\ncallbacks = [\n    ModelCheckpoint(\n        'densenet121_ocean_best.h5',\n        monitor='val_accuracy',\n        save_best_only=True,\n        save_weights_only=False,\n        verbose=1\n    ),\n    EarlyStopping(\n        monitor='val_accuracy',\n        patience=10,\n        restore_best_weights=True,\n        verbose=1\n    )\n]\n\n# Train the model\nprint(\"Starting training...\")\nhistory = model.fit(\n    train_generator,\n    validation_data=val_generator,\n    epochs=50,\n    callbacks=callbacks,\n    verbose=1\n)\n\nprint(\"Training completed!\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot training history\nplt.figure(figsize=(15, 5))\n\n# Plot training & validation accuracy\nplt.subplot(1, 3, 1)\nplt.plot(history.history['accuracy'], label='Training Accuracy')\nplt.plot(history.history['val_accuracy'], label='Validation Accuracy')\nplt.title('Model Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.legend()\nplt.grid(True)\n\n# Plot training & validation loss\nplt.subplot(1, 3, 2)\nplt.plot(history.history['loss'], label='Training Loss')\nplt.plot(history.history['val_loss'], label='Validation Loss')\nplt.title('Model Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\nplt.grid(True)\n\n# Plot learning rate if available\nplt.subplot(1, 3, 3)\nif 'lr' in history.history:\n    plt.plot(history.history['lr'], label='Learning Rate')\n    plt.title('Learning Rate')\n    plt.xlabel('Epoch')\n    plt.ylabel('Learning Rate')\n    plt.legend()\n    plt.grid(True)\nelse:\n    plt.text(0.5, 0.5, 'Learning Rate\\nNot Tracked', \n             horizontalalignment='center', verticalalignment='center')\n    plt.title('Learning Rate')\n\nplt.tight_layout()\nplt.show()","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Evaluate the model\nprint(\"\\n=== Final Evaluation ===\")\ntrain_loss, train_accuracy = model.evaluate(train_generator, verbose=0)\nval_loss, val_accuracy = model.evaluate(val_generator, verbose=0)\n\nprint(f\"Training Accuracy: {train_accuracy:.4f}\")\nprint(f\"Training Loss: {train_loss:.4f}\")\nprint(f\"Validation Accuracy: {val_accuracy:.4f}\")\nprint(f\"Validation Loss: {val_loss:.4f}\")","metadata":{},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualize some predictions\ndef plot_predictions(generator, model, num_samples=8):\n    \"\"\"Plot sample predictions\"\"\"\n    # Get a batch of data\n    X_batch, y_batch = generator[0]\n    \n    # Make predictions\n    predictions = model.predict(X_batch)\n    \n    # Create reverse label mapping\n    reverse_label_mapping = {v: k for k, v in label_mapping.items()}\n    \n    # Plot samples\n    plt.figure(figsize=(16, 8))\n    for i in range(min(num_samples, len(X_batch))):\n        plt.subplot(2, 4, i + 1)\n        \n        # Denormalize image for display\n        img = X_batch[i]\n        if img.max() <= 1.0:  # If normalized\n            img = (img * 255).astype(np.uint8)\n        else:\n            img = img.astype(np.uint8)\n            \n        plt.imshow(img)\n        \n        # Get true and predicted labels\n        true_label_idx = np.argmax(y_batch[i])\n        pred_label_idx = np.argmax(predictions[i])\n        \n        true_label = reverse_label_mapping[true_label_idx]\n        pred_label = reverse_label_mapping[pred_label_idx]\n        confidence = predictions[i][pred_label_idx]\n        \n        # Set title with color coding\n        color = 'green' if true_label_idx == pred_label_idx else 'red'\n        plt.title(f'True: {true_label}\\nPred: {pred_label}\\nConf: {confidence:.3f}', \n                 color=color, fontsize=10)\n        plt.axis('off')\n    \n    plt.tight_layout()\n    plt.suptitle('Sample Predictions (Green=Correct, Red=Incorrect)', y=1.02)\n    plt.show()\n\n# Show sample predictions\nprint(\"\\n=== Sample Predictions ===\")\nplot_predictions(val_generator, model, num_samples=8)","metadata":{},"outputs":[],"execution_count":null}]}