{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":59093,"databundleVersionId":7469972,"sourceType":"competition"},{"sourceId":7392733,"sourceType":"datasetVersion","datasetId":4297749},{"sourceId":7515232,"sourceType":"datasetVersion","datasetId":4377463},{"sourceId":7637636,"sourceType":"datasetVersion","datasetId":4450985},{"sourceId":7831856,"sourceType":"datasetVersion","datasetId":4590048},{"sourceId":7843026,"sourceType":"datasetVersion","datasetId":4598289}],"dockerImageVersionId":30636,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> HMS: <span style='color:#F1A424'>WaveNet and Efficientnet</span><span style='color:#ABABAB'> [Ensemble]</span></b> \n\n***\n\n**Consider upvoting this notebook if you find it useful 🙌🏼**\n\n- [Train notebook](https://www.kaggle.com/code/alejopaullier/hms-wavenet-pytorch-train)\n\nYour goal in this competition is to detect and classify seizures and other types of harmful brain activity. You will develop a model trained on electroencephalography (EEG) signals recorded from critically ill hospital patients.\n\nIn this notebook you will learn how to predict seizures using a `WaveNet` PyTorch model. Hope you enjoy it and find it useful.\n\nI also made a **PyTorch 🔥 version** of Chris' `EfficientNetB0` notebook here:\n- [HMS | EfficientNetB0 PyTorch [Train]](https://www.kaggle.com/code/alejopaullier/hms-efficientnetb0-pytorch-train)\n- [HMS | EfficientNetB0 PyTorch [Inference]](https://www.kaggle.com/code/alejopaullier/hms-efficientnetb0-pytorch-inference)\n\n### <b><span style='color:#F1A424'>Table of Contents</span></b> <a class='anchor' id='top'></a>\n<div style=\" background-color:#3b3745; padding: 13px 13px; border-radius: 8px; color: white\">\n<li><a href=\"#import_libraries\">Import Libraries</a></li>\n<li><a href=\"#configuration\">Configuration</a></li>\n<li><a href=\"#utils\">Utils</a></li>\n<li><a href=\"#load_data\">Load Data</a></li>\n<li><a href=\"#dataset\">Dataset</a></li>\n<li><a href=\"#dataloader\">DataLoader</a></li>\n<li><a href=\"#model\">Model</a></li>\n<li><a href=\"#inference_function\">Inference Function</a></li>\n<li><a href=\"#infer\">Infer</a></li>\n<li><a href=\"#submission\">Save Submission</a></li>\n</div>\n","metadata":{}},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Import Libraries</b><a class='anchor' id='import_libraries'></a> [↑](#top) \n\n***\n\nImport all the required libraries for this notebook.","metadata":{}},{"cell_type":"code","source":"import albumentations as A\nimport gc\nimport librosa\nimport math\nimport matplotlib.pyplot as plt\nimport multiprocessing\nimport numpy as np\nimport os\nimport pandas as pd\nimport pywt\nimport random\nimport time\nimport timm\nimport torch\nimport torch.nn as nn\n\nfrom albumentations.pytorch import ToTensorV2\nfrom glob import glob\nfrom torch.utils.data import DataLoader, Dataset\nfrom tqdm import tqdm\nfrom typing import Dict, List\n\nos.environ[\"CUDA_VISIBLE_DEVICES\"] = \"0,1\"\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nprint('Using', torch.cuda.device_count(), 'GPU(s)')","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:33.043494Z","iopub.execute_input":"2024-03-14T12:48:33.043836Z","iopub.status.idle":"2024-03-14T12:48:41.558181Z","shell.execute_reply.started":"2024-03-14T12:48:33.043807Z","shell.execute_reply":"2024-03-14T12:48:41.557003Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Configuration</b><a class='anchor' id='configuration'></a> [↑](#top) \n\n***","metadata":{}},{"cell_type":"code","source":"class config:\n    BATCH_SIZE = 64\n    BATCH_SIZE_TEST = 32 \n    MODEL2 = \"tf_efficientnet_b0\" \n    NUM_WORKERS = 0 \n    PRINT_FREQ = 20\n    SEED = 20\n    VISUALIZE = False\n    \n    \nclass paths:\n    OUTPUT_DIR = \"/kaggle/working/\"\n    TEST_CSV = \"/kaggle/input/hms-harmful-brain-activity-classification/test.csv\"\n    TEST_EEGS = \"/kaggle/input/hms-harmful-brain-activity-classification/test_eegs/\"\n    TEST_SPECTROGRAMS = \"/kaggle/input/hms-harmful-brain-activity-classification/test_spectrograms/\"\n    \n    \n#model_weights1 = [x for x in glob(\"/kaggle/input/hms-wavenet/*.pth\")]\nmodel_weights1 = [x for x in glob(\"/kaggle/input/mlip15wavenet/*.pth\")]\nprint(model_weights1)\n\nmodel_weights2 = [x for x in glob(\"/kaggle/input/mlip-efnet/*.pth\")]\nprint(model_weights2)","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:41.560183Z","iopub.execute_input":"2024-03-14T12:48:41.560882Z","iopub.status.idle":"2024-03-14T12:48:41.578179Z","shell.execute_reply.started":"2024-03-14T12:48:41.560842Z","shell.execute_reply":"2024-03-14T12:48:41.577276Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Utils</b><a class='anchor' id='utils'></a> [↑](#top) \n\n***\n\nUtility functions.","metadata":{}},{"cell_type":"code","source":"USE_WAVELET = None \n\n#for efficientnet \ndef maddest(d, axis: int = None):\n    \"\"\"\n    Denoise function.\n    \"\"\"\n    return np.mean(np.absolute(d - np.mean(d, axis)), axis)\n\ndef denoise(x: np.ndarray, wavelet: str = 'haar', level: int = 1): \n    coeff = pywt.wavedec(x, wavelet, mode=\"per\") # multilevel 1D Discrete Wavelet Transform of data.\n    sigma = (1/0.6745) * maddest(coeff[-level])\n    uthresh = sigma * np.sqrt(2*np.log(len(x)))\n    coeff[1:] = (pywt.threshold(i, value=uthresh, mode='hard') for i in coeff[1:])\n    output = pywt.waverec(coeff, wavelet, mode='per')\n    return output\n\ndef spectrogram_from_eeg(parquet_path, display=False):\n    # LOAD MIDDLE 50 SECONDS OF EEG SERIES\n    eeg = pd.read_parquet(parquet_path)\n    middle = (len(eeg)-10_000)//2\n    eeg = eeg.iloc[middle:middle+10_000]\n    \n    # VARIABLE TO HOLD SPECTROGRAM\n    img = np.zeros((128,256,4),dtype='float32')\n    \n    if display:\n        plt.figure(figsize=(10,7))\n    signals = []\n    for k in range(4):\n        COLS = FEATS[k]\n        \n        for kk in range(4):\n        \n            # COMPUTE PAIR DIFFERENCES\n            x = eeg[COLS[kk]].values - eeg[COLS[kk+1]].values\n\n            # FILL NANS\n            m = np.nanmean(x)\n            if np.isnan(x).mean()<1: x = np.nan_to_num(x,nan=m)\n            else: x[:] = 0\n\n            # DENOISE\n            if USE_WAVELET:\n                x = denoise(x, wavelet=USE_WAVELET)\n            signals.append(x)\n\n            # RAW SPECTROGRAM\n            mel_spec = librosa.feature.melspectrogram(y=x, sr=200, hop_length=len(x)//256, \n                  n_fft=1024, n_mels=128, fmin=0, fmax=20, win_length=128)\n\n            # LOG TRANSFORM\n            width = (mel_spec.shape[1]//32)*32\n            mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max).astype(np.float32)[:,:width]\n\n            # STANDARDIZE TO -1 TO 1\n            mel_spec_db = (mel_spec_db+40)/40 \n            img[:,:,k] += mel_spec_db\n                \n        # AVERAGE THE 4 MONTAGE DIFFERENCES\n        img[:,:,k] /= 4.0\n        \n        if display:\n            plt.subplot(2,2,k+1)\n            plt.imshow(img[:,:,k],aspect='auto',origin='lower')\n            plt.title(f'EEG {eeg_id} - Spectrogram {NAMES[k]}')\n            \n    if display: \n        plt.show()\n        plt.figure(figsize=(10,5))\n        offset = 0\n        for k in range(4):\n            if k>0: offset -= signals[3-k].min()\n            plt.plot(range(10_000),signals[k]+offset,label=NAMES[3-k])\n            offset += signals[3-k].max()\n        plt.legend()\n        plt.title(f'EEG {eeg_id} Signals')\n        plt.show()\n        print(); print('#'*25); print()\n        \n    return img\n\ndef plot_spectrogram(spectrogram_path: str):\n    \"\"\"\n    Source: https://www.kaggle.com/code/mvvppp/hms-eda-and-domain-journey\n    Visualize spectrogram recordings from a parquet file.\n    :param spectrogram_path: path to the spectrogram parquet.\n    \"\"\"\n    sample_spect = pd.read_parquet(spectrogram_path)\n    \n    split_spect = {\n        \"LL\": sample_spect.filter(regex='^LL', axis=1),\n        \"RL\": sample_spect.filter(regex='^RL', axis=1),\n        \"RP\": sample_spect.filter(regex='^RP', axis=1),\n        \"LP\": sample_spect.filter(regex='^LP', axis=1),\n    }\n    \n    fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(15, 12))\n    axes = axes.flatten()\n    label_interval = 5\n    for i, split_name in enumerate(split_spect.keys()):\n        ax = axes[i]\n        img = ax.imshow(np.log(split_spect[split_name]).T, cmap='viridis', aspect='auto', origin='lower')\n        cbar = fig.colorbar(img, ax=ax)\n        cbar.set_label('Log(Value)')\n        ax.set_title(split_name)\n        ax.set_ylabel(\"Frequency (Hz)\")\n        ax.set_xlabel(\"Time\")\n\n        ax.set_yticks(np.arange(len(split_spect[split_name].columns)))\n        ax.set_yticklabels([column_name[3:] for column_name in split_spect[split_name].columns])\n        frequencies = [column_name[3:] for column_name in split_spect[split_name].columns]\n        ax.set_yticks(np.arange(0, len(split_spect[split_name].columns), label_interval))\n        ax.set_yticklabels(frequencies[::label_interval])\n    plt.tight_layout()\n    plt.show()\n\n#for wavenet\ndef eeg_from_parquet(parquet_path: str) -> np.ndarray:\n    \"\"\"\n    This function reads a parquet file and extracts the middle 50 seconds of readings. Then it fills NaN values\n    with the mean value (ignoring NaNs).\n    :param parquet_path: path to parquet file.\n    :param display: whether to display EEG plots or not.\n    :return data: np.array of shape  (time_steps, eeg_features) -> (10_000, 8)\n    \"\"\"\n    # === Extract middle 50 seconds ===\n    eeg = pd.read_parquet(parquet_path, columns=eeg_features)\n    rows = len(eeg)\n    offset = (rows - 10_000) // 2 # 50 * 200 = 10_000\n    eeg = eeg.iloc[offset:offset+10_000] # middle 50 seconds, has the same amount of readings to left and right\n    # === Convert to numpy ===\n    data = np.zeros((10_000, len(eeg_features))) # create placeholder of same shape with zeros\n    for index, feature in enumerate(eeg_features):\n        x = eeg[feature].values.astype('float32') # convert to float32\n        mean = np.nanmean(x) # arithmetic mean along the specified axis, ignoring NaNs\n        nan_percentage = np.isnan(x).mean() # percentage of NaN values in feature\n        # === Fill nan values ===\n        if nan_percentage < 1: # if some values are nan, but not all\n            x = np.nan_to_num(x, nan=mean)\n        else: # if all values are nan\n            x[:] = 0\n        data[:, index] = x\n   \n    return data\n\n\ndef seed_everything(seed: int):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed) \n    \n    \ndef sep():\n    print(\"-\"*100)\n\nNAMES = ['LL','LP','RP','RR']\n\nFEATS = [['Fp1','F7','T3','T5','O1'],\n         ['Fp1','F3','C3','P3','O1'],\n         ['Fp2','F8','T4','T6','O2'],\n         ['Fp2','F4','C4','P4','O2']]\n    \ntarget_preds = [x + \"_pred\" for x in ['seizure_vote', 'lpd_vote', 'gpd_vote', 'lrda_vote', 'grda_vote', 'other_vote']]\nlabel_to_num = {'Seizure': 0, 'LPD': 1, 'GPD': 2, 'LRDA': 3, 'GRDA': 4, 'Other':5}\nnum_to_label = {v: k for k, v in label_to_num.items()}\nseed_everything(config.SEED)\n","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2024-03-14T12:48:41.579417Z","iopub.execute_input":"2024-03-14T12:48:41.579727Z","iopub.status.idle":"2024-03-14T12:48:41.617547Z","shell.execute_reply.started":"2024-03-14T12:48:41.579696Z","shell.execute_reply":"2024-03-14T12:48:41.616591Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Load Data</b><a class='anchor' id='load_data'></a> [↑](#top) \n\n***\n\nLoad the competition's data.","metadata":{}},{"cell_type":"code","source":"test_df = pd.read_csv(paths.TEST_CSV)\nprint(f\"Test dataframe shape is: {test_df.shape}\")\ntest_df.head()","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:41.619921Z","iopub.execute_input":"2024-03-14T12:48:41.620208Z","iopub.status.idle":"2024-03-14T12:48:41.650742Z","shell.execute_reply.started":"2024-03-14T12:48:41.620183Z","shell.execute_reply":"2024-03-14T12:48:41.649741Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### <b><span style='color:#F1A424'>Read one EEG parquet</span></b>\n\nAll of the EEG data (for both train and test) was collected at a frequency of 200 samples per second,\n\nEach EEG parquet results in a dataframe with `seconds` rows and 20 columns.\n\n- EEG features are: `['Fp1', 'F3', 'C3', 'P3', 'F7', 'T3', 'T5', 'O1', 'Fz', 'Cz', 'Pz', 'Fp2', 'F4', 'C4', 'P4', 'F8', 'T4', 'T6', 'O2', 'EKG']`\n- We will use these features: `['Fp1','T3','C3','O1','Fp2','C4','T4','O2']`\n\n","metadata":{}},{"cell_type":"code","source":"eeg_parquet_paths = glob(paths.TEST_EEGS + \"*.parquet\")\neeg_df = pd.read_parquet(eeg_parquet_paths[0])\neeg_features = eeg_df.columns\nprint(f'There are {len(eeg_features)} raw eeg features')\nprint(list(eeg_features))\neeg_features = ['Fp1','T3','C3','O1','Fp2','C4','T4','O2']\nfeature_to_index = {x:y for x,y in zip(eeg_features, range(len(eeg_features)))}","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:41.651911Z","iopub.execute_input":"2024-03-14T12:48:41.652221Z","iopub.status.idle":"2024-03-14T12:48:41.893214Z","shell.execute_reply.started":"2024-03-14T12:48:41.652194Z","shell.execute_reply":"2024-03-14T12:48:41.892089Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### <b><span style='color:#F1A424'>Read all EEG parquets</span></b>","metadata":{}},{"cell_type":"code","source":"CREATE_EEGS = False\nall_eegs_1 = {}\nvisualize = 1\neeg_paths_1 = glob(paths.TEST_EEGS + \"*.parquet\")\neeg_ids_1 = test_df.eeg_id.unique()\n\nfor i, eeg_id in tqdm(enumerate(eeg_ids_1)):  \n    # Save EEG to Python dictionary of numpy arrays\n    eeg_path_1 = paths.TEST_EEGS + str(eeg_id) + \".parquet\"\n    data = eeg_from_parquet(eeg_path_1)  \n    all_eegs_1[eeg_id] = data","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:41.894367Z","iopub.execute_input":"2024-03-14T12:48:41.894720Z","iopub.status.idle":"2024-03-14T12:48:41.921269Z","shell.execute_reply.started":"2024-03-14T12:48:41.894664Z","shell.execute_reply":"2024-03-14T12:48:41.919413Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### <b><span style='color:#F1A424'>Read Spectograms</span></b>","metadata":{}},{"cell_type":"code","source":"paths_spectrograms = glob(paths.TEST_SPECTROGRAMS + \"*.parquet\")\nprint(f'There are {len(paths_spectrograms)} spectrogram parquets')\nall_spectrograms = {}\n\nfor file_path in tqdm(paths_spectrograms):\n    aux = pd.read_parquet(file_path)\n    name = int(file_path.split(\"/\")[-1].split('.')[0])\n    all_spectrograms[name] = aux.iloc[:,1:].values\n    del aux\n    \nif config.VISUALIZE:\n    idx = np.random.randint(0, len(paths_spectrograms))\n    spectrogram_path = paths_spectrograms[idx]\n    plot_spectrogram(spectrogram_path)","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:41.923168Z","iopub.execute_input":"2024-03-14T12:48:41.923940Z","iopub.status.idle":"2024-03-14T12:48:41.989374Z","shell.execute_reply.started":"2024-03-14T12:48:41.923897Z","shell.execute_reply":"2024-03-14T12:48:41.988342Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### <b><span style='color:#F1A424'>Read EEG Spectrograms</span></b>\n\nThe resulting `all_eegs` dictionary contains `eeg_id` as keys (`int` keys) and the values are the eeg sequences (as 3-dimensional `np.array`) of shape `(128, 256, 4)`.\n","metadata":{}},{"cell_type":"code","source":"paths_eegs = glob(paths.TEST_EEGS + \"*.parquet\")\nprint(f'There are {len(paths_eegs)} EEG spectrograms')\nall_eegs = {}\ncounter = 0\n\nfor file_path in tqdm(paths_eegs):\n    eeg_id = file_path.split(\"/\")[-1].split(\".\")[0]\n    eeg_spectrogram = spectrogram_from_eeg(file_path, counter < 1)\n    all_eegs[int(eeg_id)] = eeg_spectrogram\n    counter += 1","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:41.990507Z","iopub.execute_input":"2024-03-14T12:48:41.990819Z","iopub.status.idle":"2024-03-14T12:48:53.844038Z","shell.execute_reply.started":"2024-03-14T12:48:41.990793Z","shell.execute_reply":"2024-03-14T12:48:53.842995Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Butter Low-Pass Filter</b><a class='anchor' id='filter'></a> [↑](#top) \n\n***\n\n- [scipy.signal.butter()][1]\n- [scipy.signal.lfilter()][2]\n\n[1]: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.butter.html#scipy.signal.butter\n[2]: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.lfilter.html#scipy.signal.lfilter","metadata":{}},{"cell_type":"code","source":"from scipy.signal import butter, lfilter\n\ndef butter_lowpass_filter(data, cutoff_freq: int = 20, sampling_rate: int = 200, order: int = 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","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:53.845511Z","iopub.execute_input":"2024-03-14T12:48:53.846022Z","iopub.status.idle":"2024-03-14T12:48:53.852040Z","shell.execute_reply.started":"2024-03-14T12:48:53.845991Z","shell.execute_reply":"2024-03-14T12:48:53.851044Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Dataset for wavenet</b><a class='anchor' id='dataset'></a> [↑](#top) \n\n***\n\nCreate a custom `Dataset` to load data.\n\n- [How to Convert EEG to Spectrograms][1]: to understand the feature engineering performed in the generation method.\n- [How To Create Spectrogram From Eeg?][2]: original post on how to create Spectrograms from EEGs.\n- [Introduction to EEG][3]: short video to better understand EEGs.\n\n[1]: https://www.kaggle.com/competitions/hms-harmful-brain-activity-classification/discussion/469760\n[2]: https://www.kaggle.com/competitions/hms-harmful-brain-activity-classification/discussion/467877\n[3]: https://www.youtube.com/watch?v=XMizSSOejg0","metadata":{}},{"cell_type":"code","source":"class Dataset_wavenet(Dataset):\n    def __init__(\n        self, df: pd.DataFrame, config,\n        eegs_1: Dict[int, np.ndarray] = all_eegs_1, downsample: int = 5\n    ): \n        self.df = df\n        self.config = config\n        self.batch_size = self.config.BATCH_SIZE_TEST\n        self.eegs = eegs_1\n        self.downsample = downsample\n        \n    def __len__(self):\n        \"\"\"\n        Length of dataset.\n        \"\"\"\n        return len(self.df)\n        \n    def __getitem__(self, index):\n        \"\"\"\n        Get one item.\n        \"\"\"\n        X = self.__data_generation(index)\n        X = X[::self.downsample, :]\n        output = {\n            \"X\": torch.tensor(X, dtype=torch.float32)\n        }\n        return output\n                        \n    def __data_generation(self, index):\n        row = self.df.iloc[index]\n        X = np.zeros((10_000, 8), dtype='float32')\n        data = self.eegs[row.eeg_id]\n        \n        # === Feature engineering ===\n        X[:,0] = data[:,feature_to_index['Fp1']] - data[:,feature_to_index['T3']]\n        X[:,1] = data[:,feature_to_index['T3']] - data[:,feature_to_index['O1']]\n\n        X[:,2] = data[:,feature_to_index['Fp1']] - data[:,feature_to_index['C3']]\n        X[:,3] = data[:,feature_to_index['C3']] - data[:,feature_to_index['O1']]\n\n        X[:,4] = data[:,feature_to_index['Fp2']] - data[:,feature_to_index['C4']]\n        X[:,5] = data[:,feature_to_index['C4']] - data[:,feature_to_index['O2']]\n\n        X[:,6] = data[:,feature_to_index['Fp2']] - data[:,feature_to_index['T4']]\n        X[:,7] = data[:,feature_to_index['T4']] - data[:,feature_to_index['O2']]\n\n        # === Standarize ===\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 = butter_lowpass_filter(X)\n            \n        return X","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:53.855595Z","iopub.execute_input":"2024-03-14T12:48:53.855947Z","iopub.status.idle":"2024-03-14T12:48:53.870577Z","shell.execute_reply.started":"2024-03-14T12:48:53.855919Z","shell.execute_reply":"2024-03-14T12:48:53.869896Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Dataset for efficientnet</b><a class='anchor' id='dataset'></a> [↑](#top) \n\n***\n\nCreate a custom `Dataset` to load data.\n\nThis dataloader outputs 4 spectrogram images as a 4 channel image of size 128x256x4 per train sample. This notebook version is not using data augmention but the code is available below to experiment with `albumentations` data augmention. Just add `augment = True` when creating the train data loader. And consider adding new transformations to the augment function below.\n\nA more detailed [explanation][1] of the `r` parameter inside the `__data_generation()` method.\n\nOur dataloader outputs both Kaggle spectrograms and EEG spectrogams as 8 channel image of size `(128, 256, 8)`\n\n[1]: https://www.kaggle.com/code/cdeotte/efficientnetb0-starter-lb-0-43/comments#2617811","metadata":{}},{"cell_type":"code","source":"class Dataset_efficientnet(Dataset):\n    def __init__(\n        self, df: pd.DataFrame, config,\n        augment: bool = False, mode: str = 'train',\n        specs: Dict[int, np.ndarray] = all_spectrograms,\n        eeg_specs: Dict[int, np.ndarray] = all_eegs\n    ): \n        self.df = df\n        self.config = config\n        self.batch_size = self.config.BATCH_SIZE\n        self.augment = augment\n        self.mode = mode\n        self.spectrograms = all_spectrograms\n        self.eeg_spectrograms = all_eegs\n        \n    def __len__(self):\n        \"\"\"\n        Denotes the number of batches per epoch.\n        \"\"\"\n        return len(self.df)\n        \n    def __getitem__(self, index):\n        \"\"\"\n        Generate one batch of data.\n        \"\"\"\n        X, y = self.__data_generation(index)\n        if self.augment:\n            X = self.__transform(X)\n        return torch.tensor(X, dtype=torch.float32), torch.tensor(y, dtype=torch.float32)\n                        \n    def __data_generation(self, index):\n        \"\"\"\n        Generates data containing batch_size samples.\n        \"\"\"\n        X = np.zeros((128, 256, 8), dtype='float32')\n        y = np.zeros(6, dtype='float32')\n        img = np.ones((128,256), dtype='float32')\n        row = self.df.iloc[index]\n        if self.mode=='test': \n            r = 0\n        else: \n            r = int((row['min'] + row['max']) // 4)\n            \n        for region in range(4):\n            img = self.spectrograms[row.spectrogram_id][r:r+300, region*100:(region+1)*100].T\n            \n            # Log transform spectrogram\n            img = np.clip(img, np.exp(-4), np.exp(8))\n            img = np.log(img)\n\n            # Standarize per image\n            ep = 1e-6\n            mu = np.nanmean(img.flatten())\n            std = np.nanstd(img.flatten())\n            img = (img-mu)/(std+ep)\n            img = np.nan_to_num(img, nan=0.0)\n            X[14:-14, :, region] = img[:, 22:-22] / 2.0\n            img = self.eeg_spectrograms[row.eeg_id]\n            X[:, :, 4:] = img\n                \n            if self.mode != 'test':\n                y = row[label_cols].values.astype(np.float32)\n            \n        return X, y\n    \n    def __transform(self, img):\n        transforms = A.Compose([\n            A.HorizontalFlip(p=0.5),\n        ])\n        return transforms(image=img)['image']","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:53.871463Z","iopub.execute_input":"2024-03-14T12:48:53.871745Z","iopub.status.idle":"2024-03-14T12:48:53.887977Z","shell.execute_reply.started":"2024-03-14T12:48:53.871722Z","shell.execute_reply":"2024-03-14T12:48:53.887005Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> DataLoader</b><a class='anchor' id='dataloader'></a> [↑](#top) \n\n***","metadata":{}},{"cell_type":"code","source":"test_dataset_wavenet = Dataset_wavenet(test_df, config)\ntest_loader_wavenet = DataLoader(\n    test_dataset_wavenet,\n    batch_size=config.BATCH_SIZE_TEST,\n    shuffle=False,\n    num_workers=config.NUM_WORKERS,\n    pin_memory=True,\n    drop_last=False\n)\noutput_wavenet = test_dataset_wavenet[0]\nX_wavenet = output_wavenet[\"X\"]\nprint(f\"X shape wavenet: {X_wavenet.shape}\")\n\n##############\n\ntest_dataset_efficientnet = Dataset_efficientnet(test_df, config, mode=\"test\")\ntest_loader_efficientnet = DataLoader(\n    test_dataset_efficientnet,\n    batch_size=config.BATCH_SIZE,\n    shuffle=False,\n    num_workers=config.NUM_WORKERS, \n    pin_memory=True, \n    drop_last=False\n)\nX_efficientnet, y_efficientnet = test_dataset_efficientnet[0]\nprint(f\"X shape efficientnet: {X_efficientnet.shape}\")\nprint(f\"y shape efficientnet: {y_efficientnet.shape}\")","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:53.889211Z","iopub.execute_input":"2024-03-14T12:48:53.889446Z","iopub.status.idle":"2024-03-14T12:48:53.943362Z","shell.execute_reply.started":"2024-03-14T12:48:53.889425Z","shell.execute_reply":"2024-03-14T12:48:53.942479Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Model for wavenet</b><a class='anchor' id='model'></a> [↑](#top) \n\n***\n\n<center><img width = 800 src=\"https://raw.githubusercontent.com/cdeotte/Kaggle_Images/main/Jan-2024/wave-model.png\"></center>","metadata":{}},{"cell_type":"code","source":"class Wave_Block(nn.Module):\n    def __init__(self, in_channels: int, out_channels: int, dilation_rates: int, kernel_size: int = 3):\n        \"\"\"\n        WaveNet building block.\n        :param in_channels: number of input channels.\n        :param out_channels: number of output channels.\n        :param dilation_rates: how many levels of dilations are used.\n        :param kernel_size: size of the convolving kernel.\n        \"\"\"\n        super(Wave_Block, self).__init__()\n        self.num_rates = dilation_rates\n        self.convs = nn.ModuleList()\n        self.filter_convs = nn.ModuleList()\n        self.gate_convs = nn.ModuleList()\n        self.convs.append(nn.Conv1d(in_channels, out_channels, kernel_size=1, bias=True))\n        \n        dilation_rates = [2 ** i for i in range(dilation_rates)]\n        for dilation_rate in dilation_rates:\n            self.filter_convs.append(\n                nn.Conv1d(out_channels, out_channels, kernel_size=kernel_size,\n                          padding=int((dilation_rate*(kernel_size-1))/2), dilation=dilation_rate))\n            self.gate_convs.append(\n                nn.Conv1d(out_channels, out_channels, kernel_size=kernel_size,\n                          padding=int((dilation_rate*(kernel_size-1))/2), dilation=dilation_rate))\n            self.convs.append(nn.Conv1d(out_channels, out_channels, kernel_size=1, bias=True))\n        \n        for i in range(len(self.convs)):\n            nn.init.xavier_uniform_(self.convs[i].weight, gain=nn.init.calculate_gain('relu'))\n            nn.init.zeros_(self.convs[i].bias)\n\n        for i in range(len(self.filter_convs)):\n            nn.init.xavier_uniform_(self.filter_convs[i].weight, gain=nn.init.calculate_gain('relu'))\n            nn.init.zeros_(self.filter_convs[i].bias)\n\n        for i in range(len(self.gate_convs)):\n            nn.init.xavier_uniform_(self.gate_convs[i].weight, gain=nn.init.calculate_gain('relu'))\n            nn.init.zeros_(self.gate_convs[i].bias)\n\n    def forward(self, x):\n        x = self.convs[0](x)\n        res = x\n        for i in range(self.num_rates):\n            tanh_out = torch.tanh(self.filter_convs[i](x))\n            sigmoid_out = torch.sigmoid(self.gate_convs[i](x))\n            x = tanh_out * sigmoid_out\n            x = self.convs[i + 1](x) \n            res = res + x\n        return res\n    \nclass WaveNet(nn.Module):\n    def __init__(self, input_channels: int = 1, kernel_size: int = 3):\n        super(WaveNet, self).__init__()\n        self.model = nn.Sequential(\n                Wave_Block(input_channels, 8, 12, kernel_size),\n                Wave_Block(8, 16, 8, kernel_size),\n                Wave_Block(16, 32, 4, kernel_size),\n                Wave_Block(32, 64, 1, kernel_size) \n        )\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        x = x.permute(0, 2, 1) \n        output = self.model(x)\n        return output\n\n\nclass Model_wavenet(nn.Module):\n    def __init__(self):\n        super(Model_wavenet, self).__init__()\n        self.model = WaveNet()\n        self.global_avg_pooling = nn.AdaptiveAvgPool1d(1)\n        self.dropout = 0.0\n        self.head = nn.Sequential(\n            nn.Linear(256, 64),\n            nn.BatchNorm1d(64),\n            nn.ReLU(),\n            nn.Dropout(self.dropout),\n            nn.Linear(64, 6)\n        )\n        \n    def forward(self, x: torch.Tensor):\n        \"\"\"\n        Forwward pass.\n        \"\"\"\n        x1 = self.model(x[:, :, 0:1])\n        x1 = self.global_avg_pooling(x1)\n        x1 = x1.squeeze(dim=2)\n        x2 = self.model(x[:, :, 1:2])\n        x2 = self.global_avg_pooling(x2)\n        x2 = x2.squeeze(dim=2)\n        z1 = torch.mean(torch.stack([x1, x2]), dim=0)\n\n        x1 = self.model(x[:, :, 2:3])\n        x1 = self.global_avg_pooling(x1)\n        x1 = x1.squeeze(dim=2)\n        x2 = self.model(x[:, :, 3:4])\n        x2 = self.global_avg_pooling(x2)\n        x2 = x2.squeeze(dim=2)\n        z2 = torch.mean(torch.stack([x1, x2]), dim=0)\n        \n        x1 = self.model(x[:, :, 4:5])\n        x1 = self.global_avg_pooling(x1)\n        x1 = x1.squeeze(dim=2)\n        x2 = self.model(x[:, :, 5:6])\n        x2 = self.global_avg_pooling(x2)\n        x2 = x2.squeeze(dim=2)\n        z3 = torch.mean(torch.stack([x1, x2]), dim=0)\n        \n        x1 = self.model(x[:, :, 6:7])\n        x1 = self.global_avg_pooling(x1)\n        x1 = x1.squeeze(dim=2)\n        x2 = self.model(x[:, :, 7:8])\n        x2 = self.global_avg_pooling(x2)\n        x2 = x2.squeeze(dim=2)\n        z4 = torch.mean(torch.stack([x1, x2]), dim=0)\n        \n        y = torch.cat([z1, z2, z3, z4], dim=1)\n        y = self.head(y)\n        \n        return y\n\nmodel_wavenet = Model_wavenet()\ntotal_params_wavenet = sum(p.numel() for p in model_wavenet.parameters())\nprint(f\"Total number of parameters: {total_params_wavenet}\")","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:53.945836Z","iopub.execute_input":"2024-03-14T12:48:53.946229Z","iopub.status.idle":"2024-03-14T12:48:54.017211Z","shell.execute_reply.started":"2024-03-14T12:48:53.946192Z","shell.execute_reply":"2024-03-14T12:48:54.016375Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Model for efficientnet</b><a class='anchor' id='model'></a> [↑](#top) \n\n***\n\nWe will be using the [timm](https://github.com/huggingface/pytorch-image-models) library for our models.\n\nOur models receives both Kaggle spectrograms and EEG spectrograms from our data loader. We then reshape these 8 spectrograms into 1 large flat image and feed it into EfficientNet.","metadata":{}},{"cell_type":"code","source":"class Model_efficientnet(nn.Module):\n    def __init__(self, config, num_classes: int = 6):\n        super(Model_efficientnet, self).__init__()\n        self.USE_KAGGLE_SPECTROGRAMS = True\n        self.USE_EEG_SPECTROGRAMS = True\n        self.model = timm.create_model(\n            config.MODEL2,\n            pretrained=False\n        )\n        self.features = nn.Sequential(*list(self.model.children())[:-2])\n        self.custom_layers = nn.Sequential(\n            nn.AdaptiveAvgPool2d(1),\n            nn.Flatten(),\n            nn.Linear(self.model.num_features, num_classes)\n        )\n\n    def __reshape_input(self, x):\n        \"\"\"\n        Reshapes input (128, 256, 8) -> (512, 512, 3) monotone image.\n        \"\"\" \n        # === Get spectrograms ===\n        spectrograms = [x[:, :, :, i:i+1] for i in range(4)]\n        spectrograms = torch.cat(spectrograms, dim=1)\n        \n        # === Get EEG spectrograms ===\n        eegs = [x[:, :, :, i:i+1] for i in range(4,8)]\n        eegs = torch.cat(eegs, dim=1)\n        \n        # === Reshape (512,512,3) ===\n        if self.USE_KAGGLE_SPECTROGRAMS & self.USE_EEG_SPECTROGRAMS:\n            x = torch.cat([spectrograms, eegs], dim=2)\n        elif self.USE_EEG_SPECTROGRAMS:\n            x = eegs\n        else:\n            x = spectrograms\n            \n        x = torch.cat([x,x,x], dim=3)\n        x = x.permute(0, 3, 1, 2)\n        return x\n    \n    def forward(self, x):\n        x = self.__reshape_input(x)\n        x = self.features(x)\n        x = self.custom_layers(x)\n        return x","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:54.018198Z","iopub.execute_input":"2024-03-14T12:48:54.018472Z","iopub.status.idle":"2024-03-14T12:48:54.029604Z","shell.execute_reply.started":"2024-03-14T12:48:54.018448Z","shell.execute_reply":"2024-03-14T12:48:54.028671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Inference Function</b><a class='anchor' id='inference_function'></a> [↑](#top) \n\n***","metadata":{}},{"cell_type":"code","source":"def inference_function_wavenet(test_loader, model, device):\n    model.eval() # set model in evaluation mode\n    softmax = nn.Softmax(dim=1)\n    prediction_dict = {}\n    preds = []\n    with tqdm(test_loader, unit=\"test_batch\", desc='Inference') as tqdm_test_loader:\n        for step, batch in enumerate(tqdm_test_loader):\n            X = batch.pop(\"X\").to(device) # send inputs to `device`\n            batch_size = X.size(0)\n            with torch.no_grad():\n                y_preds = model(X) # forward propagation pass\n            y_preds = softmax(y_preds)\n            preds.append(y_preds.to('cpu').numpy()) # save predictions\n                \n    prediction_dict[\"predictions\"] = np.concatenate(preds) # np.array() of shape (fold_size, target_cols)\n    return prediction_dict\n\n#######\n\ndef inference_function_efficientnet(test_loader, model, device):\n    model.eval()\n    softmax = nn.Softmax(dim=1)\n    prediction_dict = {}\n    preds = []\n    with tqdm(test_loader, unit=\"test_batch\", desc='Inference') as tqdm_test_loader:\n        for step, (X, y) in enumerate(tqdm_test_loader):\n            X = X.to(device)\n            y = y.to(device)\n            batch_size = y.size(0)\n            with torch.no_grad():\n                y_preds = model(X)\n            y_preds = softmax(y_preds)\n            preds.append(y_preds.to('cpu').numpy()) \n                \n    prediction_dict[\"predictions\"] = np.concatenate(preds) \n    return prediction_dict","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:54.030567Z","iopub.execute_input":"2024-03-14T12:48:54.030864Z","iopub.status.idle":"2024-03-14T12:48:54.042790Z","shell.execute_reply.started":"2024-03-14T12:48:54.030840Z","shell.execute_reply":"2024-03-14T12:48:54.041875Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Inference</b><a class='anchor' id='infer'></a> [↑](#top) \n\n***","metadata":{}},{"cell_type":"code","source":"predictions1 = []\n\nfor model_weight in model_weights1:\n    test_dataset_wavenet = Dataset_wavenet(test_df, config)\n    train_loader = DataLoader(\n        test_dataset_wavenet,\n        batch_size=config.BATCH_SIZE_TEST,\n        shuffle=False,\n        num_workers=config.NUM_WORKERS,\n        pin_memory=True,\n        drop_last=False\n    )\n    model = Model_wavenet()\n    checkpoint = torch.load(model_weight)\n    model.load_state_dict(checkpoint[\"model\"])\n    model.to(device)\n    prediction_dict1 = inference_function_wavenet(test_loader_wavenet, model, device)\n    predictions1.append(prediction_dict1[\"predictions\"])\n    torch.cuda.empty_cache()\n    gc.collect()\n    \npredictions1 = np.array(predictions1)\npredictions1 = np.mean(predictions1, axis=0)\n\n##############\n\npredictions2 = []\n\nfor model_weight in model_weights2:\n    test_dataset_efficientnet = Dataset_efficientnet(test_df, config, mode=\"test\", augment=False)\n    train_loader_efficientnet = DataLoader(\n        test_dataset_efficientnet,\n        batch_size=config.BATCH_SIZE,\n        shuffle=False,\n        num_workers=config.NUM_WORKERS,\n        pin_memory=True, drop_last=False\n    )\n    model = Model_efficientnet(config)\n    checkpoint = torch.load(model_weight)\n    model.load_state_dict(checkpoint[\"model\"])\n    model.to(device)\n    prediction_dict = inference_function_efficientnet(test_loader_efficientnet, model, device)\n    predictions2.append(prediction_dict[\"predictions\"])\n    torch.cuda.empty_cache()\n    gc.collect()\n    \npredictions2 = np.array(predictions2)\npredictions2 = np.mean(predictions2, axis=0)","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:48:54.043904Z","iopub.execute_input":"2024-03-14T12:48:54.044169Z","iopub.status.idle":"2024-03-14T12:49:00.660928Z","shell.execute_reply.started":"2024-03-14T12:48:54.044147Z","shell.execute_reply":"2024-03-14T12:49:00.659893Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Submission</b><a class='anchor' id='submission'></a> [↑](#top) \n\n***","metadata":{"execution":{"iopub.status.busy":"2024-02-15T22:27:03.071166Z","iopub.execute_input":"2024-02-15T22:27:03.071564Z","iopub.status.idle":"2024-02-15T22:27:03.084649Z","shell.execute_reply.started":"2024-02-15T22:27:03.071534Z","shell.execute_reply":"2024-02-15T22:27:03.083623Z"}}},{"cell_type":"code","source":"difference=0\nweight_wavenet=0\nweight_efficientnet=1\nTARGETS = ['seizure_vote', 'lpd_vote', 'gpd_vote', 'lrda_vote', 'grda_vote', 'other_vote']\n\nsub1 = pd.DataFrame({'eeg_id': test_df.eeg_id.values})\nsub1[TARGETS] = predictions1\nsub1.to_csv('submission_wavenet.csv',index=False)\nprint(f'Submission shape wavenet: {sub1.shape}')\nprint(sub1.head())\n\n\n############\n\nsub2 = pd.DataFrame({'eeg_id': test_df.eeg_id.values})\nsub2[TARGETS] = predictions2\nsub2.to_csv('submission_efficientnet.csv',index=False)\nprint(f'Submissionn shape efficientnet: {sub2.shape}')\nprint(sub2.head())","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:49:00.662172Z","iopub.execute_input":"2024-03-14T12:49:00.662470Z","iopub.status.idle":"2024-03-14T12:49:00.688082Z","shell.execute_reply.started":"2024-03-14T12:49:00.662445Z","shell.execute_reply":"2024-03-14T12:49:00.687104Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1A424'>|</span> Ensemble </b><a class='anchor' id='infer'></a> [↑](#top) \n\n***","metadata":{}},{"cell_type":"code","source":"submission=pd.read_csv(\"/kaggle/input/hms-harmful-brain-activity-classification/sample_submission.csv\")\nfor i in range(len(TARGETS)):\n    submission[f'{TARGETS[i]}']=(predictions1[:,i]*weight_wavenet + predictions2[:, i]*weight_efficientnet)\nsubmission.to_csv(\"submission.csv\",index=None)\nprint(f'Submissionn shape combined: {submission.shape}')\nprint(submission.head())","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:49:00.689431Z","iopub.execute_input":"2024-03-14T12:49:00.690156Z","iopub.status.idle":"2024-03-14T12:49:00.705664Z","shell.execute_reply.started":"2024-03-14T12:49:00.690117Z","shell.execute_reply":"2024-03-14T12:49:00.704724Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# SANITY CHECK TO CONFIRM PREDICTIONS SUM TO ONE\nprint(sub1.iloc[:,-6:].sum(axis=1))\n\n\n# SANITY CHECK TO CONFIRM PREDICTIONS SUM TO ONE\nprint(sub2.iloc[:,-6:].sum(axis=1))\n\n# SANITY CHECK TO CONFIRM PREDICTIONS SUM TO ONE\nprint(submission.iloc[:,-6:].sum(axis=1))","metadata":{"execution":{"iopub.status.busy":"2024-03-14T12:49:00.707144Z","iopub.execute_input":"2024-03-14T12:49:00.707961Z","iopub.status.idle":"2024-03-14T12:49:00.721456Z","shell.execute_reply.started":"2024-03-14T12:49:00.707923Z","shell.execute_reply":"2024-03-14T12:49:00.720369Z"},"trusted":true},"execution_count":null,"outputs":[]}]}