{"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":"gpu","dataSources":[{"sourceId":71549,"databundleVersionId":8561470,"sourceType":"competition"},{"sourceId":6158,"sourceType":"modelInstanceVersion","modelInstanceId":4608,"modelId":2797},{"sourceId":64765,"sourceType":"modelInstanceVersion","modelInstanceId":54020,"modelId":74163},{"sourceId":64795,"sourceType":"modelInstanceVersion","modelInstanceId":54048,"modelId":74163}],"dockerImageVersionId":30733,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# RSNA 2024 Lumbar Spine Degenerative Classification","metadata":{}},{"cell_type":"markdown","source":"## Starter Notebook for Pytorch and Deep learning techniques","metadata":{}},{"cell_type":"markdown","source":"What does this notebook contains?\n\n* Data organized in an understandable and easy to use way\n* A pretrained EfficientNetV2 for inference","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\n\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nfrom matplotlib import animation, rc\nimport seaborn as sns\n\nimport pydicom as dicom # dicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\n\nimport os\nimport random\nimport time\nimport glob\nimport json\nimport collections\nimport torch\nimport torch.nn as nn","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:30:47.562893Z","iopub.execute_input":"2024-09-19T18:30:47.563568Z","iopub.status.idle":"2024-09-19T18:30:52.135122Z","shell.execute_reply.started":"2024-09-19T18:30:47.563533Z","shell.execute_reply":"2024-09-19T18:30:52.134124Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# read data\ntrain_path = '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/'\n\ntrain  = pd.read_csv(train_path + 'train.csv')\nlabel = pd.read_csv(train_path + 'train_label_coordinates.csv')\ntrain_desc  = pd.read_csv(train_path + 'train_series_descriptions.csv')\ntest_desc   = pd.read_csv(train_path + 'test_series_descriptions.csv')\nsub         = pd.read_csv(train_path + 'sample_submission.csv')","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:30:52.136825Z","iopub.execute_input":"2024-09-19T18:30:52.137246Z","iopub.status.idle":"2024-09-19T18:30:52.302869Z","shell.execute_reply.started":"2024-09-19T18:30:52.137220Z","shell.execute_reply":"2024-09-19T18:30:52.301747Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"train shape :\", train.shape)\nprint(\"train label shape :\", label.shape)\nprint(\"train descriptions shape :\", train_desc.shape)\nprint(\"test descriptions shape :\", test_desc.shape)\nprint(\"submission shape :\", sub.shape)","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:30:52.304294Z","iopub.execute_input":"2024-09-19T18:30:52.305124Z","iopub.status.idle":"2024-09-19T18:30:52.311771Z","shell.execute_reply.started":"2024-09-19T18:30:52.305089Z","shell.execute_reply":"2024-09-19T18:30:52.310445Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def number_of_images(directory):\n    return sum([len(files) for r, d, files in os.walk(directory) if any(f.endswith('.dcm') for f in files)])\n\nprint(\"n of training images \\t:\", number_of_images(f'{train_path}/train_images'))\nprint(\"n of test images \\t:\", number_of_images(f'{train_path}/test_images'))","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:30:52.314236Z","iopub.execute_input":"2024-09-19T18:30:52.315221Z","iopub.status.idle":"2024-09-19T18:31:24.246959Z","shell.execute_reply.started":"2024-09-19T18:30:52.315186Z","shell.execute_reply":"2024-09-19T18:31:24.245996Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# values with more than 4 series Id. They still have only 3 types of images.\n# train_desc[train_desc['study_id'] == 10728036]\ntrain_desc[train_desc['study_id'] == 1009445512]","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:31:24.248208Z","iopub.execute_input":"2024-09-19T18:31:24.248583Z","iopub.status.idle":"2024-09-19T18:31:24.272013Z","shell.execute_reply.started":"2024-09-19T18:31:24.248549Z","shell.execute_reply":"2024-09-19T18:31:24.270953Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"label.head()","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:31:24.273289Z","iopub.execute_input":"2024-09-19T18:31:24.273568Z","iopub.status.idle":"2024-09-19T18:31:24.286573Z","shell.execute_reply.started":"2024-09-19T18:31:24.273544Z","shell.execute_reply":"2024-09-19T18:31:24.285740Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# number of images with at least one annotation\nunique_count = label[['series_id', 'instance_number']].drop_duplicates().shape[0]\nunique_count","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:31:24.287735Z","iopub.execute_input":"2024-09-19T18:31:24.288085Z","iopub.status.idle":"2024-09-19T18:31:24.307157Z","shell.execute_reply.started":"2024-09-19T18:31:24.288052Z","shell.execute_reply":"2024-09-19T18:31:24.306187Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def display_dicom_with_coordinates(study_id, image_paths, label_df, exact_image = True):\n    \n    fig, axs = plt.subplots(1, len(image_paths), figsize=(12,4))\n    for index, (series_id, instance_number, path) in enumerate(image_paths):\n\n        ds = dicom.dcmread(path)\n        axs[index].imshow(ds.pixel_array, cmap=plt.cm.bone)\n        axs[index].set_title(f'Series ID: {series_id}')\n        axs[index].axis('off')\n        \n        filtered_labels = label_df[(label_df['study_id'] == study_id) & (label_df['series_id'] == series_id)]\n        if exact_image:\n            filtered_labels = filtered_labels[filtered_labels['instance_number'] == instance_number]\n            \n        for _, row in filtered_labels.iterrows(): \n            axs[index].plot(row['x'], row['y'], 'ro', markersize = 3)\n    \n    fig.suptitle(f'Study ID: {study_id}')\n    plt.tight_layout()\n    plt.show()\n\nstudy_id = train['study_id'].sample(n=1).iloc[0]\nstudy_folder = f'{train_path}/train_images/{study_id}'\nimage_paths = []\nfor series_id in os.listdir(study_folder): \n    series_folder_path = os.path.join(study_folder, series_id)\n    if not os.path.isdir(series_folder_path) : continue\n    \n    for f in os.listdir(series_folder_path):\n        if f.endswith('.dcm'):\n            file = f\n            break\n            \n    instance_number = int(file.split('.')[0])\n    image_paths.append((int(series_id), instance_number, os.path.join(series_folder_path, file)))\n\ndisplay_dicom_with_coordinates(int(study_id), image_paths, label)","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:31:24.309324Z","iopub.execute_input":"2024-09-19T18:31:24.309603Z","iopub.status.idle":"2024-09-19T18:31:24.954751Z","shell.execute_reply.started":"2024-09-19T18:31:24.309580Z","shell.execute_reply":"2024-09-19T18:31:24.953817Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data Preprocessing","metadata":{}},{"cell_type":"code","source":"train.head()","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:31:24.955961Z","iopub.execute_input":"2024-09-19T18:31:24.956293Z","iopub.status.idle":"2024-09-19T18:31:24.979899Z","shell.execute_reply.started":"2024-09-19T18:31:24.956264Z","shell.execute_reply":"2024-09-19T18:31:24.978998Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# converting the data to a long format with each study id having 25 rows\ndef reshape_row(row):\n    data = {'study_id':[], 'condition':[], 'level':[], 'severity':[]}\n\n    for column, value in row.items():\n        if column not in ['study_id','series_id', 'instance_number', 'x', 'y', 'series_description']: # tidy elements : study_id는 그대로\n            parts = column.split('_') # spinal_canal_stenosis_l1_l2 -> ['spinal', 'canal', 'stenosis', 'l1', 'l2']\n            condition = ' '.join([word.capitalize() for word in parts[:-2]]) # ['spinal', 'canal', 'stenosis'] -> 'Spinal Canal Stenosis'\n            level = parts[-2].capitalize() + '/' + parts[-1].capitalize() # ['l1', 'l2'] -> 'L1/L2'\n            data['study_id'].append(row['study_id'])\n            data['condition'].append(condition)\n            data['level'].append(level)\n            data['severity'].append(value)\n\n    return pd.DataFrame(data)\n\nnew_train_df = pd.concat([reshape_row(row) for _, row in train.iterrows()], ignore_index=True)\n\n#validate if the size of long form is number of study id multiplied by 25\nassert new_train_df.shape[0] == train.shape[0] * 25, 'Some issues with format conversion!'\nnew_train_df.head(26)","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:31:24.981264Z","iopub.execute_input":"2024-09-19T18:31:24.981576Z","iopub.status.idle":"2024-09-19T18:31:26.414086Z","shell.execute_reply.started":"2024-09-19T18:31:24.981550Z","shell.execute_reply":"2024-09-19T18:31:26.413174Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now to see if we have specific images for each type.","metadata":{}},{"cell_type":"code","source":"# new_train_df, label, train_desc\nprint(\"\\nColumns in new_train_df:\")\nprint(\", \".join(new_train_df.columns))\n\nprint(\"\\nColumns in label:\")\nprint(\", \".join(label.columns))\n\nprint(\"\\nColumns in train_desc:\")\nprint(\", \".join(train_desc.columns))\n\nprint(\"\\nColumns in sub:\")\nprint(\", \".join(sub.columns))","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:31:26.415347Z","iopub.execute_input":"2024-09-19T18:31:26.415627Z","iopub.status.idle":"2024-09-19T18:31:26.421227Z","shell.execute_reply.started":"2024-09-19T18:31:26.415603Z","shell.execute_reply":"2024-09-19T18:31:26.420386Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pd.crosstab([new_train_df['condition'], new_train_df['level']], new_train_df['severity'], margins=True)","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:34:22.738521Z","iopub.execute_input":"2024-09-19T18:34:22.739386Z","iopub.status.idle":"2024-09-19T18:34:22.841528Z","shell.execute_reply.started":"2024-09-19T18:34:22.739349Z","shell.execute_reply":"2024-09-19T18:34:22.840586Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"To get more information about each type of image and what that image predicts, lets merge this with label and train_series_desc","metadata":{}},{"cell_type":"code","source":"label.head()","metadata":{"execution":{"iopub.status.busy":"2024-09-19T19:00:13.066639Z","iopub.execute_input":"2024-09-19T19:00:13.066991Z","iopub.status.idle":"2024-09-19T19:00:13.080028Z","shell.execute_reply.started":"2024-09-19T19:00:13.066966Z","shell.execute_reply":"2024-09-19T19:00:13.078996Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"merged_df = pd.merge(new_train_df, label, on=['study_id', 'condition', 'level'], how='inner')\nfinal_merged_df = pd.merge(merged_df, train_desc, on=['series_id','study_id'], how='inner')\nprint(final_merged_df.shape)\nfinal_merged_df.head(25)","metadata":{"execution":{"iopub.status.busy":"2024-09-19T19:00:29.860707Z","iopub.execute_input":"2024-09-19T19:00:29.861068Z","iopub.status.idle":"2024-09-19T19:00:29.940439Z","shell.execute_reply.started":"2024-09-19T19:00:29.861040Z","shell.execute_reply":"2024-09-19T19:00:29.939531Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pd.crosstab(final_merged_df['condition'], final_merged_df['series_description'])","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:34:19.566495Z","iopub.execute_input":"2024-09-19T18:34:19.567501Z","iopub.status.idle":"2024-09-19T18:34:19.601823Z","shell.execute_reply.started":"2024-09-19T18:34:19.567465Z","shell.execute_reply":"2024-09-19T18:34:19.600928Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Since there is a clear pattern of Conditions being Identified using a specific series, lets delete the 5 series data that used sagittal T1 for predicting Spinal Canal Stenosis as exception.","metadata":{}},{"cell_type":"code","source":"# pd.set_option('display.max_rows', None)\n# pd.crosstab([final_merged_df['condition'], final_merged_df['level'], final_merged_df['severity']], final_merged_df['series_description'], margins=True)","metadata":{"execution":{"iopub.status.busy":"2024-09-19T19:02:08.939146Z","iopub.execute_input":"2024-09-19T19:02:08.939586Z","iopub.status.idle":"2024-09-19T19:02:08.943611Z","shell.execute_reply.started":"2024-09-19T19:02:08.939557Z","shell.execute_reply":"2024-09-19T19:02:08.942608Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"final_merged_df['severity'].value_counts()","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:34:25.535135Z","iopub.execute_input":"2024-09-19T18:34:25.535536Z","iopub.status.idle":"2024-09-19T18:34:25.552064Z","shell.execute_reply.started":"2024-09-19T18:34:25.535502Z","shell.execute_reply":"2024-09-19T18:34:25.551034Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_no_coordinates = final_merged_df.drop(columns = ['x', 'y', 'instance_number'])\ndf_no_coordinates.head()","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:37:16.784560Z","iopub.execute_input":"2024-09-19T18:37:16.784904Z","iopub.status.idle":"2024-09-19T18:37:16.799891Z","shell.execute_reply.started":"2024-09-19T18:37:16.784878Z","shell.execute_reply":"2024-09-19T18:37:16.799018Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"all_study_series_id = df_no_coordinates.drop_duplicates(subset=['study_id', 'condition', 'series_id']).reset_index(drop=True)\nprint(all_study_series_id.shape)\nall_study_series_id.head()","metadata":{"execution":{"iopub.status.busy":"2024-09-19T18:57:19.341735Z","iopub.execute_input":"2024-09-19T18:57:19.342381Z","iopub.status.idle":"2024-09-19T18:57:19.367581Z","shell.execute_reply.started":"2024-09-19T18:57:19.342351Z","shell.execute_reply":"2024-09-19T18:57:19.366733Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"final_merged_df['row_id'] = (\n    final_merged_df['study_id'].astype(str) + '_' +\n    final_merged_df['condition'].str.lower().str.replace(' ', '_') + '_' +\n    final_merged_df['level'].str.lower().str.replace('/', '_')\n)\n\n# 행마다 그에 해당하는 이미지 경로를 담은 열을 생성\nfinal_merged_df['image_path'] = (\n    f'{train_path}/train_images/' + \n    final_merged_df['study_id'].astype(str) + '/' +\n    final_merged_df['series_id'].astype(str) + '/' +\n    final_merged_df['instance_number'].astype(str) + '.dcm'\n)\n\nfinal_merged_df['severity'] = final_merged_df['severity'].map({'Normal/Mild': 'normal_mild', 'Moderate': 'moderate', 'Severe': 'severe'})\n\n# Note: Check image path, since there's 1 instance id, for 1 image, but there's many more images other than the ones labelled in the instance ID. \n\n# Display the updated dataframe\nprint(final_merged_df.shape)\nprint(final_merged_df.iloc[0]['image_path'])\nfinal_merged_df.head(5)","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:02:22.485536Z","iopub.execute_input":"2024-09-18T17:02:22.485958Z","iopub.status.idle":"2024-09-18T17:02:22.731131Z","shell.execute_reply.started":"2024-09-18T17:02:22.485926Z","shell.execute_reply":"2024-09-18T17:02:22.730092Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Test Description","metadata":{}},{"cell_type":"code","source":"test_desc","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:02:22.732184Z","iopub.execute_input":"2024-09-18T17:02:22.732473Z","iopub.status.idle":"2024-09-18T17:02:22.740598Z","shell.execute_reply.started":"2024-09-18T17:02:22.732449Z","shell.execute_reply":"2024-09-18T17:02:22.739675Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# test 이미지 파일이 담긴 폴더의 경로\nbase_path = os.path.join(train_path, 'test_images')\n\n# 읽어들인 행에서 'study_id', 'series_id' 컬럼값을 불러와 이미지 파일이 있는 series 폴더 경로를 생성\n# 해당 경로가 존재하는 경우, 폴더 내에 존재하는 파일들의 경로를 리스트 형태로 반환\ndef get_image_paths(row):\n    series_folder_path = os.path.join(base_path, str(row['study_id']), str(row['series_id']))\n    if os.path.exists(series_folder_path):\n        return [os.path.join(series_folder_path, f) for f in os.listdir(series_folder_path) if f.endswith('.dcm')]\n    else:\n        print(f\"Series path does not exist: {series_folder_path}\")  # 이미지 경로가 존재하지 않는 경우 출력\n    return []\n\n# series_description으로부터 condition을 할당\n# 'side'에 좌우구분이 있는 경우('left', 'right')와 없는 경우('both')를 구분\ncondition_df = pd.DataFrame([\n    {'series_description': 'Sagittal T1', 'condition': 'left_neural_foraminal_narrowing', 'side': 'left'},\n    {'series_description': 'Sagittal T1', 'condition': 'right_neural_foraminal_narrowing', 'side': 'right'},\n    {'series_description': 'Axial T2', 'condition': 'left_subarticular_stenosis', 'side': 'left'},\n    {'series_description': 'Axial T2', 'condition': 'right_subarticular_stenosis', 'side': 'right'},\n    {'series_description': 'Sagittal T2/STIR', 'condition': 'spinal_canal_stenosis', 'side': 'both'}\n])\n\nmerged_df = pd.merge(test_desc, condition_df, on='series_description', how='left')\n\n# 'image_paths' 컬럼을 생성해, 해당 행의 'study_id' 및 'series_id'에 속한 모든 이미지 경로의 리스트를 추가\nmerged_df['image_paths'] = merged_df.apply(get_image_paths, axis=1)\n\n# explode 메서드를 통해 하나의 행에 저장된 해당 폴더의 전체 이미지 경로를 각 행으로 확장하고 다듬기\nexpanded_test_desc = merged_df.explode('image_paths') # 이미지 경로 리스트를 각 이미지 경로로 확장하기\nexpanded_test_desc = expanded_test_desc.rename(columns={'image_paths': 'image_path'}) # 'image_paths' 컬럼의 이름을 'image_path'로 변경\nexpanded_test_desc['row_id'] = expanded_test_desc.apply(lambda row: f\"{row['study_id']}_{row['condition']}\", axis=1) # 새로운 'row_id' 컬럼 추가\nexpanded_test_desc = expanded_test_desc.drop(columns=['side']) # 'side' 컬럼 드롭\nexpanded_test_desc  = expanded_test_desc.reset_index(drop=True) # 인덱스 재설정\n\n# 결과 출력\nprint(expanded_test_desc.iloc[0]['image_path'])","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:02:22.741786Z","iopub.execute_input":"2024-09-18T17:02:22.742052Z","iopub.status.idle":"2024-09-18T17:02:22.775234Z","shell.execute_reply.started":"2024-09-18T17:02:22.742028Z","shell.execute_reply":"2024-09-18T17:02:22.774305Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"expanded_test_desc[['series_description', 'condition']].value_counts()","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:02:22.776237Z","iopub.execute_input":"2024-09-18T17:02:22.776548Z","iopub.status.idle":"2024-09-18T17:02:22.787441Z","shell.execute_reply.started":"2024-09-18T17:02:22.776525Z","shell.execute_reply":"2024-09-18T17:02:22.786480Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"expanded_test_desc.head()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data = final_merged_df\ntest_data = expanded_test_desc\n\nprint(f\"\\ntrain data shape: {train_data.shape}\")\nprint(', '.join(train_data.columns))\nprint(f\"\\ntest data shape: {test_data.shape}\")\nprint(', '.join(train_data.columns.intersection(test_data.columns))) #train_data.columns 순서대로 정렬","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:02:22.788583Z","iopub.execute_input":"2024-09-18T17:02:22.789546Z","iopub.status.idle":"2024-09-18T17:02:22.796326Z","shell.execute_reply.started":"2024-09-18T17:02:22.789520Z","shell.execute_reply":"2024-09-18T17:02:22.795358Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# train_data 데이터프레임에 포함된 파일 경로가 실제로 존재하는지 확인\n\n# 주어진 path가 실제 파일 시스템에 존재하는지 확인하는 함수\ndef check_exists(path):\n    return os.path.exists(path)\n\n# 각 row에 대해 study_id에 해당하는 디렉토리가 존재하는지 확인\ndef check_study_id(row):\n    study_id = row['study_id']\n    path = f'{train_path}/train_images/{study_id}'\n    return check_exists(path)\n\n# 각 row에 대해 series_id에 해당하는 디렉토리가 존재하는지 확인\ndef check_series_id(row):\n    study_id = row['study_id']\n    series_id = row['series_id']\n    path = f'{train_path}/train_images/{study_id}/{series_id}'\n    return check_exists(path)\n\n# 각 row에 대해 image_path에 해당하는 파일이 존재하는지 확인\ndef check_image_exists(row):\n    image_path = row['image_path']\n    return check_exists(image_path)\n\n# 각 계층의 디렉토리/파일의 존재여부를 확인하여 새로운 컬럼 'study_id_exists', 'series_id_exists', 'image_exists'에 추가\ntrain_data['study_id_exists'] = train_data.apply(check_study_id, axis=1)\ntrain_data['series_id_exists'] = train_data.apply(check_series_id, axis=1)\ntrain_data['image_exists'] = train_data.apply(check_image_exists, axis=1)\n\n# study_id_exists, series_id_exists, image_exists 컬럼이 모두 True인 행만 남김\ntrain_data = train_data[(train_data['study_id_exists']) & (train_data['series_id_exists']) & (train_data['image_exists'])]\n\nprint(f\"train data shape: {train_data.shape}\")","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:02:22.797445Z","iopub.execute_input":"2024-09-18T17:02:22.797706Z","iopub.status.idle":"2024-09-18T17:02:36.441586Z","shell.execute_reply.started":"2024-09-18T17:02:22.797679Z","shell.execute_reply":"2024-09-18T17:02:36.440605Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for i, v in enumerate(train_data['image_path']):\n    if i > 10:\n        break\n    print(v)","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:05:14.047084Z","iopub.execute_input":"2024-09-18T17:05:14.047476Z","iopub.status.idle":"2024-09-18T17:05:14.053147Z","shell.execute_reply.started":"2024-09-18T17:05:14.047443Z","shell.execute_reply":"2024-09-18T17:05:14.052182Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 이미지 파일을 읽어들인 뒤 MinMax scaling\ndef load_dicom(path):\n    ds = dicom.dcmread(path)\n    img = ds.pixel_array\n    img = (img - np.min(img)) / (np.max(img) - np.min(img) + 1e-8)\n    img = (img * 255).astype(np.uint8)\n    return img\n\n# Load images randomly\nimport random\nimages = []\nrow_ids = []\nselected_indices = random.sample(range(len(train_data)), 2)\nfor i in selected_indices:\n    image = load_dicom(train_data['image_path'][i])\n    images.append(image)\n    row_ids.append(train_data['row_id'][i])\n\n# Plot images\nfig, ax = plt.subplots(1, 2, figsize=(8, 4))\nfor i in range(2):\n    ax[i].imshow(images[i], cmap=plt.cm.bone)\n    ax[i].set_title(f'Row ID: {row_ids[i]}', fontsize=8)\n    ax[i].axis('off')\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:23:59.393007Z","iopub.execute_input":"2024-09-18T17:23:59.393412Z","iopub.status.idle":"2024-09-18T17:23:59.876105Z","shell.execute_reply.started":"2024-09-18T17:23:59.393379Z","shell.execute_reply":"2024-09-18T17:23:59.875062Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 결측치 처리\nprint(train_data.isna().sum(), \"\\n\")\nprint(pd.crosstab(train_data['condition'], train_data['severity'].isna()))\ntrain_data = train_data.dropna()","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:00.462094Z","iopub.execute_input":"2024-09-18T17:24:00.462940Z","iopub.status.idle":"2024-09-18T17:24:00.553990Z","shell.execute_reply.started":"2024-09-18T17:24:00.462906Z","shell.execute_reply":"2024-09-18T17:24:00.553111Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data.head()","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:01.134057Z","iopub.execute_input":"2024-09-18T17:24:01.134710Z","iopub.status.idle":"2024-09-18T17:24:01.151447Z","shell.execute_reply.started":"2024-09-18T17:24:01.134679Z","shell.execute_reply":"2024-09-18T17:24:01.150481Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Making Custom Dataset","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.optim.lr_scheduler as lr_scheduler\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nfrom sklearn.model_selection import train_test_split\nfrom tqdm import tqdm","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:01.860207Z","iopub.execute_input":"2024-09-18T17:24:01.861074Z","iopub.status.idle":"2024-09-18T17:24:01.865822Z","shell.execute_reply.started":"2024-09-18T17:24:01.861038Z","shell.execute_reply":"2024-09-18T17:24:01.864711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Define a custom dataset class\nclass CustomDataset(Dataset):\n    def __init__(self, dataframe, transform=None):\n        self.dataframe = dataframe\n        self.transform = transform\n        self.level_map = {'L1/L2': 0, 'L2/L3': 1, 'L3/L4': 2, 'L4/L5': 3, 'L5/S1': 4}  # 레벨 인덱스 맵\n        self.severity_map = {'normal_mild': 0, 'moderate': 1, 'severe': 2}  # 심각도 맵핑\n\n    def __len__(self):\n        return len(self.dataframe)\n\n    def __getitem__(self, index):\n        image_path = self.dataframe['image_path'][index]\n        image = load_dicom(image_path)  # DICOM 이미지를 로드하는 함수\n        level = self.dataframe['level'][index]  # 레벨 정보 추출\n        severity = self.dataframe['severity'][index]  # 심각도 정보 추출\n        \n        label = np.zeros(5, dtype=int)  # 5개의 원소를 가지는 ndarray 초기화\n        label[self.level_map[level]] = self.severity_map[severity]  # 레벨 인덱스에 심각도 매핑\n        \n        if self.transform:\n            image = self.transform(image)  # 필요한 경우 이미지 변환 적용\n\n        # label을 torch 텐서로 변환\n        label = torch.tensor(label, dtype=torch.long)\n        \n        return image, label","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:02.496173Z","iopub.execute_input":"2024-09-18T17:24:02.497140Z","iopub.status.idle":"2024-09-18T17:24:02.506555Z","shell.execute_reply.started":"2024-09-18T17:24:02.497092Z","shell.execute_reply":"2024-09-18T17:24:02.505613Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Function to create datasets and dataloaders for each series description\ndef create_datasets_and_loaders(df, series_description, transform, batch_size=8):\n    \n    filtered_df = df[df['series_description'] == series_description]\n    \n    train_df, val_df = train_test_split(filtered_df, test_size=0.2, random_state=42)\n    train_df = train_df.reset_index(drop=True)\n    val_df = val_df.reset_index(drop=True)\n    \n    train_dataset = CustomDataset(train_df, transform)\n    val_dataset = CustomDataset(val_df, transform)\n    \n    trainloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n    valloader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\n    \n    return trainloader, valloader, len(train_df), len(val_df)\n\n# Define the transforms\ntransform = transforms.Compose([\n    transforms.Lambda(lambda x: (x * 255).astype(np.uint8)), \n    transforms.ToPILImage(),\n    transforms.Resize((224, 224)),\n    transforms.Grayscale(num_output_channels=3),\n    transforms.ToTensor(),\n])\n\n# Create dataloaders for each series description\ndataloaders = {}\nlengths = {}\n\ntrainloader_t1, valloader_t1, len_train_t1, len_val_t1 = create_datasets_and_loaders(train_data, 'Sagittal T1', transform)\ntrainloader_t2, valloader_t2, len_train_t2, len_val_t2 = create_datasets_and_loaders(train_data, 'Axial T2', transform)\ntrainloader_t2stir, valloader_t2stir, len_train_t2stir, len_val_t2stir = create_datasets_and_loaders(train_data, 'Sagittal T2/STIR', transform)\n\ndataloaders['Sagittal T1'] = (trainloader_t1, valloader_t1)\ndataloaders['Axial T2'] = (trainloader_t2, valloader_t2)\ndataloaders['Sagittal T2/STIR'] = (trainloader_t2stir, valloader_t2stir)\n\nlengths['Sagittal T1'] = (len_train_t1, len_val_t1)\nlengths['Axial T2'] = (len_train_t2, len_val_t2)\nlengths['Sagittal T2/STIR'] = (len_train_t2stir, len_val_t2stir)","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:02.702599Z","iopub.execute_input":"2024-09-18T17:24:02.703208Z","iopub.status.idle":"2024-09-18T17:24:02.769496Z","shell.execute_reply.started":"2024-09-18T17:24:02.703176Z","shell.execute_reply":"2024-09-18T17:24:02.768344Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Function to visualize a batch of images\ndef visualize_batch(dataloader):\n    images, labels = next(iter(dataloader))\n    fig, axes = plt.subplots(1, len(images), figsize=(20, 5))\n    for i, (img, lbl) in enumerate(zip(images, labels)):\n        ax = axes[i]\n        img = img.permute(1, 2, 0)  # Convert to HWC for visualization\n        ax.imshow(img)\n        ax.set_title(f\"{lbl}\")\n        ax.axis('off')\n    plt.show()\n\n# Visualize samples from each dataloader\nprint(\"Visualizing Sagittal T1 samples\")\nvisualize_batch(trainloader_t1)\nprint(\"Visualizing Axial T2 samples\")\nvisualize_batch(trainloader_t2)\nprint(\"Visualizing Sagittal T2/STIR samples\")\nvisualize_batch(trainloader_t2stir)","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:02.898133Z","iopub.execute_input":"2024-09-18T17:24:02.898511Z","iopub.status.idle":"2024-09-18T17:24:05.522039Z","shell.execute_reply.started":"2024-09-18T17:24:02.898480Z","shell.execute_reply":"2024-09-18T17:24:05.521075Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"image, label = next(iter(trainloader_t1))\nsample = image[1].permute(1, 2, 0)  #sample\nplt.imshow(sample)","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:05.523682Z","iopub.execute_input":"2024-09-18T17:24:05.523984Z","iopub.status.idle":"2024-09-18T17:24:06.054808Z","shell.execute_reply.started":"2024-09-18T17:24:05.523959Z","shell.execute_reply":"2024-09-18T17:24:06.053843Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Plot images\nimage, label = next(iter(trainloader_t2))\nplt.figsize=(8, 4)\nplt.imshow(images[0], cmap='gray')\nplt.title(label[0])\nplt.axis('off')\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:06.056031Z","iopub.execute_input":"2024-09-18T17:24:06.056385Z","iopub.status.idle":"2024-09-18T17:24:06.506741Z","shell.execute_reply.started":"2024-09-18T17:24:06.056346Z","shell.execute_reply":"2024-09-18T17:24:06.505762Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torchvision.models as models\nfrom torchvision import transforms\nfrom torch.utils.data import DataLoader\nfrom sklearn.model_selection import train_test_split\nimport pandas as pd\nfrom tqdm import tqdm","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:07.448203Z","iopub.execute_input":"2024-09-18T17:24:07.448588Z","iopub.status.idle":"2024-09-18T17:24:07.454687Z","shell.execute_reply.started":"2024-09-18T17:24:07.448559Z","shell.execute_reply":"2024-09-18T17:24:07.453749Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class CustomEfficientNetV2(nn.Module):    \n    def __init__(self, num_classes=3,pretrained_weights=None):\n        super(CustomEfficientNetV2, self).__init__() \n        self.model = models.efficientnet_v2_s(weights=None)\n        \n        if pretrained_weights:\n            self.model.load_state_dict(torch.load(pretrained_weights, weights_only=True))\n            \n        num_ftrs = self.model.classifier[-1].in_features \n        self.model.classifier[-1] = nn.Linear(num_ftrs, num_classes * 5)\n    \n    def forward(self, x):\n        x = self.model(x)\n        x = x.view(-1, 5, 3)  \n        return x\n    \n    def unfreeze_model(self, num_unfreeze=2):\n        for layer in list(self.model.features.children())[-num_unfreeze:]: \n            if not isinstance(layer, nn.BatchNorm2d):\n                for param in layer.parameters():\n                    param.requires_grad = True\n        \n        for param in self.model.classifier.parameters():\n            param.requires_grad = True","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:07.456230Z","iopub.execute_input":"2024-09-18T17:24:07.456567Z","iopub.status.idle":"2024-09-18T17:24:07.467206Z","shell.execute_reply.started":"2024-09-18T17:24:07.456543Z","shell.execute_reply":"2024-09-18T17:24:07.466351Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nweights_path = '/kaggle/input/efficientnetv2/pytorch/efficientnetv2s/1/efficientnet_v2_s-dd5fe13b.pth'\n\nsagittal_t1_model = CustomEfficientNetV2(num_classes=6, pretrained_weights=weights_path).to(device)\naxial_t2_model = CustomEfficientNetV2(num_classes=6, pretrained_weights=weights_path).to(device)\nsagittal_t2stir_model = CustomEfficientNetV2(num_classes=3, pretrained_weights=weights_path).to(device)","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:09.342144Z","iopub.execute_input":"2024-09-18T17:24:09.342846Z","iopub.status.idle":"2024-09-18T17:24:11.740815Z","shell.execute_reply.started":"2024-09-18T17:24:09.342814Z","shell.execute_reply":"2024-09-18T17:24:11.739828Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\nnum_f_layers = len(list(sagittal_t1_model.model.features.children()))\nprint(f\"The number of layers in self.model.features: {num_f_layers}\")\n\nnum_f_layers = len(list(sagittal_t1_model.model.classifier.children()))\nprint(f\"The number of layers in self.model.features: {num_f_layers}\")","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:11.742536Z","iopub.execute_input":"2024-09-18T17:24:11.742917Z","iopub.status.idle":"2024-09-18T17:24:11.748889Z","shell.execute_reply.started":"2024-09-18T17:24:11.742880Z","shell.execute_reply":"2024-09-18T17:24:11.747947Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\nfor param in sagittal_t1_model.model.features.parameters():\n    param.requires_grad = False\nfor param in axial_t2_model.model.features.parameters():\n    param.requires_grad = False\nfor param in sagittal_t2stir_model.model.features.parameters():\n    param.requires_grad = False\n\n\n\n#axial_t2_model.unfreeze_model(num_unfreeze=1)\n#sagittal_t2stir_model.unfreeze_model(num_unfreeze=1)\n\n# 모델의 분류기를 학습가능하도록 설정\nfor param in sagittal_t1_model.model.classifier.parameters():\n    param.requires_grad = True\nfor param in axial_t2_model.model.classifier.parameters():\n    param.requires_grad = True\nfor param in sagittal_t2stir_model.model.classifier.parameters():\n    param.requires_grad = True\n\n#손실함수를 정의\nweights = torch.tensor([1.0, 2.0, 4.0]).to(device) # 가중치 정의\ndef weighted_loss_function(outputs, labels):\n    loss = 0\n    for i in range(5):\n        loss_fn = nn.CrossEntropyLoss(weight=weights)  # 가중치가 적용된 CrossEntropyLoss\n        loss += loss_fn(outputs[:, i, :], labels[:, i])  # 각 레벨별 손실 계산 및 가중치 적용\n    return loss\n\n# 학습을 위한 optimizer 초기화\noptimizer_sagittal_t1 = torch.optim.Adam(sagittal_t1_model.model.classifier.parameters(), lr=0.001)\noptimizer_axial_t2 = torch.optim.Adam(axial_t2_model.model.classifier.parameters(), lr=0.001)\noptimizer_sagittal_t2stir = torch.optim.Adam(sagittal_t2stir_model.model.classifier.parameters(), lr=0.001)\n\n# 빠른 액세스를 위해 모델과 optimizer를 딕셔너리로 저장\ncustom_models = {\n    'Sagittal T1': sagittal_t1_model,\n    'Axial T2': axial_t2_model,\n    'Sagittal T2/STIR': sagittal_t2stir_model,\n}\noptimizers = {\n    'Sagittal T1': optimizer_sagittal_t1,\n    'Axial T2': optimizer_axial_t2,\n    'Sagittal T2/STIR': optimizer_sagittal_t2stir,\n}","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:13.180500Z","iopub.execute_input":"2024-09-18T17:24:13.181303Z","iopub.status.idle":"2024-09-18T17:24:13.211499Z","shell.execute_reply.started":"2024-09-18T17:24:13.181254Z","shell.execute_reply":"2024-09-18T17:24:13.210365Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Count trainable parameters\ntrainable_params = sum(p.numel() for p in sagittal_t1_model.parameters() if p.requires_grad)\nprint(f\"Number of parameters: {trainable_params}\")","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:15.031169Z","iopub.execute_input":"2024-09-18T17:24:15.031669Z","iopub.status.idle":"2024-09-18T17:24:15.041040Z","shell.execute_reply.started":"2024-09-18T17:24:15.031636Z","shell.execute_reply":"2024-09-18T17:24:15.039808Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def print_pytorch_model_summary(model, input_size=(3, 224, 224)):\n    print(f\"{'Layer Name':<30} {'Output Shape':<30} {'Param #':<15} {'Type':<15}\")\n    print(\"=\"*100)\n    \n    # Create a dummy input tensor with the specified input size\n    dummy_input = torch.rand(1, *input_size).to(next(model.parameters()).device)\n    \n    # Forward pass through the model to get output shapes\n    def register_hook(module, module_type):\n        def hook(module, input, output):\n            class_name = str(module.__class__).split(\".\")[-1].split(\"'\")[0]\n            module_idx = len(summary)\n\n            # Module name and output shape\n            m_key = f\"{class_name}-{module_idx + 1}\"\n            summary[m_key] = {\n                \"input_shape\": list(input[0].size()),\n                \"output_shape\": list(output.size()),\n                \"nb_params\": sum(p.numel() for p in module.parameters()),\n                \"type\": module_type\n            }\n        \n        if (\n            not isinstance(module, nn.Sequential) and \n            not isinstance(module, nn.ModuleList) and \n            module != model\n        ):\n            hooks.append(module.register_forward_hook(hook))\n    \n    # Register hooks for features and classifier separately\n    summary = {}\n    hooks = []\n    \n    # Register hooks for feature extraction layers\n    model.model.features.apply(lambda module: register_hook(module, \"Feature Extraction\"))\n    \n    # Register hooks for classifier layers\n    model.model.classifier.apply(lambda module: register_hook(module, \"Classifier\"))\n\n    # Perform a forward pass to collect output shapes\n    model(dummy_input)\n    \n    # Remove hooks\n    for hook in hooks:\n        hook.remove()\n\n    total_params = 0\n    for layer in summary:\n        # Layer name\n        print(f\"{layer:<30} {str(summary[layer]['output_shape']):<30} {summary[layer]['nb_params']:<15} {summary[layer]['type']:<15}\")\n        total_params += summary[layer][\"nb_params\"]\n    \n    print(\"=\"*100)\n    print(f\"Total Params: {total_params}\")\n\n# 모델 서머리 출력 (예: sagittal_t1_model)\n#print_pytorch_model_summary(sagittal_t1_model)\n\n# features 내부의 각 모듈을 재귀적으로 탐색하여 모든 층을 출력\ndef count_layers(module):\n    num_layers = 0\n    for child in module.children():\n        if isinstance(child, nn.Sequential):\n            num_layers += count_layers(child)\n        else:\n            num_layers += 1\n    return num_layers\n\nnum_f_layers = count_layers(sagittal_t1_model.model.features)\nprint(f\"The number of layers in self.model.features: {num_f_layers}\")\n\nnum_c_layers = count_layers(sagittal_t1_model.model.classifier)\nprint(f\"The number of layers in self.model.classifier: {num_c_layers}\")\n","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:15.165732Z","iopub.execute_input":"2024-09-18T17:24:15.166062Z","iopub.status.idle":"2024-09-18T17:24:15.181849Z","shell.execute_reply.started":"2024-09-18T17:24:15.166037Z","shell.execute_reply":"2024-09-18T17:24:15.180809Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training","metadata":{}},{"cell_type":"code","source":"# trainloader에서 불러온 입출력이 device로 정상적으로 전달되는지 체크\nfor images, labels in trainloader_t2:\n    labels = labels.clone().detach().to(device)  # 이미 텐서인 경우 clone().detach()를 사용\n    labels = labels.to(device)  # 데이터를 GPU device로 할당\n    \n    print(labels)\n    break","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:16.325211Z","iopub.execute_input":"2024-09-18T17:24:16.325615Z","iopub.status.idle":"2024-09-18T17:24:16.498087Z","shell.execute_reply.started":"2024-09-18T17:24:16.325574Z","shell.execute_reply":"2024-09-18T17:24:16.497151Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch.optim.lr_scheduler as lr_scheduler\nfrom copy import deepcopy\n\n#학습절차 정의\ndef train_model(model, trainloader, valloader, len_train, len_val, optimizer, num_epochs=20, patience=3):\n    # StepLR: 단계적 학습률 감소. 2 epoch마다 학습률이 0.1 감소.\n    scheduler = lr_scheduler.StepLR(optimizer, step_size=2, gamma=0.1)\n    \n    best_val_acc = 0.0  # 검증 정확도의 최고 값을 저장할 변수\n    best_model_wts = deepcopy(model.state_dict())  # 모델의 가중치를 저장하기 위한 변수\n    counter = 0  # 조기 종료를 위한 에포크 카운터\n    \n    for epoch in range(num_epochs): #총 num_epochs만큼 반복\n        model.train() #모델을 학습 모드로 설정\n        train_loss = 0\n        correct_train = 0\n        \n        with tqdm(trainloader, unit=\"batch\") as tepoch: #train 데이터를 tqdm으로 감싸고 배치 단위로 반복\n            for images, labels in tepoch: \n                images, labels = images.to(device), labels.clone().detach().to(device) #데이터를 GPU device로 이동\n                optimizer.zero_grad() #옵티마이저의 기울기 초기화\n                outputs = model(images) #모델에 입력 이미지를 넣어 출력 계산\n                loss = weighted_loss_function(outputs, labels) #출력과 실제 레이블 간의 손실 계산\n                loss.backward() #역전파를 통해 기울기 계산\n                optimizer.step() #옵티마이저를 통해 가중치 업데이트\n                train_loss += loss.item() #배치의 손실을 train_loss에 더함\n\n                # 레벨별로 예측 결과와 실제 레이블을 비교\n                for i in range(5):  # 5개의 레벨에 대해 반복\n                    probabilities = torch.softmax(outputs[:, i, :], dim=1)\n                    _, predicted = torch.max(probabilities, 1)\n                    correct_train += (predicted == labels[:, i]).sum().item()  # 해당 레벨의 정확도를 계산\n                \n                tepoch.set_postfix(epoch=epoch+1)\n                \n        scheduler.step() #학습률 스케줄러의 스텝을 증가시킴\n        \n        #train set의 loss, accuracy 계산\n        train_loss /= len(trainloader) #average loss(train set)\n        train_acc = 100*(correct_train/len_train)/5 #accuracy(train set)\n        \n        model.eval() #모델을 평가 모드로 설정\n        val_loss, correct_val = 0, 0 #검증 손실 및 맞춘 검증 데이터 개수 초기화\n        with torch.no_grad(): #기울기 계산 비활성화(평가에서는 불필요)\n            with tqdm(valloader, unit=\"batch\") as vepoch: #valid 데이터를 tqdm으로 감싸고 배치 단위로 반복\n                for images, labels in vepoch:\n                    images, labels = images.to(device), labels.clone().detach().to(device) #데이터를 GPU device로 이동\n                    outputs = model(images) #모델에 입력 이미지를 넣어 출력 계산\n                    loss = weighted_loss_function(outputs, labels) #출력과 실제 레이블 간의 손실 계산\n                    val_loss += loss.item() #배치의 손실을 val_loss에 더함\n\n                    for i in range(5):\n                        probabilities = torch.softmax(outputs[:, i, :], dim=1)\n                        _, predicted = torch.max(probabilities, 1)\n                        correct_val += (predicted == labels[:, i]).sum().item()\n                    \n                    vepoch.set_postfix(epoch=epoch+1) #tqdm 진행바에 현재 에포크를 출력\n        \n        #valid set의 loss, accuracy 계산\n        val_loss /= len(valloader)  #average loss\n        val_acc = 100*(correct_val/len_val)/5 #accuracy\n        \n        #현재 에포크의 학습 및 검증 결과 출력\n        print(f\"Epoch {epoch+1}, Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%, Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}%\")\n        \n        #최고 accuracy를 가진 모델 저장\n        if val_acc > best_val_acc:\n            best_val_acc = val_acc #최고 accuracy 갱신\n            best_model_wts = deepcopy(model.state_dict()) #최적의 모델 가중치 저장\n            counter = 0 #조기 종료 카운터 초기화\n            torch.save(best_model_wts, f'best_model_{epoch+1}.pth') #모델 가중치 저장\n        else:\n            counter += 1 #현재 에포크가 best가 아닌 경우 조기 종료 카운터 증가\n        \n        #설정된 patience만큼 개선이 이루어지지 않았으면 종료\n        if counter >= patience:\n            print(f\"Early stopping triggered after {epoch+1} epochs\")\n            break\n    \n    #학습 후 최적의 가중치를 모델에 적용\n    model.load_state_dict(best_model_wts)\n    return model, best_val_acc #최적의 모델과 검증 정확도를 반환","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:21.500427Z","iopub.execute_input":"2024-09-18T17:24:21.500858Z","iopub.status.idle":"2024-09-18T17:24:21.520879Z","shell.execute_reply.started":"2024-09-18T17:24:21.500826Z","shell.execute_reply":"2024-09-18T17:24:21.519705Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"'''이 부분이 병목\n#trainloader_t2에서 불러온 labels 텐서 내에 NaN(Not a Number) 값이 있는지 체크\n#NaN 값은 수학적으로 정의되지 않은 값으로, 학습 과정에서 NaN 값이 발생하면 모델 성능에 심각한 영향을 미칠 수 있음\nfor images, labels in trainloader_t2:\n    if any(label != label for label in labels):  # nan은 자기 자신과 같지 않기 때문에 이를 이용한 체크\n        print(\"Found nan label\")\n'''","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# GPU 가속이 가능한지 확인하는 코드\nif torch.cuda.is_available():\n    print(\"CUDA is available. GPU acceleration is enabled.\")\n    print(f\"Device name: {torch.cuda.get_device_name(0)}\")\nelse:\n    print(\"CUDA is not available. Running on CPU.\")","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:25.156049Z","iopub.execute_input":"2024-09-18T17:24:25.156537Z","iopub.status.idle":"2024-09-18T17:24:25.161911Z","shell.execute_reply.started":"2024-09-18T17:24:25.156505Z","shell.execute_reply":"2024-09-18T17:24:25.161021Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Training all models\nfor desc, model in custom_models.items(): #key와 value를 튜플형태로 조회\n    if desc == 'Sagittal T1':\n        trainloader, valloader, len_train, len_val = trainloader_t1, valloader_t1, len_train_t1, len_val_t1\n    elif desc == 'Axial T2':\n        trainloader, valloader, len_train, len_val = trainloader_t2, valloader_t2, len_train_t2, len_val_t2\n    elif desc == 'Sagittal T2/STIR':\n        trainloader, valloader, len_train, len_val = trainloader_t2stir, valloader_t2stir, len_train_t2stir, len_val_t2stir\n    \n    print(f\"Training model for {desc}\")\n    train_model(model, trainloader, valloader, len_train, len_val, optimizers[desc])","metadata":{"execution":{"iopub.status.busy":"2024-09-16T19:28:50.915867Z","iopub.execute_input":"2024-09-16T19:28:50.916734Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"code","source":"#train data에 존재하는 level 확인\ntrain_data['level'].unique() ","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:34.671296Z","iopub.execute_input":"2024-09-18T17:24:34.672015Z","iopub.status.idle":"2024-09-18T17:24:34.682056Z","shell.execute_reply.started":"2024-09-18T17:24:34.671984Z","shell.execute_reply":"2024-09-18T17:24:34.681076Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#test data에는 사진만 있고 level 정보가 없음\nexpanded_test_desc.head(5)","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:35.309421Z","iopub.execute_input":"2024-09-18T17:24:35.310224Z","iopub.status.idle":"2024-09-18T17:24:35.321055Z","shell.execute_reply.started":"2024-09-18T17:24:35.310196Z","shell.execute_reply":"2024-09-18T17:24:35.320136Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"levels = ['l1_l2', 'l2_l3', 'l3_l4', 'l4_l5', 'l5_s1']\n\n#row_id에 levels('l1_l2'~'l5_s1')을 붙여서 반환하는 함수\ndef update_row_id(row, levels): #인덱스 번호를 levels 리스트의 길이로 나눈 나머지를 계산하여 추가\n    level = levels[row.name % len(levels)] #apply 함수 내에서 row.name은 현재 행의 인덱스 번호를 나타냄\n    return f\"{row['study_id']}_{row['condition']}_{level}\"\n\n#row_id에 levels를 붙여서 expanded_test_desc를 확장 \nexpanded_test_desc['row_id'] = expanded_test_desc.apply(lambda row: update_row_id(row, levels), axis=1)","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:45.074882Z","iopub.execute_input":"2024-09-18T17:24:45.075261Z","iopub.status.idle":"2024-09-18T17:24:45.085951Z","shell.execute_reply.started":"2024-09-18T17:24:45.075231Z","shell.execute_reply":"2024-09-18T17:24:45.084906Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"expanded_test_desc.head()","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:46.324799Z","iopub.execute_input":"2024-09-18T17:24:46.325168Z","iopub.status.idle":"2024-09-18T17:24:46.338251Z","shell.execute_reply.started":"2024-09-18T17:24:46.325139Z","shell.execute_reply":"2024-09-18T17:24:46.336798Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"expanded_test_desc[expanded_test_desc['series_description'] == 'Sagittal T2/STIR']['image_path'].str.split('/').str[-3:].str.join('/')","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:46.646949Z","iopub.execute_input":"2024-09-18T17:24:46.647869Z","iopub.status.idle":"2024-09-18T17:24:46.657160Z","shell.execute_reply.started":"2024-09-18T17:24:46.647833Z","shell.execute_reply":"2024-09-18T17:24:46.656238Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Define a custom test dataset class\nclass TestDataset(Dataset):\n    #instance = CustomDataset(test_data)\n    #instance.dataframe -> dataframe\n    #instance.transform -> transform\n    def __init__(self, dataframe, transform=None):\n        self.dataframe = dataframe\n        self.transform = transform\n\n    #len(instance) -> len(dataframe)\n    def __len__(self):\n        return len(self.dataframe)\n\n    #instance[index] -> image, label\n    def __getitem__(self, index):\n        image_path = self.dataframe['image_path'][index] #인덱스를 통해 경로 호출\n        image = load_dicom(image_path)  #경로로부터 DICOM image 로딩 #train과 달리 label은 없음!\n        if self.transform:\n            image = self.transform(image) #정의된 변환이 있을 경우 이미지에 변환 적용(리스케일 후 PyTorch Tensor로)\n        return image \n\n# Define the transforms\ntransform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((224, 224)),\n    transforms.Grayscale(num_output_channels=3),\n    transforms.ToTensor(),\n])\n\n# Create a test dataset and dataloader\ntest_dataset = TestDataset(expanded_test_desc, transform) #TestDataset에 expanded_test_desc를 넣어 이미지를 불러올 준비를 마침\ntestloader = DataLoader(test_dataset, batch_size=1, shuffle=False) #batch_size=1으로 한 번에 하나의 샘플에 대해 예측을 수행DataLoader 객체 선언","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:49.166320Z","iopub.execute_input":"2024-09-18T17:24:49.166936Z","iopub.status.idle":"2024-09-18T17:24:49.175470Z","shell.execute_reply.started":"2024-09-18T17:24:49.166907Z","shell.execute_reply":"2024-09-18T17:24:49.174394Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#testloader에 이미지가 정확한 형태로 로딩되었는지 확인\n#첫번째 루프(첫 번째 배치)만 불러와 형태를 출력한 뒤 break \n#(batch_size, channels, height, width)\nfor image in testloader:\n    print(image.shape)\n    break","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:50.768955Z","iopub.execute_input":"2024-09-18T17:24:50.769732Z","iopub.status.idle":"2024-09-18T17:24:50.814003Z","shell.execute_reply.started":"2024-09-18T17:24:50.769698Z","shell.execute_reply":"2024-09-18T17:24:50.812898Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_model(series_description):\n    return custom_models.get(series_description, None)\n\ndef predict_test_data(testloader, expanded_test_desc):\n    # 예측 결과를 저장할 리스트 선언\n    predictions = []\n    \n    # 모든 모델을 평가 모드로 설정\n    for model in custom_models.values():\n        model.eval()\n\n    # 추론 과정에서는 기울기 계산을 비활성화하여 메모리 사용을 줄임   \n    with torch.no_grad():\n        # 전체 인덱스를 위한 카운터\n        total_index = 0\n        # testloader에서 배치 단위로 데이터를 가져옴\n        for images in tqdm(testloader):\n            images = images.to(device)  # 이미지를 GPU로 이동\n            batch_size = images.size(0) # 현재 배치의 크기 확인\n\n            for i in range(batch_size):\n                series_description = expanded_test_desc.iloc[total_index]['series_description']  # 현재 이미지에 해당하는 series_description\n                model = get_model(series_description)  # 해당 series description에 맞는 모델\n                if model:  # 모델이 존재하면\n                    outputs = model(images[i].unsqueeze(0))  # (1, 5, 3)의 shape\n                    probs = torch.softmax(outputs, dim=2).squeeze(0)  # 각 레벨에 대한 클래스 확률 계산 -> pytorch tensor 객체\n\n                    # 각 레벨별로 예측된 확률을 저장\n                    level_predictions = []\n                    for level_idx in range(5):\n                        level_predictions.append({\n                            'normal_mild': probs[level_idx, 0].item(),\n                            'moderate': probs[level_idx, 1].item(),\n                            'severe': probs[level_idx, 2].item(),\n                            'level': level_idx,\n                            'series_description': series_description\n                        })\n                    predictions.append(level_predictions)\n                else:\n                    # 모델이 없으면 None으로 채움\n                    predictions.append([{'normal_mild': None, 'moderate': None, 'severe': None, 'level': None, 'condition': None}] * 5)\n                \n                # 다음 이미지로 넘어가기 위해 전체 인덱스 증가\n                total_index += 1\n    \n    return predictions\n","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:50.945603Z","iopub.execute_input":"2024-09-18T17:24:50.946356Z","iopub.status.idle":"2024-09-18T17:24:50.957841Z","shell.execute_reply.started":"2024-09-18T17:24:50.946313Z","shell.execute_reply":"2024-09-18T17:24:50.956733Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 예측 수행\nresults = predict_test_data(testloader, expanded_test_desc)\n\nresults_df = pd.DataFrame()\n\nfor result in results:\n    row = pd.DataFrame(result)\n    results_df = pd.concat([results_df, row])","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:24:51.777141Z","iopub.execute_input":"2024-09-18T17:24:51.777519Z","iopub.status.idle":"2024-09-18T17:25:00.363485Z","shell.execute_reply.started":"2024-09-18T17:24:51.777491Z","shell.execute_reply":"2024-09-18T17:25:00.362536Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"condition_df = pd.DataFrame([\n    {'series_description': 'Sagittal T1', 'condition': 'left_neural_foraminal_narrowing'},\n    {'series_description': 'Sagittal T1', 'condition': 'right_neural_foraminal_narrowing'},\n    {'series_description': 'Axial T2', 'condition': 'left_subarticular_stenosis'},\n    {'series_description': 'Axial T2', 'condition': 'right_subarticular_stenosis'},\n    {'series_description': 'Sagittal T2/STIR', 'condition': 'spinal_canal_stenosis'}\n])\n\nresults_df_with_condition = pd.merge(results_df, condition_df, on='series_description', how='left')\n\ninverse_level_map = {0 : 'l1_l2', 1:'l2_l3', 2: 'l3_l4', 3: 'l4_l5', 4: 'l5_s1'}\nresults_df_with_condition['level'] = results_df_with_condition['level'].map(inverse_level_map)\nresults_df_with_condition['row_id'] = \"44036939_\" + results_df_with_condition['condition'] + \"_\" + results_df_with_condition['level']\nresults_df_with_condition = results_df_with_condition[['row_id', 'normal_mild', 'moderate', 'severe']]","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:25:00.365001Z","iopub.execute_input":"2024-09-18T17:25:00.365352Z","iopub.status.idle":"2024-09-18T17:25:00.379798Z","shell.execute_reply.started":"2024-09-18T17:25:00.365316Z","shell.execute_reply":"2024-09-18T17:25:00.378913Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 'row_id'를 기준으로 그룹화하고 각 레벨의 평균을 계산\nsubmission = results_df_with_condition.groupby('row_id').mean().reset_index()\n\n# 합계가 1이 되는지 확인\nrow_sum = submission[['normal_mild', 'moderate', 'severe']].sum(axis=1)\n\n# 제출 파일 저장\nsubmission.to_csv(\"/kaggle/working/submission.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:25:07.951457Z","iopub.execute_input":"2024-09-18T17:25:07.952305Z","iopub.status.idle":"2024-09-18T17:25:07.965932Z","shell.execute_reply.started":"2024-09-18T17:25:07.952257Z","shell.execute_reply":"2024-09-18T17:25:07.964944Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission","metadata":{"execution":{"iopub.status.busy":"2024-09-18T17:25:26.222851Z","iopub.execute_input":"2024-09-18T17:25:26.223726Z","iopub.status.idle":"2024-09-18T17:25:26.238431Z","shell.execute_reply.started":"2024-09-18T17:25:26.223690Z","shell.execute_reply":"2024-09-18T17:25:26.237510Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}