{"metadata":{"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":59093,"databundleVersionId":7469972,"sourceType":"competition"},{"sourceId":7526248,"sourceType":"datasetVersion","datasetId":4308295},{"sourceId":6127,"sourceType":"modelInstanceVersion","modelInstanceId":4598}],"dockerImageVersionId":30648,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"kernelspec":{"display_name":"Python 3","language":"python","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"},"papermill":{"default_parameters":{},"duration":3846.080383,"end_time":"2024-01-14T04:20:19.064569","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2024-01-14T03:16:12.984186","version":"2.4.0"},"widgets":{"application/vnd.jupyter.widget-state+json":{"state":{"08983a9c6aff42578980f4f7113c3ee2":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_4411aefc021d46d0ada7b645eb53ec48","placeholder":"​","style":"IPY_MODEL_09a10a8cf9334c51857397ed50398c8e","value":"Searching best thr : 100%"}},"09a10a8cf9334c51857397ed50398c8e":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"1f3989a0c01248328e16875075e9d1c4":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_08983a9c6aff42578980f4f7113c3ee2","IPY_MODEL_22cfcc0a7cc6455fbf3bb7c788c8a4e1","IPY_MODEL_c8392e8075224e3b8a020a16c1a08447"],"layout":"IPY_MODEL_6cec9a2c2fac450d87248aed8dd62f86"}},"22cfcc0a7cc6455fbf3bb7c788c8a4e1":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"","description_tooltip":null,"layout":"IPY_MODEL_dffe80502d954bdea0bbb6353dbf5515","max":20,"min":0,"orientation":"horizontal","style":"IPY_MODEL_7ce1b34a4f864a42a6619eec82311eb0","value":20}},"4411aefc021d46d0ada7b645eb53ec48":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"6cec9a2c2fac450d87248aed8dd62f86":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"7ce1b34a4f864a42a6619eec82311eb0":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":""}},"83fe40a0b8f047cc8602206909d42361":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"9384babdb7054d55aecdf3e989ddc926":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}},"c8392e8075224e3b8a020a16c1a08447":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_83fe40a0b8f047cc8602206909d42361","placeholder":"​","style":"IPY_MODEL_9384babdb7054d55aecdf3e989ddc926","value":" 20/20 [04:34&lt;00:00, 12.66s/it]"}},"dffe80502d954bdea0bbb6353dbf5515":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}}},"version_major":2,"version_minor":0}}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Harmful Brain Activity Classification - Tutorial Supplement\nThis notebook has been adapted from the [HMS-HBAC: KerasCV Starter Notebook Version 3](https://www.kaggle.com/code/awsaf49/hms-hbac-kerascv-starter-notebook), adding more information on healthcare implications of machine learning design decisions from this analysis, to supplement a separate presentation more generally about deep learning for harmful brain activity classification from EEG data. ","metadata":{}},{"cell_type":"markdown","source":"# HMS - Harmful Brain Activity Classification with [KerasCV](https://github.com/keras-team/keras-cv) and [Keras](https://github.com/keras-team/keras)\n\n> The objective of this competition is to classify seizures and other patterns of harmful brain activity in critically ill patients\n\nThis notebook guides you through the process of training and inferring a Deep Learning model, specifically EfficientNetV2, using KerasCV on the competition dataset. Specificaclly, this notebook uses spectrogram of the eeg data to classify the patterns.\n\nFun fact: This notebook is backend-agnostic, supporting TensorFlow, PyTorch, and JAX. Utilizing KerasCV and Keras allows us to choose our preferred backend. Explore more details on [Keras](https://keras.io/keras_core/announcement/).\n\nIn this notebook, you will learn:\n\n* Loading the data efficiently using [`tf.data`](https://www.tensorflow.org/guide/data).\n* Creating the model using KerasCV presets.\n* Training the model.\n* Inference and Submission on test data.\n\n**Note**: For a more in-depth understanding of KerasCV, refer to the [KerasCV guides](https://keras.io/guides/keras_cv/).","metadata":{}},{"cell_type":"markdown","source":"# 🛠 | Install Libraries  \n\nSince internet access is **disabled** during inference, we cannot install libraries in the usual `!pip install <lib_name>` manner. Instead, we need to install libraries from local files. In the following cell, we will install libraries from our local files. The installation code stays very similar - we just use the `filepath` instead of the `filename` of the library. So now the code is `!pip install <local_filepath>`. \n\n> The `filepath` of these local libraries look quite complicated, but don't be intimidated! Also `--no-deps` argument ensures that we are not installing any additional libraries.","metadata":{"papermill":{"duration":0.011416,"end_time":"2024-01-14T03:16:16.470167","exception":false,"start_time":"2024-01-14T03:16:16.458751","status":"completed"},"tags":[]}},{"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":{"execution":{"iopub.status.busy":"2024-03-28T08:31:52.493161Z","iopub.execute_input":"2024-03-28T08:31:52.493505Z","iopub.status.idle":"2024-03-28T08:33:49.088557Z","shell.execute_reply.started":"2024-03-28T08:31:52.493477Z","shell.execute_reply":"2024-03-28T08:33:49.087239Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 📚 | Import Libraries ","metadata":{"papermill":{"duration":0.010878,"end_time":"2024-01-14T03:17:49.510159","exception":false,"start_time":"2024-01-14T03:17:49.499281","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import os\nos.environ[\"KERAS_BACKEND\"] = \"jax\" # you can also use tensorflow or torch\n\nimport keras_cv\nimport keras\nfrom keras import ops\nimport tensorflow as tf\n\nimport cv2\nimport pandas as pd\nimport numpy as np\nfrom glob import glob\nfrom tqdm.notebook import tqdm\nimport joblib\n\nimport matplotlib.pyplot as plt ","metadata":{"papermill":{"duration":10.671979,"end_time":"2024-01-14T03:18:00.193134","exception":false,"start_time":"2024-01-14T03:17:49.521155","status":"completed"},"tags":[],"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-03-28T08:35:45.586706Z","iopub.execute_input":"2024-03-28T08:35:45.587800Z","iopub.status.idle":"2024-03-28T08:35:55.577751Z","shell.execute_reply.started":"2024-03-28T08:35:45.587762Z","shell.execute_reply":"2024-03-28T08:35:55.576832Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Library Versions","metadata":{"papermill":{"duration":0.010958,"end_time":"2024-01-14T03:18:00.215704","exception":false,"start_time":"2024-01-14T03:18:00.204746","status":"completed"},"tags":[]}},{"cell_type":"code","source":"print(\"TensorFlow:\", tf.__version__)\nprint(\"Keras:\", keras.__version__)\nprint(\"KerasCV:\", keras_cv.__version__)","metadata":{"papermill":{"duration":0.019435,"end_time":"2024-01-14T03:18:00.246368","exception":false,"start_time":"2024-01-14T03:18:00.226933","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:36:04.395966Z","iopub.execute_input":"2024-03-28T08:36:04.396578Z","iopub.status.idle":"2024-03-28T08:36:04.401881Z","shell.execute_reply.started":"2024-03-28T08:36:04.396549Z","shell.execute_reply":"2024-03-28T08:36:04.400939Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# ⚙️ | Configuration","metadata":{"papermill":{"duration":0.010922,"end_time":"2024-01-14T03:18:00.26855","exception":false,"start_time":"2024-01-14T03:18:00.257628","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"The spectrograms in the analysis are represented in a 2D image format, with dimensions of 400x300 pixels: `image_size = [400, 300]`.\n\n### Opportunities for Improvement\n* Tuning hyperparameters including the batch size and number of epochs may improve training efficiency and enable the model to achieve higher accuracy. ","metadata":{}},{"cell_type":"code","source":"class CFG:\n    verbose = 1  # Verbosity\n    seed = 42  # Random seed\n    preset = \"efficientnetv2_b2_imagenet\"  # Name of pretrained classifier\n    image_size = [400, 300]  # Input image size\n    epochs = 13 # Training epochs\n    batch_size = 64  # 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()}","metadata":{"papermill":{"duration":0.018795,"end_time":"2024-01-14T03:18:00.298534","exception":false,"start_time":"2024-01-14T03:18:00.279739","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:36:16.448148Z","iopub.execute_input":"2024-03-28T08:36:16.448503Z","iopub.status.idle":"2024-03-28T08:36:16.454959Z","shell.execute_reply.started":"2024-03-28T08:36:16.448477Z","shell.execute_reply":"2024-03-28T08:36:16.453958Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# ♻️ | Reproducibility \nSets value for random seed to produce similar result in each run.","metadata":{"papermill":{"duration":0.010907,"end_time":"2024-01-14T03:18:00.32063","exception":false,"start_time":"2024-01-14T03:18:00.309723","status":"completed"},"tags":[]}},{"cell_type":"code","source":"keras.utils.set_random_seed(CFG.seed)","metadata":{"papermill":{"duration":0.018371,"end_time":"2024-01-14T03:18:00.350074","exception":false,"start_time":"2024-01-14T03:18:00.331703","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:37:36.595224Z","iopub.execute_input":"2024-03-28T08:37:36.596096Z","iopub.status.idle":"2024-03-28T08:37:36.601502Z","shell.execute_reply.started":"2024-03-28T08:37:36.596044Z","shell.execute_reply":"2024-03-28T08:37:36.600671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 📁 | Dataset Path ","metadata":{"papermill":{"duration":0.010888,"end_time":"2024-01-14T03:18:00.372053","exception":false,"start_time":"2024-01-14T03:18:00.361165","status":"completed"},"tags":[]}},{"cell_type":"code","source":"BASE_PATH = \"/kaggle/input/hms-harmful-brain-activity-classification\"\n\nSPEC_DIR = \"/tmp/dataset/hms-hbac\"\nos.makedirs(SPEC_DIR+'/train_spectrograms', exist_ok=True)\nos.makedirs(SPEC_DIR+'/test_spectrograms', exist_ok=True)","metadata":{"papermill":{"duration":0.017704,"end_time":"2024-01-14T03:18:00.400852","exception":false,"start_time":"2024-01-14T03:18:00.383148","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:37:42.238961Z","iopub.execute_input":"2024-03-28T08:37:42.239666Z","iopub.status.idle":"2024-03-28T08:37:42.245815Z","shell.execute_reply.started":"2024-03-28T08:37:42.239637Z","shell.execute_reply":"2024-03-28T08:37:42.244896Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 📖 | Meta Data ","metadata":{"papermill":{"duration":0.011434,"end_time":"2024-01-14T03:18:00.472401","exception":false,"start_time":"2024-01-14T03:18:00.460967","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"# Class Labels\nThis analysis relies on the Expert Consensus, which is an aggregate vote among the individual expert votes. \n\nWithin the dataset, data is well balanced across the class labels. \n\n## Opportunities for Improvement\nThere may be more nuanced information contained in the varying distributions of consensus among the multiple votes for each EEG annotation. ","metadata":{}},{"cell_type":"code","source":"# Train + Valid\ndf = pd.read_csv(f'{BASE_PATH}/train.csv')\ndf['eeg_path'] = f'{BASE_PATH}/train_eegs/'+df['eeg_id'].astype(str)+'.parquet'\ndf['spec_path'] = f'{BASE_PATH}/train_spectrograms/'+df['spectrogram_id'].astype(str)+'.parquet'\ndf['spec2_path'] = f'{SPEC_DIR}/train_spectrograms/'+df['spectrogram_id'].astype(str)+'.npy'\ndf['class_name'] = df.expert_consensus.copy()\ndf['class_label'] = df.expert_consensus.map(CFG.name2label)\ndisplay(df.head(2))\n\n# Test\ntest_df = pd.read_csv(f'{BASE_PATH}/test.csv')\ntest_df['eeg_path'] = f'{BASE_PATH}/test_eegs/'+test_df['eeg_id'].astype(str)+'.parquet'\ntest_df['spec_path'] = f'{BASE_PATH}/test_spectrograms/'+test_df['spectrogram_id'].astype(str)+'.parquet'\ntest_df['spec2_path'] = f'{SPEC_DIR}/test_spectrograms/'+test_df['spectrogram_id'].astype(str)+'.npy'\ndisplay(test_df.head(2))","metadata":{"execution":{"iopub.status.busy":"2024-03-28T08:37:46.265867Z","iopub.execute_input":"2024-03-28T08:37:46.266691Z","iopub.status.idle":"2024-03-28T08:37:46.821900Z","shell.execute_reply.started":"2024-03-28T08:37:46.266657Z","shell.execute_reply":"2024-03-28T08:37:46.820992Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Frequency Content of EEGs\n\nIn a healthy awake brain, an EEG captures a mix of frequencies reflecting normal neural activity:\n* Gamma waves (30-45 Hz) – Learning, memory and processing\n* Alpha waves (8-13 Hz) - relaxed wakefulness\n* Beta waves (>13 Hz) - active thinking, alertness \n* Theta waves (4-7 Hz) - drowsiness\n* Delta waves (<4 Hz) - deep sleep\n\n# Abnormal Patterns in EEGs\n\nIn neurological disorders, characteristic abnormal EEG patterns emerge:\n* Seizures show rhythmic, evolving discharges distinct from background that may be focal (local) or generalized (diffuse)\n* Periodic Discharges (0.5-2 Hz) are repetitive waveforms that may be localized to one hemisphere or region \n* Rhythmic delta activity occurs in the delta frequency range (≤4 Hz), can be continuous or nearly continuous, and can be lateralized, frontally predominant or occipitally predominant\n\nAbnormal patterns categorized as 'Other' include: \n* Sporadic epileptiform discharges (spikes, sharp waves, polyspikes)\n* Focal or generalized slowing (decreasing rate of periodicity of waveforms)\n* Burst Suppression (repeating bursts of activity that decay)\n* Low voltage or attenuated background (reduction in intensity of waveforms)\n\nAn EEG analysis can detect abnormalities associated with these issues, even when these issues do not occur\n\n# EEG data as Spectrograms\n\nFor this analysis, the original EEG waveform data has been pre-converted into spectrogram images. \n\nThe spectrogram encodes data as:\n* Time advances along the horizontal axis. \n* Frequency components are distributed along the vertical axis. \n* Signal energy for given time/frequency point is represented with color. \n\n# Opportunities for Improvement\n* Analysis on the EEG waveforms in their original format may expose the original amplitude and waveform patterns in ways that provide additional information. \n* Analysis across combinations of spectrograms from different sensors can expose information that can help diagnosis and differentiation among types of issues. \n* Identifying separate categories within the 'Other' category by differentiating among various characteristic patterns may help improve inference. ","metadata":{}},{"cell_type":"markdown","source":"## Convert `.parquet` to `.npy`\n\nTo facilitate easier data loading, we will convert the EEG spectrograms from `parquet` to `npy` format. This process involves saving the spectrogram data, and since the content of the files remains the same, no significant changes are made. \n\n> It's worth noting that the `time` column is excluded, as it is not part of the spectrogram.","metadata":{}},{"cell_type":"code","source":"# Define a function to process a single eeg_id\ndef process_spec(spec_id, split=\"train\"):\n    spec_path = f\"{BASE_PATH}/{split}_spectrograms/{spec_id}.parquet\"\n    spec = pd.read_parquet(spec_path)\n    spec = spec.fillna(0).values[:, 1:].T # fill NaN values with 0, transpose for (Time, Freq) -> (Freq, Time)\n    spec = spec.astype(\"float32\")\n    np.save(f\"{SPEC_DIR}/{split}_spectrograms/{spec_id}.npy\", spec)\n\n# Get unique spec_ids of train and valid data\nspec_ids = df[\"spectrogram_id\"].unique()\n\n# Parallelize the processing using joblib for training data\n_ = joblib.Parallel(n_jobs=-1, backend=\"loky\")(\n    joblib.delayed(process_spec)(spec_id, \"train\")\n    for spec_id in tqdm(spec_ids, total=len(spec_ids))\n)\n\n# Get unique spec_ids of test data\ntest_spec_ids = test_df[\"spectrogram_id\"].unique()\n\n# Parallelize the processing using joblib for test data\n_ = joblib.Parallel(n_jobs=-1, backend=\"loky\")(\n    joblib.delayed(process_spec)(spec_id, \"test\")\n    for spec_id in tqdm(test_spec_ids, total=len(test_spec_ids))\n)","metadata":{"papermill":{"duration":0.86264,"end_time":"2024-01-14T03:18:01.346487","exception":false,"start_time":"2024-01-14T03:18:00.483847","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:37:59.026229Z","iopub.execute_input":"2024-03-28T08:37:59.026855Z","iopub.status.idle":"2024-03-28T08:41:09.510148Z","shell.execute_reply.started":"2024-03-28T08:37:59.026821Z","shell.execute_reply":"2024-03-28T08:41:09.509119Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🍚 | DataLoader\n\nThis DataLoader first reads `npy` spectrogram files and extracts labeled subsamples using specified `offset` values. Then, it converts the spectrogram data into `log spectrogram` and applies the popular signal augmentation `MixUp`.\n\n> Note that, we are converting the mono channel signal to a 3-channel signal for using \"ImageNet\" weights of pretrained model.","metadata":{"papermill":{"duration":0.011843,"end_time":"2024-01-14T03:18:01.457956","exception":false,"start_time":"2024-01-14T03:18:01.446113","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"# Preprocessing and Augmentation\nPreprocessing and augmentation steps help achieve generalization in identifying relevant EEG patterns from the spectrogram data, with robustness to variations across patients and acquisition settings.","metadata":{"execution":{"iopub.status.busy":"2024-04-01T22:31:14.085489Z","iopub.execute_input":"2024-04-01T22:31:14.086703Z","iopub.status.idle":"2024-04-01T22:31:14.094595Z","shell.execute_reply.started":"2024-04-01T22:31:14.086653Z","shell.execute_reply":"2024-04-01T22:31:14.093165Z"}}},{"cell_type":"markdown","source":"## Augmentation\n\nData augmentation is crucial for improving a model's robustness and generalization ability, especially when working with limited medical datasets. This analysis applies augmentation techniques to the spectrogram images, including MixUp and RandomCutout for frequency and time masking. \n\n### MixUp\nMixUp creates new training examples by linearly combining pairs of inputs and their labels, encouraging the model to learn smoother decision boundaries.\n\n### RandomCutout\nFrequency and time masking randomly mask out portions of the spectrogram, helping the model to be invariant to small perturbations and noise.","metadata":{}},{"cell_type":"code","source":"def build_augmenter(dim=CFG.image_size):\n    augmenters = [\n        keras_cv.layers.MixUp(alpha=2.0),\n        keras_cv.layers.RandomCutout(height_factor=(1.0, 1.0),\n                                     width_factor=(0.06, 0.1)), # freq-masking\n        keras_cv.layers.RandomCutout(height_factor=(0.06, 0.1),\n                                     width_factor=(1.0, 1.0)), # time-masking\n    ]\n    \n    def augment(img, label):\n        data = {\"images\":img, \"labels\":label}\n        for augmenter in augmenters:\n            if tf.random.uniform([]) < 0.5:\n                data = augmenter(data, training=True)\n        return data[\"images\"], data[\"labels\"]\n    \n    return augment","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Preprocessing\n\n### Log scaling\nThe spectrogram values are converted to a logarithmic scale using `tf.math.log()`. This is a common technique in signal processing to improve visualization and emphasize smaller values. In the context of EEG, it can help enhance the visibility of low-amplitude patterns that may be clinically relevant.\n\n### Normalization\nThe log spectrogram is normalized by subtracting the mean and dividing by the standard deviation. Normalization is important for standardizing the input data and improving the stability of the learning process. It ensures that the model is not biased by differences in overall amplitude between recordings.\n\n### Channel expansion\nThe single-channel spectrogram is replicated to create a 3-channel image. This is done to match the expected input shape of the pre-trained EfficientNetV2 model, which was originally trained on 3-channel color images.","metadata":{}},{"cell_type":"code","source":"def build_decoder(with_labels=True, target_size=CFG.image_size, dtype=32):\n    def decode_signal(path, offset=None):\n        # Read .npy files and process the signal\n        file_bytes = tf.io.read_file(path)\n        sig = tf.io.decode_raw(file_bytes, tf.float32)\n        sig = sig[1024//dtype:]  # Remove header tag\n        sig = tf.reshape(sig, [400, -1])\n        \n        # Extract labeled subsample from full spectrogram using \"offset\"\n        if offset is not None: \n            offset = offset // 2  # Only odd values are given\n            sig = sig[:, offset:offset+300]\n            \n            # Pad spectrogram to ensure the same input shape of [400, 300]\n            pad_size = tf.math.maximum(0, 300 - tf.shape(sig)[1])\n            sig = tf.pad(sig, [[0, 0], [0, pad_size]])\n            sig = tf.reshape(sig, [400, 300])\n        \n        # Log spectrogram \n        sig = tf.clip_by_value(sig, tf.math.exp(-4.0), tf.math.exp(8.0)) # avoid 0 in log\n        sig = tf.math.log(sig)\n        \n        # Normalize spectrogram\n        sig -= tf.math.reduce_mean(sig)\n        sig /= tf.math.reduce_std(sig) + 1e-6\n        \n        # Mono channel to 3 channels to use \"ImageNet\" weights\n        sig = tf.tile(sig[..., None], [1, 1, 3])\n        return sig\n    \n    def decode_label(label):\n        label = tf.one_hot(label, CFG.num_classes)\n        label = tf.cast(label, tf.float32)\n        label = tf.reshape(label, [CFG.num_classes])\n        return label\n    \n    def decode_with_labels(path, offset=None, label=None):\n        sig = decode_signal(path, offset)\n        label = decode_label(label)\n        return (sig, label)\n    \n    return decode_with_labels if with_labels else decode_signal\n\n\ndef build_dataset(paths, offsets=None, labels=None, batch_size=32, cache=True,\n                  decode_fn=None, augment_fn=None,\n                  augment=False, repeat=True, shuffle=1024, \n                  cache_dir=\"\", drop_remainder=False):\n    if cache_dir != \"\" and cache is True:\n        os.makedirs(cache_dir, exist_ok=True)\n    \n    if decode_fn is None:\n        decode_fn = build_decoder(labels is not None)\n    \n    if augment_fn is None:\n        augment_fn = build_augmenter()\n    \n    AUTO = tf.data.experimental.AUTOTUNE\n    slices = (paths, offsets) if labels is None else (paths, offsets, labels)\n    \n    ds = tf.data.Dataset.from_tensor_slices(slices)\n    ds = ds.map(decode_fn, num_parallel_calls=AUTO)\n    ds = ds.cache(cache_dir) if cache else ds\n    ds = ds.repeat() if repeat else ds\n    if shuffle: \n        ds = ds.shuffle(shuffle, seed=CFG.seed)\n        opt = tf.data.Options()\n        opt.experimental_deterministic = False\n        ds = ds.with_options(opt)\n    ds = ds.batch(batch_size, drop_remainder=drop_remainder)\n    ds = ds.map(augment_fn, num_parallel_calls=AUTO) if augment else ds\n    ds = ds.prefetch(AUTO)\n    return ds","metadata":{"papermill":{"duration":0.039133,"end_time":"2024-01-14T03:18:01.509017","exception":false,"start_time":"2024-01-14T03:18:01.469884","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:41:34.436402Z","iopub.execute_input":"2024-03-28T08:41:34.436785Z","iopub.status.idle":"2024-03-28T08:41:34.458044Z","shell.execute_reply.started":"2024-03-28T08:41:34.436755Z","shell.execute_reply":"2024-03-28T08:41:34.457161Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🔪 | Data Split\n\nIn the following code snippet, the data is divided into `5` folds. Note that, the `groups` argument is used to prevent any overlap of patients between the training and validation sets, thus avoiding potential **data leakage** issues. Additionally, each split is stratified based on the `class_label`, ensuring a uniform distribution of class labels in each fold.","metadata":{"papermill":{"duration":0.012174,"end_time":"2024-01-14T03:18:01.538524","exception":false,"start_time":"2024-01-14T03:18:01.52635","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"# Data Splitting and Cross-Validation \nData splitting and cross-validation techniques are crucial for evaluating the performance and generalization ability of machine learning models in healthcare applications.\n\n## Stratified Group K-Fold\nThe data is split into 5 folds using Stratified Group K-Fold cross-validation from the scikit-learn library. This technique ensures that each fold has a similar distribution of class labels (stratification) while also keeping all samples from the same patient together (grouping by patient ID). Stratification is important in healthcare datasets where class imbalance is common, as it ensures that each fold is representative of the overall class distribution.","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import StratifiedGroupKFold\n\nsgkf = StratifiedGroupKFold(n_splits=5, shuffle=True, random_state=CFG.seed)\n\ndf[\"fold\"] = -1\ndf.reset_index(drop=True, inplace=True)\nfor 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\ndf.groupby([\"fold\", \"class_name\"])[[\"eeg_id\"]].count().T","metadata":{"papermill":{"duration":0.037496,"end_time":"2024-01-14T03:18:01.587924","exception":false,"start_time":"2024-01-14T03:18:01.550428","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:42:20.841753Z","iopub.execute_input":"2024-03-28T08:42:20.842462Z","iopub.status.idle":"2024-03-28T08:42:22.607364Z","shell.execute_reply.started":"2024-03-28T08:42:20.842429Z","shell.execute_reply":"2024-03-28T08:42:22.606452Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Build Train & Valid Dataset\n\nOnly first sample for each `spectrogram_id` is used in order to keep the dataset size managable. Feel free to train on full data.","metadata":{"papermill":{"duration":0.011875,"end_time":"2024-01-14T03:18:01.611955","exception":false,"start_time":"2024-01-14T03:18:01.60008","status":"completed"},"tags":[]}},{"cell_type":"code","source":"# Sample from full data\nsample_df = df.groupby(\"spectrogram_id\").head(1).reset_index(drop=True)\ntrain_df = sample_df[sample_df.fold != CFG.fold]\nvalid_df = sample_df[sample_df.fold == CFG.fold]\nprint(f\"# Num Train: {len(train_df)} | Num Valid: {len(valid_df)}\")\n\n# Train\ntrain_paths = train_df.spec2_path.values\ntrain_offsets = train_df.spectrogram_label_offset_seconds.values.astype(int)\ntrain_labels = train_df.class_label.values\ntrain_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\nvalid_paths = valid_df.spec2_path.values\nvalid_offsets = valid_df.spectrogram_label_offset_seconds.values.astype(int)\nvalid_labels = valid_df.class_label.values\nvalid_ds = build_dataset(valid_paths, valid_offsets, valid_labels, batch_size=CFG.batch_size,\n                         repeat=False, shuffle=False, augment=False, cache=True)","metadata":{"execution":{"iopub.status.busy":"2024-03-28T08:42:41.018615Z","iopub.execute_input":"2024-03-28T08:42:41.019527Z","iopub.status.idle":"2024-03-28T08:42:43.648577Z","shell.execute_reply.started":"2024-03-28T08:42:41.019493Z","shell.execute_reply":"2024-03-28T08:42:43.647688Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Dataset Check\n\nLet's visualize some samples from the dataset.","metadata":{}},{"cell_type":"code","source":"imgs, tars = next(iter(train_ds))\n\nnum_imgs = 8\nplt.figure(figsize=(4*4, num_imgs//4*5))\nfor i in range(num_imgs):\n    plt.subplot(num_imgs//4, 4, i + 1)\n    img = imgs[i].numpy()[...,0]  # Adjust as per your image data format\n    img -= img.min()\n    img /= img.max() + 1e-4\n    tar = CFG.label2name[np.argmax(tars[i].numpy())]\n    plt.imshow(img)\n    plt.title(f\"Target: {tar}\")\n    plt.axis('off')\n    \nplt.tight_layout()\nplt.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2024-03-28T08:43:58.198246Z","iopub.execute_input":"2024-03-28T08:43:58.198978Z","iopub.status.idle":"2024-03-28T08:44:00.962521Z","shell.execute_reply.started":"2024-03-28T08:43:58.198945Z","shell.execute_reply":"2024-03-28T08:44:00.960985Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🔍 | Loss & Metric\n\nThe evaluation metric in this competition is **KL Divergence**, defined as,\n\n$$\nD_{\\text{KL}}(P \\parallel Q) = \\sum_{i} P(i) \\log\\left(\\frac{P(i)}{Q(i)}\\right)\n$$\n\nWhere:\n- $P$ is the true distribution.\n- $Q$ is the predicted distribution.\n\nInterestingly, as KL Divergence is differentiable, we can directly use it as our loss function. Thus, we don't need to use a third-party metric like **Accuracy** to evaluate our model. Therefore, `valid_loss` can stand alone as an indicator for our evaluation. In keras, we already have impelementation for KL Divergence loss so we only need to import it.","metadata":{}},{"cell_type":"markdown","source":"# KL Divergence\nKL Divergence provides a more nuanced evaluation of the model's performance compared to simple accuracy. It takes into account not only the predicted class labels but also the confidence (probability) assigned to each class. This is particularly relevant for medical applications, where the model's uncertainty and the relative probabilities of different diagnoses can be informative for clinical decision-making.\n\n## Healthcare Implications\n### Focus on probability estimation\nThe model is trained to output reliable probability estimates for each class, rather than just the most likely class label. This additional information can be valuable for clinicians in interpreting the model's predictions and making treatment decisions.\n### Robustness to class imbalance\nKL Divergence is less sensitive to class imbalance compared to accuracy, as it considers the relative probabilities rather than just the binary correctness of predictions. This is important in medical datasets where some conditions may be rarer than others.\n### Confidence in predictions\nA well-calibrated model with low KL Divergence can provide clinicians with a measure of confidence in its predictions. This can help in triaging patients, prioritizing cases for further review, and communicating the level of certainty to patients and families.\n### Potential for decision support\nBy accurately estimating the probabilities of different EEG patterns, the model can assist clinicians in differential diagnosis and guide further testing or treatment options.","metadata":{}},{"cell_type":"code","source":"LOSS = keras.losses.KLDivergence()","metadata":{"execution":{"iopub.status.busy":"2024-03-28T08:44:44.377205Z","iopub.execute_input":"2024-03-28T08:44:44.377854Z","iopub.status.idle":"2024-03-28T08:44:44.382350Z","shell.execute_reply.started":"2024-03-28T08:44:44.377824Z","shell.execute_reply":"2024-03-28T08:44:44.381297Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🤖 | Modeling\n\nThis notebook uses the `EfficientNetV2 B2` from KerasCV's collection of pretrained models. To explore other models, simply modify the `preset` in the `CFG` (config). Check the [KerasCV website](https://keras.io/api/keras_cv/models/tasks/image_classifier/) for a list of available pretrained models.","metadata":{"papermill":{"duration":0.016849,"end_time":"2024-01-14T03:18:38.613991","exception":false,"start_time":"2024-01-14T03:18:38.597142","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"# Model Selection\nLeveraging transfer learning from models pre-trained on large-scale datasets like ImageNet can significantly improve performance and reduce the need for extensive labeled medical data.\n\n# Opportunities for Improvement \n* Adaptations to the base EfficientNetV2 architecture may be beneficial to accommodate the nature of spectrograms.\n* The latent space of visual concepts in the pre-trained model may not map well to those in the spectrogram data. Selecting a model pre-trained on spectrograms, and ideally specifically EEG waveforms can help capture more nuance from relevant patterns in the spectrogram data.\n* Hyperparameters like learning rate, batch size, and regularization may benefit from further tuning to the specific characteristics of the EEG spectrogram dataset.\n* Other model architectures may be more capable at identifying specific nuances in the data. \n* Combinations of models can exceed the performance of individual models. ","metadata":{}},{"cell_type":"code","source":"# Build Classifier\nmodel = keras_cv.models.ImageClassifier.from_preset(\n    CFG.preset, num_classes=CFG.num_classes\n)\n\n# Compile the model  \nmodel.compile(optimizer=keras.optimizers.Adam(learning_rate=1e-4),\n              loss=LOSS)\n\n# Model Sumamry\nmodel.summary()","metadata":{"papermill":{"duration":10.446166,"end_time":"2024-01-14T03:18:49.186176","exception":false,"start_time":"2024-01-14T03:18:38.74001","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:44:55.182912Z","iopub.execute_input":"2024-03-28T08:44:55.183267Z","iopub.status.idle":"2024-03-28T08:45:17.823846Z","shell.execute_reply.started":"2024-03-28T08:44:55.183243Z","shell.execute_reply":"2024-03-28T08:45:17.822951Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# ⚓ | LR Schedule\n\nA well-structured learning rate schedule is essential for efficient model training, ensuring optimal convergence and avoiding issues such as overshooting or stagnation.","metadata":{"papermill":{"duration":0.016209,"end_time":"2024-01-14T03:18:49.21924","exception":false,"start_time":"2024-01-14T03:18:49.203031","status":"completed"},"tags":[]}},{"cell_type":"code","source":"import math\n\ndef get_lr_callback(batch_size=8, mode='cos', epochs=10, plot=False):\n    lr_start, lr_max, lr_min = 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: lr = (lr_max - lr_start) / lr_ramp_ep * epoch + lr_start\n        elif epoch < lr_ramp_ep + lr_sus_ep: lr = lr_max\n        elif mode == 'exp': lr = (lr_max - lr_min) * lr_decay**(epoch - lr_ramp_ep - lr_sus_ep) + lr_min\n        elif mode == 'step': lr = lr_max * lr_decay**((epoch - lr_ramp_ep - lr_sus_ep) // 2)\n        elif mode == 'cos':\n            decay_total_epochs, decay_epoch_index = epochs - lr_ramp_ep - lr_sus_ep + 3, 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=False)  # Create lr callback","metadata":{"papermill":{"duration":0.028945,"end_time":"2024-01-14T03:18:49.264535","exception":false,"start_time":"2024-01-14T03:18:49.23559","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:46:37.349259Z","iopub.execute_input":"2024-03-28T08:46:37.349913Z","iopub.status.idle":"2024-03-28T08:46:37.360135Z","shell.execute_reply.started":"2024-03-28T08:46:37.349880Z","shell.execute_reply":"2024-03-28T08:46:37.359230Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"lr_cb = get_lr_callback(CFG.batch_size, mode=CFG.lr_mode, plot=True)","metadata":{"papermill":{"duration":0.297147,"end_time":"2024-01-14T03:18:49.578089","exception":false,"start_time":"2024-01-14T03:18:49.280942","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:47:02.656139Z","iopub.execute_input":"2024-03-28T08:47:02.656745Z","iopub.status.idle":"2024-03-28T08:47:02.870688Z","shell.execute_reply.started":"2024-03-28T08:47:02.656716Z","shell.execute_reply":"2024-03-28T08:47:02.869771Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 💾 | Model Checkpointing","metadata":{"papermill":{"duration":0.017199,"end_time":"2024-01-14T03:18:49.613648","exception":false,"start_time":"2024-01-14T03:18:49.596449","status":"completed"},"tags":[]}},{"cell_type":"code","source":"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')","metadata":{"papermill":{"duration":0.024529,"end_time":"2024-01-14T03:18:49.655708","exception":false,"start_time":"2024-01-14T03:18:49.631179","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:47:10.344664Z","iopub.execute_input":"2024-03-28T08:47:10.345322Z","iopub.status.idle":"2024-03-28T08:47:10.349946Z","shell.execute_reply.started":"2024-03-28T08:47:10.345286Z","shell.execute_reply":"2024-03-28T08:47:10.348962Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🚂 | Training","metadata":{"papermill":{"duration":0.01671,"end_time":"2024-01-14T03:18:49.689354","exception":false,"start_time":"2024-01-14T03:18:49.672644","status":"completed"},"tags":[]}},{"cell_type":"code","source":"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":{"papermill":{"duration":3374.692199,"end_time":"2024-01-14T04:15:04.398389","exception":false,"start_time":"2024-01-14T03:18:49.70619","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T08:47:18.748035Z","iopub.execute_input":"2024-03-28T08:47:18.748937Z","iopub.status.idle":"2024-03-28T09:03:53.535526Z","shell.execute_reply.started":"2024-03-28T08:47:18.748909Z","shell.execute_reply":"2024-03-28T09:03:53.534549Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 🧪 | Prediction","metadata":{"papermill":{"duration":0.693309,"end_time":"2024-01-14T04:15:05.731839","exception":false,"start_time":"2024-01-14T04:15:05.03853","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"## Load Best Model","metadata":{"papermill":{"duration":0.632183,"end_time":"2024-01-14T04:15:06.991143","exception":false,"start_time":"2024-01-14T04:15:06.35896","status":"completed"},"tags":[]}},{"cell_type":"code","source":"model.load_weights(\"best_model.keras\")","metadata":{"papermill":{"duration":20.428261,"end_time":"2024-01-14T04:15:28.044401","exception":false,"start_time":"2024-01-14T04:15:07.61614","status":"completed"},"tags":[],"execution":{"iopub.status.busy":"2024-03-28T09:04:39.292137Z","iopub.execute_input":"2024-03-28T09:04:39.292561Z","iopub.status.idle":"2024-03-28T09:04:46.528422Z","shell.execute_reply.started":"2024-03-28T09:04:39.292530Z","shell.execute_reply":"2024-03-28T09:04:46.527600Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Build Test Dataset","metadata":{"papermill":{"duration":0.703901,"end_time":"2024-01-14T04:20:09.745279","exception":false,"start_time":"2024-01-14T04:20:09.041378","status":"completed"},"tags":[]}},{"cell_type":"code","source":"test_paths = test_df.spec2_path.values\ntest_ds = build_dataset(test_paths, batch_size=min(CFG.batch_size, len(test_df)),\n                         repeat=False, shuffle=False, cache=False, augment=False)","metadata":{"execution":{"iopub.status.busy":"2024-03-28T09:05:06.444452Z","iopub.execute_input":"2024-03-28T09:05:06.445105Z","iopub.status.idle":"2024-03-28T09:05:06.488457Z","shell.execute_reply.started":"2024-03-28T09:05:06.445072Z","shell.execute_reply":"2024-03-28T09:05:06.487674Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Inference","metadata":{}},{"cell_type":"code","source":"preds = model.predict(test_ds)","metadata":{"execution":{"iopub.status.busy":"2024-03-28T09:05:12.956838Z","iopub.execute_input":"2024-03-28T09:05:12.957319Z","iopub.status.idle":"2024-03-28T09:05:37.297646Z","shell.execute_reply.started":"2024-03-28T09:05:12.957289Z","shell.execute_reply":"2024-03-28T09:05:37.296453Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Opportunity for Improvement \n\nThe following techniques can help with interpretability:\n* Attention Maps or Saliency Maps highlight the most informative portions of spectrograms\n* Feature Visualizations produce synthetic spectrograms that are most representative of each category\n* Concept Activation Vectors map specific human-interpretable patterns with the model's intermediate representations, for use in sensitivity analysis\n* Interpretable Model Architectures (i.e. Attention-Based Models, Prototypical Part Networks) tend to create prototypical representations that are inherently more interpretable ","metadata":{}},{"cell_type":"markdown","source":"# 📩 | Submission","metadata":{}},{"cell_type":"code","source":"pred_df = test_df[[\"eeg_id\"]].copy()\ntarget_cols = [x.lower()+'_vote' for x in CFG.class_names]\npred_df[target_cols] = preds.tolist()\n\nsub_df = pd.read_csv(f'{BASE_PATH}/sample_submission.csv')\nsub_df = sub_df[[\"eeg_id\"]].copy()\nsub_df = sub_df.merge(pred_df, on=\"eeg_id\", how=\"left\")\nsub_df.to_csv(\"submission.csv\", index=False)\nsub_df.head()","metadata":{"execution":{"iopub.status.busy":"2024-03-28T09:06:27.319864Z","iopub.execute_input":"2024-03-28T09:06:27.320536Z","iopub.status.idle":"2024-03-28T09:06:27.397698Z","shell.execute_reply.started":"2024-03-28T09:06:27.320501Z","shell.execute_reply":"2024-03-28T09:06:27.396780Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 📌 | Reference\n* [HMS-HBAC: ResNet34d Baseline [Training]](https://www.kaggle.com/code/ttahara/hms-hbac-resnet34d-baseline-training) \n* [EfficientNetB2 Starter - [LB 0.57]](https://www.kaggle.com/code/cdeotte/efficientnetb2-starter-lb-0-57)","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}