{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":59093,"databundleVersionId":7469972,"sourceType":"competition"},{"sourceId":13831,"sourceType":"modelInstanceVersion","modelInstanceId":11450}],"dockerImageVersionId":30665,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Setup","metadata":{}},{"cell_type":"code","source":"import warnings\nimport timm\nimport io\nimport joblib\nfrom tqdm import tqdm\nfrom fastai.vision.all import *\nfrom fastcore.parallel import *\n\npath = Path('/kaggle/input/hms-harmful-brain-activity-classification')\n\npath.ls()","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2024-03-05T04:46:52.821553Z","iopub.execute_input":"2024-03-05T04:46:52.822408Z","iopub.status.idle":"2024-03-05T04:46:52.833459Z","shell.execute_reply.started":"2024-03-05T04:46:52.822368Z","shell.execute_reply":"2024-03-05T04:46:52.832406Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Background\n\nIn this notebook I will import my resnet34 model that was fine-tuned [in this training notebook](https://www.kaggle.com/code/vishalbakshi/hms-hbac-fastai-resnet34-starter-train) on competition data.\n\nI will be submitting this notebook as part of the competition. Since I only exported the model and not the whole fastai `Learner` object, I'll have to recreate the `DataLoaders` object, which means I'll have to recreate the steps to convert the training and test spectrogram parquet files into images.","metadata":{}},{"cell_type":"markdown","source":"## Converting Training and Test Data to Images","metadata":{}},{"cell_type":"markdown","source":"Here are my functions which take the parquet data and average them across the four brain regions to create a single averaged spectrogram `DataFrame`:","metadata":{}},{"cell_type":"code","source":"def remove_col_prefix(df):\n    df.columns = df.columns.str.replace(r'^[A-Z]+_', '', regex=True)\n    return df\n\ndef avg_sgram(sgram_path):\n    # read the parquet file and separate into four DataFrames\n    sample_spect = pd.read_parquet(sgram_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    # concanate the four DataFrames with column prefixes removed\n    sgram_avg_df = pd.concat([remove_col_prefix(df) for df in split_spect.values()])\n    sgram_avg_df = sgram_avg_df.groupby(sgram_avg_df.index).mean()\n    \n    return sgram_avg_df","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:48:39.629654Z","iopub.execute_input":"2024-03-05T04:48:39.630596Z","iopub.status.idle":"2024-03-05T04:48:39.640037Z","shell.execute_reply.started":"2024-03-05T04:48:39.630552Z","shell.execute_reply":"2024-03-05T04:48:39.639036Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I'll create temporary folders to store the images:","metadata":{}},{"cell_type":"code","source":"# create temporary folders to hold spectrograms\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":{"execution":{"iopub.status.busy":"2024-03-05T04:46:03.511976Z","iopub.execute_input":"2024-03-05T04:46:03.512701Z","iopub.status.idle":"2024-03-05T04:46:03.519139Z","shell.execute_reply.started":"2024-03-05T04:46:03.512666Z","shell.execute_reply":"2024-03-05T04:46:03.518005Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"And the `process_spec` function which take the `parquet` data and saves it as an image:","metadata":{}},{"cell_type":"code","source":"def process_spec(spec_id, split=\"train\"):\n    # take the average value of four regions' spectrogram data\n    data = avg_sgram(path/f'{split}_spectrograms'/f'{spec_id}.parquet')\n    \n    # replace NA with 0\n    data = data.fillna(0)\n    \n    # convert DataFrame to array\n    data = data.values[:, 1:]\n    data = data.astype(\"float32\")\n    \n    # convert array to PILImage and save\n    im = PILImage.create(Image.fromarray((data * 255).astype(np.uint8)))\n    im.save(f\"{SPEC_DIR}/{split}_spectrograms/{spec_id}.png\")","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:46:29.306564Z","iopub.execute_input":"2024-03-05T04:46:29.306902Z","iopub.status.idle":"2024-03-05T04:46:29.316549Z","shell.execute_reply.started":"2024-03-05T04:46:29.306876Z","shell.execute_reply":"2024-03-05T04:46:29.315566Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I'll load the training data so I can get all of the `spectrogram_id` values:","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(path/'train.csv')\ndf.head(3)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:46:40.417025Z","iopub.execute_input":"2024-03-05T04:46:40.417387Z","iopub.status.idle":"2024-03-05T04:46:40.711956Z","shell.execute_reply.started":"2024-03-05T04:46:40.417359Z","shell.execute_reply":"2024-03-05T04:46:40.710820Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"spec_ids = df[\"spectrogram_id\"].unique()","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:46:43.508829Z","iopub.execute_input":"2024-03-05T04:46:43.509366Z","iopub.status.idle":"2024-03-05T04:46:43.520510Z","shell.execute_reply.started":"2024-03-05T04:46:43.509327Z","shell.execute_reply":"2024-03-05T04:46:43.519371Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(spec_ids)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:46:43.995835Z","iopub.execute_input":"2024-03-05T04:46:43.996674Z","iopub.status.idle":"2024-03-05T04:46:44.003369Z","shell.execute_reply.started":"2024-03-05T04:46:43.996633Z","shell.execute_reply":"2024-03-05T04:46:44.002246Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"And then save them as image files using `fastcore.parallel` which takes about four and a half minutes.","metadata":{}},{"cell_type":"code","source":"warnings.filterwarnings(\"ignore\")\nparallel(process_spec, spec_ids, split='train', n_workers=4)\nwarnings.filterwarnings(\"default\")","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:49:27.990171Z","iopub.execute_input":"2024-03-05T04:49:27.990896Z","iopub.status.idle":"2024-03-05T04:53:37.380841Z","shell.execute_reply.started":"2024-03-05T04:49:27.990853Z","shell.execute_reply":"2024-03-05T04:53:37.379913Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I'll load a training spectrogram image to make sure:","metadata":{}},{"cell_type":"code","source":"PILImage.create(Path('/tmp/dataset/hms-hbac/train_spectrograms').ls()[0])","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:57:07.103919Z","iopub.execute_input":"2024-03-05T04:57:07.104616Z","iopub.status.idle":"2024-03-05T04:57:07.150838Z","shell.execute_reply.started":"2024-03-05T04:57:07.104581Z","shell.execute_reply":"2024-03-05T04:57:07.149751Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Looks good! I'll do the test spectrograms next:","metadata":{}},{"cell_type":"code","source":"test_df = pd.read_csv(path/'test.csv')\ntest_df.head(3)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:55:52.295612Z","iopub.execute_input":"2024-03-05T04:55:52.296245Z","iopub.status.idle":"2024-03-05T04:55:52.308585Z","shell.execute_reply.started":"2024-03-05T04:55:52.296215Z","shell.execute_reply":"2024-03-05T04:55:52.307558Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"spec_ids = test_df['spectrogram_id'].unique()\nlen(spec_ids)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:56:03.409254Z","iopub.execute_input":"2024-03-05T04:56:03.410053Z","iopub.status.idle":"2024-03-05T04:56:03.417745Z","shell.execute_reply.started":"2024-03-05T04:56:03.410014Z","shell.execute_reply":"2024-03-05T04:56:03.416610Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"warnings.filterwarnings(\"ignore\")\nparallel(process_spec, spec_ids, split='test', n_workers=4)\nwarnings.filterwarnings(\"default\")","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:56:21.558305Z","iopub.execute_input":"2024-03-05T04:56:21.559148Z","iopub.status.idle":"2024-03-05T04:56:21.857403Z","shell.execute_reply.started":"2024-03-05T04:56:21.559103Z","shell.execute_reply":"2024-03-05T04:56:21.856191Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"PILImage.create(Path('/tmp/dataset/hms-hbac/test_spectrograms').ls()[0])","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:57:20.649169Z","iopub.execute_input":"2024-03-05T04:57:20.649947Z","iopub.status.idle":"2024-03-05T04:57:20.671154Z","shell.execute_reply.started":"2024-03-05T04:57:20.649909Z","shell.execute_reply":"2024-03-05T04:57:20.670210Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Creating the `DataLoaders` Object","metadata":{}},{"cell_type":"markdown","source":"While I'm not training a model, I still need the `DataLoaders` object in order to apply the same transforms to the `test_dl` which I'll use for inference.","metadata":{}},{"cell_type":"code","source":"df['img_path'] = '/tmp/dataset/hms-hbac/train_spectrograms/' + df['spectrogram_id'].astype(str) + '.png'","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:57:28.492644Z","iopub.execute_input":"2024-03-05T04:57:28.493008Z","iopub.status.idle":"2024-03-05T04:57:28.583931Z","shell.execute_reply.started":"2024-03-05T04:57:28.492976Z","shell.execute_reply":"2024-03-05T04:57:28.583057Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.head(3)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:57:29.236281Z","iopub.execute_input":"2024-03-05T04:57:29.236620Z","iopub.status.idle":"2024-03-05T04:57:29.254272Z","shell.execute_reply.started":"2024-03-05T04:57:29.236595Z","shell.execute_reply":"2024-03-05T04:57:29.253162Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Next, I'll create a `DataFrame` with unique `eeg_id` and `spectrogram_id` and average votes for eac","metadata":{}},{"cell_type":"code","source":"cols = ['eeg_id', 'spectrogram_id', 'img_path', 'seizure_vote', 'lpd_vote', 'gpd_vote', 'lrda_vote', 'grda_vote', 'other_vote']\nagg_funcs = {c: 'sum' for c in cols if 'vote' in c}\n\nunique_df = df[cols].groupby(['eeg_id', 'spectrogram_id', 'img_path'], as_index=False).agg(agg_funcs)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:58:13.968564Z","iopub.execute_input":"2024-03-05T04:58:13.968933Z","iopub.status.idle":"2024-03-05T04:58:14.050762Z","shell.execute_reply.started":"2024-03-05T04:58:13.968895Z","shell.execute_reply":"2024-03-05T04:58:14.049787Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I'll set the `target` as the column with the largest number of votes for a given spectrogram.","metadata":{}},{"cell_type":"code","source":"unique_df['target'] = unique_df[[c for c in cols if 'vote' in c]].idxmax(axis=1)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:58:17.024112Z","iopub.execute_input":"2024-03-05T04:58:17.025009Z","iopub.status.idle":"2024-03-05T04:58:17.037132Z","shell.execute_reply.started":"2024-03-05T04:58:17.024974Z","shell.execute_reply":"2024-03-05T04:58:17.036132Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"And finally, an `is_valid` column which indicates whether a row is set aside for the training or validation set---this doesn't matter for inference but I see no need to change the code.","metadata":{}},{"cell_type":"code","source":"train_bool = [False for _ in range(int(0.8 * len(unique_df)))]\nvalid_bool = [True for _ in range(len(unique_df) - int(0.8 * len(unique_df)))]\nis_valid_bool = pd.Series(train_bool + valid_bool).sample(frac=1).reset_index(drop=True)\nlen(is_valid_bool)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:58:38.525652Z","iopub.execute_input":"2024-03-05T04:58:38.526488Z","iopub.status.idle":"2024-03-05T04:58:38.543384Z","shell.execute_reply.started":"2024-03-05T04:58:38.526446Z","shell.execute_reply":"2024-03-05T04:58:38.542204Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"unique_df[\"is_valid\"] = is_valid_bool","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:58:40.335678Z","iopub.execute_input":"2024-03-05T04:58:40.336124Z","iopub.status.idle":"2024-03-05T04:58:40.341773Z","shell.execute_reply.started":"2024-03-05T04:58:40.336074Z","shell.execute_reply":"2024-03-05T04:58:40.340746Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"unique_df.head(3)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:58:40.936682Z","iopub.execute_input":"2024-03-05T04:58:40.937392Z","iopub.status.idle":"2024-03-05T04:58:40.955202Z","shell.execute_reply.started":"2024-03-05T04:58:40.937356Z","shell.execute_reply":"2024-03-05T04:58:40.954129Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now I can create my `DataBlock`. Since I now have paths to images ready to train on, I can use `ImageBlock` for my inputs. I'll also make sure to `Resize` my images so they are 224 x 224 squares.","metadata":{}},{"cell_type":"code","source":"dblock = DataBlock(\n            blocks=(ImageBlock, CategoryBlock),\n            splitter=ColSplitter(),\n            get_x=ColReader('img_path'),\n            get_y=ColReader('target'),\n            item_tfms=Resize(224, method='squish'))\n\ndls = dblock.dataloaders(unique_df)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:58:49.385816Z","iopub.execute_input":"2024-03-05T04:58:49.386204Z","iopub.status.idle":"2024-03-05T04:58:50.888580Z","shell.execute_reply.started":"2024-03-05T04:58:49.386174Z","shell.execute_reply":"2024-03-05T04:58:50.887604Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dls.show_batch(nrows=1, ncols=3)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:58:51.848663Z","iopub.execute_input":"2024-03-05T04:58:51.848995Z","iopub.status.idle":"2024-03-05T04:58:52.829190Z","shell.execute_reply.started":"2024-03-05T04:58:51.848970Z","shell.execute_reply":"2024-03-05T04:58:52.828131Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# check dls vocab\ndls.vocab","metadata":{"execution":{"iopub.status.busy":"2024-03-05T04:58:57.780305Z","iopub.execute_input":"2024-03-05T04:58:57.781230Z","iopub.status.idle":"2024-03-05T04:58:57.788656Z","shell.execute_reply.started":"2024-03-05T04:58:57.781189Z","shell.execute_reply":"2024-03-05T04:58:57.787571Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn = vision_learner(dls, 'resnet34' ,metrics=accuracy, pretrained=False)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T05:04:46.820443Z","iopub.execute_input":"2024-03-05T05:04:46.821235Z","iopub.status.idle":"2024-03-05T05:04:47.220239Z","shell.execute_reply.started":"2024-03-05T05:04:46.821198Z","shell.execute_reply":"2024-03-05T05:04:47.219355Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn.load('/kaggle/input/hms-hbac-resnet34/pytorch/1/1/hms_hbac_resnet34')","metadata":{"execution":{"iopub.status.busy":"2024-03-05T05:04:51.700246Z","iopub.execute_input":"2024-03-05T05:04:51.700937Z","iopub.status.idle":"2024-03-05T05:04:51.875198Z","shell.execute_reply.started":"2024-03-05T05:04:51.700902Z","shell.execute_reply":"2024-03-05T05:04:51.873949Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Finally, I'll get the predictions on the test data and generate a submission.csv file---I'll make sure to add an `img_path` file to `test_df` since that is what the `DataLoaders` object will try to get with `get_x`.","metadata":{}},{"cell_type":"code","source":"test_df['img_path'] = '/tmp/dataset/hms-hbac/test_spectrograms/' + test_df['spectrogram_id'].astype(str) + '.png'","metadata":{"execution":{"iopub.status.busy":"2024-03-05T05:07:16.876515Z","iopub.execute_input":"2024-03-05T05:07:16.876893Z","iopub.status.idle":"2024-03-05T05:07:16.883733Z","shell.execute_reply.started":"2024-03-05T05:07:16.876863Z","shell.execute_reply":"2024-03-05T05:07:16.882621Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tst_dl = learn.dls.test_dl(test_df)\ntst_dl.show_batch()","metadata":{"execution":{"iopub.status.busy":"2024-03-05T05:07:18.882736Z","iopub.execute_input":"2024-03-05T05:07:18.883189Z","iopub.status.idle":"2024-03-05T05:07:18.997096Z","shell.execute_reply.started":"2024-03-05T05:07:18.883152Z","shell.execute_reply":"2024-03-05T05:07:18.996248Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Then, I'll finally get the predictions:","metadata":{}},{"cell_type":"code","source":"probs,_,idxs = learn.get_preds(dl=tst_dl, with_decoded=True)\nprobs_df = pd.DataFrame(probs, columns=dls.vocab)\nprobs_df['eeg_id'] = test_df['eeg_id']\nprobs_df = probs_df[['eeg_id', 'seizure_vote', 'lpd_vote', 'gpd_vote', 'lrda_vote', 'grda_vote', 'other_vote']]\nprobs_df","metadata":{"execution":{"iopub.status.busy":"2024-03-05T05:07:57.628334Z","iopub.execute_input":"2024-03-05T05:07:57.629477Z","iopub.status.idle":"2024-03-05T05:07:58.878806Z","shell.execute_reply.started":"2024-03-05T05:07:57.629443Z","shell.execute_reply":"2024-03-05T05:07:58.877525Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Check that they equal 1:","metadata":{}},{"cell_type":"code","source":"probs_df[['seizure_vote', 'lpd_vote', 'gpd_vote', 'lrda_vote', 'grda_vote', 'other_vote']].sum(axis=1) == 1","metadata":{"execution":{"iopub.status.busy":"2024-03-05T05:08:32.070744Z","iopub.execute_input":"2024-03-05T05:08:32.071695Z","iopub.status.idle":"2024-03-05T05:08:32.083070Z","shell.execute_reply.started":"2024-03-05T05:08:32.071647Z","shell.execute_reply":"2024-03-05T05:08:32.081944Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"And save the submission file:","metadata":{}},{"cell_type":"code","source":"probs_df.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2024-03-05T05:08:59.606481Z","iopub.execute_input":"2024-03-05T05:08:59.607331Z","iopub.status.idle":"2024-03-05T05:08:59.616463Z","shell.execute_reply.started":"2024-03-05T05:08:59.607291Z","shell.execute_reply":"2024-03-05T05:08:59.615341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!head submission.csv","metadata":{"execution":{"iopub.status.busy":"2024-03-05T05:09:00.964940Z","iopub.execute_input":"2024-03-05T05:09:00.965328Z","iopub.status.idle":"2024-03-05T05:09:01.948734Z","shell.execute_reply.started":"2024-03-05T05:09:00.965298Z","shell.execute_reply":"2024-03-05T05:09:01.947484Z"},"trusted":true},"execution_count":null,"outputs":[]}]}