{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:10:37.825361Z","iopub.execute_input":"2026-08-02T08:10:37.825849Z","iopub.status.idle":"2026-08-02T08:11:26.019161Z","shell.execute_reply.started":"2026-08-02T08:10:37.825819Z","shell.execute_reply":"2026-08-02T08:11:26.017651Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# Check HMS dataset\nprint(\"=== HMS Dataset ===\")\nprint(os.listdir('/kaggle/input/competitions/hms-harmful-brain-activity-classification'))\n\n# Check KerasV3 library\nprint(\"\\n=== KerasV3 Library ===\")\nprint(os.listdir('/kaggle/input/models/keras/efficientnetv2/keras/efficientnetv2_b2/2'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:26.020840Z","iopub.execute_input":"2026-08-02T08:11:26.021173Z","iopub.status.idle":"2026-08-02T08:11:26.028779Z","shell.execute_reply.started":"2026-08-02T08:11:26.021147Z","shell.execute_reply":"2026-08-02T08:11:26.027780Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# Check what is actually in the input folder\nprint(\"=== Everything in input folder ===\")\nfor item in os.listdir('/kaggle/input'):\n    print(item)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:26.029876Z","iopub.execute_input":"2026-08-02T08:11:26.030197Z","iopub.status.idle":"2026-08-02T08:11:26.043537Z","shell.execute_reply.started":"2026-08-02T08:11:26.030148Z","shell.execute_reply":"2026-08-02T08:11:26.042680Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# Check full paths\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    print(dirname)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:26.045224Z","iopub.execute_input":"2026-08-02T08:11:26.045588Z","iopub.status.idle":"2026-08-02T08:11:29.646896Z","shell.execute_reply.started":"2026-08-02T08:11:26.045564Z","shell.execute_reply":"2026-08-02T08:11:29.646151Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# EEG Brain Activity Classification for IONM Applications\n### By Aarthy Shree.R | B.Tech CSME Final Year | Chennai, India\n\n## What is this problem?\nCritically ill ICU patients experience harmful brain activity patterns\nthat need immediate detection. This notebook classifies 6 types:\n- Seizure (most dangerous)\n- LPD — Lateralized Periodic Discharges  \n- GPD — Generalized Periodic Discharges\n- LRDA — Lateralized Rhythmic Delta Activity\n- GRDA — Generalized Rhythmic Delta Activity\n- Other\n\n## My IONM connection:\nIntraoperative Neurophysiological Monitoring (IONM) uses these\nexact same EEG patterns during surgery to prevent nerve damage.\nThis model directly applies to real surgical monitoring systems.\n\n## What I added beyond the starter notebook:\n- Class distribution analysis\n- Spectrogram quality visualization per class\n- Reduced epochs for faster training\n- Added my own observations at each step","metadata":{}},{"cell_type":"code","source":"!pip install -q /kaggle/input/kerasv3-lib-ds/keras_cv-0.8.2-py3-none-any.whl --no-deps\n!pip install -q /kaggle/input/kerasv3-lib-ds/tensorflow-2.15.0.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl --no-deps\n!pip install -q /kaggle/input/kerasv3-lib-ds/keras-3.0.4-py3-none-any.whl --no-deps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:29.647921Z","iopub.execute_input":"2026-08-02T08:11:29.648220Z","iopub.status.idle":"2026-08-02T08:11:35.081420Z","shell.execute_reply.started":"2026-08-02T08:11:29.648199Z","shell.execute_reply":"2026-08-02T08:11:35.080548Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q keras_cv==0.8.2 tensorflow==2.15.0 keras==3.0.4","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:35.083001Z","iopub.execute_input":"2026-08-02T08:11:35.083960Z","iopub.status.idle":"2026-08-02T08:11:37.152981Z","shell.execute_reply.started":"2026-08-02T08:11:35.083914Z","shell.execute_reply":"2026-08-02T08:11:37.152210Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q keras_cv==0.8.2 --no-deps\n!pip install -q keras==3.0.4 --no-deps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:37.154297Z","iopub.execute_input":"2026-08-02T08:11:37.155155Z","iopub.status.idle":"2026-08-02T08:11:40.739445Z","shell.execute_reply.started":"2026-08-02T08:11:37.155123Z","shell.execute_reply":"2026-08-02T08:11:40.738350Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport keras\nprint(\"TF version:\", tf.__version__)\nprint(\"Keras version:\", keras.__version__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:40.741037Z","iopub.execute_input":"2026-08-02T08:11:40.741453Z","iopub.status.idle":"2026-08-02T08:11:40.747585Z","shell.execute_reply.started":"2026-08-02T08:11:40.741421Z","shell.execute_reply":"2026-08-02T08:11:40.746633Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import keras_cv\nprint(\"keras_cv version:\", keras_cv.__version__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:40.748648Z","iopub.execute_input":"2026-08-02T08:11:40.749045Z","iopub.status.idle":"2026-08-02T08:11:40.762341Z","shell.execute_reply.started":"2026-08-02T08:11:40.749012Z","shell.execute_reply":"2026-08-02T08:11:40.761540Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q keras_cv==0.8.2 keras==3.0.4 --no-deps","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:40.766580Z","iopub.execute_input":"2026-08-02T08:11:40.766832Z","iopub.status.idle":"2026-08-02T08:11:42.519720Z","shell.execute_reply.started":"2026-08-02T08:11:40.766809Z","shell.execute_reply":"2026-08-02T08:11:42.518804Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nos.environ[\"KERAS_BACKEND\"] = \"jax\"\nimport keras_cv\nimport keras\nfrom keras import ops\nimport tensorflow as tf\nimport cv2\nimport pandas as pd\nimport numpy as np\nfrom glob import glob\nfrom tqdm.notebook import tqdm\nimport joblib\nimport matplotlib.pyplot as plt\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"Keras:\", keras.__version__)\nprint(\"KerasCV:\", keras_cv.__version__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:42.521273Z","iopub.execute_input":"2026-08-02T08:11:42.521683Z","iopub.status.idle":"2026-08-02T08:11:42.528319Z","shell.execute_reply.started":"2026-08-02T08:11:42.521652Z","shell.execute_reply":"2026-08-02T08:11:42.527512Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nos.environ[\"KERAS_BACKEND\"] = \"jax\"\nimport keras_cv\nimport keras\nfrom keras import ops\nimport tensorflow as tf\nimport cv2\nimport pandas as pd\nimport numpy as np\nfrom glob import glob\nfrom tqdm.notebook import tqdm\nimport joblib\nimport matplotlib.pyplot as plt\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"Keras:\", keras.__version__)\nprint(\"KerasCV:\", keras_cv.__version__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:42.529375Z","iopub.execute_input":"2026-08-02T08:11:42.530214Z","iopub.status.idle":"2026-08-02T08:11:42.545233Z","shell.execute_reply.started":"2026-08-02T08:11:42.530170Z","shell.execute_reply":"2026-08-02T08:11:42.544323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CFG:\n    verbose = 1\n    seed = 42\n    preset = \"efficientnetv2_b2_imagenet\"\n    image_size = [400, 300]\n    epochs = 9  # Changed from 13 to 9 — faster training, less overfitting\n    batch_size = 64\n    lr_mode = \"cos\"\n    drop_remainder = True\n    num_classes = 6\n    fold = 0\n    class_names = ['Seizure', 'LPD', 'GPD', 'LRDA','GRDA', 'Other']\n    label2name = dict(enumerate(class_names))\n    name2label = {v:k for k, v in label2name.items()}\n\nkeras.utils.set_random_seed(CFG.seed)\nprint(\"Configuration set successfully\")\nprint(f\"Classes: {CFG.class_names}\")\nprint(f\"Epochs: {CFG.epochs}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:42.546557Z","iopub.execute_input":"2026-08-02T08:11:42.546914Z","iopub.status.idle":"2026-08-02T08:11:42.687418Z","shell.execute_reply.started":"2026-08-02T08:11:42.546891Z","shell.execute_reply":"2026-08-02T08:11:42.686616Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BASE_PATH = \"/kaggle/input/competitions/hms-harmful-brain-activity-classification\"\nSPEC_DIR = \"/tmp/dataset/hms-hbac\"\nos.makedirs(SPEC_DIR+'/train_spectrograms', exist_ok=True)\nos.makedirs(SPEC_DIR+'/test_spectrograms', exist_ok=True)\n\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)\n\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'\n\nprint(\"Data loaded successfully\")\nprint(f\"Training samples: {len(df)}\")\nprint(f\"Test samples: {len(test_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:42.689735Z","iopub.execute_input":"2026-08-02T08:11:42.690526Z","iopub.status.idle":"2026-08-02T08:11:43.097270Z","shell.execute_reply.started":"2026-08-02T08:11:42.690440Z","shell.execute_reply":"2026-08-02T08:11:43.096548Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"##  Analysis 1: Class Distribution\n### Before training any model, I want to understand the data balance\n### Imbalanced classes can cause the model to ignore rare patterns\n","metadata":{}},{"cell_type":"code","source":"# My original analysis — class distribution\nplt.figure(figsize=(10, 5))\nclass_counts = df['class_name'].value_counts()\ncolors = ['red', 'orange', 'blue', 'green', 'purple', 'gray']\n\nbars = plt.bar(class_counts.index, class_counts.values, color=colors)\nplt.title('Brain Activity Class Distribution\\n(Before Training)', \n          fontsize=14, fontweight='bold')\nplt.xlabel('Brain Activity Type')\nplt.ylabel('Number of Samples')\n\nfor bar, count in zip(bars, class_counts.values):\n    plt.text(bar.get_x() + bar.get_width()/2, \n             bar.get_height() + 50,\n             str(count), ha='center', fontweight='bold')\n\nplt.tight_layout()\nplt.savefig('class_distribution.png', dpi=150)\nplt.show()\n\nprint(\"\\nClass Distribution:\")\nfor cls, count in class_counts.items():\n    pct = count/len(df)*100\n    print(f\"{cls:10}: {count:6} samples ({pct:.1f}%)\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:43.098394Z","iopub.execute_input":"2026-08-02T08:11:43.098762Z","iopub.status.idle":"2026-08-02T08:11:43.464852Z","shell.execute_reply.started":"2026-08-02T08:11:43.098727Z","shell.execute_reply":"2026-08-02T08:11:43.464081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def 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\n    spec = spec.astype(\"float32\")\n    np.save(f\"{SPEC_DIR}/{split}_spectrograms/{spec_id}.npy\", spec)\n\nspec_ids = df[\"spectrogram_id\"].unique()\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\ntest_spec_ids = test_df[\"spectrogram_id\"].unique()\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)\nprint(\"Spectrograms processed successfully\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:11:43.466054Z","iopub.execute_input":"2026-08-02T08:11:43.466621Z","iopub.status.idle":"2026-08-02T08:14:52.627637Z","shell.execute_reply.started":"2026-08-02T08:11:43.466597Z","shell.execute_reply":"2026-08-02T08:14:52.626875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Keep build_augmenter, build_decoder, build_dataset exactly as original\n# These are complex signal processing functions — don't modify them\n\ndef 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)),\n        keras_cv.layers.RandomCutout(height_factor=(0.06, 0.1),\n                                     width_factor=(1.0, 1.0)),\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    return augment\n\ndef build_decoder(with_labels=True, target_size=CFG.image_size, dtype=32):\n    def decode_signal(path, offset=None):\n        file_bytes = tf.io.read_file(path)\n        sig = tf.io.decode_raw(file_bytes, tf.float32)\n        sig = sig[1024//dtype:]\n        sig = tf.reshape(sig, [400, -1])\n        if offset is not None:\n            offset = offset // 2\n            sig = sig[:, offset:offset+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        sig = tf.clip_by_value(sig, tf.math.exp(-4.0), tf.math.exp(8.0))\n        sig = tf.math.log(sig)\n        sig -= tf.math.reduce_mean(sig)\n        sig /= tf.math.reduce_std(sig) + 1e-6\n        sig = tf.tile(sig[..., None], [1, 1, 3])\n        return sig\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    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    return decode_with_labels if with_labels else decode_signal\n\ndef build_dataset(paths, offsets=None, labels=None, batch_size=32, cache=True,\n                  decode_fn=None, augment_fn=None, augment=False, repeat=True,\n                  shuffle=1024, cache_dir=\"\", drop_remainder=False):\n    if cache_dir != \"\" and cache is True:\n        os.makedirs(cache_dir, exist_ok=True)\n    if decode_fn is None:\n        decode_fn = build_decoder(labels is not None)\n    if augment_fn is None:\n        augment_fn = build_augmenter()\n    AUTO = tf.data.experimental.AUTOTUNE\n    slices = (paths, offsets) if labels is None else (paths, offsets, labels)\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\n\nprint(\"All functions defined successfully\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:14:52.633403Z","iopub.execute_input":"2026-08-02T08:14:52.633958Z","iopub.status.idle":"2026-08-02T08:14:52.651360Z","shell.execute_reply.started":"2026-08-02T08:14:52.633928Z","shell.execute_reply":"2026-08-02T08:14:52.650565Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import StratifiedGroupKFold\nsgkf = StratifiedGroupKFold(n_splits=5, shuffle=True, random_state=CFG.seed)\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\n\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\"Train samples: {len(train_df)} | Validation samples: {len(valid_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:14:52.652393Z","iopub.execute_input":"2026-08-02T08:14:52.652693Z","iopub.status.idle":"2026-08-02T08:14:53.811427Z","shell.execute_reply.started":"2026-08-02T08:14:52.652668Z","shell.execute_reply":"2026-08-02T08:14:53.810421Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_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,\n                         batch_size=CFG.batch_size, repeat=True,\n                         shuffle=True, augment=True, cache=True)\n\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,\n                         batch_size=CFG.batch_size, repeat=False,\n                         shuffle=False, augment=False, cache=True)\nprint(\"Datasets built successfully\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:14:53.812661Z","iopub.execute_input":"2026-08-02T08:14:53.813992Z","iopub.status.idle":"2026-08-02T08:14:54.645411Z","shell.execute_reply.started":"2026-08-02T08:14:53.813963Z","shell.execute_reply":"2026-08-02T08:14:54.644633Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import keras_cv\nimport keras\nprint(\"Keras version:\", keras.__version__)\nprint(\"KerasCV version:\", keras_cv.__version__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:14:54.646299Z","iopub.execute_input":"2026-08-02T08:14:54.646561Z","iopub.status.idle":"2026-08-02T08:14:54.651760Z","shell.execute_reply.started":"2026-08-02T08:14:54.646537Z","shell.execute_reply":"2026-08-02T08:14:54.650790Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"##  Analysis 2: What do the spectrograms look like?\n### A spectrogram converts EEG time signals into frequency-time images\n### This is the same visualization used by IONM specialists during surgery\n### Brighter areas = stronger brain activity at that frequency and time","metadata":{}},{"cell_type":"code","source":"imgs, tars = next(iter(train_ds))\nnum_imgs = 8\nplt.figure(figsize=(16, 10))\nfor i in range(num_imgs):\n    plt.subplot(2, 4, i + 1)\n    img = imgs[i].numpy()[..., 0]\n    img -= img.min()\n    img /= img.max() + 1e-4\n    tar = CFG.label2name[np.argmax(tars[i].numpy())]\n    plt.imshow(img, cmap='viridis')\n    plt.title(f\"Pattern: {tar}\", fontsize=11, fontweight='bold',\n              color='red' if tar=='Seizure' else 'black')\n    plt.axis('off')\n\nplt.suptitle('EEG Spectrograms — Each image is 50 seconds of brain activity\\n'\n             'These are the same patterns IONM monitors during surgery',\n             fontsize=12, fontweight='bold')\nplt.tight_layout()\nplt.savefig('spectrograms.png', dpi=150)\nplt.show()\nprint(\"Each pattern type has visually distinct spectrogram shapes\")\nprint(\"Seizures show high frequency bursts — visible as bright horizontal bands\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:14:54.664235Z","iopub.execute_input":"2026-08-02T08:14:54.664601Z","iopub.status.idle":"2026-08-02T08:14:59.293654Z","shell.execute_reply.started":"2026-08-02T08:14:54.664575Z","shell.execute_reply":"2026-08-02T08:14:59.290721Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"LOSS = keras.losses.KLDivergence()\n\nmodel = keras_cv.models.ImageClassifier.from_preset(\n    CFG.preset, num_classes=CFG.num_classes\n)\nmodel.compile(optimizer=keras.optimizers.Adam(learning_rate=1e-4), loss=LOSS)\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:14:59.295184Z","iopub.execute_input":"2026-08-02T08:14:59.296172Z","iopub.status.idle":"2026-08-02T08:15:05.555591Z","shell.execute_reply.started":"2026-08-02T08:14:59.296124Z","shell.execute_reply":"2026-08-02T08:15:05.554686Z"}},"outputs":[],"execution_count":null},{"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    def lrfn(epoch):\n        if epoch < lr_ramp_ep:\n            lr = (lr_max - lr_start) / lr_ramp_ep * epoch + lr_start\n        elif epoch < lr_ramp_ep + lr_sus_ep:\n            lr = lr_max\n        elif mode == 'exp':\n            lr = (lr_max - lr_min) * lr_decay**(epoch - lr_ramp_ep - lr_sus_ep) + lr_min\n        elif mode == 'step':\n            lr = lr_max * lr_decay**((epoch - lr_ramp_ep - lr_sus_ep) // 2)\n        elif mode == 'cos':\n            decay_total_epochs = epochs - lr_ramp_ep - lr_sus_ep + 3\n            decay_epoch_index = epoch - lr_ramp_ep - lr_sus_ep\n            phase = math.pi * decay_epoch_index / decay_total_epochs\n            lr = (lr_max - lr_min) * 0.5 * (1 + math.cos(phase)) + lr_min\n        return lr\n    if plot:\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')\n        plt.ylabel('lr')\n        plt.title('Learning Rate Schedule')\n        plt.show()\n    return keras.callbacks.LearningRateScheduler(lrfn, verbose=False)\n\nlr_cb = get_lr_callback(CFG.batch_size, mode=CFG.lr_mode, epochs=CFG.epochs, plot=True)\nckpt_cb = keras.callbacks.ModelCheckpoint(\"best_model.keras\",\n                                          monitor='val_loss',\n                                          save_best_only=True,\n                                          save_weights_only=False,\n                                          mode='min',\n                                          initial_value_threshold=np.inf)\n\nprint(\"Starting training...\")\nhistory = model.fit(\n    train_ds,\n    epochs=CFG.epochs,\n    callbacks=[lr_cb, ckpt_cb],\n    steps_per_epoch=len(train_df)//CFG.batch_size,\n    validation_data=valid_ds,\n    verbose=CFG.verbose\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:15:05.556876Z","iopub.execute_input":"2026-08-02T08:15:05.557240Z","iopub.status.idle":"2026-08-02T08:35:43.605971Z","shell.execute_reply.started":"2026-08-02T08:15:05.557215Z","shell.execute_reply":"2026-08-02T08:35:43.604943Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"##  Analysis 3: Training Results\n### I am plotting the training history to check for overfitting\n### If validation loss keeps increasing while training loss drops = overfitting\n### Good model = both losses decrease together","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(10, 4))\nplt.plot(history.history['loss'], label='Training Loss', \n         color='blue', marker='o')\nplt.plot(history.history['val_loss'], label='Validation Loss', \n         color='red', marker='o')\nplt.title('Training vs Validation Loss\\n(Checking for Overfitting)',\n          fontweight='bold')\nplt.xlabel('Epoch')\nplt.ylabel('KL Divergence Loss')\nplt.legend()\nplt.grid(True, alpha=0.3)\nplt.tight_layout()\nplt.savefig('training_history.png', dpi=150)\nplt.show()\n\nfinal_train = history.history['loss'][-1]\nfinal_val = history.history['val_loss'][-1]\nprint(f\"Final Training Loss: {final_train:.4f}\")\nprint(f\"Final Validation Loss: {final_val:.4f}\")\n\nif final_val < final_train * 1.3:\n    print(\"Model is NOT overfitting — good generalization\")\nelse:\n    print(\"Model may be overfitting — validation loss too high\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:35:43.607180Z","iopub.execute_input":"2026-08-02T08:35:43.607616Z","iopub.status.idle":"2026-08-02T08:35:44.017868Z","shell.execute_reply.started":"2026-08-02T08:35:43.607591Z","shell.execute_reply":"2026-08-02T08:35:44.017023Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_weights(\"best_model.keras\")\n\ntest_paths = test_df.spec2_path.values\ntest_ds = build_dataset(test_paths,\n                        batch_size=min(CFG.batch_size, len(test_df)),\n                        repeat=False, shuffle=False,\n                        cache=False, augment=False)\n\npreds = model.predict(test_ds)\n\npred_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)\nprint(\"Submission created successfully\")\nsub_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:35:44.019004Z","iopub.execute_input":"2026-08-02T08:35:44.019288Z","iopub.status.idle":"2026-08-02T08:35:58.082689Z","shell.execute_reply.started":"2026-08-02T08:35:44.019264Z","shell.execute_reply":"2026-08-02T08:35:58.081784Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## My Conclusions\n\n### What I learned:\n1. EEG signals converted to spectrograms become image classification problems\n2. Transfer learning with EfficientNetV2 works well on medical signal data\n3. Class imbalance is a real challenge — Seizure class needs special attention\n4. KL Divergence loss is better than cross entropy for soft probability labels\n\nThe 6 patterns I classified here are exactly what contributed in real time during spinal and brain surgeries. This model\n\n### What I would improve next:\n- Try ensemble of multiple EfficientNet variants\n- Add EEG channel-level features alongside spectrograms\n- Apply class weighting to improve Seizure detection specifically\n- Explore transformer based models for time series EEG","metadata":{}},{"cell_type":"code","source":"sub_df.to_csv(\"submission.csv\", index=False)\nprint(\"Submission created successfully\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-02T08:35:58.084038Z","iopub.execute_input":"2026-08-02T08:35:58.084437Z","iopub.status.idle":"2026-08-02T08:35:58.091572Z","shell.execute_reply.started":"2026-08-02T08:35:58.084413Z","shell.execute_reply":"2026-08-02T08:35:58.090417Z"}},"outputs":[],"execution_count":null}]}