{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","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"}],"dockerImageVersionId":30665,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport random\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras.models import load_model\nfrom tensorflow.keras.layers import Input, Dense, GlobalAveragePooling2D, GlobalAveragePooling1D, Concatenate\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.losses import KLDivergence\nfrom sklearn.model_selection import GroupKFold\nimport gc\nimport albumentations as albu\nimport os\n\nif not os.path.exists(LOAD_BACKBONE_FROM):\n    raise Exception(f\"Model file not found: {LOAD_BACKBONE_FROM}\")\n\n# Constants and configurations\nLOAD_BACKBONE_FROM = '/kaggle/input/efficientnetb-tf-keras/EfficientNetB2.h5'\nLOAD_MODELS_FROM = '/kaggle/input/features-head-starter-models/'\nVER = 47\nDATA_TYPE = 'both'  # both|eeg|kaggle|raw\nTEST_MODE = False\nsubmission = True\nENSEMBLE = True\nLBs = [0.37, 0.39, 0.41, 0.41]\nnp.random.seed(42)\nrandom.seed(42)\ntf.random.set_seed(42)\n\n# Optimized Data Generator using tf.data API\nclass OptimizedDataGenerator(tf.keras.utils.Sequence):\n    def __init__(self, data, specs=None, eeg_specs=None, raw_eegs=None, augment=False, mode='train', data_type=DATA_TYPE):\n        self.data = data\n        self.specs = specs\n        self.eeg_specs = eeg_specs\n        self.raw_eegs = raw_eegs\n        self.augment = augment\n        self.mode = mode\n        self.data_type = data_type\n        self.on_epoch_end()\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, index):\n        if self.data_type == 'both':\n            X, y = self.generate_all_specs(index)\n        elif self.data_type in ['eeg', 'kaggle']:\n            X, y = self.generate_specs(index)\n        elif self.data_type == 'raw':\n            X, y = self.generate_raw(index)\n        if self.augment:\n            X = self.augmentation(X)\n        return X, y\n\n    def on_epoch_end(self):\n        if self.mode == 'train':\n            self.data = self.data.sample(frac=1).reset_index(drop=True)\n\n    def data_generation(self, index):\n        if self.data_type == 'both':\n            X,y = self.generate_all_specs(index)\n        elif self.data_type == 'eeg' or self.data_type == 'kaggle':\n            X,y = self.generate_specs(index)\n        elif self.data_type == 'raw':\n            X,y = self.generate_raw(index)\n\n        return X,y\n    \n    def generate_all_specs(self, index):\n        X = np.zeros((512,512,3),dtype='float32')\n        y = np.zeros((6,),dtype='float32')\n        \n        row = self.data.iloc[index]\n        if self.mode=='test': \n            offset = 0\n        else:\n            offset = int(row.offset/2)\n            \n        eeg = self.eeg_specs[row.eeg_id]\n        spec = self.specs[row.spec_id]\n        \n        imgs = [spec[offset:offset+300,k*100:(k+1)*100].T for k in [0,2,1,3]] # to match kaggle with eeg\n        img = np.stack(imgs,axis=-1)\n        # LOG TRANSFORM SPECTROGRAM\n        img = np.clip(img,np.exp(-4),np.exp(8))\n        img = np.log(img)\n            \n        # STANDARDIZE PER IMAGE\n        img = np.nan_to_num(img, nan=0.0)    \n            \n        mn = img.flatten().min()\n        mx = img.flatten().max()\n        ep = 1e-5\n        img = 255 * (img - mn) / (mx - mn + ep)\n        \n        X[0_0+56:100+56,:256,0] = img[:,22:-22,0] # LL_k\n        X[100+56:200+56,:256,0] = img[:,22:-22,2] # RL_k\n        X[0_0+56:100+56,:256,1] = img[:,22:-22,1] # LP_k\n        X[100+56:200+56,:256,1] = img[:,22:-22,3] # RP_k\n        X[0_0+56:100+56,:256,2] = img[:,22:-22,2] # RL_k\n        X[100+56:200+56,:256,2] = img[:,22:-22,1] # LP_k\n        \n        X[0_0+56:100+56,256:,0] = img[:,22:-22,0] # LL_k\n        X[100+56:200+56,256:,0] = img[:,22:-22,2] # RL_k\n        X[0_0+56:100+56,256:,1] = img[:,22:-22,1] # LP_k\n        X[100+56:200+56,256:,1] = img[:,22:-22,3] # RP_K\n        \n        # EEG\n        img = eeg\n        mn = img.flatten().min()\n        mx = img.flatten().max()\n        ep = 1e-5\n        img = 255 * (img - mn) / (mx - mn + ep)\n        X[200+56:300+56,:256,0] = img[:,22:-22,0] # LL_e\n        X[300+56:400+56,:256,0] = img[:,22:-22,2] # RL_e\n        X[200+56:300+56,:256,1] = img[:,22:-22,1] # LP_e\n        X[300+56:400+56,:256,1] = img[:,22:-22,3] # RP_e\n        X[200+56:300+56,:256,2] = img[:,22:-22,2] # RL_e\n        X[300+56:400+56,:256,2] = img[:,22:-22,1] # LP_e\n        \n        X[200+56:300+56,256:,0] = img[:,22:-22,0] # LL_e\n        X[300+56:400+56,256:,0] = img[:,22:-22,2] # RL_e\n        X[200+56:300+56,256:,1] = img[:,22:-22,1] # LP_e\n        X[300+56:400+56,256:,1] = img[:,22:-22,3] # RP_e\n\n        if self.mode!='test':\n            y[:] = row[TARGETS]\n        \n        return X,y\n    \n    def generate_specs(self, index):\n        X = np.zeros((512,512,3),dtype='float32')\n        y = np.zeros((6,),dtype='float32')\n        \n        row = self.data.iloc[index]\n        if self.mode=='test': \n            offset = 0\n        else:\n            offset = int(row.offset/2)\n            \n        if self.data_type == 'eeg':\n            img = self.eeg_specs[row.eeg_id]\n        elif self.data_type == 'kaggle':\n            spec = self.specs[row.spec_id]\n            imgs = [spec[offset:offset+300,k*100:(k+1)*100].T for k in [0,2,1,3]] # to match kaggle with eeg\n            img = np.stack(imgs,axis=-1)\n            # LOG TRANSFORM SPECTROGRAM\n            img = np.clip(img,np.exp(-4),np.exp(8))\n            img = np.log(img)\n            \n            # STANDARDIZE PER IMAGE\n            img = np.nan_to_num(img, nan=0.0)    \n            \n        mn = img.flatten().min()\n        mx = img.flatten().max()\n        ep = 1e-5\n        img = 255 * (img - mn) / (mx - mn + ep)\n        \n        X[0_0+56:100+56,:256,0] = img[:,22:-22,0]\n        X[100+56:200+56,:256,0] = img[:,22:-22,2]\n        X[0_0+56:100+56,:256,1] = img[:,22:-22,1]\n        X[100+56:200+56,:256,1] = img[:,22:-22,3]\n        X[0_0+56:100+56,:256,2] = img[:,22:-22,2]\n        X[100+56:200+56,:256,2] = img[:,22:-22,1]\n        \n        X[0_0+56:100+56,256:,0] = img[:,22:-22,0]\n        X[100+56:200+56,256:,0] = img[:,22:-22,1]\n        X[0_0+56:100+56,256:,1] = img[:,22:-22,2]\n        X[100+56:200+56,256:,1] = img[:,22:-22,3]\n        \n        X[200+56:300+56,:256,0] = img[:,22:-22,0]\n        X[300+56:400+56,:256,0] = img[:,22:-22,1]\n        X[200+56:300+56,:256,1] = img[:,22:-22,2]\n        X[300+56:400+56,:256,1] = img[:,22:-22,3]\n        X[200+56:300+56,:256,2] = img[:,22:-22,3]\n        X[300+56:400+56,:256,2] = img[:,22:-22,2]\n        \n        X[200+56:300+56,256:,0] = img[:,22:-22,0]\n        X[300+56:400+56,256:,0] = img[:,22:-22,2]\n        X[200+56:300+56,256:,1] = img[:,22:-22,1]\n        X[300+56:400+56,256:,1] = img[:,22:-22,3]\n        \n        if self.mode!='test':\n            y[:] = row[TARGETS]\n        \n        return X,y\n    \n    def generate_raw(self,index):\n        X = np.zeros((10_000,8),dtype='float32')\n        y = np.zeros((6,),dtype='float32')\n        \n        row = self.data.iloc[index]\n        eeg = self.raw_eegs[row.eeg_id]\n            \n        # FEATURE ENGINEER\n        X[:,0] = eeg[:,FEAT2IDX['Fp1']] - eeg[:,FEAT2IDX['T3']]\n        X[:,1] = eeg[:,FEAT2IDX['T3']] - eeg[:,FEAT2IDX['O1']]\n            \n        X[:,2] = eeg[:,FEAT2IDX['Fp1']] - eeg[:,FEAT2IDX['C3']]\n        X[:,3] = eeg[:,FEAT2IDX['C3']] - eeg[:,FEAT2IDX['O1']]\n            \n        X[:,4] = eeg[:,FEAT2IDX['Fp2']] - eeg[:,FEAT2IDX['C4']]\n        X[:,5] = eeg[:,FEAT2IDX['C4']] - eeg[:,FEAT2IDX['O2']]\n            \n        X[:,6] = eeg[:,FEAT2IDX['Fp2']] - eeg[:,FEAT2IDX['T4']]\n        X[:,7] = eeg[:,FEAT2IDX['T4']] - eeg[:,FEAT2IDX['O2']]\n            \n        # STANDARDIZE\n        X = np.clip(X,-1024,1024)\n        X = np.nan_to_num(X, nan=0) / 32.0\n            \n        # BUTTER LOW-PASS FILTER\n        X = self.butter_lowpass_filter(X)\n        # Downsample\n        X = X[::5,:]\n        \n        if self.mode!='test':\n            y[:] = row[TARGETS]\n                \n        return X,y\n        \n    def butter_lowpass_filter(self, data, cutoff_freq=20, sampling_rate=200, order=4):\n        nyquist = 0.5 * sampling_rate\n        normal_cutoff = cutoff_freq / nyquist\n        b, a = butter(order, normal_cutoff, btype='low', analog=False)\n        filtered_data = lfilter(b, a, data, axis=0)\n        return filtered_data\n    \n    def resize(self, img,size):\n        composition = albu.Compose([\n                albu.Resize(size[0],size[1])\n            ])\n        return composition(image=img)['image']\n            \n    def augmentation(self, img):\n        composition = albu.Compose([\n                albu.HorizontalFlip(p=0.4)\n            ])\n        return composition(image=img)['image']\n    \n# Optimized model building\ndef build_optimized_model(input_shape=(512, 512, 3), num_classes=6):\n    inp = Input(shape=input_shape)\n    base_model = load_model(f'{LOAD_BACKBONE_FROM}')\n    x = base_model(inp)\n    x = GlobalAveragePooling2D()(x)\n    output = Dense(num_classes, activation='softmax', dtype='float32')(x)\n    model = tf.keras.Model(inputs=inp, outputs=output)\n    model.compile(optimizer=Adam(learning_rate=1e-3), loss=KLDivergence())\n    return model\n\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping\n\n# Training with K-Fold cross-validation, optimized resource management, and callbacks\ndef train_and_evaluate(train_data):\n    gkf = GroupKFold(n_splits=5)\n    for fold, (train_idx, val_idx) in enumerate(gkf.split(train_data, groups=train_data['patient_id'])):\n        print(f\"Training fold {fold + 1}\")\n        train_subset, val_subset = train_data.iloc[train_idx], train_data.iloc[val_idx]\n        train_gen = OptimizedDataGenerator(train_subset, augment=True)\n        val_gen = OptimizedDataGenerator(val_subset)\n        \n        model = build_optimized_model()\n\n        # Implementing ModelCheckpoint to save the best model\n        checkpoint_path = f'model_{DATA_TYPE}_{VER}_{fold}.weights.h5'\n        model_checkpoint = ModelCheckpoint(checkpoint_path, monitor='val_loss', save_best_only=True, verbose=1)\n\n        # Implementing EarlyStopping\n        early_stopping = EarlyStopping(monitor='val_loss', patience=10, verbose=1, restore_best_weights=True)\n        \n        # Adding callbacks to the fit function\n        callbacks_list = [model_checkpoint, early_stopping]\n\n        model.fit(train_gen, validation_data=val_gen, epochs=5, callbacks=callbacks_list)\n\n        del model\n        gc.collect()\n        # Clear session and manually collect garbage to ensure memory is managed efficiently\n\n# Inference with optimized batch processing and ensemble logic\ndef predict(test_data, ensemble_weights=None):\n    # Initialize predictions array\n    ensemble_predictions = []\n\n    # Define data types or model variants\n    data_types = ['kaggle', 'eeg', 'both', 'raw'] if ENSEMBLE else [DATA_TYPE]\n\n    # Iterate over each data type or model variant\n    for data_type in data_types:\n        test_gen = OptimizedDataGenerator(test_data, mode='test', data_type=data_type)\n        model = build_optimized_model()\n        \n        # Load model weights for each fold and predict\n        fold_predictions = []\n        for fold in range(5):  # Assuming 5-fold CV\n            model.load_weights(f'{LOAD_MODELS_FROM}model_{data_type}_{VER}_{fold}.weights.h5')\n            fold_pred = model.predict(test_gen, verbose=1)\n            fold_predictions.append(fold_pred)\n        \n        # Average predictions across folds\n        data_type_predictions = np.mean(fold_predictions, axis=0)\n        ensemble_predictions.append(data_type_predictions)\n\n    # Combine predictions from all data types using weighted or simple average\n    if ensemble_weights:\n        # Ensure the weights sum up to 1\n        ensemble_weights = np.array(ensemble_weights)\n        assert np.isclose(ensemble_weights.sum(), 1), \"Weights should sum up to 1.\"\n        \n        # Use np.average to compute the weighted average\n        final_predictions = np.average(ensemble_predictions, axis=0, weights=ensemble_weights)\n    else:\n        # Use np.mean to compute the simple average\n        final_predictions = np.mean(ensemble_predictions, axis=0)\n\n    return final_predictions\n\n# Example usage\npredictions = predict(test_data, ensemble_weights=[0.25, 0.25, 0.25, 0.25])  # Adjust weights as needed\n\n\n# Main execution flow\nif not submission:\n    train_data = pd.read_csv('/kaggle/input/hms-harmful-brain-activity-classification/train.csv')\n    train_and_evaluate(train_data)\nelse:\n    test_data = pd.read_csv('/kaggle/input/hms-harmful-brain-activity-classification/test.csv')\n    predictions = predict(test_data)\n    \n    TARGETS = ['seizure_vote', 'lpd_vote', 'gpd_vote', 'lrda_vote', 'grda_vote', 'other_vote']\n    print(predictions.shape)  # Should be (number_of_test_samples, len(TARGETS))\n\n    if submission:\n        sub = pd.DataFrame({'eeg_id': test_data.eeg_id.values})\n        print(\"Predictions sample:\", predictions[0])  # Check the first row of predictions\n        sub[TARGETS] = predictions\n        sub.to_csv('submission.csv', index=False)\n        print('Submission shape', sub.shape)\n        print(sub.head().to_string())\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-03-04T19:24:51.816705Z","iopub.execute_input":"2024-03-04T19:24:51.817488Z","iopub.status.idle":"2024-03-04T19:24:52.38249Z","shell.execute_reply.started":"2024-03-04T19:24:51.817444Z","shell.execute_reply":"2024-03-04T19:24:52.380763Z"},"trusted":true},"execution_count":null,"outputs":[]}]}