{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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"},"accelerator":"GPU","colab":{"gpuType":"T4","provenance":[]},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":20270,"databundleVersionId":1222630,"sourceType":"competition"},{"sourceId":63056,"databundleVersionId":9094797,"sourceType":"competition"},{"sourceId":243949596,"sourceType":"kernelVersion"},{"sourceId":243950493,"sourceType":"kernelVersion"},{"sourceId":426388,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":347594,"modelId":368851}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"\n!pip install albumentations\n\n# !unzip skin-cancer.v6i.folder.zip -d /content/skin-cancer-dataset\n","metadata":{"id":"SVThgGffA9Vb","outputId":"13349d29-93db-4341-e44b-a75f6856bfc5","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport os\n\n# Step 1: Load Metadata\n# Assuming the metadata file is in the extracted directory\nmetadata_path = '/kaggle/input/isic-2024-challenge/train-metadata.csv'\ntry:\n    metadata_df = pd.read_csv(metadata_path)\n    print(\"Metadata loaded successfully.\")\n    display(metadata_df.head())\n    display(metadata_df.info())\nexcept FileNotFoundError:\n    print(f\"Error: Metadata file not found at {metadata_path}\")\n    # If not found, try listing files in the expected directory to help debug\n    extracted_dir = 'skin-cancer-dataset/'\n    print(f\"Listing contents of {extracted_dir}:\")\n    if os.path.exists(extracted_dir):\n        print(os.listdir(extracted_dir))\n    else:\n        print(f\"Directory not found: {extracted_dir}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:20:06.352129Z","iopub.execute_input":"2025-06-06T04:20:06.352383Z","iopub.status.idle":"2025-06-06T04:20:12.483936Z","shell.execute_reply.started":"2025-06-06T04:20:06.352366Z","shell.execute_reply":"2025-06-06T04:20:12.483196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 2: Filter Classes and Limit Samples\n# Filter for 'nevus' (0) and 'melanoma' (1)\nfiltered_df = metadata_df[metadata_df['target'].isin([0, 1])].copy()\n\n# Limit to a maximum of 500 images per class\nlimited_df = filtered_df.groupby('target').head(500).reset_index(drop=True)\n\nprint(\"\\nFiltered and limited data:\")\ndisplay(limited_df['target'].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:20:12.485014Z","iopub.execute_input":"2025-06-06T04:20:12.485191Z","iopub.status.idle":"2025-06-06T04:20:12.604863Z","shell.execute_reply.started":"2025-06-06T04:20:12.485174Z","shell.execute_reply":"2025-06-06T04:20:12.604246Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 3: Split Data\nfrom sklearn.model_selection import train_test_split\n\n# Split into training and the rest (validation + test)\ntrain_df, rest_df = train_test_split(limited_df, test_size=0.3, random_state=42, stratify=limited_df['target'])\n\n# Split the rest into validation and test sets\nval_df, test_df = train_test_split(rest_df, test_size=0.5, random_state=42, stratify=rest_df['target'])\n\nprint(\"\\nDataset split distribution:\")\nprint(\"Train set shape:\", train_df.shape)\nprint(\"Validation set shape:\", val_df.shape)\nprint(\"Test set shape:\", test_df.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:20:12.605397Z","iopub.execute_input":"2025-06-06T04:20:12.605596Z","iopub.status.idle":"2025-06-06T04:20:12.707903Z","shell.execute_reply.started":"2025-06-06T04:20:12.605582Z","shell.execute_reply":"2025-06-06T04:20:12.707297Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 4: Organize Data by Class\nimport shutil\n\nbase_img_dir = '/kaggle/input/isic-2024-challenge/train-image/image' # Assuming images are in the train folder within the extracted data\n\n# Create directories\ntrain_dir = 'split_dataset/train'\nvalid_dir = 'split_dataset/valid'\ntest_dir = 'split_dataset/test'\n\nfor directory in [train_dir, valid_dir, test_dir]:\n    os.makedirs(os.path.join(directory, '0'), exist_ok=True) # Class 0\n    os.makedirs(os.path.join(directory, '1'), exist_ok=True) # Class 1\n\n# Function to copy images\ndef copy_images(df, dest_dir, base_img_dir):\n    for index, row in df.iterrows():\n        img_name = row['isic_id'] + '.jpg' # Assuming images are .jpg\n        src_path = os.path.join(base_img_dir, str(row['target']), img_name) # Assuming images are already in class subdirectories\n        dest_path = os.path.join(dest_dir, str(row['target']), img_name)\n\n        # Check if source file exists before copying\n        if os.path.exists(src_path):\n            shutil.copy(src_path, dest_path)\n        else:\n            # If not found in class subdirectories, try the base image directory directly\n            src_path_flat = os.path.join(base_img_dir, img_name)\n            if os.path.exists(src_path_flat):\n                 shutil.copy(src_path_flat, dest_path)\n            else:\n                print(f\"Warning: Image not found: {src_path} or {src_path_flat}\")\n\n\nprint(\"\\nCopying training images...\")\ncopy_images(train_df, train_dir, base_img_dir)\n\nprint(\"Copying validation images...\")\ncopy_images(val_df, valid_dir, base_img_dir)\n\nprint(\"Copying test images...\")\ncopy_images(test_df, test_dir, base_img_dir)\n\nprint(\"Image copying complete.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:20:14.820284Z","iopub.execute_input":"2025-06-06T04:20:14.820844Z","iopub.status.idle":"2025-06-06T04:20:25.499552Z","shell.execute_reply.started":"2025-06-06T04:20:14.820825Z","shell.execute_reply":"2025-06-06T04:20:25.498680Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dir = '/kaggle/working/split_dataset/train'\nvalid_dir = '/kaggle/working/split_dataset/valid'\ntest_dir = '/kaggle/working/split_dataset/test'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:20:25.500720Z","iopub.execute_input":"2025-06-06T04:20:25.500938Z","iopub.status.idle":"2025-06-06T04:20:25.504225Z","shell.execute_reply.started":"2025-06-06T04:20:25.500922Z","shell.execute_reply":"2025-06-06T04:20:25.503669Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# import os\n# import shutil\n\n# # unlabeled_dir = '/content/skin-cancer-dataset/skin-cancer.v5i.folder/unlabeled'\n# dummy_dir = os.path.join(unlabeled_dir, 'dummy')\n\n# # Create dummy folder if it doesn't exist\n# os.makedirs(dummy_dir, exist_ok=True)\n\n# # Move all images into /unlabeled/dummy/\n# for filename in os.listdir(unlabeled_dir):\n#     filepath = os.path.join(unlabeled_dir, filename)\n#     if os.path.isfile(filepath) and filename.lower().endswith(('.jpg', '.jpeg', '.png')):\n#         shutil.move(filepath, os.path.join(dummy_dir, filename))\n","metadata":{"id":"oq3_U9iWBAP6","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import backend as K\nfrom tensorflow.keras.applications import EfficientNetB0, EfficientNetV2B0, EfficientNetV2M\nfrom tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau\nfrom sklearn.utils import class_weight\nfrom tensorflow.keras.losses import CategoricalCrossentropy\nfrom tensorflow.keras.optimizers.schedules import PiecewiseConstantDecay\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport cv2\nimport numpy as np\nfrom tensorflow.keras.utils import Sequence\nfrom tensorflow.keras.preprocessing import image\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom tensorflow.keras.callbacks import Callback\nimport os\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import Callback","metadata":{"id":"U8szI9MqB9e8","trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:20:25.504848Z","iopub.execute_input":"2025-06-06T04:20:25.505050Z","iopub.status.idle":"2025-06-06T04:20:30.378135Z","shell.execute_reply.started":"2025-06-06T04:20:25.505032Z","shell.execute_reply":"2025-06-06T04:20:30.377138Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.utils import Sequence\n\nclass AlbumentationsDataGenerator(Sequence):\n    \"\"\"\n    Custom data generator using Albumentations for augmentation.\n    \"\"\"\n    def __init__(self, folder_path, batch_size, transforms=None, shuffle=True):\n        self.folder_path = folder_path\n        self.batch_size = batch_size\n        self.transforms = transforms\n        self.shuffle = shuffle\n        self.image_paths = []\n        self.labels = []\n        self.class_names = sorted(os.listdir(folder_path))\n        self.class_indices = {name: i for i, name in enumerate(self.class_names)}\n\n\n        for class_folder in self.class_names:\n            class_path = os.path.join(folder_path, class_folder)\n            for img_file in os.listdir(class_path):\n                self.image_paths.append(os.path.join(class_path, img_file))\n                self.labels.append(self.class_indices[class_folder])\n\n\n        self.indexes = np.arange(len(self.image_paths))\n        if self.shuffle:\n            self.on_epoch_end()\n\n    def __len__(self):\n        return int(np.floor(len(self.image_paths) / self.batch_size))\n\n    def __getitem__(self, index):\n        batch_indexes = self.indexes[index * self.batch_size:(index + 1) * self.batch_size]\n\n        batch_x = []\n        batch_y = []\n\n        for i in batch_indexes:\n            img = cv2.imread(self.image_paths[i])\n            if img is None:\n                print(f\"Error loading image: {self.image_paths[i]}\")\n                continue\n\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n            if self.transforms:\n                augmented = self.transforms(image=img)\n                img = augmented['image']\n\n            batch_x.append(img)\n            batch_y.append(self.labels[i])\n\n        batch_x = np.array(batch_x)\n        batch_y = tf.keras.utils.to_categorical(batch_y, num_classes=len(self.class_names))\n\n        return batch_x, batch_y\n\n    def on_epoch_end(self):\n        if self.shuffle:\n            np.random.shuffle(self.indexes)","metadata":{"id":"4-xkqujGOBvt","trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:20:30.379878Z","iopub.execute_input":"2025-06-06T04:20:30.380140Z","iopub.status.idle":"2025-06-06T04:20:30.388580Z","shell.execute_reply.started":"2025-06-06T04:20:30.380121Z","shell.execute_reply":"2025-06-06T04:20:30.387523Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# class AlbumentationsDataset(Sequence):\n#     def __init__(self, folder_path, batch_size=32, transform=None, shuffle=True):\n#         self.folder_path = folder_path\n#         self.batch_size = batch_size\n#         self.transform = transform\n#         self.shuffle = shuffle\n#         self.image_paths = []\n#         self.labels = []\n#         self.class_names = sorted(os.listdir(folder_path))\n#         self.images = []  # Or self.image_paths if you store paths\n#         self.classes = [] # Initialize for class names\n\n\n#         self.images = [] # Initialize to store images\n#         for idx, class_folder in enumerate(self.class_names):\n#             class_path = os.path.join(folder_path, class_folder)\n#             for img_file in os.listdir(class_path):\n#                 self.image_paths.append(os.path.join(class_path, img_file))\n#                 self.labels.append(idx)\n#                 img_path = os.path.join(class_path, img_file)\n#                 self.images.append(img_path)  # If storing paths instead\n#                 self.classes.append(class_folder) # Actual class name\n#                 self.labels.append(idx)  # Numerical label\n\n\n\n#         self.indexes = np.arange(len(self.image_paths))\n#         if self.shuffle:\n#             np.random.shuffle(self.indexes)\n\n\n\n#     def __len__(self):\n#         return int(np.floor(len(self.image_paths) / self.batch_size))\n\n#     def __getitem__(self, index):\n#         batch_indexes = self.indexes[index * self.batch_size:(index + 1) * self.batch_size]\n\n#         batch_x = []\n#         batch_y = []\n\n#         for i in batch_indexes:\n#             img = cv2.imread(self.image_paths[i])\n\n#             # Check if image was loaded successfully\n#             if img is None:\n#                 print(f\"Error loading image: {self.image_paths[i]}\")\n#                 continue  # Skip this image if it couldn't be loaded\n\n#             img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n#             img = cv2.resize(img, (224, 224))\n\n#             if self.transform:\n#                 augmented = self.transform(image=img)\n#                 img = augmented['image'].numpy()\n#             else:\n#                 img = img / 255.0\n\n#             batch_x.append(img)\n#             batch_y.append(self.labels[i])\n\n#         # Check if any images were loaded\n#         if not batch_x:  # If batch_x is empty\n#             return None, None  # Skip this batch\n\n#         batch_x = np.array(batch_x)\n\n#         # Check if batch_x has the expected dimensions before transposing\n#         if batch_x.ndim == 4:\n#             batch_x = batch_x.transpose(0, 2, 3, 1)\n#         else:\n#             print(f\"Skipping batch with unexpected shape: {batch_x.shape}\")\n#             return None, None # or handle differently, e.g., return empty batch\n\n#         batch_y = tf.keras.utils.to_categorical(batch_y, num_classes=len(self.class_names))\n\n#         return batch_x, batch_y\n\n\n#     def on_epoch_end(self):\n#         if self.shuffle:\n#             np.random.shuffle(self.indexes)\n\n# class AlbumentationsDatasetFromArrays(Sequence):\n#     def __init__(self, images, labels, batch_size=32, transform=None, shuffle=True, classes=None):  # Add classes\n#         \"\"\"\n#         Args:\n#             images: numpy array of shape (N, H, W, C)\n#             labels: numpy array of shape (N,) or (N, num_classes)\n#             batch_size: int\n#             transform: albumentations.Compose object\n#             shuffle: bool\n#         \"\"\"\n#         self.images = images\n#         self.labels = labels\n#         self.batch_size = batch_size\n#         self.transform = transform\n#         self.shuffle = shuffle\n#         self.indexes = np.arange(len(self.images))\n#         self.classes = classes\n\n#         if self.shuffle:\n#             np.random.shuffle(self.indexes)\n\n#     def __len__(self):\n#         return int(np.floor(len(self.images) / self.batch_size))\n\n#     def __getitem__(self, index):\n#         batch_indexes = self.indexes[index * self.batch_size:(index + 1) * self.batch_size]\n\n#         batch_x = []\n#         batch_y = []\n\n#         for i in batch_indexes:\n#             img = self.images[i]\n\n#             # If transform provided (Albumentations), apply it\n#             if self.transform:\n#                 augmented = self.transform(image=img)\n#                 img = augmented['image'].numpy()\n#             else:\n#                 img = img / 255.0\n\n#             batch_x.append(img)\n#             batch_y.append(self.labels[i])\n\n#         batch_x = np.array(batch_x)\n\n#         # If labels are integers, convert to categorical\n#         if len(batch_y) > 0 and len(np.array(batch_y).shape) == 1:\n#             batch_y = tf.keras.utils.to_categorical(batch_y, num_classes=np.unique(self.labels).shape[0])\n#         else:\n#             batch_y = np.array(batch_y)\n\n#         return batch_x, batch_y\n\n#     def on_epoch_end(self):\n#         if self.shuffle:\n#             np.random.shuffle(self.indexes)\n","metadata":{"id":"9xuaWLOgCC19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from tensorflow.keras.preprocessing.image import ImageDataGenerator\n# from tensorflow.keras.callbacks import Callback\n# from tensorflow.keras.applications.efficientnet_v2 import preprocess_input\n\n# datagen = ImageDataGenerator(preprocessing_function=preprocess_input)\n\n# # Easy augmentations (start here)\n# # easy_gen = ImageDataGenerator(\n# #     rescale=1./255,\n# #     horizontal_flip=True,\n# #     vertical_flip=True,\n# #     rotation_range=45,\n# #     zoom_range=0.1,\n# #     width_shift_range=0.05,\n# #     height_shift_range=0.05,\n# #     fill_mode='nearest'\n# # )\n\n# # Hard augmentations (switch later)\n# train_aug = ImageDataGenerator(\n#     rescale=1./255,\n#     horizontal_flip=True,\n#     vertical_flip=True,\n#     rotation_range=90,\n#     brightness_range=[0.8, 1.2],\n#     width_shift_range=0.15,\n#     height_shift_range=0.15,\n#     shear_range=0.2,\n#     zoom_range=0.3,\n#     fill_mode='nearest'\n# )\n\n# valid_aug = ImageDataGenerator(rescale=1./255)\n\n\n# train_gen = datagen.flow_from_directory(\n#     '/content/skin-cancer-dataset/train',\n#     target_size=(224, 224),\n#     batch_size=32,\n#     class_mode='categorical'\n# )\n\n# val_gen = datagen.flow_from_directory(\n#     '/content/skin-cancer-dataset/valid',\n#     target_size=(224, 224),\n#     batch_size=16,\n#     class_mode='categorical',\n#     shuffle=False\n# )\n\n# test_gen = datagen.flow_from_directory(\n#     '/content/skin-cancer-dataset/test',\n#     target_size=(224, 224),\n#     batch_size=16,\n#     class_mode='categorical'\n# )\n","metadata":{"id":"FXmJVyJHCHtq","outputId":"cf7e58a3-080f-41b6-8efb-467909b0d4a3","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport cv2\nimport numpy as np\n\nIMG_SIZE = 224\n\ntrain_transforms = A.Compose([\n    A.Transpose(p=0.5),\n    A.VerticalFlip(p=0.5),\n    A.HorizontalFlip(p=0.5),\n    A.RandomBrightnessContrast(brightness_limit=0.2, contrast_limit=0.2, p=0.75),\n\n    A.OneOf([\n        A.MotionBlur(blur_limit=5),\n        A.MedianBlur(blur_limit=5),\n        A.GaussianBlur(blur_limit=5),\n        A.GaussNoise(var_limit=(5.0, 30.0)),\n    ], p=0.7),\n\n    A.OneOf([\n        A.OpticalDistortion(distort_limit=1.0),\n        A.GridDistortion(num_steps=5, distort_limit=1.0),\n        A.ElasticTransform(alpha=3),\n    ], p=0.7),\n\n    A.CLAHE(clip_limit=4.0, p=0.5),\n    A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=20, val_shift_limit=10, p=0.5),\n    A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.1, rotate_limit=15, border_mode=cv2.BORDER_REFLECT_101, p=0.85), # Use cv2 border mode\n\n    A.Resize(IMG_SIZE, IMG_SIZE),\n\n    A.CoarseDropout(\n        max_holes=1,\n        max_height=int(IMG_SIZE * 0.3),\n        max_width=int(IMG_SIZE * 0.3),\n        num_holes_range=(1, 1),\n        p=0.5\n    ),\n\n    A.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225],\n        max_pixel_value=255.0,\n        p=1.0\n    ),\n\n    # ToTensorV2() # ToTensorV2 is for PyTorch, remove for TensorFlow/Keras\n], p=1.0)\n\n\n\nvalid_transforms = A.Compose([\n    A.Resize(IMG_SIZE, IMG_SIZE),\n\n    A.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225],\n        max_pixel_value=255.0,\n        p=1.0\n    ),\n\n    # ToTensorV2() # ToTensorV2 is for PyTorch, remove for TensorFlow/Keras\n], p=1.0)\n\ntrain_gen = AlbumentationsDataGenerator(\n    folder_path=train_dir,\n    batch_size=32,\n    transforms=train_transforms,\n    shuffle=True\n)\n\nval_gen = AlbumentationsDataGenerator(\n    folder_path=valid_dir,\n    batch_size=16,\n    transforms=valid_transforms,\n    shuffle=False # No shuffling for validation\n)\n\ntest_gen = AlbumentationsDataGenerator(\n    folder_path=test_dir,\n    batch_size=16,\n    transforms=valid_transforms, # Use validation transforms for testing\n    shuffle=False # No shuffling for testing\n)","metadata":{"id":"u9CH2CPHNCs1","trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:20:30.389318Z","iopub.execute_input":"2025-06-06T04:20:30.389583Z","iopub.status.idle":"2025-06-06T04:20:30.422673Z","shell.execute_reply.started":"2025-06-06T04:20:30.389547Z","shell.execute_reply":"2025-06-06T04:20:30.421674Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 6: Inspect Data Generators\nprint(\"\\nTrain Generator:\")\nprint(f\"Number of batches: {len(train_gen)}\")\nprint(f\"Number of images: {len(train_gen.image_paths)}\")\nprint(f\"Class names: {train_gen.class_names}\")\nprint(f\"Class indices: {train_gen.class_indices}\")\n\n\nprint(\"\\nValidation Generator:\")\nprint(f\"Number of batches: {len(val_gen)}\")\nprint(f\"Number of images: {len(val_gen.image_paths)}\")\nprint(f\"Class names: {val_gen.class_names}\")\nprint(f\"Class indices: {val_gen.class_indices}\")\n\n\nprint(\"\\nTest Generator:\")\nprint(f\"Number of batches: {len(test_gen)}\")\nprint(f\"Number of images: {len(test_gen.image_paths)}\")\nprint(f\"Class names: {test_gen.class_names}\")\nprint(f\"Class indices: {test_gen.class_indices}\")\n\n# Verify class distribution in generators\nfrom collections import Counter\n\ntrain_labels = [train_gen.class_names[label] for label in train_gen.labels]\nval_labels = [val_gen.class_names[label] for label in val_gen.labels]\ntest_labels = [test_gen.class_names[label] for label in test_gen.labels]\n\nprint(\"\\nClass distribution in generators:\")\nprint(\"Train:\", Counter(train_labels))\nprint(\"Validation:\", Counter(val_labels))\nprint(\"Test:\", Counter(test_labels))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:20:33.834409Z","iopub.execute_input":"2025-06-06T04:20:33.834702Z","iopub.status.idle":"2025-06-06T04:20:33.841233Z","shell.execute_reply.started":"2025-06-06T04:20:33.834683Z","shell.execute_reply":"2025-06-06T04:20:33.840633Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def load_test_dataset_no_aug(folder_path, batch_size=32):\n    \"\"\"Loads the test dataset without augmentations (only resizing and normalization).\"\"\"\n\n    class SimpleDataset(Sequence):\n        def __init__(self, image_paths, labels, batch_size):\n            self.image_paths = image_paths\n            self.labels = labels\n            self.batch_size = batch_size\n            self.indexes = np.arange(len(self.image_paths))\n\n        def __len__(self):\n            return int(np.floor(len(self.image_paths) / self.batch_size))\n\n        def __getitem__(self, index):\n            batch_indexes = self.indexes[index * self.batch_size:(index + 1) * self.batch_size]\n            batch_x = []\n            batch_y = []\n\n            for i in batch_indexes:\n                img = cv2.imread(self.image_paths[i])\n                if img is None:\n                    print(f\"Error loading image: {self.image_paths[i]}\")\n                    continue\n\n                img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n                img = cv2.resize(img, (224, 224))\n                img = img / 255.0\n\n                batch_x.append(img)\n                batch_y.append(self.labels[i])\n\n            if not batch_x:\n                return None, None\n\n            batch_x = np.array(batch_x)\n            batch_y = tf.keras.utils.to_categorical(batch_y, num_classes=3) # Assuming 3 classes\n\n            return batch_x, batch_y\n\n    image_paths = []\n    labels = []\n    class_names = sorted(os.listdir(folder_path))\n    for idx, class_folder in enumerate(class_names):\n        class_path = os.path.join(folder_path, class_folder)\n        for img_file in os.listdir(class_path):\n            image_paths.append(os.path.join(class_path, img_file))\n            labels.append(idx)\n\n    return SimpleDataset(image_paths, labels, batch_size)\n\n","metadata":{"id":"HRZHneKNLIz1","trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:20:39.049865Z","iopub.execute_input":"2025-06-06T04:20:39.050546Z","iopub.status.idle":"2025-06-06T04:20:39.060338Z","shell.execute_reply.started":"2025-06-06T04:20:39.050521Z","shell.execute_reply":"2025-06-06T04:20:39.059373Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SAMModel(tf.keras.Model):\n    def __init__(self, base_model, rho=0.05):\n        super(SAMModel, self).__init__()\n        self.base_model = base_model\n        self.rho = rho\n\n    def train_step(self, data):\n        x, y = data\n\n        with tf.GradientTape() as tape:\n            pred = self.base_model(x, training=True)\n            loss = self.compiled_loss(y, pred)\n        grads = tape.gradient(loss, self.base_model.trainable_variables)\n\n        # Compute perturbation e_w\n        e_ws = [g * self.rho / (tf.norm(g) + 1e-12) for g in grads]\n\n        # Snapshot current weights (FIX: use tf.identity instead of numpy)\n        old_weights = [tf.identity(w) for w in self.base_model.trainable_variables]\n\n        # Perturb weights\n        for w, e in zip(self.base_model.trainable_variables, e_ws):\n            w.assign_add(e)\n\n        # Second forward-backward pass\n        with tf.GradientTape() as tape2:\n            pred2 = self.base_model(x, training=True)\n            loss2 = self.compiled_loss(y, pred2)\n        grads2 = tape2.gradient(loss2, self.base_model.trainable_variables)\n\n        # Restore original weights\n        for w, old_w in zip(self.base_model.trainable_variables, old_weights):\n            w.assign(old_w)\n\n        # Apply gradients from second loss\n        self.optimizer.apply_gradients(zip(grads2, self.base_model.trainable_variables))\n\n        # Update metrics\n        self.compiled_metrics.update_state(y, pred2)\n        return {m.name: m.result() for m in self.metrics}\n","metadata":{"id":"dfRNcFiFIxkL","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def mixup(batch_x, batch_y, alpha=0.2):\n    lam = np.random.beta(alpha, alpha)\n    batch_size = batch_x.shape[0]\n    index = np.random.permutation(batch_size)\n    mixed_x = lam * batch_x + (1 - lam) * batch_x[index]\n    mixed_y = lam * batch_y + (1 - lam) * batch_y[index]\n    return mixed_x, mixed_y\n\nclass MixupGenerator(tf.keras.utils.Sequence):\n    def __init__(self, generator, alpha=0.2):\n        self.generator = generator\n        self.alpha = alpha\n\n    def __len__(self):\n        return len(self.generator)\n\n    def __getitem__(self, idx):\n        x, y = self.generator[idx]\n        return mixup(x, y, self.alpha)\n\ndef focal_loss(alpha, gamma=2.0):\n    def focal_loss_fixed(y_true, y_pred):\n        y_pred = K.clip(y_pred, 1e-7, 1 - 1e-7)  # prevent log(0)\n        cross_entropy = -y_true * K.log(y_pred)\n        weight = alpha * K.pow(1 - y_pred, gamma)\n        loss = weight * cross_entropy\n        return K.sum(loss, axis=1)\n    return focal_loss_fixed\n\ndef sharpen(p, T=0.5):\n    p = np.array(p)\n    p_sharpen = p ** (1 / T)\n    return p_sharpen / np.sum(p_sharpen, axis=1, keepdims=True)\n\ndef mixup(x1, y1, x2, y2, alpha=0.75):\n    lam = np.random.beta(alpha, alpha)\n    x_mix = lam * x1 + (1 - lam) * x2\n    y_mix = lam * y1 + (1 - lam) * y2\n    return x_mix, y_mix\n\ndef mixmatch_generator(labeled_gen, unlabeled_gen, model, batch_size, K=2, T=0.5, alpha=0.75):\n    while True:\n        # Get labeled data\n        x_l, y_l = next(labeled_gen)\n\n        # Get unlabeled data and predict labels\n        x_u = next(unlabeled_gen)  # Assuming unlabeled_gen yields (images, None)\n        #x_u = x_u[0] #Removing the slice as the shape is already (batch_size, 224, 224, 3)\n        preds = [model.predict(x_u, verbose=0) for _ in range(K)]\n        avg_preds = np.mean(preds, axis=0)\n        y_u = sharpen(avg_preds, T)\n\n        # Concatenate and apply MixUp\n        x_all = np.concatenate([x_l, x_u], axis=0)\n        y_all = np.concatenate([y_l, y_u], axis=0)\n\n        indices = np.random.permutation(len(x_all))\n        x_all, y_all = x_all[indices], y_all[indices]\n\n        # Fix: Ensure both batches have the same size for mixup\n        x1, y1 = x_all[:batch_size], y_all[:batch_size]\n        x2, y2 = x_all[batch_size:2 * batch_size], y_all[batch_size:2 * batch_size]\n\n        #Handle case where there are not enough samples for the second batch\n        num_samples = x_all.shape[0]\n        if num_samples < 2 * batch_size:\n            x2, y2 = x_all[:batch_size], y_all[:batch_size] #Reuse first batch if not enough samples\n\n\n        x_mix, y_mix = mixup(x1, y1, x2, y2, alpha)\n\n        yield x_mix, y_mix\n\ndef rand_bbox(size, lam):\n    W = size[1]\n    H = size[2]\n    cut_rat = np.sqrt(1. - lam)\n    cut_w = int(W * cut_rat)\n    cut_h = int(H * cut_rat)\n    cx = np.random.randint(W)\n    cy = np.random.randint(H)\n    bbx1 = np.clip(cx - cut_w // 2, 0, W)\n    bby1 = np.clip(cy - cut_h // 2, 0, H)\n    bbx2 = np.clip(cx + cut_w // 2, 0, W)\n    bby2 = np.clip(cy + cut_h // 2, 0, H)\n    return bbx1, bby1, bbx2, bby2\n\ndef mix_cut_batch(gen):\n    while True:\n        x1, y1 = next(gen)\n        x2, y2 = next(gen)\n        if np.random.rand() < 0.5:\n            lam = np.random.beta(0.4, 0.4)\n            x_mix = lam * x1 + (1 - lam) * x2\n            y_mix = lam * y1 + (1 - lam) * y2\n        else:\n            lam = np.random.beta(1.0, 1.0)\n            bbx1, bby1, bbx2, bby2 = rand_bbox(x1.shape, lam)\n            x1[:, bbx1:bbx2, bby1:bby2, :] = x2[:, bbx1:bbx2, bby1:bby2, :]\n            lam_adj = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (x1.shape[1] * x1.shape[2]))\n            y_mix = lam_adj * y1 + (1 - lam_adj) * y2\n            x_mix = x1\n        yield x_mix, y_mix\n\ndef weighted_focal_loss(class_weights, gamma=2.0):\n    def loss(y_true, y_pred):\n        y_pred = K.clip(y_pred, 1e-7, 1 - 1e-7)\n        ce = -y_true * K.log(y_pred)\n        weights = class_weights * y_true\n        weights = tf.reduce_sum(weights, axis=-1)\n        focal = K.pow(1 - y_pred, gamma)\n        focal = tf.reduce_sum(focal * y_true, axis=-1)\n        return weights * focal * K.sum(ce, axis=-1)\n    return loss\n\n\ndef build_model(num_classes=3, input_shape=(224,224,3)):\n    base_model = tf.keras.applications.EfficientNetV2B0(\n        weights=\"imagenet\",\n        include_top=False,\n        input_shape=input_shape\n    )\n    base_model.trainable = False  # freeze base model initially\n\n    x = base_model.output\n    x = tf.keras.layers.GlobalAveragePooling2D()(x)\n    x = tf.keras.layers.Dropout(0.3)(x)\n    x = tf.keras.layers.Dense(128, activation='relu')(x)\n    x = tf.keras.layers.Dropout(0.2)(x)\n    outputs = tf.keras.layers.Dense(num_classes, activation='softmax')(x)\n\n    model = tf.keras.models.Model(inputs=base_model.input, outputs=outputs)\n    return model, base_model\n\nclass CurriculumSwitchCallback(Callback):\n    def __init__(self, train_gen, hard_gen, switch_epoch=15):\n        self.train_gen = train_gen\n        self.hard_gen = hard_gen\n        self.switch_epoch = switch_epoch\n\n    def on_epoch_begin(self, epoch, logs=None):\n        if epoch == self.switch_epoch:\n            print(f\"\\n🧠 Switching to HARD ImageDataGenerator augmentations at epoch {epoch}\")\n            self.model.stop_training = True  # Hack to restart with new generator","metadata":{"id":"diGkrwDeCOR1","trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:20:43.582325Z","iopub.execute_input":"2025-06-06T04:20:43.582666Z","iopub.status.idle":"2025-06-06T04:20:43.603856Z","shell.execute_reply.started":"2025-06-06T04:20:43.582643Z","shell.execute_reply":"2025-06-06T04:20:43.603074Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.models import load_model\nfrom sklearn.utils.class_weight import compute_class_weight\nimport tensorflow.keras.backend as K\nfrom tensorflow.keras.optimizers.schedules import CosineDecay\n\n\nbase_model = EfficientNetV2M(weights='imagenet', include_top=False, input_shape=(224, 224, 3))\nbase_model.trainable = False  # freeze for now\n\nx = base_model.output\nx = GlobalAveragePooling2D()(x)\nx = Dropout(0.3)(x)\nx = Dense(128, activation='relu')(x)\nx = Dropout(0.2)(x)\noutput = Dense(2, activation='softmax')(x) # Changed to 2 for 2 classes\n\nmodel = Model(inputs=base_model.input, outputs=output)\n\ny_train = train_gen.labels\nclass_weights_array = compute_class_weight(\n    class_weight='balanced',\n    classes=np.unique(y_train),\n    y=y_train\n)\n\n\nclass_weights_tensor = tf.constant(class_weights_array, dtype=tf.float32)\n# model = load_model(\"skin_cancer_detector.keras\", compile=False)  # compile=False to recompile later with new loss\nloss_fn_focal = weighted_focal_loss(class_weights_tensor)\nloss_fn = tf.keras.losses.CategoricalCrossentropy()\n\ninitial_lr = 1e-4\ndecay_steps = len(train_gen)\ncosine_lr = CosineDecay(\n    initial_learning_rate = initial_lr,\n    decay_steps=decay_steps,\n    alpha=1e-2\n)\n# model = SAMModel(model)\n\nclass_weights = dict(enumerate(class_weights_array))\n","metadata":{"id":"docJlqOcMfqf","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from sklearn.utils.class_weight import compute_class_weight\n# import tensorflow.keras.backend as K\n\n# y_train = train_gen.classes\n# class_weights_array = compute_class_weight(\n#     class_weight='balanced',\n#     classes=np.unique(y_train),\n#     y=y_train\n# )\n\n# class_weights_tensor = tf.constant(class_weights_array, dtype=tf.float32)\n# loss_fn = weighted_focal_loss(class_weights_tensor)\n\n# model, base_model = build_model(num_classes=3)\n\nmodel.compile(optimizer=Adam(1e-4), loss=loss_fn, metrics=['accuracy'])\ncallbacks = [\n    ModelCheckpoint(\"efficientnetv2m_best.h5\", save_best_only=True),\n    ReduceLROnPlateau(monitor=\"val_loss\", factor=0.2, patience=3),\n    EarlyStopping(monitor=\"val_loss\", patience=4, restore_best_weights=True)\n]\n# callbacks = [\n#     ModelCheckpoint(\"best_model.h5\", save_best_only=True),\n#     ReduceLROnPlateau(patience=3)\n# ]\n\n# base_model_name = 'efficientnetv2-b0'\n# base_model = model.layers[0]  # Replace 0 with the index of the EfficientNetV2B0 layer if needed\n","metadata":{"id":"mLulmiCGCW7v","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history = model.fit(\n    train_gen,  # 3k training generator\n    validation_data=val_gen,\n    epochs=25,\n    class_weight=class_weights, # Commented out as it might not be compatible with ImageDataGenerator or SAMModel\n    callbacks=callbacks\n)\n\n# Switch to hard aug\n# train_gen = hard_gen.flow_from_directory(\n#     '/content/skin-cancer-dataset/train',\n#     target_size=(224, 224),\n#     batch_size=16,\n#     class_mode='categorical'\n# )\n\n# # Phase 2: Continue with harder augmentations\n# model.fit(\n#     train_gen,\n#     steps_per_epoch=len(train_gen),\n#     epochs=10,  # or more\n#     validation_data=val_gen,\n#     callbacks=callbacks\n# )","metadata":{"id":"bzUrC1LZCaGx","outputId":"c4363833-85ca-47df-a405-01234487a59c","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 1: Unfreeze\nbase_model.trainable = True\n\nloss_fn_base = tf.keras.losses.CategoricalCrossentropy()\n\ninitial_lr_finetune = 1e-5 # Starting lower learning rate for fine-tuning\n# Calculate decay steps for the fine-tuning phase\n# Assuming 15 fine-tuning epochs and the same steps_per_epoch as the initial training phase\ndecay_steps_finetune = len(train_gen) * 20\n# Step 2: Use a lower learning rate for fine-tuning\ncosine_lr_finetune = CosineDecay(\n    initial_learning_rate=initial_lr_finetune,\n    decay_steps=decay_steps_finetune,\n    alpha=1e-2 # Can adjust alpha for minimum learning rate\n)\n\n# Compile the model with the new lower learning rate schedule\nopt_finetune = Adam(learning_rate=initial_lr_finetune) # Use the new schedule\n\nmodel.compile(\n    optimizer=opt_finetune,\n    loss=loss_fn_base,  # your weighted focal loss\n    metrics=['accuracy']\n)\n\n# Step 3: Fine-tune\nhistory_finetune = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=20,\n    class_weight=class_weights,\n    callbacks=[\n        ModelCheckpoint(\"finetuned_with_base.keras\", save_best_only=True),\n        ReduceLROnPlateau(monitor=\"val_loss\", factor=0.2, patience=2),\n        EarlyStopping(monitor=\"val_loss\", patience=4, restore_best_weights=True)\n    ]\n)\n","metadata":{"id":"ih16treBCgQ_","outputId":"2d7ecf19-5338-490f-a0a8-c23e6cd48622","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# # Predict Unlabeled Set\n# pseudo_preds = model.predict(unlabeled_dataset, verbose=1)\n\n# # Get confidence and predicted class\n# confidence_scores = np.max(pseudo_preds, axis=1)\n# predicted_classes = np.argmax(pseudo_preds, axis=1)\n\n# # Filter by confidence threshold\n# threshold = 0.50\n# confident_indices = np.where(confidence_scores >= threshold)[0]\n\n# # Select pseudo-labeled data\n# print(f\"Number of confident samples: {len(confident_indices)}\")\n# pseudo_images = unlabeled_dataset.images[confident_indices]\n# pseudo_labels = predicted_classes[confident_indices]\n\n# print(f\"Number of confident pseudo-labeled samples: {len(pseudo_images)}\")\n\n# # Merge images and labels\n# final_train_images = np.concatenate([train_dataset.images, pseudo_images], axis=0)\n# final_train_labels = np.concatenate([train_dataset.labels, pseudo_labels], axis=0)\n\n# # Create new dataset\n# merged_train_dataset = AlbumentationsDatasetFromArrays(\n#     images=final_train_images,\n#     labels=final_train_labels,\n#     batch_size=16,\n#     transform=easy_transform  # curriculum will switch this later\n# )\n\n# # Same curriculum callback but attached to merged_train_dataset\n# curriculum_callback_merged = CurriculumCallback(\n#     dataset_obj=merged_train_dataset,\n#     new_transform=hard_transform,\n#     switch_epoch=15\n# )\n\n# train_generator = custom_batch_generator(merged_train_dataset, use_mixup_cutmix=True)\n# steps_per_epoch = len(merged_train_dataset)\n\n# history = model.fit(\n#     train_generator,\n#     steps_per_epoch=steps_per_epoch,\n#     validation_data=val_dataset,\n#     epochs=25,\n#     callbacks=[\n#         ModelCheckpoint(\"best_model_after_pseudo.h5\", save_best_only=True),\n#         ReduceLROnPlateau(monitor=\"val_loss\", factor=0.2, patience=3),\n#         curriculum_callback_merged\n#     ]\n# )\n\n# # Unfreeze base again (already unfrozen but good practice)\n# base_model.trainable = True\n\n# # Even Lower LR now for delicate fine-tuning\n# opt = Adam(learning_rate=1e-6)\n\n# model.compile(\n#     optimizer=opt,\n#     loss=focal_loss(alpha=loss_fn, gamma=2),\n#     metrics=['accuracy']\n# )\n\n# history_finetune = model.fit(\n#     train_generator,\n#     steps_per_epoch=steps_per_epoch,\n#     validation_data=val_dataset,\n#     epochs=10,\n#     callbacks=[\n#         ModelCheckpoint(\"final_best_model.h5\", save_best_only=True),\n#     ]\n# )","metadata":{"id":"wCI6Io5jCkgp","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow import keras\nimport numpy as np\nimport cv2\nimport tensorflow.keras.backend as K\n\n# Method 1: Improved Focal Loss Function\ndef focal_loss_fixed(y_true, y_pred):\n    \"\"\"\n    Focal Loss implementation with numerical stability improvements\n    \"\"\"\n    gamma = 2.0\n    alpha = 0.25\n    epsilon = K.epsilon()  # Small value to prevent log(0)\n    \n    # Clip predictions to prevent log(0)\n    y_pred = K.clip(y_pred, epsilon, 1.0 - epsilon)\n    \n    # Calculate focal loss for positive and negative classes\n    pt_1 = tf.where(tf.equal(y_true, 1), y_pred, tf.ones_like(y_pred))\n    pt_0 = tf.where(tf.equal(y_true, 0), y_pred, tf.zeros_like(y_pred))\n    \n    # Add epsilon to prevent log(0) and pow(0, gamma)\n    pt_1 = K.clip(pt_1, epsilon, 1.0 - epsilon)\n    pt_0 = K.clip(pt_0, epsilon, 1.0 - epsilon)\n    \n    # Calculate focal loss components\n    loss_1 = -alpha * K.pow(1. - pt_1, gamma) * K.log(pt_1)\n    loss_0 = -(1 - alpha) * K.pow(pt_0, gamma) * K.log(1. - pt_0)\n    \n    return K.mean(loss_1 + loss_0)\n\n# Method 2: Alternative Focal Loss (more stable)\ndef focal_loss_stable(y_true, y_pred):\n    \"\"\"\n    More numerically stable focal loss implementation\n    \"\"\"\n    gamma = 2.0\n    alpha = 0.25\n    epsilon = 1e-8\n    \n    # Ensure y_pred is in valid range\n    y_pred = tf.clip_by_value(y_pred, epsilon, 1.0 - epsilon)\n    \n    # Convert to float32 for numerical stability\n    y_true = tf.cast(y_true, tf.float32)\n    y_pred = tf.cast(y_pred, tf.float32)\n    \n    # Calculate cross entropy\n    ce = -y_true * tf.math.log(y_pred) - (1 - y_true) * tf.math.log(1 - y_pred)\n    \n    # Calculate focal weight\n    pt = tf.where(tf.equal(y_true, 1), y_pred, 1 - y_pred)\n    focal_weight = alpha * tf.pow(1 - pt, gamma)\n    \n    # Apply focal weight\n    focal_loss = focal_weight * ce\n    \n    return tf.reduce_mean(focal_loss)\n\n# Method 3: Try loading with different approaches\ndef load_model_safely(model_path):\n    \"\"\"\n    Try multiple approaches to load the model safely\n    \"\"\"\n    print(\"Attempting to load model...\")\n    \n    # Approach 1: Load with original focal loss\n    try:\n        print(\"Trying with focal_loss_fixed...\")\n        model = keras.models.load_model(\n            model_path, \n            custom_objects={'focal_loss_fixed': focal_loss_fixed}\n        )\n        print(\"✓ Successfully loaded with focal_loss_fixed\")\n        return model\n    except Exception as e:\n        print(f\"✗ Failed with focal_loss_fixed: {str(e)}\")\n    \n    # Approach 2: Load with stable focal loss\n    try:\n        print(\"Trying with focal_loss_stable...\")\n        model = keras.models.load_model(\n            model_path, \n            custom_objects={'focal_loss_fixed': focal_loss_stable}\n        )\n        print(\"✓ Successfully loaded with focal_loss_stable\")\n        return model\n    except Exception as e:\n        print(f\"✗ Failed with focal_loss_stable: {str(e)}\")\n    \n    # Approach 3: Load without compiling (ignore the loss function)\n    try:\n        print(\"Trying to load without compiling...\")\n        model = keras.models.load_model(model_path, compile=False)\n        print(\"✓ Successfully loaded without compiling\")\n        print(\"Note: You'll need to compile the model before training\")\n        return model\n    except Exception as e:\n        print(f\"✗ Failed loading without compiling: {str(e)}\")\n    \n    # Approach 4: Load architecture and weights separately\n    try:\n        print(\"Trying to load weights only...\")\n        # This assumes you have the model architecture defined elsewhere\n        # You would need to recreate your model architecture first\n        print(\"This approach requires recreating the model architecture\")\n        return None\n    except Exception as e:\n        print(f\"✗ Failed loading weights only: {str(e)}\")\n    \n    print(\"All loading approaches failed!\")\n    return None\n\n# Load the model\nmodel_path = '/kaggle/input/keras-skin-cancer/keras/default/1/finetuned_with_base (3).keras'\nmodel = load_model_safely(model_path)\n\nif model is not None:\n    print(\"\\nModel loaded successfully!\")\n    print(f\"Model summary:\")\n    model.summary()\n    \n    # If loaded without compiling, you can recompile with a working loss function\n    if not hasattr(model, 'optimizer') or model.optimizer is None:\n        print(\"\\nRecompiling model...\")\n        model.compile(\n            optimizer='adam',\n            loss=focal_loss_stable,  # Use the stable version\n            metrics=['accuracy']\n        )\n        print(\"Model recompiled successfully!\")\n        \nelse:\n    print(\"\\nFailed to load model. Consider these alternatives:\")\n    print(\"1. Check if the model file exists and is not corrupted\")\n    print(\"2. Verify the focal loss function used during training\")\n    print(\"3. Try loading without custom objects and redefine the loss\")\n    print(\"4. Recreate the model architecture and load weights separately\")\n\n# Alternative if all else fails - create a dummy focal loss\ndef dummy_focal_loss(y_true, y_pred):\n    \"\"\"\n    Dummy focal loss that just returns categorical crossentropy\n    Use this as a last resort to load the model\n    \"\"\"\n    return keras.losses.categorical_crossentropy(y_true, y_pred)\n\n# Last resort loading attempt\nif model is None:\n    try:\n        print(\"\\nLast resort: Loading with dummy focal loss...\")\n        model = keras.models.load_model(\n            model_path, \n            custom_objects={'focal_loss_fixed': dummy_focal_loss}\n        )\n        print(\"✓ Loaded with dummy focal loss\")\n        print(\"Warning: Loss function is now categorical crossentropy, not focal loss\")\n    except Exception as e:\n        print(f\"✗ Even dummy focal loss failed: {str(e)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:55:49.299035Z","iopub.execute_input":"2025-06-06T04:55:49.299288Z","iopub.status.idle":"2025-06-06T04:56:00.054836Z","shell.execute_reply.started":"2025-06-06T04:55:49.299271Z","shell.execute_reply":"2025-06-06T04:56:00.054245Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfrom sklearn.metrics import classification_report, confusion_matrix, accuracy_score\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport collections\nimport tensorflow as tf\n\ndef evaluate_model_robust(model, test_gen, max_batches=None):\n    \"\"\"\n    Robust model evaluation with comprehensive error handling\n    \"\"\"\n    print(\"Starting model evaluation...\")\n    \n    # Step 1: Reset generator and collect predictions\n    y_true = []\n    y_pred = []\n    y_probs = []\n    \n    try:\n        # Reset the generator to start from beginning\n        test_gen.reset()\n        print(f\"Test generator reset. Total samples: {test_gen.samples}\")\n        print(f\"Batch size: {test_gen.batch_size}\")\n        print(f\"Number of classes: {test_gen.num_classes}\")\n        \n    except Exception as e:\n        print(f\"Warning: Could not reset generator: {e}\")\n    \n    # Get class names safely\n    try:\n        if hasattr(test_gen, 'class_indices'):\n            class_names = list(test_gen.class_indices.keys())\n        elif hasattr(test_gen, 'class_names'):\n            class_names = test_gen.class_names\n        else:\n            class_names = [f'Class_{i}' for i in range(test_gen.num_classes)]\n        print(f\"Class names: {class_names}\")\n    except Exception as e:\n        print(f\"Warning: Could not get class names: {e}\")\n        class_names = ['melanoma', 'nevus']  # Default for your case\n    \n    # Iterate through batches\n    batch_count = 0\n    total_samples = 0\n    \n    try:\n        for batch_x, batch_y in test_gen:\n            print(f\"Processing batch {batch_count + 1}...\")\n            \n            # Check batch shapes\n            print(f\"Batch X shape: {batch_x.shape}\")\n            print(f\"Batch Y shape: {batch_y.shape}\")\n            \n            # Make predictions\n            try:\n                batch_preds = model.predict(batch_x, verbose=0)\n                print(f\"Predictions shape: {batch_preds.shape}\")\n                \n                # Handle different prediction formats\n                if len(batch_preds.shape) == 1:\n                    # Binary classification with single output\n                    batch_pred_classes = (batch_preds > 0.5).astype(int)\n                    batch_preds_2d = np.column_stack([1-batch_preds, batch_preds])\n                elif batch_preds.shape[1] == 1:\n                    # Binary classification with single column\n                    batch_pred_classes = (batch_preds.flatten() > 0.5).astype(int)\n                    batch_preds_2d = np.column_stack([1-batch_preds.flatten(), batch_preds.flatten()])\n                else:\n                    # Multi-class classification\n                    batch_pred_classes = np.argmax(batch_preds, axis=1)\n                    batch_preds_2d = batch_preds\n                \n                # Handle true labels\n                if len(batch_y.shape) == 1:\n                    # Already class indices\n                    batch_true_classes = batch_y.astype(int)\n                elif batch_y.shape[1] == 1:\n                    # Single column (binary)\n                    batch_true_classes = batch_y.flatten().astype(int)\n                else:\n                    # One-hot encoded\n                    batch_true_classes = np.argmax(batch_y, axis=1)\n                \n                # Store results\n                y_true.extend(batch_true_classes)\n                y_pred.extend(batch_pred_classes)\n                y_probs.extend(batch_preds_2d)\n                \n                batch_count += 1\n                total_samples += len(batch_x)\n                \n                # Debug info for first batch\n                if batch_count == 1:\n                    print(f\"First batch - True classes: {batch_true_classes[:5]}\")\n                    print(f\"First batch - Pred classes: {batch_pred_classes[:5]}\")\n                    print(f\"First batch - Pred probs: {batch_preds_2d[:5]}\")\n                \n            except Exception as e:\n                print(f\"Error making predictions for batch {batch_count}: {e}\")\n                break\n            \n            # Safety check - avoid infinite loops\n            if max_batches and batch_count >= max_batches:\n                print(f\"Reached maximum batches limit: {max_batches}\")\n                break\n                \n            if total_samples >= test_gen.samples:\n                print(f\"Processed all samples: {total_samples}\")\n                break\n                \n    except Exception as e:\n        print(f\"Error during batch processing: {e}\")\n        if len(y_true) == 0:\n            print(\"No predictions were made. Check your generator and model compatibility.\")\n            return None, None, None\n    \n    # Convert to numpy arrays\n    y_true = np.array(y_true)\n    y_pred = np.array(y_pred)\n    y_probs = np.array(y_probs)\n    \n    print(f\"\\nEvaluation completed!\")\n    print(f\"Total samples processed: {len(y_true)}\")\n    print(f\"True class distribution: {collections.Counter(y_true)}\")\n    print(f\"Predicted class distribution: {collections.Counter(y_pred)}\")\n    \n    # Basic accuracy\n    if len(y_true) > 0:\n        accuracy = accuracy_score(y_true, y_pred)\n        print(f\"Accuracy: {accuracy:.4f}\")\n    \n    return y_true, y_pred, y_probs, class_names\n\ndef plot_confusion_matrix(y_true, y_pred, class_names):\n    \"\"\"\n    Plot confusion matrix with error handling\n    \"\"\"\n    try:\n        cm = confusion_matrix(y_true, y_pred)\n        \n        plt.figure(figsize=(8, 6))\n        sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n                   xticklabels=class_names, yticklabels=class_names)\n        plt.xlabel(\"Predicted\")\n        plt.ylabel(\"Actual\")\n        plt.title(\"Confusion Matrix\")\n        plt.tight_layout()\n        plt.show()\n        \n        return cm\n        \n    except Exception as e:\n        print(f\"Error creating confusion matrix: {e}\")\n        return None\n\ndef print_classification_report(y_true, y_pred, class_names):\n    \"\"\"\n    Print classification report with error handling\n    \"\"\"\n    try:\n        report = classification_report(y_true, y_pred, \n                                     target_names=class_names,\n                                     zero_division=0)\n        print(\"\\nClassification Report:\")\n        print(report)\n        \n    except Exception as e:\n        print(f\"Error generating classification report: {e}\")\n        # Fallback to basic metrics\n        if len(y_true) > 0:\n            accuracy = accuracy_score(y_true, y_pred)\n            print(f\"Basic accuracy: {accuracy:.4f}\")\n\n# Main evaluation execution\nprint(\"=\" * 50)\nprint(\"STARTING MODEL EVALUATION\")\nprint(\"=\" * 50)\n\n# Run evaluation\ntry:\n    y_true, y_pred, y_probs, class_names = evaluate_model_robust(model, test_gen, max_batches=100)\n    \n    if y_true is not None and len(y_true) > 0:\n        # Plot confusion matrix\n        print(\"\\n\" + \"=\" * 30)\n        print(\"CONFUSION MATRIX\")\n        print(\"=\" * 30)\n        cm = plot_confusion_matrix(y_true, y_pred, class_names)\n        \n        # Print classification report\n        print(\"\\n\" + \"=\" * 30)\n        print(\"CLASSIFICATION REPORT\")\n        print(\"=\" * 30)\n        print_classification_report(y_true, y_pred, class_names)\n        \n        # Additional statistics\n        print(\"\\n\" + \"=\" * 30)\n        print(\"ADDITIONAL STATISTICS\")\n        print(\"=\" * 30)\n        \n        unique_true = np.unique(y_true)\n        unique_pred = np.unique(y_pred)\n        \n        print(f\"Unique true classes: {unique_true}\")\n        print(f\"Unique predicted classes: {unique_pred}\")\n        \n        # Per-class accuracy\n        if cm is not None:\n            per_class_acc = cm.diagonal() / cm.sum(axis=1)\n            for i, acc in enumerate(per_class_acc):\n                print(f\"{class_names[i]} accuracy: {acc:.4f}\")\n                \n    else:\n        print(\"No valid predictions were obtained. Please check:\")\n        print(\"1. Model and generator compatibility\")\n        print(\"2. Generator configuration\")\n        print(\"3. Model output shape\")\n        \nexcept Exception as e:\n    print(f\"Critical error during evaluation: {e}\")\n    print(\"\\nTroubleshooting steps:\")\n    print(\"1. Check if test_gen is properly configured\")\n    print(\"2. Verify model input/output shapes\")\n    print(\"3. Try with a single batch first\")\n    \n    # Emergency single batch test\n    print(\"\\nTrying single batch test...\")\n    try:\n        single_batch_x, single_batch_y = next(iter(test_gen))\n        print(f\"Single batch X shape: {single_batch_x.shape}\")\n        print(f\"Single batch Y shape: {single_batch_y.shape}\")\n        \n        single_pred = model.predict(single_batch_x[:1])  # Just first sample\n        print(f\"Single prediction shape: {single_pred.shape}\")\n        print(f\"Single prediction: {single_pred}\")\n        \n    except Exception as e2:\n        print(f\"Single batch test also failed: {e2}\")","metadata":{"id":"YfxAuiWUCtKq","trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:56:19.284446Z","iopub.execute_input":"2025-06-06T04:56:19.284680Z","iopub.status.idle":"2025-06-06T04:56:24.330044Z","shell.execute_reply.started":"2025-06-06T04:56:19.284661Z","shell.execute_reply":"2025-06-06T04:56:24.329246Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow import keras\nimport numpy as np\nimport cv2\nimport tensorflow.keras.backend as K\n\n# Method 1: Improved Focal Loss Function\ndef focal_loss_fixed(y_true, y_pred):\n    \"\"\"\n    Focal Loss implementation with numerical stability improvements\n    \"\"\"\n    gamma = 2.0\n    alpha = 0.25\n    epsilon = K.epsilon()  # Small value to prevent log(0)\n    \n    # Clip predictions to prevent log(0)\n    y_pred = K.clip(y_pred, epsilon, 1.0 - epsilon)\n    \n    # Calculate focal loss for positive and negative classes\n    pt_1 = tf.where(tf.equal(y_true, 1), y_pred, tf.ones_like(y_pred))\n    pt_0 = tf.where(tf.equal(y_true, 0), y_pred, tf.zeros_like(y_pred))\n    \n    # Add epsilon to prevent log(0) and pow(0, gamma)\n    pt_1 = K.clip(pt_1, epsilon, 1.0 - epsilon)\n    pt_0 = K.clip(pt_0, epsilon, 1.0 - epsilon)\n    \n    # Calculate focal loss components\n    loss_1 = -alpha * K.pow(1. - pt_1, gamma) * K.log(pt_1)\n    loss_0 = -(1 - alpha) * K.pow(pt_0, gamma) * K.log(1. - pt_0)\n    \n    return K.mean(loss_1 + loss_0)\n\n# Method 2: Alternative Focal Loss (more stable)\ndef focal_loss_stable(y_true, y_pred):\n    \"\"\"\n    More numerically stable focal loss implementation\n    \"\"\"\n    gamma = 2.0\n    alpha = 0.25\n    epsilon = 1e-8\n    \n    # Ensure y_pred is in valid range\n    y_pred = tf.clip_by_value(y_pred, epsilon, 1.0 - epsilon)\n    \n    # Convert to float32 for numerical stability\n    y_true = tf.cast(y_true, tf.float32)\n    y_pred = tf.cast(y_pred, tf.float32)\n    \n    # Calculate cross entropy\n    ce = -y_true * tf.math.log(y_pred) - (1 - y_true) * tf.math.log(1 - y_pred)\n    \n    # Calculate focal weight\n    pt = tf.where(tf.equal(y_true, 1), y_pred, 1 - y_pred)\n    focal_weight = alpha * tf.pow(1 - pt, gamma)\n    \n    # Apply focal weight\n    focal_loss = focal_weight * ce\n    \n    return tf.reduce_mean(focal_loss)\n\n# Method 3: Try loading with different approaches\ndef load_model_safely(model_path):\n    \"\"\"\n    Try multiple approaches to load the model safely\n    \"\"\"\n    print(\"Attempting to load model...\")\n    \n    # Approach 1: Load with original focal loss\n    try:\n        print(\"Trying with focal_loss_fixed...\")\n        model = keras.models.load_model(\n            model_path, \n            custom_objects={'focal_loss_fixed': focal_loss_fixed}\n        )\n        print(\"✓ Successfully loaded with focal_loss_fixed\")\n        return model\n    except Exception as e:\n        print(f\"✗ Failed with focal_loss_fixed: {str(e)}\")\n    \n    # Approach 2: Load with stable focal loss\n    try:\n        print(\"Trying with focal_loss_stable...\")\n        model = keras.models.load_model(\n            model_path, \n            custom_objects={'focal_loss_fixed': focal_loss_stable}\n        )\n        print(\"✓ Successfully loaded with focal_loss_stable\")\n        return model\n    except Exception as e:\n        print(f\"✗ Failed with focal_loss_stable: {str(e)}\")\n    \n    # Approach 3: Load without compiling (ignore the loss function)\n    try:\n        print(\"Trying to load without compiling...\")\n        model = keras.models.load_model(model_path, compile=False)\n        print(\"✓ Successfully loaded without compiling\")\n        print(\"Note: You'll need to compile the model before training\")\n        return model\n    except Exception as e:\n        print(f\"✗ Failed loading without compiling: {str(e)}\")\n    \n    # Approach 4: Load architecture and weights separately\n    try:\n        print(\"Trying to load weights only...\")\n        # This assumes you have the model architecture defined elsewhere\n        # You would need to recreate your model architecture first\n        print(\"This approach requires recreating the model architecture\")\n        return None\n    except Exception as e:\n        print(f\"✗ Failed loading weights only: {str(e)}\")\n    \n    print(\"All loading approaches failed!\")\n    return None\n\n# Load the model\nmodel_path_nt = \"/kaggle/input/skin-cancer-notebook/finetuned_with_base.keras\"\nmodel_notebook = load_model_safely(model_path_nt)\n\nif model is not None:\n    print(\"\\nModel loaded successfully!\")\n    print(f\"Model summary:\")\n    model.summary()\n    \n    # If loaded without compiling, you can recompile with a working loss function\n    if not hasattr(model, 'optimizer') or model.optimizer is None:\n        print(\"\\nRecompiling model...\")\n        model.compile(\n            optimizer='adam',\n            loss=focal_loss_stable,  # Use the stable version\n            metrics=['accuracy']\n        )\n        print(\"Model recompiled successfully!\")\n        \nelse:\n    print(\"\\nFailed to load model. Consider these alternatives:\")\n    print(\"1. Check if the model file exists and is not corrupted\")\n    print(\"2. Verify the focal loss function used during training\")\n    print(\"3. Try loading without custom objects and redefine the loss\")\n    print(\"4. Recreate the model architecture and load weights separately\")\n\n# Alternative if all else fails - create a dummy focal loss\ndef dummy_focal_loss(y_true, y_pred):\n    \"\"\"\n    Dummy focal loss that just returns categorical crossentropy\n    Use this as a last resort to load the model\n    \"\"\"\n    return keras.losses.categorical_crossentropy(y_true, y_pred)\n\n# Last resort loading attempt\nif model is None:\n    try:\n        print(\"\\nLast resort: Loading with dummy focal loss...\")\n        model = keras.models.load_model(\n            model_path, \n            custom_objects={'focal_loss_fixed': dummy_focal_loss}\n        )\n        print(\"✓ Loaded with dummy focal loss\")\n        print(\"Warning: Loss function is now categorical crossentropy, not focal loss\")\n    except Exception as e:\n        print(f\"✗ Even dummy focal loss failed: {str(e)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:30:12.001209Z","iopub.execute_input":"2025-06-06T04:30:12.001434Z","iopub.status.idle":"2025-06-06T04:30:25.154886Z","shell.execute_reply.started":"2025-06-06T04:30:12.001417Z","shell.execute_reply":"2025-06-06T04:30:25.154292Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfrom sklearn.metrics import classification_report, confusion_matrix, accuracy_score\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport collections\nimport tensorflow as tf\n\ndef evaluate_model_robust(model, test_gen, max_batches=None):\n    \"\"\"\n    Robust model evaluation with comprehensive error handling\n    \"\"\"\n    print(\"Starting model evaluation...\")\n    \n    # Step 1: Reset generator and collect predictions\n    y_true = []\n    y_pred = []\n    y_probs = []\n    \n    try:\n        # Reset the generator to start from beginning\n        test_gen.reset()\n        print(f\"Test generator reset. Total samples: {test_gen.samples}\")\n        print(f\"Batch size: {test_gen.batch_size}\")\n        print(f\"Number of classes: {test_gen.num_classes}\")\n        \n    except Exception as e:\n        print(f\"Warning: Could not reset generator: {e}\")\n    \n    # Get class names safely\n    try:\n        if hasattr(test_gen, 'class_indices'):\n            class_names = list(test_gen.class_indices.keys())\n        elif hasattr(test_gen, 'class_names'):\n            class_names = test_gen.class_names\n        else:\n            class_names = [f'Class_{i}' for i in range(test_gen.num_classes)]\n        print(f\"Class names: {class_names}\")\n    except Exception as e:\n        print(f\"Warning: Could not get class names: {e}\")\n        class_names = ['melanoma', 'nevus']  # Default for your case\n    \n    # Iterate through batches\n    batch_count = 0\n    total_samples = 0\n    \n    try:\n        for batch_x, batch_y in test_gen:\n            print(f\"Processing batch {batch_count + 1}...\")\n            \n            # Check batch shapes\n            print(f\"Batch X shape: {batch_x.shape}\")\n            print(f\"Batch Y shape: {batch_y.shape}\")\n            \n            # Make predictions\n            try:\n                batch_preds = model.predict(batch_x, verbose=0)\n                print(f\"Predictions shape: {batch_preds.shape}\")\n                \n                # Handle different prediction formats\n                if len(batch_preds.shape) == 1:\n                    # Binary classification with single output\n                    batch_pred_classes = (batch_preds > 0.5).astype(int)\n                    batch_preds_2d = np.column_stack([1-batch_preds, batch_preds])\n                elif batch_preds.shape[1] == 1:\n                    # Binary classification with single column\n                    batch_pred_classes = (batch_preds.flatten() > 0.5).astype(int)\n                    batch_preds_2d = np.column_stack([1-batch_preds.flatten(), batch_preds.flatten()])\n                else:\n                    # Multi-class classification\n                    batch_pred_classes = np.argmax(batch_preds, axis=1)\n                    batch_preds_2d = batch_preds\n                \n                # Handle true labels\n                if len(batch_y.shape) == 1:\n                    # Already class indices\n                    batch_true_classes = batch_y.astype(int)\n                elif batch_y.shape[1] == 1:\n                    # Single column (binary)\n                    batch_true_classes = batch_y.flatten().astype(int)\n                else:\n                    # One-hot encoded\n                    batch_true_classes = np.argmax(batch_y, axis=1)\n                \n                # Store results\n                y_true.extend(batch_true_classes)\n                y_pred.extend(batch_pred_classes)\n                y_probs.extend(batch_preds_2d)\n                \n                batch_count += 1\n                total_samples += len(batch_x)\n                \n                # Debug info for first batch\n                if batch_count == 1:\n                    print(f\"First batch - True classes: {batch_true_classes[:5]}\")\n                    print(f\"First batch - Pred classes: {batch_pred_classes[:5]}\")\n                    print(f\"First batch - Pred probs: {batch_preds_2d[:5]}\")\n                \n            except Exception as e:\n                print(f\"Error making predictions for batch {batch_count}: {e}\")\n                break\n            \n            # Safety check - avoid infinite loops\n            if max_batches and batch_count >= max_batches:\n                print(f\"Reached maximum batches limit: {max_batches}\")\n                break\n                \n            if total_samples >= test_gen.samples:\n                print(f\"Processed all samples: {total_samples}\")\n                break\n                \n    except Exception as e:\n        print(f\"Error during batch processing: {e}\")\n        if len(y_true) == 0:\n            print(\"No predictions were made. Check your generator and model compatibility.\")\n            return None, None, None\n    \n    # Convert to numpy arrays\n    y_true = np.array(y_true)\n    y_pred = np.array(y_pred)\n    y_probs = np.array(y_probs)\n    \n    print(f\"\\nEvaluation completed!\")\n    print(f\"Total samples processed: {len(y_true)}\")\n    print(f\"True class distribution: {collections.Counter(y_true)}\")\n    print(f\"Predicted class distribution: {collections.Counter(y_pred)}\")\n    \n    # Basic accuracy\n    if len(y_true) > 0:\n        accuracy = accuracy_score(y_true, y_pred)\n        print(f\"Accuracy: {accuracy:.4f}\")\n    \n    return y_true, y_pred, y_probs, class_names\n\ndef plot_confusion_matrix(y_true, y_pred, class_names):\n    \"\"\"\n    Plot confusion matrix with error handling\n    \"\"\"\n    try:\n        cm = confusion_matrix(y_true, y_pred)\n        \n        plt.figure(figsize=(8, 6))\n        sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n                   xticklabels=class_names, yticklabels=class_names)\n        plt.xlabel(\"Predicted\")\n        plt.ylabel(\"Actual\")\n        plt.title(\"Confusion Matrix\")\n        plt.tight_layout()\n        plt.show()\n        \n        return cm\n        \n    except Exception as e:\n        print(f\"Error creating confusion matrix: {e}\")\n        return None\n\ndef print_classification_report(y_true, y_pred, class_names):\n    \"\"\"\n    Print classification report with error handling\n    \"\"\"\n    try:\n        report = classification_report(y_true, y_pred, \n                                     target_names=class_names,\n                                     zero_division=0)\n        print(\"\\nClassification Report:\")\n        print(report)\n        \n    except Exception as e:\n        print(f\"Error generating classification report: {e}\")\n        # Fallback to basic metrics\n        if len(y_true) > 0:\n            accuracy = accuracy_score(y_true, y_pred)\n            print(f\"Basic accuracy: {accuracy:.4f}\")\n\n# Main evaluation execution\nprint(\"=\" * 50)\nprint(\"STARTING MODEL EVALUATION\")\nprint(\"=\" * 50)\n\n# Run evaluation\ntry:\n    y_true, y_pred, y_probs, class_names = evaluate_model_robust(model, test_gen, max_batches=100)\n    \n    if y_true is not None and len(y_true) > 0:\n        # Plot confusion matrix\n        print(\"\\n\" + \"=\" * 30)\n        print(\"CONFUSION MATRIX\")\n        print(\"=\" * 30)\n        cm = plot_confusion_matrix(y_true, y_pred, class_names)\n        \n        # Print classification report\n        print(\"\\n\" + \"=\" * 30)\n        print(\"CLASSIFICATION REPORT\")\n        print(\"=\" * 30)\n        print_classification_report(y_true, y_pred, class_names)\n        \n        # Additional statistics\n        print(\"\\n\" + \"=\" * 30)\n        print(\"ADDITIONAL STATISTICS\")\n        print(\"=\" * 30)\n        \n        unique_true = np.unique(y_true)\n        unique_pred = np.unique(y_pred)\n        \n        print(f\"Unique true classes: {unique_true}\")\n        print(f\"Unique predicted classes: {unique_pred}\")\n        \n        # Per-class accuracy\n        if cm is not None:\n            per_class_acc = cm.diagonal() / cm.sum(axis=1)\n            for i, acc in enumerate(per_class_acc):\n                print(f\"{class_names[i]} accuracy: {acc:.4f}\")\n                \n    else:\n        print(\"No valid predictions were obtained. Please check:\")\n        print(\"1. Model and generator compatibility\")\n        print(\"2. Generator configuration\")\n        print(\"3. Model output shape\")\n        \nexcept Exception as e:\n    print(f\"Critical error during evaluation: {e}\")\n    print(\"\\nTroubleshooting steps:\")\n    print(\"1. Check if test_gen is properly configured\")\n    print(\"2. Verify model input/output shapes\")\n    print(\"3. Try with a single batch first\")\n    \n    # Emergency single batch test\n    print(\"\\nTrying single batch test...\")\n    try:\n        single_batch_x, single_batch_y = next(iter(test_gen))\n        print(f\"Single batch X shape: {single_batch_x.shape}\")\n        print(f\"Single batch Y shape: {single_batch_y.shape}\")\n        \n        single_pred = model.predict(single_batch_x[:1])  # Just first sample\n        print(f\"Single prediction shape: {single_pred.shape}\")\n        print(f\"Single prediction: {single_pred}\")\n        \n    except Exception as e2:\n        print(f\"Single batch test also failed: {e2}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T04:30:25.156014Z","iopub.execute_input":"2025-06-06T04:30:25.156197Z","iopub.status.idle":"2025-06-06T04:30:26.895364Z","shell.execute_reply.started":"2025-06-06T04:30:25.156182Z","shell.execute_reply":"2025-06-06T04:30:26.894762Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import cohen_kappa_score\nprint(\"Cohen’s Kappa Score:\", cohen_kappa_score(y_true, y_pred))\n\nfrom sklearn.preprocessing import label_binarize\nfrom sklearn.metrics import roc_curve, auc\n\nn_classes = 3\ny_true_onehot = label_binarize(y_true, classes=[0, 1, 2])\nfpr = dict()\ntpr = dict()\nroc_auc = dict()\n\nfor i in range(n_classes):\n    fpr[i], tpr[i], _ = roc_curve(y_true_onehot[:, i], y_pred_probs[:, i])\n    roc_auc[i] = auc(fpr[i], tpr[i])\n\n# Plot all ROCs\nplt.figure(figsize=(8,6))\nfor i in range(n_classes):\n    plt.plot(fpr[i], tpr[i], label=f\"Class {i} AUC = {roc_auc[i]:.2f}\")\nplt.plot([0,1], [0,1], 'k--')\nplt.xlabel(\"False Positive Rate\")\nplt.ylabel(\"True Positive Rate\")\nplt.title(\"ROC Curve per Class\")\nplt.legend()\nplt.grid()\nplt.show()","metadata":{"id":"8isFJZ9ICzna","outputId":"969c0f93-fd73-4251-cb37-ee6971af965a","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\nfrom sklearn.metrics import cohen_kappa_score\nprint(\"Cohen’s Kappa Score:\", cohen_kappa_score(y_true, y_pred))\n\nimport matplotlib.pyplot as plt\n\n# Get filenames (requires shuffle=False)\nfilenames = test_gen.filenames\nerrors = np.where(y_pred != y_true)[0]\n\nfor i in errors[:5]:  # show first 5 mistakes\n    img_path = test_gen.filepaths[i]\n    img = plt.imread(img_path)\n    plt.imshow(img)\n    plt.title(f\"True: {y_true[i]}, Pred: {y_pred[i]}\")\n    plt.axis('off')\n    plt.show()\n\nimport seaborn as sns\n\ncm = confusion_matrix(y_true, y_pred, normalize='true')  # row-normalized\nplt.figure(figsize=(6,6))\nsns.heatmap(cm, annot=True, fmt='.2f', cmap='Purples', xticklabels=['Nevus', 'Atypical', 'Melanoma'], yticklabels=['Nevus', 'Atypical', 'Melanoma'])\nplt.title(\"Normalized Confusion Matrix\")\nplt.ylabel('Actual')\nplt.xlabel('Predicted')\nplt.show()\n\nfrom sklearn.metrics import precision_recall_curve\n\nfor i in range(n_classes):\n    precision, recall, _ = precision_recall_curve(y_true_onehot[:, i], y_pred_probs[:, i])\n    plt.plot(recall, precision, label=f'Class {i}')\nplt.xlabel(\"Recall\")\nplt.ylabel(\"Precision\")\nplt.title(\"Precision-Recall Curve\")\nplt.legend()\nplt.grid()\nplt.show()\n\nimport numpy as np\nfrom collections import Counter\nimport matplotlib.pyplot as plt\n\n# Get one batch (you can loop this to check multiple)\nbatch_data, batch_labels = next(train_dataset)\n\n# If it's one-hot encoded, convert to class indices\nif batch_labels.ndim > 1:\n    batch_classes = np.argmax(batch_labels, axis=1)\nelse:\n    batch_classes = batch_labels  # Already class indices\n\n# Count how many samples per class\nclass_counts = Counter(batch_classes)\n\n# Print raw counts\nprint(\"Batch class distribution:\", class_counts)\n\n# Optional: visualize as a bar chart\nplt.bar(class_counts.keys(), class_counts.values(), tick_label=['Nevus', 'Atypical', 'Melanoma'])\nplt.title(\"Class distribution in one training batch\")\nplt.xlabel(\"Class\")\nplt.ylabel(\"Count\")\nplt.show()\n\nx_l, _ = next(train_gen)\nx_u = next(unlabeled_gen)\n\n# Visualize both\nimport matplotlib.pyplot as plt\n\nfig, axs = plt.subplots(1, 2)\naxs[0].imshow(x_l[0])\naxs[0].set_title(\"Labeled Augmented\")\naxs[1].imshow(x_u[0])\naxs[1].set_title(\"Unlabeled Augmented\")\nplt.show()\n\nimport os\n\ndef get_class_distribution(directory):\n  \"\"\"Calculates the number of images in each class within a directory.\"\"\"\n  class_distribution = {}\n  for class_name in os.listdir(directory):\n    class_path = os.path.join(directory, class_name)\n    if os.path.isdir(class_path):\n      class_distribution[class_name] = len(os.listdir(class_path))\n  return class_distribution\n\n# Get distributions for train, validation, and test sets\ntrain_distribution = get_class_distribution(train_dir)\nval_distribution = get_class_distribution(valid_dir)\ntest_distribution = get_class_distribution(test_dir)\n\n# Print the results\nprint(\"Training set class distribution:\", train_distribution)\nprint(\"Validation set class distribution:\", val_distribution)\nprint(\"Testing set class distribution:\", test_distribution)\n\nprint(train_gen.class_indices)\nprint(\"Samples per class:\", dict(zip(np.unique(train_gen.classes, return_counts=True)[0],\n                                     np.unique(train_gen.classes, return_counts=True)[1])))\n\ndef get_gradcam_heatmap(model, img_array, class_index, last_conv_layer_name):\n    # Create a model that maps input image to activations of the last conv layer and predictions\n    grad_model = tf.keras.models.Model(\n        [model.inputs],\n        [model.get_layer(last_conv_layer_name).output, model.output]\n    )\n\n    # Gradient tape to get gradients of the class output wrt last conv layer\n    with tf.GradientTape() as tape:\n        conv_outputs, predictions = grad_model(img_array)\n        loss = predictions[:, class_index]\n\n    # Get gradients of loss wrt conv layer output\n    grads = tape.gradient(loss, conv_outputs)\n\n    # Mean intensity of gradients (importance of each feature map)\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n\n    # Multiply feature maps by importance weights\n    conv_outputs = conv_outputs[0]\n    heatmap = tf.reduce_sum(tf.multiply(pooled_grads, conv_outputs), axis=-1)\n\n    # Normalize to [0, 1]\n    heatmap = np.maximum(heatmap, 0)\n    heatmap /= np.max(heatmap) + 1e-6\n\n    return heatmap # Return the numpy array directly\n\n\ndef overlay_heatmap(img_path, heatmap, alpha=0.5, colormap=cv2.COLORMAP_JET):\n    # Load the original image\n    img = cv2.imread(img_path)\n    img = cv2.resize(img, (224, 224))\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n    # Resize heatmap to image size\n    heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))\n\n    # Apply colormap\n    heatmap = np.uint8(255 * heatmap)\n    heatmap_colored = cv2.applyColorMap(heatmap, colormap)\n\n    # Overlay heatmap on image\n    overlayed = cv2.addWeighted(img, alpha, heatmap_colored, 1 - alpha, 0)\n\n    return overlayed\nfrom tensorflow.keras.preprocessing import image\n\ndef load_and_preprocess_image(img_path):\n    img = image.load_img(img_path, target_size=(224, 224))\n    img_array = image.img_to_array(img) / 255.0\n    img_array = np.expand_dims(img_array, axis=0)\n    return img_array\n\n# Select a test image path\nimg_path = \"/content/skin-cancer-dataset/skin-cancer.v4i.folder/test/1/IMD023_bmp.rf.5afc020009f4b9d030c4c43db7b529c9.jpg\"  # change as needed\n\n# Load and preprocess image\nimg_tensor = load_and_preprocess_image(img_path)\n\n# Predict class\npreds = model.predict(img_tensor)\npredicted_class = np.argmax(preds)\n\n# Use name of last conv layer in EfficientNetV2B0\nlast_conv_layer_name = \"top_conv\"  # usually this layer in EfficientNetV2*\n\n# Generate heatmap\nheatmap = get_gradcam_heatmap(model, img_tensor, predicted_class, last_conv_layer_name)\n\n# Overlay and plot\nresult = overlay_heatmap(img_path, heatmap)\n\nplt.figure(figsize=(6, 6))\nplt.imshow(result)\nplt.axis('off')\nplt.title(f\"Predicted: {predicted_class} | Class Prob: {preds[0][predicted_class]:.2f}\")\nplt.show()\n\nfilenames = test_gen.filenames\ny_true = test_gen.classes\ny_pred_probs = model.predict(test_gen)\ny_pred = np.argmax(y_pred_probs, axis=1)\n\n# Identify misclassified images\nerrors = np.where(y_pred != y_true)[0]\n\n# Visualize Grad-CAM for misclassified images\nlast_conv_layer_name = \"top_conv\"\nfor i in errors[:10]:  # Visualize first 5 misclassifications\n    img_path = test_gen.filepaths[i]\n    img_tensor = load_and_preprocess_image(img_path)\n    heatmap = get_gradcam_heatmap(model, img_tensor, y_pred[i], last_conv_layer_name)\n    result = overlay_heatmap(img_path, heatmap)\n\n    plt.figure(figsize=(6, 6))\n    plt.imshow(result)\n    plt.title(f\"True: {y_true[i]}, Pred: {y_pred[i]}\")\n    plt.axis('off')\n    plt.show()\n\nmodel.save(\"skin_cancer_detector_70.keras\")\n\nmodel = keras.models.load_model('/content/skin_cancer_detector_70.keras')\n\n# Assuming you have preprocessed your input data as 'input_data'\npredictions = model.predict(input_data)","metadata":{"id":"VGcDFkVaAiMl","outputId":"b1eda26d-44ee-4c66-9980-dd0aa3cfb829","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Update the model training to use the Albumentations generators\n# history = model.fit(\n#     train_alb_gen,\n#     validation_data=val_alb_gen,\n#     epochs=25, # You can adjust the number of epochs\n#     class_weight=class_weights,\n#     callbacks=callbacks\n# )","metadata":{"id":"cbfab508","trusted":true},"outputs":[],"execution_count":null}]}