{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":71549,"databundleVersionId":8561470,"sourceType":"competition"},{"sourceId":8940855,"sourceType":"datasetVersion","datasetId":5379752}],"dockerImageVersionId":30746,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import csv\nimport os\nimport glob\nimport numpy as np\nimport pandas as pd\nimport pandas.api.types\nimport sklearn.metrics\nimport cv2\nimport pydicom\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau, StepLR, CosineAnnealingLR\nfrom torch.utils.data import DataLoader\nfrom torch.utils.data import Dataset\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport matplotlib.pyplot as plt\nfrom torchinfo import summary\nimport os\nfrom PIL import Image\nfrom tqdm import tqdm\nfrom sklearn.model_selection import StratifiedGroupKFold\nfrom torch.utils.data import Subset","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(os.environ.get('CUDA_VISIBLE_DEVICES'))\nprint(torch.cuda.current_device())\nprint(torch.cuda.get_device_name(0))\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nprint(device)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"image_size = 224","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"glob.glob('/kaggle/input/lsdc-png/train_images_png/4003253/2448190387/*')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"rd='/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification'\n\n# CSV 파일에서 데이터 읽기\nseries_file_path = f'{rd}/train_series_descriptions.csv'\ncoordinate_file_path = f'{rd}/train_label_coordinates.csv'\nlabel_file_path = f'{rd}/train.csv'\n\nseries_df = pd.read_csv(series_file_path)\ncoordinate_df = pd.read_csv(coordinate_file_path)\nlabel_df = pd.read_csv(label_file_path)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"study_ids_to_remove = [2492114990, 2780132468, 3008676218, 208289456,  2581283971, 3303545110, 3324678907, 3369277408, 3867046855]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(len(series_df), len(coordinate_df), len(label_df))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"series_df = series_df[~series_df['study_id'].isin(study_ids_to_remove)]\ncoordinate_df = coordinate_df[~coordinate_df['study_id'].isin(study_ids_to_remove)]\nlabel_df = label_df[~label_df['study_id'].isin(study_ids_to_remove)]\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(len(series_df), len(coordinate_df), len(label_df))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"label_df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 레이블 컬럼을 3가지 상태로 변환\nlabel_columns = label_df.columns[1:]  # 첫 번째 컬럼은 study_id이므로 제외\nlabel_df[label_columns] = label_df[label_columns].fillna('Normal/Mild')\nlabel_df[label_columns] = label_df[label_columns].applymap(lambda x: {'Normal/Mild': 0, 'Moderate': 1, 'Severe': 2}[x])\n\n# 원-핫 인코딩을 수행하여 75개의 이진 클래스로 변환\none_hot_labels = np.eye(3)[label_df[label_columns].values].reshape(label_df.shape[0], -1)\n\n# 레이블을 딕셔너리로 변환\nlabel_dict = {study_id: labels for study_id, labels in zip(label_df['study_id'], one_hot_labels)}\n# Merge the coordinate_df with series_df to get the description for each series_id\nmerged_df = pd.merge(coordinate_df, series_df[['study_id', 'series_id', 'series_description']], on=['study_id', 'series_id'], how='left')\n\n# Group by study_id, series_description, series_id, and collect unique instance_number\nrequired_slices_with_description = merged_df.groupby(['study_id', 'series_description', 'series_id'])['instance_number'].apply(lambda x: sorted(list(set(x)))).reset_index()\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"label_dict","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Merged DataFrame:\")\nmerged_df.head()\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Grouped DataFrame with required slices:\")\nrequired_slices_with_description.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Label DataFrame:\")\nlabel_df.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Convert the result to a nested dictionary\nrequired_slices_nested_dict = required_slices_with_description.groupby('study_id').apply(\n    lambda x: x.groupby('series_description').apply(\n        lambda y: y.set_index('series_id')['instance_number'].to_dict()\n    ).to_dict()\n).to_dict()\n\n\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"\\nNested Dictionary:\")\nrequired_slices_nested_dict","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#     def get_img_paths(self, study_id, series_desc):\n#         pdf = self.series_df[self.series_df['study_id'] == study_id]\n#         pdf_ = pdf[pdf['series_description'] == series_desc]\n#         all_imgs = []\n#         for _, row in pdf_.iterrows():\n#             imgs = glob.glob(f'{rd}train_images\\\\{study_id}\\\\{row[\"series_id\"]}\\\\*.dcm')\n#             imgs = sorted(imgs, key=lambda x: int(x.split('\\\\')[-1].split('.')[0]))\n#             all_imgs.extend(imgs)\n#         return all_imgs","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 그룹 데이터 준비 (study_id)\ngroups = label_df['study_id'].values\n\n# 각 그룹의 최대 레이블을 사용하여 단일 레이블 생성\nlabels = label_df.iloc[:, 1:].values\nmax_labels = labels.max(axis=1)\n\n# Stratified Group K-Fold 생성\nn_splits = 5\nsgkf = StratifiedGroupKFold(n_splits=n_splits)\n\nfolds = list(sgkf.split(X=np.zeros(len(max_labels)), y=max_labels, groups=groups))","metadata":{"scrolled":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class CustomDataset(Dataset):\n    def __init__(self, series_df, required_slices_dict, label_dict, transform=None):\n        self.series_df = series_df\n        self.required_slices_dict = required_slices_dict\n        self.label_dict = label_dict\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.series_df['study_id'].unique())\n\n    def get_img_paths(self, study_id, series_desc):\n        pdf = self.series_df[self.series_df['study_id'] == study_id]\n        pdf_ = pdf[pdf['series_description'] == series_desc]\n        all_imgs = []\n        for _, row in pdf_.iterrows():\n            imgs = glob.glob(f'{rd}/train_images/{study_id}/{row[\"series_id\"]}/*.dcm')\n            imgs = sorted(imgs, key=lambda x: int(x.split('/')[-1].split('.')[0]))\n            all_imgs.extend(imgs)\n        return all_imgs\n\n    def read_dcm(self, filepath):\n        dicom_data = pydicom.dcmread(filepath)\n        image = dicom_data.pixel_array\n        image = (image - image.min()) / (image.max() - image.min() + 1e-6) * 255\n        image = cv2.resize(image, (image_size, image_size), interpolation=cv2.INTER_CUBIC)\n        return image.astype(np.uint8)\n\n    def get_selected_indices(self, all_imgs_dict, n_slices, required_instances_dict):\n        selected_indices = []\n\n        num_series = len(all_imgs_dict)\n        slices_per_series = n_slices // num_series\n        remaining_slices = n_slices % num_series\n\n        for series_id, instances in required_instances_dict.items():\n            all_imgs = all_imgs_dict[series_id]\n            n_required = len(instances)\n\n            if n_required >= slices_per_series:\n                selected_indices.extend([(series_id, idx) for idx in instances[:slices_per_series]])\n            else:\n                additional_needed = slices_per_series - n_required\n                step = len(all_imgs) / (additional_needed + 1)\n                st = step / 2.0\n                additional_indices = [max(1, int((i + 0.4999).round())) for i in np.arange(st, len(all_imgs) + 1.0001, step)]\n                additional_indices = [idx for idx in additional_indices if idx not in instances and idx <= len(all_imgs)]\n                selected_indices.extend([(series_id, idx) for idx in sorted(set(instances) | set(additional_indices))[:slices_per_series]])\n\n        if remaining_slices > 0:\n            for series_id, instances in required_instances_dict.items():\n                if remaining_slices == 0:\n                    break\n                all_imgs = all_imgs_dict[series_id]\n                additional_indices = [i for i in range(1, len(all_imgs) + 1) if i not in instances]\n                additional_to_add = min(remaining_slices, len(additional_indices))\n                selected_indices.extend([(series_id, idx) for idx in additional_indices[:additional_to_add]])\n                remaining_slices -= additional_to_add\n\n        if len(selected_indices) < n_slices:\n            for series_id, instances in required_instances_dict.items():\n                if len(selected_indices) >= n_slices:\n                    break\n                all_imgs = all_imgs_dict[series_id]\n                additional_needed = n_slices - len(selected_indices)\n                additional_indices = [i for i in range(1, len(all_imgs) + 1) if (series_id, i) not in selected_indices]\n                additional_to_add = min(additional_needed, len(additional_indices))\n                selected_indices.extend([(series_id, idx) for idx in additional_indices[:additional_to_add]])\n\n        return selected_indices\n\n    def __getitem__(self, idx):\n        study_id = self.series_df['study_id'].unique()[idx]\n        x = np.zeros((image_size, image_size, 30), dtype=np.uint8)\n        \n        series_list = ['Axial T2', 'Sagittal T1', 'Sagittal T2/STIR']\n        num_slices = {'Axial T2': 15, 'Sagittal T1': 10, 'Sagittal T2/STIR': 5}\n        channel_offset = 0\n        combined_selected_indices = []\n\n        for series_desc in series_list:\n            pdf = self.series_df[self.series_df['study_id'] == study_id]\n            pdf_ = pdf[pdf['series_description'] == series_desc]\n            all_imgs_dict = {}\n            for _, row in pdf_.iterrows():\n                series_id = row['series_id']\n                imgs = glob.glob(f'{rd}/train_images/{study_id}/{series_id}/*.dcm')\n                imgs = sorted(imgs, key=lambda x: int(x.split('/')[-1].split('.')[0]))\n                all_imgs_dict[series_id] = imgs\n            \n            required_instances_dict = self.required_slices_dict.get(study_id, {}).get(series_desc, {})\n            indices = self.get_selected_indices(all_imgs_dict, num_slices[series_desc], required_instances_dict)\n            combined_selected_indices.extend(indices)\n\n            for j, (series_id, ind) in enumerate(indices):\n                try:\n                    img = self.read_dcm(all_imgs_dict[series_id][ind-1])\n                    x[..., channel_offset + j] = img\n                except Exception as e:\n                    print(f\"Error reading image for study_id {study_id}, series_desc {series_desc}, index {ind}: {e}\")\n\n            channel_offset += num_slices[series_desc]\n\n        if self.transform:\n            x = self.transform(image=x)['image']\n\n        label = torch.tensor(self.label_dict[int(study_id)], dtype=torch.float)\n        if self.transform != None: \n            if x.shape != torch.Size([30, image_size, image_size]):\n                print(f\"Sample shape mismatch: {x.shape} for study_id: {study_id}\")\n\n        return x, label, combined_selected_indices, study_id","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class CustomPNGDataset(Dataset):\n    def __init__(self, series_df, required_slices_dict, label_dict, transform=None):\n        self.series_df = series_df\n        self.required_slices_dict = required_slices_dict\n        self.label_dict = label_dict\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.series_df['study_id'].unique())\n\n    def get_img_paths(self, study_id, series_desc):\n        pdf = self.series_df[self.series_df['study_id'] == study_id]\n        pdf_ = pdf[pdf['series_description'] == series_desc]\n        all_imgs = []\n        for _, row in pdf_.iterrows():\n            imgs = glob.glob(f'/kaggle/input/lsdc-png/train_images_png/{study_id}/{row[\"series_id\"]}/*.png')\n            imgs = sorted(imgs, key=lambda x: int(x.split('/')[-1].split('.')[0]))\n            all_imgs.extend(imgs)\n#         print(f\"Found {len(all_imgs)} images for study_id {study_id}, series_desc {series_desc}\")  # 디버깅 출력\n        return all_imgs\n\n    def read_png(self, filepath):\n        image = Image.open(filepath).convert('L')\n        image = image.resize((image_size, image_size), Image.BICUBIC)\n        return np.array(image, dtype=np.uint8)\n\n    def get_selected_indices(self, all_imgs_dict, n_slices, required_instances_dict):\n        selected_indices = []\n\n        num_series = len(all_imgs_dict)\n        slices_per_series = n_slices // num_series\n        remaining_slices = n_slices % num_series\n\n        for series_id, instances in required_instances_dict.items():\n            all_imgs = all_imgs_dict[series_id]\n            n_required = len(instances)\n#             print(f\"Series ID: {series_id}, Required Instances: {n_required}, All Images: {len(all_imgs)}\")  # 디버깅 출력\n            \n            \n#             if len(all_imgs) == 0:\n#                 print(f\"No images found for series_id {series_id}\")  # 디버깅 출력\n                \n            if n_required >= slices_per_series:\n                selected_indices.extend([(series_id, idx) for idx in instances[:slices_per_series]])\n            else:\n                additional_needed = slices_per_series - n_required\n                step = len(all_imgs) / (additional_needed + 1)\n                st = step / 2.0\n                additional_indices = [max(1, int((i + 0.4999).round())) for i in np.arange(st, len(all_imgs) + 1.0001, step)]\n                additional_indices = [idx for idx in additional_indices if idx not in instances and idx <= len(all_imgs)]\n                selected_indices.extend([(series_id, idx) for idx in sorted(set(instances) | set(additional_indices))[:slices_per_series]])\n\n        if remaining_slices > 0:\n            for series_id, instances in required_instances_dict.items():\n                if remaining_slices == 0:\n                    break\n                all_imgs = all_imgs_dict[series_id]\n                additional_indices = [i for i in range(1, len(all_imgs) + 1) if i not in instances]\n                additional_to_add = min(remaining_slices, len(additional_indices))\n                selected_indices.extend([(series_id, idx) for idx in additional_indices[:additional_to_add]])\n                remaining_slices -= additional_to_add\n\n        if len(selected_indices) < n_slices:\n            for series_id, instances in required_instances_dict.items():\n                if len(selected_indices) >= n_slices:\n                    break\n                all_imgs = all_imgs_dict[series_id]\n                additional_needed = n_slices - len(selected_indices)\n                additional_indices = [i for i in range(1, len(all_imgs) + 1) if (series_id, i) not in selected_indices]\n                additional_to_add = min(additional_needed, len(additional_indices))\n                selected_indices.extend([(series_id, idx) for idx in additional_indices[:additional_to_add]])\n\n        return selected_indices\n\n    def __getitem__(self, idx):\n        study_id = self.series_df['study_id'].unique()[idx]\n        x = np.zeros((image_size, image_size, 30), dtype=np.uint8)\n        \n        series_list = ['Axial T2', 'Sagittal T1', 'Sagittal T2/STIR']\n        num_slices = {'Axial T2': 15, 'Sagittal T1': 10, 'Sagittal T2/STIR': 5}\n        channel_offset = 0\n        combined_selected_indices = []\n\n        for series_desc in series_list:\n            pdf = self.series_df[self.series_df['study_id'] == study_id]\n            pdf_ = pdf[pdf['series_description'] == series_desc]\n            all_imgs_dict = {}\n            for _, row in pdf_.iterrows():\n                series_id = row['series_id']\n                imgs = glob.glob(f'/kaggle/input/lsdc-png/train_images_png/{study_id}/{series_id}/*.png')\n                imgs = sorted(imgs, key=lambda x: int(x.split('/')[-1].split('.')[0]))\n                all_imgs_dict[series_id] = imgs\n            \n            required_instances_dict = self.required_slices_dict.get(study_id, {}).get(series_desc, {})\n            indices = self.get_selected_indices(all_imgs_dict, num_slices[series_desc], required_instances_dict)\n            combined_selected_indices.extend(indices)\n\n            for j, (series_id, ind) in enumerate(indices):\n                try:\n                    img = self.read_png(all_imgs_dict[series_id][ind-1])\n                    x[..., channel_offset + j] = img\n                except Exception as e:\n                    print(f\"Error reading image for study_id {study_id}, series_desc {series_desc}, index {ind}: {e}\")\n\n            channel_offset += num_slices[series_desc]\n\n        if self.transform:\n            x = self.transform(image=x)['image']\n\n        label = torch.tensor(self.label_dict[int(study_id)], dtype=torch.float)\n        if self.transform != None: \n            if x.shape != torch.Size([30, image_size, image_size]):\n                print(f\"Sample shape mismatch: {x.shape} for study_id: {study_id}\")\n\n        return x, label, combined_selected_indices, study_id","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\ntrain_transform = A.Compose([\n    A.Resize(image_size, image_size),\n    A.CenterCrop(220, 200, p=0.3),\n    A.RandomBrightnessContrast(p=0.2),\n    A.GaussNoise(p=0.2),\n    A.GridDistortion(p=0.2),\n    A.Resize(image_size, image_size),  # 크기를 다시 원래대로 조정\n    A.Normalize(mean=0.5, std=0.5),\n    ToTensorV2()\n])\n\n# 검증용 데이터 증강\nvalid_transform = A.Compose([\n    A.Resize(image_size, image_size),\n    A.Normalize(mean=0.5, std=0.5),\n    ToTensorV2()\n])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 시각화 함수 수정: 슬라이스 정보를 포함\ndef visualize_slices_with_info(image_data, slice_indices, study_id):\n    num_slices = image_data.shape[2]\n    fig, axes = plt.subplots(3, 10, figsize=(20, 6))\n\n    for i in range(num_slices):\n        ax = axes[i // 10, i % 10]\n        ax.imshow(image_data[:, :, i], cmap='gray')\n        ax.axis('off')\n        # 인덱스가 실제 슬라이스 수보다 적을 수 있으므로 조건 추가\n        if i < len(slice_indices):\n            ax.set_title(f'{slice_indices[i]}')\n        else:\n            ax.set_title(f'Slice {i + 1}\\nIndex N/A')\n\n    plt.suptitle(f'Study ID: {study_id} Selected slices')\n    plt.show()\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# dataset = CustomDataset(series_df, required_slices_nested_dict, label_dict, transform=train_transform)\n\n# # 첫 번째 데이터 확인 및 시각화\n# sample, label, slice_indices, study_id = dataset[0]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Dataset 인스턴스 생성\ndataset = CustomPNGDataset(series_df, required_slices_nested_dict, label_dict, transform=train_transform)\n\n# 첫 번째 데이터 확인 및 시각화\nsample, label, slice_indices, study_id = dataset[0]\nvisualize_slices_with_info(sample.transpose(0,1).transpose(1,2), slice_indices, study_id)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sample.shape","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# for i in tqdm(range(len(dataset)), desc=\"Checking dataset\"):\n#     try:\n#         x, label, indices = dataset[i]\n#         # x의 크기가 (30, image_size, image_size)가 아닌 경우 출력\n#         if x.shape != torch.Size([30, image_size, image_size]):\n#             print(f\"Sample shape mismatch: {x.shape} for study_id: {dataset.series_df['study_id'].unique()[i]}\")\n#     except Exception as e:\n#         study_id = dataset.series_df['study_id'].unique()[i]\n#         print(f\"Error in dataset index {i}, study_id {study_id}: {e}\")\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# batch_size = 1\n# train_loader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=8)\n# # 데이터 로더에서 샘플 데이터 가져오기\n# data_iter = iter(train_loader)\n# images, labels, slice_indices, study_id = next(data_iter)\n# study_id = dataset.series_df['study_id'].unique()[0]  # 첫 번째 study_id 가져오기\n\n# # 데이터 형상 및 내용 확인\n# print(f\"Images shape: {images.shape}\")\n# print(f\"Labels: {labels}\")\n# slice_indices_list = [int(a) for a in slice_indices]\n# print(f\"Slice indices: {slice_indices_list}\")\n\n# # 시각화 및 출력\n# visualize_slices_with_info(images[0].transpose(0,1).transpose(1,2).numpy(), slice_indices_list, study_id)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Model","metadata":{}},{"cell_type":"code","source":"class ImprovedCNN(nn.Module):\n    def __init__(self):\n        super(ImprovedCNN, self).__init__()\n        \n        self.conv1 = nn.Conv2d(30, 64, kernel_size=3, padding=1)\n        self.bn1 = nn.BatchNorm2d(64)\n        self.conv2 = nn.Conv2d(64, 128, kernel_size=3, padding=1)\n        self.bn2 = nn.BatchNorm2d(128)\n        self.conv3 = nn.Conv2d(128, 256, kernel_size=3, padding=1)\n        self.bn3 = nn.BatchNorm2d(256)\n        \n        self.pool = nn.MaxPool2d(2, 2)\n        \n        self.fc1 = nn.Linear(256 * 28 * 28, 512)  # 224 -> 112 -> 56 -> 28 (3개의 MaxPool 레이어)\n        self.dropout = nn.Dropout(0.5)\n        self.fc2 = nn.Linear(512, 75)  # 25 groups * 3 classes\n\n    def forward(self, x):\n        x = self.pool(F.relu(self.bn1(self.conv1(x))))\n        x = self.pool(F.relu(self.bn2(self.conv2(x))))\n        x = self.pool(F.relu(self.bn3(self.conv3(x))))\n        \n        x = x.view(x.size(0), -1)  # Flatten the tensor\n        x = F.relu(self.fc1(x))\n        x = self.dropout(x)\n        x = self.fc2(x)\n        return x.view(x.size(0), 25, 3)  # (batch_size, 25 groups, 3 classes)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torchvision.models as models","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class EfficientNetB4WithDropout(nn.Module):\n    def __init__(self, num_classes=75, dropout_prob=0.5):\n        super(EfficientNetB4WithDropout, self).__init__()\n        self.efficientnet = models.efficientnet_b4(pretrained=True)\n        \n        # 입력 채널을 30으로 변경\n        self.efficientnet.features[0][0] = nn.Conv2d(30, 48, kernel_size=3, stride=2, padding=1, bias=False)\n        \n        # 마지막 fully connected layer 변경\n        num_ftrs = self.efficientnet.classifier[1].in_features\n        self.efficientnet.classifier[1] = nn.Sequential(\n            nn.Dropout(p=dropout_prob),\n            nn.Linear(num_ftrs, num_classes)\n        )\n    \n    def forward(self, x):\n        x = self.efficientnet(x)\n        x = x.view(x.size(0), 25, 3)\n        return x","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class ResNet50WithDropout(nn.Module):\n    def __init__(self, num_classes=75, dropout_prob=0.5):\n        super(ResNet50WithDropout, self).__init__()\n        self.resnet50 = models.resnet50(pretrained=True)\n        self.resnet50.conv1 = nn.Conv2d(30, 64, kernel_size=7, stride=2, padding=3, bias=False)  # 입력 채널을 30으로 변경\n        # Replace the final fully connected layer\n        num_ftrs = self.resnet50.fc.in_features\n        self.resnet50.fc = nn.Sequential(\n            nn.Dropout(dropout_prob),\n            nn.Linear(num_ftrs, num_classes)\n        )\n    \n    def forward(self, x):\n        x = self.resnet50(x)\n        x = x.view(x.size(0), 25, 3)\n        return x","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = ResNet50WithDropout(num_classes=75, dropout_prob=0.5).to(device)\n# 손실 함수와 옵티마이저 정의\nclass_weights = torch.tensor([1.0, 2.0, 4.0]).to(device)\n\n# 손실 함수 정의\ncriterion = nn.CrossEntropyLoss(weight=class_weights)\noptimizer = optim.Adam(model.parameters(), lr=1e-4)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"summary(model, (128,30,224,224))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del model","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def custom_collate_fn(batch):\n    images = []\n    labels = []\n    study_ids = []\n    for sample in batch:\n        x, label, _, study_id = sample\n        if x.shape == torch.Size([30, image_size, image_size]):\n            images.append(x)\n            labels.append(label)\n            study_ids.append(study_id)\n        else:\n            print(f\"Skipping sample with shape {x.shape}\")\n    return torch.stack(images), torch.stack(labels), study_ids","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class CustomSubset(Subset):\n    def __init__(self, dataset, indices, transform=None):\n        super().__init__(dataset, indices)\n        self.transform = transform\n\n    def __getitem__(self, idx):\n        x, y, indices, study_id = self.dataset[self.indices[idx]]\n        if self.transform:\n            x = self.transform(image=x)['image']\n        return x, y, indices, study_id","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class ParticipantVisibleError(Exception):\n    pass\n\ndef get_condition(full_location: str) -> str:\n    for injury_condition in ['spinal', 'foraminal', 'subarticular']:\n        if injury_condition in full_location:\n            return injury_condition\n    raise ValueError(f'condition not found in {full_location}')\n\ndef score(\n        solution: pd.DataFrame,\n        submission: pd.DataFrame,\n        row_id_column_name: str,\n        any_severe_scalar: float,\n        class_weights: dict\n    ) -> float:\n    '''\n    Pseudocode:\n    1. Calculate the sample weighted log loss for each medical condition with class weights:\n    2. Derive a new any_severe label.\n    3. Calculate the sample weighted log loss for the new any_severe label.\n    4. Return the average of all of the label group log losses as the final score, normalized for the number of columns in each group.\n       This mitigates the impact of spinal stenosis having only half as many columns as the other two conditions.\n    '''\n\n    target_levels = ['normal_mild', 'moderate', 'severe']\n\n    # Run basic QC checks on the inputs\n    if not pandas.api.types.is_numeric_dtype(submission[target_levels].values):\n        raise ValueError('All submission values must be numeric')\n\n    if not np.isfinite(submission[target_levels].values).all():\n        raise ValueError('All submission values must be finite')\n\n    if solution[target_levels].min().min() < 0:\n        raise ValueError('All labels must be at least zero')\n    if submission[target_levels].min().min() < 0:\n        raise ValueError('All predictions must be at least zero')\n\n    solution['study_id'] = solution['row_id'].apply(lambda x: x.split('_')[0])\n    solution['location'] = solution['row_id'].apply(lambda x: '_'.join(x.split('_')[1:]))\n    solution['condition'] = solution['row_id'].apply(get_condition)\n\n    del solution[row_id_column_name]\n    del submission[row_id_column_name]\n    assert sorted(submission.columns) == sorted(target_levels)\n\n    submission['study_id'] = solution['study_id']\n    submission['location'] = solution['location']\n    submission['condition'] = solution['condition']\n\n    condition_losses = []\n    condition_weights = []\n    for condition in ['spinal', 'foraminal', 'subarticular']:\n        condition_indices = solution.loc[solution['condition'] == condition].index.values\n        \n        sample_weight = solution.loc[condition_indices, 'sample_weight'].values.copy()\n        for level, weight in class_weights.items():\n            sample_weight += solution.loc[condition_indices, level].values * weight\n\n        condition_loss = sklearn.metrics.log_loss(\n            y_true=solution.loc[condition_indices, target_levels].values,\n            y_pred=submission.loc[condition_indices, target_levels].values,\n            sample_weight=sample_weight\n        )\n        condition_losses.append(condition_loss)\n        condition_weights.append(1)\n\n    any_severe_spinal_labels = pd.Series(solution.loc[solution['condition'] == 'spinal'].groupby('study_id')['severe'].max())\n    any_severe_spinal_weights = pd.Series(solution.loc[solution['condition'] == 'spinal'].groupby('study_id')['sample_weight'].max())\n    any_severe_spinal_predictions = pd.Series(submission.loc[submission['condition'] == 'spinal'].groupby('study_id')['severe'].max())\n    any_severe_spinal_loss = sklearn.metrics.log_loss(\n        y_true=any_severe_spinal_labels,\n        y_pred=any_severe_spinal_predictions,\n        sample_weight=any_severe_spinal_weights\n    )\n    condition_losses.append(any_severe_spinal_loss)\n    condition_weights.append(any_severe_scalar)\n\n    return np.average(condition_losses, weights=condition_weights)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def validate(model, val_loader, criterion, device):\n    model.eval()\n    val_loss = 0.0\n    predictions = []\n    ground_truths = []\n\n    with torch.no_grad():\n        for images, labels, study_ids in tqdm(val_loader, desc=\"Validating\"):\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            outputs = outputs.view(outputs.size(0), 25, 3)\n            probabilities = torch.softmax(outputs, dim=2)\n            \n            \n            # 손실 계산\n            total_loss = 0\n            for j in range(25):\n                group_output = outputs[:, j, :]\n                group_label = labels[:, j*3:(j+1)*3].argmax(dim=1)\n                loss = criterion(group_output, group_label)\n                total_loss += loss\n\n            val_loss += total_loss.item()\n\n            for i in range(len(study_ids)):\n                study_id = study_ids[i]\n                pred = probabilities[i].cpu().numpy().squeeze()\n                gt = labels[i].cpu().numpy().reshape(-1, 3)\n                \n                index_names = [\n                    f'{study_id}_spinal_canal_stenosis_l1_l2',\n                    f'{study_id}_spinal_canal_stenosis_l2_l3',\n                    f'{study_id}_spinal_canal_stenosis_l3_l4',\n                    f'{study_id}_spinal_canal_stenosis_l4_l5',\n                    f'{study_id}_spinal_canal_stenosis_l5_s1',\n                    f'{study_id}_left_neural_foraminal_narrowing_l1_l2',\n                    f'{study_id}_left_neural_foraminal_narrowing_l2_l3',\n                    f'{study_id}_left_neural_foraminal_narrowing_l3_l4',\n                    f'{study_id}_left_neural_foraminal_narrowing_l4_l5',\n                    f'{study_id}_left_neural_foraminal_narrowing_l5_s1',\n                    f'{study_id}_right_neural_foraminal_narrowing_l1_l2',\n                    f'{study_id}_right_neural_foraminal_narrowing_l2_l3',\n                    f'{study_id}_right_neural_foraminal_narrowing_l3_l4',\n                    f'{study_id}_right_neural_foraminal_narrowing_l4_l5',\n                    f'{study_id}_right_neural_foraminal_narrowing_l5_s1',\n                    f'{study_id}_left_subarticular_stenosis_l1_l2',\n                    f'{study_id}_left_subarticular_stenosis_l2_l3',\n                    f'{study_id}_left_subarticular_stenosis_l3_l4',\n                    f'{study_id}_left_subarticular_stenosis_l4_l5',\n                    f'{study_id}_left_subarticular_stenosis_l5_s1',\n                    f'{study_id}_right_subarticular_stenosis_l1_l2',\n                    f'{study_id}_right_subarticular_stenosis_l2_l3',\n                    f'{study_id}_right_subarticular_stenosis_l3_l4',\n                    f'{study_id}_right_subarticular_stenosis_l4_l5',\n                    f'{study_id}_right_subarticular_stenosis_l5_s1',\n                ]\n\n                for idx, name in enumerate(index_names):\n                    predictions.append([name, pred[idx, 0], pred[idx, 1], pred[idx, 2]])\n                    ground_truths.append([name, gt[idx, 0], gt[idx, 1], gt[idx, 2]])\n\n    # 예측 결과를 데이터프레임으로 변환\n    pred_df = pd.DataFrame(predictions, columns=['row_id', 'normal_mild', 'moderate', 'severe'])\n    # 정답 레이블을 데이터프레임으로 변환\n    gt_df = pd.DataFrame(ground_truths, columns=['row_id', 'normal_mild', 'moderate', 'severe'])\n    gt_df['sample_weight'] = 1.0  # 샘플 가중치 설정\n\n    return (val_loss / len(val_loader)), pred_df, gt_df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"num_epochs = 50\nany_severe_scalar = 1.0  # Adjust based on your needs\nscheduler = ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5, verbose=True)\nresults_file = 'res50_training_results.csv'\n# 파일이 존재하지 않으면 헤더를 추가\nif not os.path.exists(results_file):\n    with open(results_file, mode='w', newline='') as file:\n        writer = csv.writer(file)\n        writer.writerow(['Fold', 'Epoch', 'Train Loss', 'Validation Loss', 'Validation Score'])\nfor fold, (train_idx, valid_idx) in enumerate(folds):\n    print(f'Fold {fold + 1}/{n_splits}')\n    \n    #데이터셋 생성 후 분할\n    full_dataset = CustomDataset(series_df=series_df, required_slices_dict=required_slices_nested_dict, label_dict=label_dict, transform=valid_transform)\n    \n    train_dataset = CustomSubset(full_dataset, train_idx, transform=train_transform)\n    valid_dataset = CustomSubset(full_dataset, valid_idx, transform=valid_transform)\n                                 \n    \n    # 데이터 로더 생성\n    train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=4, collate_fn=custom_collate_fn, prefetch_factor=2, pin_memory=True)\n    val_loader = DataLoader(valid_dataset, batch_size=128, shuffle=False, num_workers=4, collate_fn=custom_collate_fn, prefetch_factor=2, pin_memory=True)\n    \n    # 모델 초기화\n    model = ResNet50WithDropout(num_classes=75, dropout_prob=0.5).to(device)\n    model = nn.DataParallel(model)\n    optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)\n    print(f'Train Length: {len(train_dataset)}, Validation Length: {len(valid_dataset)}')\n    best_valid_score = np.inf\n    for epoch in range(num_epochs):\n        model.train()\n        train_loss = 0.0\n        \n        print(f'Epoch: {epoch + 1}')\n        \n        for i, (images, labels, _) in enumerate(tqdm(train_loader, desc=f\"Training Epoch {epoch+1}/{num_epochs}\")):\n            images, labels = images.to(device), labels.to(device)\n            \n            # 옵티마이저 초기화\n            optimizer.zero_grad()\n            \n            # 순전파 및 손실 계산\n            try:\n                outputs = model(images)\n            \n                # 각 그룹에 대한 CrossEntropyLoss를 적용\n                total_loss = 0\n                for j in range(25):  # 25 그룹\n                    group_output = outputs[:, j, :]\n                    group_label = labels[:, j*3:(j+1)*3].argmax(dim=1)\n                    loss = criterion(group_output, group_label)\n                    total_loss += loss\n            \n                # 역전파 및 가중치 업데이트\n                total_loss.backward()\n                optimizer.step()\n            \n                train_loss += total_loss.item()\n            \n            except Exception as e:\n                print(f\"Error in training loop at batch {i}, epoch {epoch + 1}: {e}\")\n            \n            # 메모리 관리\n            del images, labels, outputs, group_output, group_label, total_loss, loss\n            torch.cuda.empty_cache()\n        \n        print(f\"Epoch [{epoch+1}/{num_epochs}], Loss: {train_loss/len(train_loader):.4f}\")\n    \n        # Validation\n        val_loss, pred_df, gt_df = validate(model, val_loader, criterion, device)\n        print(f\"Validation Loss: {val_loss:.4f}\")\n#         print(pred_df)\n#         print(gt_df)\n        #Metric 계산\n        class_weights = {'normal_mild': 1.0, 'moderate': 2.0, 'severe': 4.0}\n        val_score = score(gt_df, pred_df, row_id_column_name='row_id', any_severe_scalar=1.0, class_weights = class_weights)\n        print(f\"Validation Score: {val_score:.4f}\")\n        \n        scheduler.step(val_loss)\n        # Save best model\n        if val_score < best_valid_score:\n            best_valid_score = val_score\n            model_save_path = f'res50_best_model_fold_{fold + 1}_DP.pth'\n            torch.save(model.state_dict(), model_save_path)\n            print(f'Saved best model with validation score {best_valid_score:.4f}')\n        \n        # 결과를 파일에 저장\n        with open(results_file, mode='a', newline='') as file:\n            writer = csv.writer(file)\n            writer.writerow([fold + 1, epoch + 1, train_loss/len(train_loader), val_loss, val_score])","metadata":{"scrolled":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}