{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":59093,"databundleVersionId":7469972,"sourceType":"competition"},{"sourceId":7526248,"sourceType":"datasetVersion","datasetId":4308295},{"sourceId":11476122,"sourceType":"datasetVersion","datasetId":7192249},{"sourceId":11508930,"sourceType":"datasetVersion","datasetId":7216372},{"sourceId":11513724,"sourceType":"datasetVersion","datasetId":7220150},{"sourceId":11526750,"sourceType":"datasetVersion","datasetId":7190142},{"sourceId":11539511,"sourceType":"datasetVersion","datasetId":7236743},{"sourceId":11544822,"sourceType":"datasetVersion","datasetId":7239961},{"sourceId":11582392,"sourceType":"datasetVersion","datasetId":7262168},{"sourceId":158958765,"sourceType":"kernelVersion"},{"sourceId":6113,"sourceType":"modelInstanceVersion","modelInstanceId":4618,"modelId":2799},{"sourceId":6127,"sourceType":"modelInstanceVersion","modelInstanceId":4598,"modelId":2797}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# !pip install -q /kaggle/input/kerasv3-lib-ds/keras_cv-0.8.2-py3-none-any.whl --no-deps\n# !pip install -q /kaggle/input/kerasv3-lib-ds/tensorflow-2.15.0.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl --no-deps\n# !pip install -q /kaggle/input/kerasv3-lib-ds/keras-3.0.4-py3-none-any.whl --no-deps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:56:34.533652Z","iopub.execute_input":"2025-04-28T15:56:34.533923Z","iopub.status.idle":"2025-04-28T15:56:34.540167Z","shell.execute_reply.started":"2025-04-28T15:56:34.533897Z","shell.execute_reply":"2025-04-28T15:56:34.539060Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nfrom glob import glob\nfrom tqdm.notebook import tqdm\nimport joblib\nimport tensorflow as tf\nfrom sklearn.model_selection import StratifiedGroupKFold, GroupKFold\nimport keras\nimport keras_cv\nimport matplotlib.pyplot as plt\nimport math\nimport warnings\nimport seaborn as sns\nfrom sklearn.metrics import confusion_matrix, classification_report, roc_auc_score\nimport tensorflow.keras.backend as K, gc\nwarnings.filterwarnings('ignore')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-29T07:54:54.683429Z","iopub.execute_input":"2025-04-29T07:54:54.683705Z","iopub.status.idle":"2025-04-29T07:55:24.413710Z","shell.execute_reply.started":"2025-04-29T07:54:54.683681Z","shell.execute_reply":"2025-04-29T07:55:24.412722Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"gpus = tf.config.list_physical_devices('GPU')\nif len(gpus)<=1: \n    strategy = tf.distribute.OneDeviceStrategy(device=\"/gpu:0\")\n    print(f'Using {len(gpus)} GPU')\nelse: \n    strategy = tf.distribute.MirroredStrategy()\n    print(f'Using {len(gpus)} GPUs')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:01.090124Z","iopub.execute_input":"2025-04-28T15:57:01.090619Z","iopub.status.idle":"2025-04-28T15:57:01.101455Z","shell.execute_reply.started":"2025-04-28T15:57:01.090595Z","shell.execute_reply":"2025-04-28T15:57:01.100343Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CFG:\n    verbose = 1  # Verbosity\n    seed = 42  # Random seed\n    preset = 'efficientnetv2_b2_imagenet' #\"mobilenet_v3_large_imagenet\"  # Name of pretrained classifier\n    image_size = [128, 256, 4]  # Input image size\n    epochs = 20  # Training epochs\n    batch_size = 16  # Batch size\n    lr_mode = \"cos\"  # LR scheduler mode from one of \"cos\", \"step\", \"exp\"\n    drop_remainder = True  # Drop incomplete batches\n    num_classes = 6  # Number of classes in the dataset\n    fold = 0  # Which fold to set as validation data\n    class_names = ['Seizure', 'LPD', 'GPD', 'LRDA', 'GRDA', 'Other']\n    label2name = dict(enumerate(class_names))\n    name2label = {v: k for k, v in label2name.items()}\n\n    keras.utils.set_random_seed(seed) ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-29T07:55:33.619387Z","iopub.execute_input":"2025-04-29T07:55:33.619667Z","iopub.status.idle":"2025-04-29T07:55:33.626542Z","shell.execute_reply.started":"2025-04-29T07:55:33.619648Z","shell.execute_reply":"2025-04-29T07:55:33.625363Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#NPY_DIR = '/kaggle/input/spectrograms-500/spectrograms_npy_balanced/'\n\n# NPY_DIR = '/kaggle/input/spectrograms-3000/spectrograms_npy_balanced_3000/'\n# NPY_DIR = '/kaggle/input/spectrograms-3000-128-512/spectrograms_npy_balanced_3000_128_512/'\n# NPY_DIR = '/kaggle/input/spectrograms-500/spectrograms_3000_128_512_corrected/'\n# NPY_DIR = '/kaggle/input/spectrogram-128-256-partial-90/spectrograms_all_128_256/'\nNPY_DIR = '/kaggle/input/brain-spectrogram-128-256-90/spectrograms_all_128_256/'\n\n\n# NPY_DIR = '/kaggle/input/brain-spectrograms-128-512/spectrograms_all_128_512_corrected/'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-29T07:55:36.275195Z","iopub.execute_input":"2025-04-29T07:55:36.275541Z","iopub.status.idle":"2025-04-29T07:55:36.280528Z","shell.execute_reply.started":"2025-04-29T07:55:36.275515Z","shell.execute_reply":"2025-04-29T07:55:36.279419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def load_data():\n#     BASE_PATH = \"data\"\n#     SPEC_DIR = \"/tmp/dataset/hms-hbac\"\n#     os.makedirs(SPEC_DIR + '/train_spectrograms', exist_ok=True)\n#     os.makedirs(SPEC_DIR + '/test_spectrograms', exist_ok=True)\n\n#     # Train + Valid\n#     df = pd.read_csv(f'{BASE_PATH}/train.csv')\n#     df['eeg_path'] = f'{BASE_PATH}/train_eegs/' + df['eeg_id'].astype(str) + '.parquet'\n#     df['spec_path'] = f'{BASE_PATH}/train_spectrograms/' + df['spectrogram_id'].astype(str) + '.parquet'\n#     df['spec2_path'] = f'{SPEC_DIR}/train_spectrograms/' + df['spectrogram_id'].astype(str) + '.npy'\n#     df['class_name'] = df.expert_consensus.copy()\n#     df['class_label'] = df.expert_consensus.map(CFG.name2label)\n#     display(df.head(2))\n\n#     # Test\n#     test_df = pd.read_csv(f'{BASE_PATH}/test.csv')\n#     test_df['eeg_path'] = f'{BASE_PATH}/test_eegs/' + test_df['eeg_id'].astype(str) + '.parquet'\n#     test_df['spec_path'] = f'{BASE_PATH}/test_spectrograms/' + test_df['spectrogram_id'].astype(str) + '.parquet'\n#     test_df['spec2_path'] = f'{SPEC_DIR}/test_spectrograms/' + test_df['spectrogram_id'].astype(str) + '.npy'\n#     display(test_df.head(2))\n\n#     return df, test_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:01.148569Z","iopub.execute_input":"2025-04-28T15:57:01.148792Z","iopub.status.idle":"2025-04-28T15:57:01.167819Z","shell.execute_reply.started":"2025-04-28T15:57:01.148775Z","shell.execute_reply":"2025-04-28T15:57:01.166940Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def load_and_preprocess_meta_data(base_path):\n\"\"\"Load and preprocess data with patient-aware grouping\"\"\"\n# Load and group data as per your preprocessing\ndf = pd.read_csv('/kaggle/input/hms-harmful-brain-activity-classification/train.csv')\nTARGETS = df.columns[-6:]\n\n# Create grouped dataframe\ntrain = df.groupby('eeg_id')[['spectrogram_id','spectrogram_label_offset_seconds']].agg(\n    {'spectrogram_id':'first','spectrogram_label_offset_seconds':'min'})\ntrain.columns = ['spec_id','min']\n\ntmp = df.groupby('eeg_id')[['spectrogram_id','spectrogram_label_offset_seconds']].agg(\n    {'spectrogram_label_offset_seconds':'max'})\ntrain['max'] = tmp\n\ntmp = df.groupby('eeg_id')[['patient_id']].agg('first') \ntrain['patient_id'] = tmp\n\n# train['total_evaluators'] = df[['seizure_vote', 'lpd_vote', 'gpd_vote', 'lrda_vote', 'grda_vote', 'other_vote']].sum(axis=1)\n\ntmp = df.groupby('eeg_id')[TARGETS].agg('sum')\nsum_targets = tmp.sum(axis=1)\nmax_vote_percentage = tmp.max(axis=1) / sum_targets\ntrain['max_vote_percentage'] = max_vote_percentage\n\ntmp = df.groupby('eeg_id')[TARGETS].agg('sum')\nfor t in TARGETS:\n    train[t] = tmp[t].values\n    \ny_data = train[TARGETS].values\ny_data = y_data / y_data.sum(axis=1,keepdims=True)\ntrain[TARGETS] = y_data\n\ntmp = df.groupby('eeg_id')[['expert_consensus']].agg('first') \ntrain['target'] = tmp\ntrain = train.reset_index()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-29T07:55:38.388821Z","iopub.execute_input":"2025-04-29T07:55:38.389489Z","iopub.status.idle":"2025-04-29T07:55:38.893594Z","shell.execute_reply.started":"2025-04-29T07:55:38.389440Z","shell.execute_reply":"2025-04-29T07:55:38.892532Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from sklearn.preprocessing import LabelEncoder\n\n# # Encode string labels to integers\n# le = LabelEncoder()\n# train['target_encoded'] = le.fit_transform(train['target'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:01.540284Z","iopub.execute_input":"2025-04-28T15:57:01.540586Z","iopub.status.idle":"2025-04-28T15:57:01.544736Z","shell.execute_reply.started":"2025-04-28T15:57:01.540564Z","shell.execute_reply":"2025-04-28T15:57:01.543913Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train = train[train['max_vote_percentage']>=.9]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-29T07:55:42.050944Z","iopub.execute_input":"2025-04-29T07:55:42.051284Z","iopub.status.idle":"2025-04-29T07:55:42.059616Z","shell.execute_reply.started":"2025-04-29T07:55:42.051261Z","shell.execute_reply":"2025-04-29T07:55:42.058194Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_names = ['GPD', 'GRDA', 'LPD', 'LRDA', 'Other', 'Seizure']\nclass_to_idx = {name: i for i, name in enumerate(class_names)}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-29T07:55:45.748164Z","iopub.execute_input":"2025-04-29T07:55:45.749393Z","iopub.status.idle":"2025-04-29T07:55:45.755006Z","shell.execute_reply.started":"2025-04-29T07:55:45.749344Z","shell.execute_reply":"2025-04-29T07:55:45.753757Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# print(train[train['max_vote_percentage']<1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:01.573566Z","iopub.execute_input":"2025-04-28T15:57:01.573865Z","iopub.status.idle":"2025-04-28T15:57:01.586909Z","shell.execute_reply.started":"2025-04-28T15:57:01.573845Z","shell.execute_reply":"2025-04-28T15:57:01.586040Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# npy_files = [f for f in os.listdir(NPY_DIR) if f.endswith('.npy')]\n# npy_ids = {f.split('.')[0] for f in npy_files}\n\n# # Get metadata IDs\n# meta_ids = set(train['eeg_id'].astype(str))\n\n# # Find perfect matches\n# matched_ids = npy_ids & meta_ids\n# print(f\"Found {len(matched_ids)} perfect matches between NPY files and metadata\")\n\n# # Filter both NPY files and metadata\n# matched_npy_files = [f for f in npy_files if f.split('.')[0] in matched_ids]\n# matched_meta = train[train['eeg_id'].astype(str).isin(matched_ids)].copy()\n# # print(matched_meta['target'].value_counts())\n# # Sort both to ensure same order\n# matched_npy_files.sort()\n# matched_meta = matched_meta.sort_values('eeg_id')\n# train = matched_meta","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:01.587936Z","iopub.execute_input":"2025-04-28T15:57:01.588189Z","iopub.status.idle":"2025-04-28T15:57:01.601434Z","shell.execute_reply.started":"2025-04-28T15:57:01.588165Z","shell.execute_reply":"2025-04-28T15:57:01.600524Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# meta_df, TARGETS = load_and_preprocess_meta_data(\"/kaggle/input/hms-harmful-brain-activity-classification/\")\n        \n# npy_files = [f for f in os.listdir(NPY_DIR) if f.endswith('.npy')]\n# npy_ids = {f.split('.')[0] for f in npy_files}\n\n# # Get metadata IDs\n# meta_ids = set(meta_df['eeg_id'].astype(str))\n\n# # Find perfect matches\n# matched_ids = npy_ids & meta_ids\n# print(f\"Found {len(matched_ids)} perfect matches between NPY files and metadata\")\n\n# # Filter both NPY files and metadata\n# matched_npy_files = [f for f in npy_files if f.split('.')[0] in matched_ids]\n# matched_meta = meta_df[meta_df['eeg_id'].astype(str).isin(matched_ids)].copy()\n# # print(matched_meta['target'].value_counts())\n# # Sort both to ensure same order\n# matched_npy_files.sort()\n# matched_meta = matched_meta.sort_values('eeg_id')\n# train = matched_meta","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:01.602238Z","iopub.execute_input":"2025-04-28T15:57:01.602498Z","iopub.status.idle":"2025-04-28T15:57:01.621411Z","shell.execute_reply.started":"2025-04-28T15:57:01.602455Z","shell.execute_reply":"2025-04-28T15:57:01.620562Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_eegs = {}\nfor i,e in enumerate(train.eeg_id.values):    \n    x = np.load(f'{NPY_DIR}{e}.npy')\n    all_eegs[e] = x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-29T07:55:54.082494Z","iopub.execute_input":"2025-04-29T07:55:54.082863Z","iopub.status.idle":"2025-04-29T07:55:54.317801Z","shell.execute_reply.started":"2025-04-29T07:55:54.082831Z","shell.execute_reply":"2025-04-29T07:55:54.316788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#!pip install -U albumentations","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.172627Z","iopub.execute_input":"2025-04-28T15:57:03.173325Z","iopub.status.idle":"2025-04-28T15:57:03.177187Z","shell.execute_reply.started":"2025-04-28T15:57:03.173293Z","shell.execute_reply":"2025-04-28T15:57:03.176366Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# spectrogram_aug = keras_cv.layers.Augmenter([\n#     keras_cv.layers.RandomTimeMasking(max_mask_size=20, p=0.5),\n#     keras_cv.layers.RandomFrequencyMasking(max_mask_size=10, p=0.5),\n# ])\n\n# mixup = keras_cv.layers.MixUp(alpha=0.2)\n# mixup_layer = keras_cv.layers.MixUp(alpha=0.6)\n\n# Time & frequency masking + flipping\n# spectrogram_aug_layers = [\n#     keras_cv.layers.RandomFlip(\"horizontal\", seed=42),\n#     keras_cv.layers.RandomCutout(\n#         height_factor=0.3, width_factor=0.1, fill_mode=\"constant\", fill_value=0.0, seed=42\n#     ),\n#     keras_cv.layers.RandomCutout(\n#         height_factor=0.1, width_factor=0.3, fill_mode=\"constant\", fill_value=0.0, seed=42\n#     ),\n# ]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.178178Z","iopub.execute_input":"2025-04-28T15:57:03.178443Z","iopub.status.idle":"2025-04-28T15:57:03.194375Z","shell.execute_reply.started":"2025-04-28T15:57:03.178423Z","shell.execute_reply":"2025-04-28T15:57:03.193460Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import albumentations as albu\nclass DataGenerator(tf.keras.utils.Sequence):\n    'Generates data for Keras'\n    def __init__(self, data, batch_size=32, shuffle=False, augment=False, mode='train',\n                 # specs = spectrograms,\n                 eeg_specs = all_eegs): \n\n        self.data = data\n        self.batch_size = batch_size\n        self.shuffle = shuffle\n        self.augment = augment\n        self.mode = mode\n        # self.specs = specs\n        self.eeg_specs = eeg_specs\n        self.on_epoch_end()\n        \n        # self.augmentation_layers = tf.keras.Sequential([\n        #     tf.keras.layers.RandomFlip(mode=\"horizontal\"),\n        #     tf.keras.layers.RandomRotation(factor=0.02),  # ~2% rotation\n        #     tf.keras.layers.RandomZoom(height_factor=0.05, width_factor=0.05),  # ~5% zoom\n        #     tf.keras.layers.RandomTranslation(height_factor=0.05, width_factor=0.05),  # ~5% shift\n        #     tf.keras.layers.GaussianNoise(stddev=0.01),  # tiny noise\n        # ])\n        \n    def __len__(self):\n        'Denotes the number of batches per epoch'\n        ct = int( np.ceil( len(self.data) / self.batch_size ) )\n        return ct\n\n    def __getitem__(self, index):\n        indexes = self.indexes[index * self.batch_size : (index + 1) * self.batch_size]\n        X, y = self.__data_generation(indexes)\n\n        if self.augment:\n            # X = self.augmentation_layers(X, training=True)\n            X = self.__augment_batch(X)\n\n        return X, y\n\n    def on_epoch_end(self):\n        'Updates indexes after each epoch'\n        self.indexes = np.arange( len(self.data) )\n        if self.shuffle: np.random.shuffle(self.indexes)\n        \n    def __data_generation(self, indexes):\n        'Generates data containing batch_size samples' \n        \n        X = np.zeros((len(indexes),CFG.image_size[0], CFG.image_size[1],4),dtype='float32')\n        y = np.zeros((len(indexes),6),dtype='float32')\n        img = np.ones((CFG.image_size[0], CFG.image_size[1]),dtype='float32')\n        \n        for j,i in enumerate(indexes):\n            row = self.data.iloc[i]\n            X[j] = self.eeg_specs[row.eeg_id]\n            \n            # Create one-hot vector from target string\n            target_str = row['target']\n            y[j, class_to_idx[target_str]] = 1.0\n                \n        return X,y\n\n    def __random_transform(self, img):\n        composition = albu.Compose([\n            albu.HorizontalFlip(p=0.5),\n            albu.CoarseDropout(max_holes=8,max_height=32,max_width=32,fill_value=0,p=0.5),\n        ])\n        return composition(image=img)['image']\n            \n    def __augment_batch(self, img_batch):\n        for i in range(img_batch.shape[0]):\n            img_batch[i, ] = self.__random_transform(img_batch[i, ])\n        return img_batch","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.195307Z","iopub.execute_input":"2025-04-28T15:57:03.195597Z","iopub.status.idle":"2025-04-28T15:57:03.216189Z","shell.execute_reply.started":"2025-04-28T15:57:03.195577Z","shell.execute_reply":"2025-04-28T15:57:03.215257Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#will try this augmentation next\n# def __random_transform(self, img):\n#     composition = albu.Compose([\n#         albu.HorizontalFlip(p=0.5),  # simulates left/right brain activity variations\n\n#         albu.RandomBrightnessContrast(p=0.5),\n#         albu.ShiftScaleRotate(shift_limit=0.05, scale_limit=0.05, rotate_limit=10, p=0.3, border_mode=0),\n\n#         albu.GaussianBlur(blur_limit=(3, 5), p=0.2),  # reduce overfitting to sharp edges\n\n#         albu.CoarseDropout(\n#             max_holes=8, max_height=16, max_width=16,\n#             fill_value=0, mask_fill_value=None, p=0.3\n#         ),\n\n#         albu.Normalize(),  # standardize the spectrograms\n#     ])\n#     return composition(image=img)['image']","metadata":{"trusted":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-04-28T15:57:03.217154Z","iopub.execute_input":"2025-04-28T15:57:03.217513Z","iopub.status.idle":"2025-04-28T15:57:03.237265Z","shell.execute_reply.started":"2025-04-28T15:57:03.217456Z","shell.execute_reply":"2025-04-28T15:57:03.236384Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_model():\n    # Build Classifier\n    model = keras_cv.models.ImageClassifier.from_preset(\n        CFG.preset, num_classes=CFG.num_classes\n    )\n\n    # Compile the model  \n    LOSS = keras.losses.KLDivergence()\n    model.compile(optimizer=keras.optimizers.Adam(learning_rate=1e-4),\n                  loss=LOSS)\n    \n    return model ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.238157Z","iopub.execute_input":"2025-04-28T15:57:03.238457Z","iopub.status.idle":"2025-04-28T15:57:03.253328Z","shell.execute_reply.started":"2025-04-28T15:57:03.238429Z","shell.execute_reply":"2025-04-28T15:57:03.252445Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#validation loss does not improve. Stuck in 1.4\n# def build_model():\n#     # Get the backbone with 4 input channels\n#     backbone = keras_cv.models.MobileNetV3Backbone.from_preset(\n#         CFG.preset,\n#         input_shape=(CFG.image_size[0], CFG.image_size[1], 4), load_weights=False\n#     )\n    \n#     # Build classifier with this backbone\n#     model = keras.Sequential([\n#         backbone,\n#         keras.layers.GlobalAveragePooling2D(),\n#         keras.layers.Dense(CFG.num_classes, activation='softmax')\n#     ])\n\n#     # Compile the model  \n#     LOSS = keras.losses.KLDivergence()\n#     model.compile(optimizer=keras.optimizers.Adam(learning_rate=1e-4),\n#                 loss=LOSS)\n    \n#     return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.254297Z","iopub.execute_input":"2025-04-28T15:57:03.254805Z","iopub.status.idle":"2025-04-28T15:57:03.267531Z","shell.execute_reply.started":"2025-04-28T15:57:03.254775Z","shell.execute_reply":"2025-04-28T15:57:03.266745Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_model_2():\n    # Get the backbone with 4 input channels\n    backbone = keras_cv.models.MobileNetV3Backbone.from_preset(\n        CFG.preset,\n        input_shape=(CFG.image_size[0], CFG.image_size[1], 4), load_weights=False\n    )\n\n    # Freeze some layers of the backbone to prevent overfitting\n    backbone.trainable = True\n    for layer in backbone.layers[:100]:\n        layer.trainable = False\n    \n    # Build classifier with this backbone\n    model = keras.Sequential([\n        backbone,\n        keras.layers.GlobalAveragePooling2D(),\n        keras.layers.Dropout(0.3), #added to avoid overfitting\n        keras.layers.Dense(CFG.num_classes, activation='softmax',\n                           kernel_regularizer=keras.regularizers.l2(1e-4)) #added to avoid overfitting\n    ])\n\n    # Compile the model  \n    LOSS = keras.losses.KLDivergence()\n    model.compile(optimizer=keras.optimizers.Adam(learning_rate=1e-4),\n                loss=LOSS)\n    \n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.268540Z","iopub.execute_input":"2025-04-28T15:57:03.268781Z","iopub.status.idle":"2025-04-28T15:57:03.281631Z","shell.execute_reply.started":"2025-04-28T15:57:03.268757Z","shell.execute_reply":"2025-04-28T15:57:03.280886Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#with this model, validation loss increses and train loss fluctuates!!\ndef build_model_3():\n    model = keras.Sequential([\n        keras.layers.Conv2D(64, (3,3), activation='relu', padding='same', input_shape=(CFG.image_size[0], CFG.image_size[1], 4)),\n        keras.layers.BatchNormalization(),\n        keras.layers.MaxPooling2D((2,2)),\n\n        keras.layers.Conv2D(128, (3,3), activation='relu', padding='same'),\n        keras.layers.BatchNormalization(),\n        keras.layers.MaxPooling2D((2,2)),\n\n        keras.layers.Conv2D(256, (3,3), activation='relu', padding='same'),\n        keras.layers.BatchNormalization(),\n        keras.layers.MaxPooling2D((2,2)),\n\n        keras.layers.GlobalAveragePooling2D(),\n        # keras.layers.Dropout(0.3),\n        keras.layers.Dense(64, activation='relu'),\n        keras.layers.Dense(CFG.num_classes, activation='softmax')\n    ])\n\n    model.compile(\n        optimizer=keras.optimizers.Adam(learning_rate=1e-4),\n        loss=keras.losses.KLDivergence(),\n        metrics=['accuracy']\n    )\n\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.282348Z","iopub.execute_input":"2025-04-28T15:57:03.282659Z","iopub.status.idle":"2025-04-28T15:57:03.302149Z","shell.execute_reply.started":"2025-04-28T15:57:03.282634Z","shell.execute_reply":"2025-04-28T15:57:03.301323Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_efficientnet_model():\n    base = keras.applications.EfficientNetB0(\n        input_shape=(CFG.image_size[0], CFG.image_size[1], 4),\n        include_top=False,\n        weights=None\n    )\n    base.trainable = True\n    \n    model = keras.Sequential([\n        base,\n        keras.layers.GlobalAveragePooling2D(),\n        keras.layers.Dropout(0.5),\n        keras.layers.Dense(CFG.num_classes, activation='softmax')\n    ])\n    \n    model.compile(optimizer=keras.optimizers.Adam(1e-4),\n                 loss=keras.losses.KLDivergence(),\n                 metrics=['accuracy'])\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.303222Z","iopub.execute_input":"2025-04-28T15:57:03.303544Z","iopub.status.idle":"2025-04-28T15:57:03.320877Z","shell.execute_reply.started":"2025-04-28T15:57:03.303514Z","shell.execute_reply":"2025-04-28T15:57:03.320065Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import efficientnet.tfkeras as efn\n\ndef build_model_4():\n    # Input shape: 4-channel EEG spectrogram image\n    inp = tf.keras.Input(shape=(CFG.image_size[0], CFG.image_size[1], 4))  # (height, width, channels)\n\n    # Learn a 4-to-3 channel projection using a 1x1 conv layer\n    x = tf.keras.layers.Conv2D(\n        filters=3,\n        kernel_size=(1, 1),\n        padding='same',\n        activation='linear',\n        use_bias=False,\n        name='channel_projection'\n    )(inp)\n\n    # Load pretrained EfficientNetB0 without the top classification layer\n    base_model = keras.applications.EfficientNetB0(\n        include_top=False,\n        weights=None,  # use None here and load weights manually below\n        input_shape=(128, 512, 3)  # input now has 3 channels\n    )\n    # Load pretrained ImageNet weights\n    # base_model.load_weights('/kaggle/input/tf-efficientnet-imagenet/efficientnet-b0_weights_tf_dim_ordering_tf_kernels_autoaugment_notop.h5')\n\n    # Pass through base model\n    x = base_model(x)\n\n    # Global average pooling to reduce spatial dimensions\n    x = tf.keras.layers.GlobalAveragePooling2D()(x)\n\n    # Classification layer (softmax for multi-class classification)\n    x = tf.keras.layers.Dense(6, activation='softmax', dtype='float32')(x)\n\n    # Define the model\n    model = tf.keras.Model(inputs=inp, outputs=x)\n\n    # Compile the model with Adam optimizer and KL divergence loss\n    model.compile(\n        optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n        loss=tf.keras.losses.KLDivergence(),\n        metrics=['accuracy']\n    )\n\n    return model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.321715Z","iopub.execute_input":"2025-04-28T15:57:03.322079Z","iopub.status.idle":"2025-04-28T15:57:03.333369Z","shell.execute_reply.started":"2025-04-28T15:57:03.322058Z","shell.execute_reply":"2025-04-28T15:57:03.332607Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow import keras\nfrom tensorflow.keras import layers, regularizers\n\ndef build_cnn_model(input_shape=(CFG.image_size[0], CFG.image_size[1], 4), num_classes=6):\n    model = keras.Sequential([\n        layers.Conv2D(64, (3, 3), padding='same', kernel_regularizer=regularizers.l2(1e-4),\n                      activation='relu', input_shape=input_shape),\n        layers.BatchNormalization(),\n        layers.MaxPooling2D(),\n\n        layers.Conv2D(128, (3, 3), padding='same', kernel_regularizer=regularizers.l2(1e-4), activation='relu'),\n        layers.BatchNormalization(),\n        layers.MaxPooling2D(),\n\n        layers.Conv2D(256, (3, 3), padding='same', kernel_regularizer=regularizers.l2(1e-4), activation='relu'),\n        layers.BatchNormalization(),\n        layers.MaxPooling2D(),\n\n        layers.Conv2D(512, (3, 3), padding='same', kernel_regularizer=regularizers.l2(1e-4), activation='relu'),\n        layers.BatchNormalization(),\n        layers.GlobalAveragePooling2D(),\n\n        layers.Dropout(0.5),\n        layers.Dense(128, activation='relu'),\n        layers.Dropout(0.3),\n        layers.Dense(num_classes, activation='softmax')  # Output is a probability distribution\n    ])\n\n    model.compile(\n        optimizer=keras.optimizers.Adam(learning_rate=1e-4),\n        loss=keras.losses.KLDivergence(),\n        metrics=['accuracy']\n    )\n\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.334144Z","iopub.execute_input":"2025-04-28T15:57:03.334404Z","iopub.status.idle":"2025-04-28T15:57:03.347965Z","shell.execute_reply.started":"2025-04-28T15:57:03.334384Z","shell.execute_reply":"2025-04-28T15:57:03.347110Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# class MacroF1Score(tf.keras.metrics.Metric):\n#     def __init__(self, num_classes, name='macro_f1', **kwargs):\n#         super(MacroF1Score, self).__init__(name=name, **kwargs)\n#         self.num_classes = num_classes\n#         self.precision = tf.keras.metrics.Precision(class_id=None, average='macro')\n#         self.recall = tf.keras.metrics.Recall(class_id=None, average='macro')\n\n#     def update_state(self, y_true, y_pred, sample_weight=None):\n#         y_true_labels = tf.argmax(y_true, axis=-1)\n#         y_pred_labels = tf.argmax(y_pred, axis=-1)\n\n#         self.precision.update_state(y_true_labels, y_pred_labels, sample_weight)\n#         self.recall.update_state(y_true_labels, y_pred_labels, sample_weight)\n\n#     def result(self):\n#         p = self.precision.result()\n#         r = self.recall.result()\n#         return 2 * (p * r) / (p + r + 1e-7)\n\n#     def reset_states(self):\n#         self.precision.reset_states()\n#         self.recall.reset_states()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.348765Z","iopub.execute_input":"2025-04-28T15:57:03.349045Z","iopub.status.idle":"2025-04-28T15:57:03.367467Z","shell.execute_reply.started":"2025-04-28T15:57:03.349020Z","shell.execute_reply":"2025-04-28T15:57:03.366667Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#We will try this model next\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models\n\nL2_REG = 8e-5  # Adjustable\n\ndef conv_block(x, filters, kernel_size=3, stride=1):\n    shortcut = x\n\n    x = layers.Conv2D(filters, kernel_size, strides=stride, padding='same',\n                      kernel_regularizer=regularizers.l2(L2_REG))(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.ReLU()(x)\n\n    x = layers.Conv2D(filters, kernel_size, padding='same',\n                      kernel_regularizer=regularizers.l2(L2_REG))(x)\n    x = layers.BatchNormalization()(x)\n\n    if stride != 1 or shortcut.shape[-1] != filters:\n        shortcut = layers.Conv2D(filters, 1, strides=stride,\n                                 kernel_regularizer=regularizers.l2(L2_REG))(shortcut)\n        shortcut = layers.BatchNormalization()(shortcut)\n\n    x = layers.Add()([x, shortcut])\n    x = layers.ReLU()(x)\n    return x\n\ndef build_custom_resnet34(input_shape=(128, 256, 4), num_classes=6):\n    inputs = tf.keras.Input(shape=input_shape)\n    x = layers.Conv2D(64, 7, strides=2, padding='same',\n                      kernel_regularizer=regularizers.l2(L2_REG))(inputs)\n    x = layers.BatchNormalization()(x)\n    x = layers.ReLU()(x)\n    x = layers.MaxPooling2D(3, strides=2, padding='same')(x)\n\n    # Residual blocks\n    for filters, blocks, stride in [(64, 3, 1), (128, 4, 2), (256, 6, 2), (512, 3, 2)]:\n        for i in range(blocks):\n            x = conv_block(x, filters, stride=stride if i == 0 else 1)\n\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(0.5)(x)\n    x = layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(L2_REG))(x)  # ⬅️ more capacity\n    x = layers.Dropout(0.3)(x)  # ⬅️ small dropout before final softmax\n    outputs = layers.Dense(num_classes, activation='softmax')(x)\n\n    model = tf.keras.Model(inputs, outputs)\n    \n    initial_learning_rate = 8e-4      # Start slightly higher than fixed\n    decay_steps = 514 * CFG.epochs    # Rule of thumb: steps_per_epoch * num_epochs\n    alpha = 1e-6                      # Minimum LR value after decay\n\n    cosine_decay_schedule = tf.keras.optimizers.schedules.CosineDecay(\n        initial_learning_rate=initial_learning_rate,\n        decay_steps=decay_steps,\n        alpha=alpha\n    )\n\n    optimizer = tf.keras.optimizers.AdamW(\n        learning_rate=cosine_decay_schedule,\n        weight_decay=1e-5\n    )\n    model.compile(\n        optimizer=optimizer,\n        loss=tf.keras.losses.KLDivergence(reduction=tf.keras.losses.Reduction.SUM_OVER_BATCH_SIZE),\n        metrics=[\n            'accuracy',\n            tf.keras.metrics.Precision(name='precision'), \n            tf.keras.metrics.Recall(name='recall')\n            # MacroF1Score(num_classes=6)\n        ]\n    )\n    return model\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.371625Z","iopub.execute_input":"2025-04-28T15:57:03.371938Z","iopub.status.idle":"2025-04-28T15:57:03.388574Z","shell.execute_reply.started":"2025-04-28T15:57:03.371916Z","shell.execute_reply":"2025-04-28T15:57:03.387654Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_lr_callback(batch_size=8, mode='cos', epochs=10, plot=False):\n    lr_start, lr_max, lr_min = 1e-4, 8e-4, 1e-5 #5e-5, 6e-6 * batch_size, 1e-5\n    lr_ramp_ep, lr_sus_ep, lr_decay = 3, 0, 0.75\n\n    def lrfn(epoch):  # Learning rate update function\n        if epoch < lr_ramp_ep:\n            lr = (lr_max - lr_start) / lr_ramp_ep * epoch + lr_start\n        elif epoch < lr_ramp_ep + lr_sus_ep:\n            lr = lr_max\n        elif mode == 'exp':\n            lr = (lr_max - lr_min) * lr_decay**(epoch - lr_ramp_ep - lr_sus_ep) + lr_min\n        elif mode == 'step':\n            lr = lr_max * lr_decay**((epoch - lr_ramp_ep - lr_sus_ep) // 2)\n        elif mode == 'cos':\n            decay_total_epochs = epochs - lr_ramp_ep - lr_sus_ep + 3\n            decay_epoch_index = epoch - lr_ramp_ep - lr_sus_ep\n            phase = math.pi * decay_epoch_index / decay_total_epochs\n            lr = (lr_max - lr_min) * 0.5 * (1 + math.cos(phase)) + lr_min\n        return lr\n\n    if plot:  # Plot lr curve if plot is True\n        plt.figure(figsize=(10, 5))\n        plt.plot(np.arange(epochs), [lrfn(epoch) for epoch in np.arange(epochs)], marker='o')\n        plt.xlabel('epoch'); plt.ylabel('lr')\n        plt.title('LR Scheduler')\n        plt.show()\n\n    return keras.callbacks.LearningRateScheduler(lrfn, verbose=True)  # Create lr callback","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.389615Z","iopub.execute_input":"2025-04-28T15:57:03.389935Z","iopub.status.idle":"2025-04-28T15:57:03.409231Z","shell.execute_reply.started":"2025-04-28T15:57:03.389909Z","shell.execute_reply":"2025-04-28T15:57:03.408217Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.410156Z","iopub.execute_input":"2025-04-28T15:57:03.410515Z","iopub.status.idle":"2025-04-28T15:57:03.452449Z","shell.execute_reply.started":"2025-04-28T15:57:03.410466Z","shell.execute_reply":"2025-04-28T15:57:03.451456Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.utils import class_weight\n\nclasses = np.unique(train['target'])\nclass_weights = class_weight.compute_class_weight(class_weight='balanced',\n                                                  classes=classes,\n                                                  y=train['target'])\nclass_weight_dict = dict(zip(classes, class_weights))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.453420Z","iopub.execute_input":"2025-04-28T15:57:03.453780Z","iopub.status.idle":"2025-04-28T15:57:03.459596Z","shell.execute_reply.started":"2025-04-28T15:57:03.453752Z","shell.execute_reply":"2025-04-28T15:57:03.458731Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def main():    \n#     # Stratified Group K-Fold\n#     sgkf = StratifiedGroupKFold(n_splits=5, shuffle=True, random_state=CFG.seed)\n#     lr_plateau_cb = keras.callbacks.ReduceLROnPlateau(\n#         monitor='val_loss', factor=0.2, patience=3, min_lr=1e-6\n#     )\n\n#     # Model Checkpoint\n#     ckpt_cb = keras.callbacks.ModelCheckpoint(\"best_model.keras\",\n#                                              monitor='val_loss',\n#                                              save_best_only=True,\n#                                              save_weights_only=False,\n#                                              mode='min')\n#     #Early Stopping\n#     early_stopping = keras.callbacks.EarlyStopping(\n#         monitor=\"val_loss\", patience=3, restore_best_weights=True\n#     )\n    \n#     model = build_custom_resnet34() #build_cnn_model()\n#     model.summary()\n    \n#     all_fold_preds = []\n#     all_fold_trues = []\n\n#     for i, (train_index, valid_index) in enumerate(sgkf.split(train, train.target, train.patient_id)):  \n#         print('#'*25)\n#         print(f'### Fold {i+1}')\n\n#         # Get the patient IDs for this fold\n#         train_patients = train.iloc[train_index]['patient_id'].unique()\n#         valid_patients = train.iloc[valid_index]['patient_id'].unique()\n        \n#         # Check for patient leakage between train and validation\n#         common_patients = set(train_patients) & set(valid_patients)\n#         if len(common_patients) > 0:\n#             print(f\"WARNING: Data leakage detected! {len(common_patients)} patients appear in both train and validation sets\")\n#             print(f\"Common patients: {common_patients}\")\n#         else:\n#             print(\"No patient leakage detected between train and validation sets\")\n        \n#         # Check class distribution\n#         train_targets = train.iloc[train_index]['target']\n#         valid_targets = train.iloc[valid_index]['target']\n        \n#         # Verify no samples from same patient are in both sets\n#         assert len(set(train.iloc[train_index]['patient_id']) & \n#                set(train.iloc[valid_index]['patient_id'])) == 0, \"Patient leakage detected!\"\n        \n#         train_ds = DataGenerator(train.iloc[train_index], shuffle=True, batch_size=CFG.batch_size, augment=False)\n#         valid_ds = DataGenerator(train.iloc[valid_index], shuffle=False, batch_size=CFG.batch_size, mode='valid')        \n        \n#         # Train Model\n#         history = model.fit(\n#             train_ds, \n#             epochs=CFG.epochs,\n#             callbacks=[lr_plateau_cb, ckpt_cb, early_stopping],\n#             validation_data=valid_ds,\n#             verbose=CFG.verbose\n#         )\n        \n#         preds = model.predict(valid_ds)\n#         preds_classes = np.argmax(preds, axis=1)\n#         # true_classes = train.iloc[valid_index]['target'].values\n#         true_classes = train.iloc[valid_index]['target_encoded'].values\n        \n#         # AUC Score (multi-class, average='macro')\n#         auc = roc_auc_score(tf.one_hot(true_classes, depth=preds.shape[1]).numpy(), preds, average='macro')\n#         print(f\"Fold {i+1} AUC Score (Macro Average): {auc:.4f}\")\n\n#         # Confusion Matrix\n#         cm = confusion_matrix(true_classes, preds_classes)\n#         print(f\"Fold {i+1} Confusion Matrix:\\n{cm}\")\n\n#         # Classification Report\n#         report = classification_report(true_classes, preds_classes, digits=4)\n#         print(f\"Fold {i+1} Classification Report:\\n{report}\")\n\n#         plt.figure(figsize=(8, 6))\n#         sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n#                     xticklabels=[str(i) for i in range(preds.shape[1])], \n#                     yticklabels=[str(i) for i in range(preds.shape[1])])\n#         plt.title(f'Confusion Matrix - Fold {i+1}')\n#         plt.xlabel('Predicted Label')\n#         plt.ylabel('True Label')\n#         plt.show()\n\n#         # Store for later if you want overall average\n#         all_fold_preds.append(preds_classes)\n#         all_fold_trues.append(true_classes)\n\n#         # ========== NEW BLOCK ENDS HERE ==========\n\n#     # Optionally after all folds:\n#     all_fold_preds = np.concatenate(all_fold_preds)\n#     all_fold_trues = np.concatenate(all_fold_trues)\n    \n#     overall_auc = roc_auc_score(tf.one_hot(all_fold_trues, depth=preds.shape[1]).numpy(), \n#                                 tf.one_hot(all_fold_preds, depth=preds.shape[1]).numpy(), \n#                                 average='macro')\n#     print(f\"Overall AUC Score across all folds: {overall_auc:.4f}\")\n#     print(\"Overall Confusion Matrix:\")\n#     print(confusion_matrix(all_fold_trues, all_fold_preds))\n#     print(\"Overall Classification Report:\")\n#     print(classification_report(all_fold_trues, all_fold_preds, digits=4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.460624Z","iopub.execute_input":"2025-04-28T15:57:03.461318Z","iopub.status.idle":"2025-04-28T15:57:03.477418Z","shell.execute_reply.started":"2025-04-28T15:57:03.461296Z","shell.execute_reply":"2025-04-28T15:57:03.476314Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.utils.class_weight import compute_class_weight\nfrom sklearn.metrics import roc_auc_score, confusion_matrix, classification_report\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\ndef main():    \n    sgkf = StratifiedGroupKFold(n_splits=5, shuffle=True, random_state=CFG.seed)\n\n    all_fold_preds = []\n    all_fold_trues = []\n    fold_metrics = []\n\n    for i, (train_index, valid_index) in enumerate(sgkf.split(train, train.target, train.patient_id)):  \n        print('#' * 25)\n        print(f'### Fold {i+1}')\n\n        # StratifiedGroupKFold splits\n        train_data = train.iloc[train_index]\n        valid_data = train.iloc[valid_index]\n\n        # Double-check leakage\n        assert len(set(train_data['patient_id']) & set(valid_data['patient_id'])) == 0, \"Patient leakage detected!\"\n\n        # Datasets\n        train_ds = DataGenerator(train_data, shuffle=True, batch_size=CFG.batch_size, augment=True)\n        valid_ds = DataGenerator(valid_data, shuffle=False, batch_size=CFG.batch_size, mode='valid')\n\n        # Model\n        model = build_custom_resnet34()  # fresh model for each fold\n        # model.summary()\n\n        # Callbacks\n        # lr_plateau_cb = keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=3, \n        #                                                   min_lr=1e-6)\n        \n        ckpt_cb = keras.callbacks.ModelCheckpoint(f\"best_model_fold{i+1}.keras\", \n                                                   monitor='val_loss', save_best_only=True, mode='min')\n        \n        early_stopping_cb = keras.callbacks.EarlyStopping(monitor='val_loss', patience=5, \n                                                          restore_best_weights=True)\n\n        # Train\n        history = model.fit(\n            train_ds,\n            epochs=CFG.epochs,\n            validation_data=valid_ds,\n            callbacks=[ckpt_cb, early_stopping_cb],\n            verbose=CFG.verbose,\n            class_weight=class_weight_dict\n        )\n\n        preds = model.predict(valid_ds)\n\n        # Convert predictions to class indices\n        pred_classes = np.argmax(preds, axis=1)\n        \n        # Get true class indices\n        true_classes = np.array([class_to_idx[t] for t in train.iloc[valid_index]['target']])\n        true_classes2 = np.array([class_to_idx[t] for t in valid_data['target']])\n        print(true_classes, true_classes2)\n        cr = classification_report(true_classes, pred_classes, \n                                   target_names=class_names, labels=list(range(len(class_names))), digits=4,\n                                   zero_division=0, output_dict=True)\n        # 1. Classification Report\n        cr_df = pd.DataFrame(cr).transpose()    # <-- FIX: pretty print\n        display(cr_df)\n    \n        # 2. Confusion Matrix\n        plt.figure(figsize=(10,8))\n        cm = confusion_matrix(true_classes, pred_classes, labels=np.arange(len(class_names)))\n        sns.heatmap(cm, annot=True, fmt='d',\n                   xticklabels=class_names,\n                   yticklabels=class_names,\n                   cmap='Blues')\n        plt.title(f'Fold {i+1} Confusion Matrix')\n        plt.xlabel('Predicted')\n        plt.ylabel('True')\n        plt.show()\n        \n        # Store fold metrics\n        fold_metrics.append({\n            'fold': i+1,\n            'accuracy': cr['accuracy'],\n            'class_report': cr,\n            'confusion_matrix': cm\n        })\n        \n        del model, preds\n        gc.collect()\n\n    # Calculate overall metrics\n    print('\\n' + '#'*50)\n    print('### FINAL OVERALL RESULTS ACROSS ALL FOLDS')\n    print('#'*50 + '\\n')\n    \n    # Convert to arrays\n    all_true = np.array(all_fold_trues)\n    all_pred = np.array(all_fold_preds)\n    \n    # 1. Overall Classification Report\n    print(\"Overall Classification Report:\")\n    overall_cr = classification_report(all_true, all_pred,\n                               target_names=class_names,\n                               labels=np.arange(len(class_names)),\n                               digits=4,\n                               zero_division=0,\n                               output_dict=True)\n    \n    overall_cr_df = pd.DataFrame(overall_cr).transpose()\n    display(overall_cr_df)\n    \n    # 2. Overall Confusion Matrix\n    plt.figure(figsize=(10,8))\n    overall_cm = confusion_matrix(all_true, all_pred, labels=np.arange(len(class_names)))\n    sns.heatmap(overall_cm, annot=True, fmt='d',\n               xticklabels=class_names,\n               yticklabels=class_names,\n               cmap='Blues')\n    \n    plt.title('Overall Confusion Matrix')\n    plt.xlabel('Predicted')\n    plt.ylabel('True')\n    plt.show()\n    \n    # 4. Print fold-wise summary\n    print(\"\\nFold-wise Performance Summary:\")\n    summary_df = pd.DataFrame(fold_metrics)[['fold', 'accuracy']]\n    print(summary_df.to_string(index=False))\n    \n    print(f\"\\nMean Accuracy: {summary_df['accuracy'].mean():.4f}\")\n\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.478339Z","iopub.execute_input":"2025-04-28T15:57:03.478866Z","iopub.status.idle":"2025-04-28T15:57:03.501270Z","shell.execute_reply.started":"2025-04-28T15:57:03.478840Z","shell.execute_reply":"2025-04-28T15:57:03.500375Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"main()\n#validation loss stuck at 1.37 with spectrogram-3000 data and spectrogram_3000-128_512. See version 1 of this notebook","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:57:03.502140Z","iopub.execute_input":"2025-04-28T15:57:03.503163Z","iopub.status.idle":"2025-04-28T15:59:39.243322Z","shell.execute_reply.started":"2025-04-28T15:57:03.503132Z","shell.execute_reply":"2025-04-28T15:59:39.242440Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import matplotlib.pyplot as plt\n# import seaborn as sns\n# from sklearn.metrics import confusion_matrix\n\n# def plot_conf_matrix(y_true, y_pred, class_names, fold_idx):\n#     cm = confusion_matrix(y_true, y_pred)\n#     plt.figure(figsize=(6, 5))\n#     sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n#                 xticklabels=class_names, yticklabels=class_names)\n#     plt.xlabel('Predicted')\n#     plt.ylabel('True')\n#     plt.title(f'Fold {fold_idx+1} Confusion Matrix')\n#     plt.tight_layout()\n#     plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T15:59:39.244584Z","iopub.execute_input":"2025-04-28T15:59:39.244870Z","iopub.status.idle":"2025-04-28T15:59:39.249148Z","shell.execute_reply.started":"2025-04-28T15:59:39.244848Z","shell.execute_reply":"2025-04-28T15:59:39.248252Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# for fold, (train_idx, valid_idx) in enumerate(\n    #     sgkf.split(df, y=df[\"class_label\"], groups=df[\"patient_id\"])\n    # ):\n    #     df.loc[valid_idx, \"fold\"] = fold\n    # df.groupby([\"fold\", \"class_name\"])[[\"eeg_id\"]].count().T\n\n    # # Sample from full data\n    # sample_df = df.groupby(\"spectrogram_id\").head(1).reset_index(drop=True)\n    # train_df = sample_df[sample_df.fold != CFG.fold]\n    # valid_df = sample_df[sample_df.fold == CFG.fold]\n    # print(f\"# Num Train: {len(train_df)} | Num Valid: {len(valid_df)}\")\n\n    # # Build Datasets\n    # train_paths = train_df.spec2_path.values\n    # train_offsets = train_df.spectrogram_label_offset_seconds.values.astype(int)\n    # train_labels = train_df.class_label.values\n    # train_ds = build_dataset(train_paths, train_offsets, train_labels, batch_size=CFG.batch_size,\n    #                          repeat=True, shuffle=True, augment=True, cache=True)\n\n    # valid_paths = valid_df.spec2_path.values\n    # valid_offsets = valid_df.spectrogram_label_offset_seconds.values.astype(int)\n    # valid_labels = valid_df.class_label.values\n    # valid_ds = build_dataset(valid_paths, valid_offsets, valid_labels, batch_size=CFG.batch_size,\n    #                          repeat=False, shuffle=False, augment=False, cache=True)\n\n    # # Dataset Check\n    # # plot_dataset_samples(train_ds)\n    \n    # # Build Model\n    # model = build_model()\n    # model.summary()\n\n    # # LR Schedule\n    # lr_cb = get_lr_callback(batch_size=CFG.batch_size, mode=CFG.lr_mode, plot=True)\n\n    # # Model Checkpoint\n    # ckpt_cb = keras.callbacks.ModelCheckpoint(\"best_model.keras\",\n    #                                          monitor='val_loss',\n    #                                          save_best_only=True,\n    #                                          save_weights_only=False,\n    #                                          mode='min')\n\n    # # Train Model\n    # history = model.fit(\n    #     train_ds, \n    #     epochs=CFG.epochs,\n    #     callbacks=[lr_cb, ckpt_cb], \n    #     steps_per_epoch=len(train_df)//CFG.batch_size,\n    #     validation_data=valid_ds, \n    #     verbose=CFG.verbose\n    # )","metadata":{"trusted":true,"jupyter":{"source_hidden":true},"execution":{"iopub.status.busy":"2025-04-28T15:59:39.250206Z","iopub.execute_input":"2025-04-28T15:59:39.251186Z","iopub.status.idle":"2025-04-28T15:59:39.270198Z","shell.execute_reply.started":"2025-04-28T15:59:39.251156Z","shell.execute_reply":"2025-04-28T15:59:39.269393Z"}},"outputs":[],"execution_count":null}]}