{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":59093,"databundleVersionId":7469972},{"sourceType":"datasetVersion","sourceId":16054636,"datasetId":10295827,"databundleVersionId":17022135},{"sourceType":"datasetVersion","sourceId":16056318,"datasetId":10296884,"databundleVersionId":17023930}],"dockerImageVersionId":31329,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader\n\nimport timm","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-02T13:15:18.180865Z","iopub.execute_input":"2026-05-02T13:15:18.181733Z","iopub.status.idle":"2026-05-02T13:15:18.186726Z","shell.execute_reply.started":"2026-05-02T13:15:18.181699Z","shell.execute_reply":"2026-05-02T13:15:18.185740Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_single_parquet(file_path):\n    data = pd.read_parquet(file_path).ffill().fillna(0)\n    data.drop(columns=['time'], inplace=True, errors='ignore')\n\n    # Clip values before log transform\n    data = data.clip(lower=np.float32(np.exp(-4)),\n                     upper=np.float32(np.exp(7)),\n                     axis=0)\n\n    # log transform\n    data = np.log(data)\n\n    # normalize values\n    data_vals = data.values\n    data_vals = (data_vals - data_vals.mean()) / (data_vals.std() + 1e-8)\n    \n    return data_vals","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-02T13:15:18.188517Z","iopub.execute_input":"2026-05-02T13:15:18.188872Z","iopub.status.idle":"2026-05-02T13:15:18.204657Z","shell.execute_reply.started":"2026-05-02T13:15:18.188843Z","shell.execute_reply":"2026-05-02T13:15:18.203966Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TestDataset(torch.utils.data.Dataset):\n    def __init__(self, df, spec_dir):\n        self.df = df\n        self.spec_dir = spec_dir\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n            row = self.df.iloc[idx]\n            spec_id = row[\"spectrogram_id\"]\n            eeg_id = row[\"eeg_id\"]\n\n            # read data\n            file_path = f\"{self.spec_dir}/{spec_id}.parquet\"\n            data_vals = preprocess_single_parquet(file_path)\n            \n            # convert to \n            img = torch.FloatTensor(data_vals.copy())\n            \n            # ensure consistent row number \n            if img.shape[0] < 300:\n                padding = 300 - img.shape[0]\n                img = F.pad(img, (0, 0, 0, padding), \"constant\", 0)\n            else:\n                img = img[:300, :]\n    \n            # copy across 3 rgb channels\n            img = img.T.repeat(3, 1, 1) \n            \n            return img, eeg_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-02T13:15:18.205553Z","iopub.execute_input":"2026-05-02T13:15:18.205874Z","iopub.status.idle":"2026-05-02T13:15:18.225931Z","shell.execute_reply.started":"2026-05-02T13:15:18.205843Z","shell.execute_reply":"2026-05-02T13:15:18.225387Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\ndef instantiate_custom_convnext(path, device, model_name='convnext_tiny'):\n    # instantiate base convnext model\n    model = timm.create_model(model_name, pretrained=False)\n\n    # classification head to match competition\n    num_ftrs = model.head.fc.in_features\n    model.head.fc = nn.Sequential(\n        nn.Dropout(p=0.5), \n        nn.Linear(num_ftrs, 6)\n    )\n\n    #load model weights\n    state_dict = torch.load(path, map_location=device)\n    model.load_state_dict(state_dict)\n\n    # move to gpu and put in eval mode\n    model.to(device)\n    model.eval()\n    \n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-02T13:15:18.226724Z","iopub.execute_input":"2026-05-02T13:15:18.227465Z","iopub.status.idle":"2026-05-02T13:15:18.240600Z","shell.execute_reply.started":"2026-05-02T13:15:18.227430Z","shell.execute_reply":"2026-05-02T13:15:18.239751Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Kaggle paths\nTEST_CSV = \"/kaggle/input/competitions/hms-harmful-brain-activity-classification/test.csv\"\nTEST_SPEC_DIR = \"/kaggle/input/competitions/hms-harmful-brain-activity-classification/test_spectrograms\"\nSAMPLE_SUB = \"/kaggle/input/competitions/hms-harmful-brain-activity-classification/sample_submission.csv\"\n\n\n# load metadata\ntest_df = pd.read_csv(TEST_CSV)\nsubmission_df = pd.read_csv(SAMPLE_SUB)\n\n# instantiate Dataset and DataLoader\ntest_dataset = TestDataset(test_df, TEST_SPEC_DIR)\ntest_dataloader = DataLoader(test_dataset, batch_size=64, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-02T13:15:18.242103Z","iopub.execute_input":"2026-05-02T13:15:18.242484Z","iopub.status.idle":"2026-05-02T13:15:18.265183Z","shell.execute_reply.started":"2026-05-02T13:15:18.242428Z","shell.execute_reply":"2026-05-02T13:15:18.264388Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Convnext ensemble\nmodel_paths = [\n    \"/kaggle/input/datasets/jarodkelly/convnext-ensemble/convnext_tiny_only_best_model_3channel_1.pth\",\n    \"/kaggle/input/datasets/jarodkelly/convnext-ensemble/convnext_tiny_only_best_model_3channel_2.pth\",\n    \"/kaggle/input/datasets/jarodkelly/convnext-ensemble/convnext_tiny_only_best_model_3channel_3.pth\",\n    \"/kaggle/input/datasets/jarodkelly/convnext-ensemble/convnext_tiny_only_best_model_3channel_4.pth\",\n    \"/kaggle/input/datasets/jarodkelly/convnext-ensemble/convnext_tiny_only_best_model_3channel_5.pth\",\n]\n\n    \n#store the accumulated predictions: shape [num_test_samples, 6_classes]\nfinal_preds = np.zeros((len(test_df), 6))\n\nall_eeg_ids = test_df['eeg_id'].values\n\n# iterate through each fold\nfor fold, path in enumerate(model_paths):\n    print(f\"Running inference for Fold {fold}...\")\n    \n    model = instantiate_custom_convnext(path, device) \n\n    fold_preds = []\n\n    with torch.no_grad():\n        for X, eeg_id in test_dataloader:\n            X = X.to(device)\n            logits = model(X)\n            probs = F.softmax(logits, dim=1)\n            \n            fold_preds.append(probs.cpu().numpy())\n\n    # Add this fold's predictions to the total\n    final_preds += np.vstack(fold_preds)\n    \n    # Clean up memory\n    del model\n    torch.cuda.empty_cache()\n\n#average the predictions\nfinal_preds /= len(model_paths)\n\n#format final predictions\ntarget_cols = ['seizure_vote', 'lpd_vote', 'gpd_vote', 'lrda_vote', 'grda_vote', 'other_vote']\npred_df = pd.DataFrame(final_preds, columns=target_cols)\npred_df['eeg_id'] = all_eeg_ids\n\nsubmission = pd.merge(submission_df[['eeg_id']], pred_df, on='eeg_id', how='left')\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"Submission file saved!\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-02T13:15:18.266119Z","iopub.execute_input":"2026-05-02T13:15:18.266499Z","iopub.status.idle":"2026-05-02T13:15:30.338509Z","shell.execute_reply.started":"2026-05-02T13:15:18.266474Z","shell.execute_reply":"2026-05-02T13:15:30.337417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-02T13:15:37.385470Z","iopub.execute_input":"2026-05-02T13:15:37.386099Z","iopub.status.idle":"2026-05-02T13:15:37.410070Z","shell.execute_reply.started":"2026-05-02T13:15:37.386065Z","shell.execute_reply":"2026-05-02T13:15:37.409381Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}