{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":99552,"databundleVersionId":13851420,"sourceType":"competition"},{"sourceId":12637336,"sourceType":"datasetVersion","datasetId":7981664},{"sourceId":13313227,"sourceType":"datasetVersion","datasetId":8439513},{"sourceId":13313319,"sourceType":"datasetVersion","datasetId":8439588}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"56e82235","cell_type":"markdown","source":"Reference:\n- [RSNA-IAD | Swin Transformer| LB](https://www.kaggle.com/code/zshashz/rsna-iad-swin-transformer-lb)  \n- [Train 224x224 DICOM->PNGs EfficientNetV2S](https://www.kaggle.com/code/ichigoe/train-224x224-dicom-pngs-efficientnetv2s)","metadata":{}},{"id":"09fa3a65-a82f-46aa-84e0-427bf82369a3","cell_type":"markdown","source":"Summary:  \n1. Convert .dcm files to 384x384 npy files, save them, and simultaneously save the patient's metadata to a .csv file\n2. Calculate mean, std, and kurtosis channel-wise for each SeriesInstanceUID and save them to npy files\n4. Save the path of each npy file to a .csv file\n5. Create a classification model using image data and meta data based on timm.createmodel(‘swin_small_patch4_window7_224’).\n6. Use a custom criterion.\n7. Split into 5 folds using Multilabel Stratified KFold and train for 20 epochs on each fold.","metadata":{}},{"id":"e7afb930","cell_type":"markdown","source":"# 1. Import","metadata":{}},{"id":"9933ea12","cell_type":"code","source":"# Data handling\nimport os\nimport random\nimport warnings\nwarnings.filterwarnings('ignore')\nfrom typing import List, Dict, Optional, Tuple\nfrom IPython.display import display\nimport datetime\nimport time\nimport numpy as np\nimport polars as pl\nimport pandas as pd\nfrom sklearn.model_selection import  StratifiedKFold\n\n# Medical imaging\nimport cv2\n\n# Machine Lerning \nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.amp import autocast\nimport timm\n\n# Transformations\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# Visualization\nimport matplotlib.pyplot as plt\nfrom tqdm.notebook import tqdm\n\n# Experiment Management\nimport wandb\n\n# Competition API\n# import kaggle_evaluation.rsna_inference_server","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:14.781312Z","iopub.execute_input":"2025-10-09T16:19:14.781859Z","iopub.status.idle":"2025-10-09T16:19:14.78733Z","shell.execute_reply.started":"2025-10-09T16:19:14.781835Z","shell.execute_reply":"2025-10-09T16:19:14.786678Z"}},"outputs":[],"execution_count":null},{"id":"79324f0b","cell_type":"markdown","source":"# 2. Configuration","metadata":{}},{"id":"b76d9ee9","cell_type":"code","source":"# datetime for unique checkpoint filenames\ndate_time = datetime.datetime.now()\ndate_time = date_time.strftime('%Y-%m-%d_%H-%M-%S')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:14.788418Z","iopub.execute_input":"2025-10-09T16:19:14.788918Z","iopub.status.idle":"2025-10-09T16:19:14.808132Z","shell.execute_reply.started":"2025-10-09T16:19:14.788899Z","shell.execute_reply":"2025-10-09T16:19:14.807562Z"}},"outputs":[],"execution_count":null},{"id":"dd18a0cb","cell_type":"code","source":"# Run Configuration\nRUN_NAME = \"swin-s-original-agg-meta-5folds\"\nSAVE_DIR = \"/kaggle/working/results\"\nTEST_RUN = True\nSEED = 42\nDEVICE = \"cuda\"\n\n# Model Configuration\nPRETRAINED = False\n\n# Input Data Configuration\nIMAGE_SIZE = 384\nNUM_SLICES = 3\nUSE_AGGREGATED_SLICES = True\nBATCH_SIZE = 5\nNUM_FOLDS = 5\nLABEL_NAMES = [\n    # 13 classes\n    'Left Infraclinoid Internal Carotid Artery',\n    'Right Infraclinoid Internal Carotid Artery',\n    'Left Supraclinoid Internal Carotid Artery',\n    'Right Supraclinoid Internal Carotid Artery',\n    'Left Middle Cerebral Artery',\n    'Right Middle Cerebral Artery',\n    'Anterior Communicating Artery',\n    'Left Anterior Cerebral Artery',\n    'Right Anterior Cerebral Artery',\n    'Left Posterior Communicating Artery',\n    'Right Posterior Communicating Artery',\n    'Basilar Tip',\n    'Other Posterior Circulation',\n    # 'Aneurysm Present',\n]\nNUM_LABELS = len(LABEL_NAMES)\n\n# Training Configuration\nNUM_EPOCHS = 20\nPATIENCE = 5\nPOS_WEIGHT = torch.tensor([\n    54.743589743589745,\n    43.36734693877551,\n    12.13595166163142,\n    14.696750902527075,\n    18.85388127853881,\n    13.789115646258503,\n    10.977961432506888,\n    93.52173913043478,\n    76.64285714285714,\n    49.55813953488372,\n    42.04950495049505,\n    38.527272727272724,\n    37.47787610619469,\n    # 1.332618025751073\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:14.80937Z","iopub.execute_input":"2025-10-09T16:19:14.80967Z","iopub.status.idle":"2025-10-09T16:19:14.825937Z","shell.execute_reply.started":"2025-10-09T16:19:14.809655Z","shell.execute_reply":"2025-10-09T16:19:14.825247Z"}},"outputs":[],"execution_count":null},{"id":"e01f6b29","cell_type":"code","source":"RUN_NAME = RUN_NAME + f'-{IMAGE_SIZE}-{NUM_SLICES}'\nSAVE_DIR = SAVE_DIR + '/' + RUN_NAME + f'-{date_time}'\n\n# Weights & Biases Configuration\nif TEST_RUN:\n    USE_WANDB = False\n    WANDB_INIT = {}\n    ARTIFACT = {}\nelse:\n    USE_WANDB = True\n    WANDB_INIT = {\n        'project': 'RSNA-IAD',\n        'group': 'Image Classification',\n        'job_type': 'training model',\n        'save_code': True,\n    }\n    ARTIFACT = {\n        'name': RUN_NAME,\n        'type': 'model, optimizer, scheduler',\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:14.826641Z","iopub.execute_input":"2025-10-09T16:19:14.826873Z","iopub.status.idle":"2025-10-09T16:19:14.844414Z","shell.execute_reply.started":"2025-10-09T16:19:14.826858Z","shell.execute_reply":"2025-10-09T16:19:14.843909Z"}},"outputs":[],"execution_count":null},{"id":"9dff7a4b","cell_type":"code","source":"class Configuration:\n    \n    # Run\n    run_name = RUN_NAME\n    save_dir = SAVE_DIR\n    test_run = TEST_RUN\n    seed = SEED\n    device = DEVICE\n    \n    # Model\n    pretrained = PRETRAINED\n    \n    # Input Data\n    image_size = IMAGE_SIZE\n    num_slices = NUM_SLICES\n    use_aggregated_slices = USE_AGGREGATED_SLICES\n    batch_size = BATCH_SIZE\n    num_folds = NUM_FOLDS\n    label_names = LABEL_NAMES\n    num_labels = NUM_LABELS\n    \n    # Training\n    num_epochs = NUM_EPOCHS\n    patience = PATIENCE\n    pos_weight = POS_WEIGHT\n    \n    # Weights & Biases\n    use_wandb = USE_WANDB\n    wandb_init = WANDB_INIT\n    artifact = ARTIFACT\n\nCFG = Configuration\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:14.845617Z","iopub.execute_input":"2025-10-09T16:19:14.845826Z","iopub.status.idle":"2025-10-09T16:19:14.861547Z","shell.execute_reply.started":"2025-10-09T16:19:14.845811Z","shell.execute_reply":"2025-10-09T16:19:14.860789Z"}},"outputs":[],"execution_count":null},{"id":"628c2897","cell_type":"code","source":"# Set device\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n    print(f\"Memory: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB\")\n    print(f\"CUDA version: {torch.version.cuda}\")\n    torch.cuda.empty_cache()\n    CFG.device = 'cuda'\n    CFG.pos_weight = CFG.pos_weight.to(CFG.device, dtype=torch.float32)\nelse:\n    raise RuntimeError(\"CUDA is not available! This code requires GPU.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:14.862386Z","iopub.execute_input":"2025-10-09T16:19:14.862622Z","iopub.status.idle":"2025-10-09T16:19:14.999949Z","shell.execute_reply.started":"2025-10-09T16:19:14.862603Z","shell.execute_reply":"2025-10-09T16:19:14.999135Z"}},"outputs":[],"execution_count":null},{"id":"35d0f324","cell_type":"code","source":"def set_random_seed(seed=CFG.seed, deterministic=True):\n    \"\"\"\n    Set random seed.\n    \n    Args:\n        seed (int): Seed to be used.\n        deterministic (bool): Whether to set the deterministic option for\n            CUDNN backend, i.e., set `torch.backends.cudnn.deterministic`\n            to True and `torch.backends.cudnn.benchmark` to False.\n            Default: False.\n    \"\"\"\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    \n    if deterministic:\n        torch.backends.cudnn.benchmark = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:15.001539Z","iopub.execute_input":"2025-10-09T16:19:15.001782Z","iopub.status.idle":"2025-10-09T16:19:15.006652Z","shell.execute_reply.started":"2025-10-09T16:19:15.001758Z","shell.execute_reply":"2025-10-09T16:19:15.005901Z"}},"outputs":[],"execution_count":null},{"id":"869f959b","cell_type":"code","source":"set_random_seed(seed=CFG.seed, deterministic=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:15.007589Z","iopub.execute_input":"2025-10-09T16:19:15.008064Z","iopub.status.idle":"2025-10-09T16:19:15.022088Z","shell.execute_reply.started":"2025-10-09T16:19:15.008046Z","shell.execute_reply":"2025-10-09T16:19:15.021504Z"}},"outputs":[],"execution_count":null},{"id":"5cf18207","cell_type":"markdown","source":"# 3. Weights & Biases","metadata":{}},{"id":"7df88fa9","cell_type":"code","source":"if CFG.use_wandb:\n    os.environ['WANDB_NOTEBOOK_NAME'] = CFG.run_name\n    wandb.login()\n    run = wandb.init(**CFG.wandb_init)\n    artifact = wandb.Artifact(**CFG.artifact)\nelse:\n    run = None\n    artifact = None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:15.022783Z","iopub.execute_input":"2025-10-09T16:19:15.023045Z","iopub.status.idle":"2025-10-09T16:19:15.035318Z","shell.execute_reply.started":"2025-10-09T16:19:15.023022Z","shell.execute_reply":"2025-10-09T16:19:15.034694Z"}},"outputs":[],"execution_count":null},{"id":"0b593a9c","cell_type":"markdown","source":"# 4. Model","metadata":{}},{"id":"71313d5a","cell_type":"code","source":"class SwinWithMetaModel(nn.Module):\n    \n    def __init__(self, model_name='swin_s', pretrained=CFG.pretrained,\n                 num_classes=CFG.num_labels):\n        super().__init__()\n        self.model_name = model_name\n        \n        if model_name == 'swin_s':\n            self.backbone = timm.create_model(\n                'swin_small_patch4_window7_224',\n                pretrained=pretrained,\n                img_size=CFG.image_size,\n                num_classes=0,\n                drop_rate=0.3,\n                drop_path_rate=0.2,\n                global_poopling='')\n            \n            # input layer modification: 3 channels -> CFG.num_slices channels\n            self.backbone.patch_embed.proj = nn.Conv2d(\n                in_channels=CFG.num_slices,\n                out_channels=96,\n                kernel_size=4,\n                stride=4,\n            )\n        else:\n            raise ValueError(f\"Model {model_name} is not supported.\")\n        \n        with torch.no_grad():\n            dummy_input = torch.zeros(\n                1,\n                CFG.num_slices,\n                CFG.image_size,\n                CFG.image_size\n                )\n            features = self.backbone(dummy_input)\n            \n            if len(features.shape) == 4:\n                # Conv features (batch, channels, height, width)\n                num_features = features.shape[1]\n                self.needs_pool = True\n            elif len(features.shape) == 3:\n                # Transformer features (batch, sequence, features)\n                num_features = features.shape[-1]\n                self.needs_pool = False\n                self.needs_seq_pool = True\n            else:\n                # Already flat features (batch, features)\n                num_features = features.shape[1]\n                self.needs_pool = False\n                self.needs_seq_pool = False\n        # print(f\"Model name: {model_name}\")\n        # print(f\"Features: {num_features}\")\n        # print(f\"Output Shape: {features.shape}\")\n        \n        # Add global pooling for models that output spatial features\n        if self.needs_pool:\n            self.global_pool = nn.AdaptiveAvgPool2d(1)\n        \n        self.meta_features = nn.Sequential(\n            nn.Linear(2, 16),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(16, 32),\n            nn.ReLU()\n        )\n        \n        # According to \"LB #1\"\n        self.classifier = nn.Sequential(\n            nn.Linear(768 + 32, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, 256),\n            nn.BatchNorm1d(256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(256, num_classes)\n        )\n        \n    def forward(self, images, meta):\n        # Extract image features\n        image_features = self.backbone(images)\n        \n        # Apply appropriate pooling based on model type\n        if hasattr(self, 'needs_pool') and self.needs_pool:\n            # Conv features - apply global pooling\n            image_features = self.global_pool(image_features)\n            image_features = image_features.flatten(1)\n        elif hasattr(self, 'needs_seq_pool') and self.needs_seq_pool:\n            # Transformer features - average across sequence dimension\n            image_features = image_features.mean(dim=1)\n        elif len(image_features.shape) == 4:\n            # Fallback for any 4D output\n            image_features = F.adaptive_avg_pool2d(image_features, 1).flatten(1)\n        elif len(image_features.shape) == 3:\n            # Fallback for any 3D output\n            image_features = image_features.mean(dim=1)\n        \n        # Process Meta Features\n        meta_fieatures = self.meta_features(meta)\n        \n        # Combine Features\n        x = torch.cat([image_features, meta_fieatures], dim=1)\n        \n        # Classicication\n        x = self.classifier(x)\n        x = torch.nn.Sigmoid()(x)\n        return x\n\nmodel = SwinWithMetaModel(model_name='swin_s', pretrained=CFG.pretrained)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:15.036014Z","iopub.execute_input":"2025-10-09T16:19:15.036243Z","iopub.status.idle":"2025-10-09T16:19:16.403236Z","shell.execute_reply.started":"2025-10-09T16:19:15.036229Z","shell.execute_reply":"2025-10-09T16:19:16.402567Z"}},"outputs":[],"execution_count":null},{"id":"acbea94e","cell_type":"code","source":"model.to(CFG.device)\n\nis_in_cuda_list = []\n\nfor name, parameter in model.named_parameters():\n    # determination of cuda and its storage\n    is_in_cuda_list.append(parameter.is_cuda)\n    \nif all(is_in_cuda_list):\n    print('All parameters is in cuda')\n        \nelse:\n    print('One of the parameters is not in the cuda.')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.4058Z","iopub.execute_input":"2025-10-09T16:19:16.406058Z","iopub.status.idle":"2025-10-09T16:19:16.476621Z","shell.execute_reply.started":"2025-10-09T16:19:16.406041Z","shell.execute_reply":"2025-10-09T16:19:16.475945Z"}},"outputs":[],"execution_count":null},{"id":"d600170e","cell_type":"markdown","source":"# 5. Criterion","metadata":{}},{"id":"a9d846b0","cell_type":"code","source":"class FocalLoss(nn.Module):\n    \"\"\"Focal Loss for addressing class imbalance\"\"\"\n    def __init__(self, alpha=1, gamma=2):\n        super(FocalLoss, self).__init__()\n        self.alpha = alpha\n        self.gamma = gamma\n        \n    def forward(self, inputs, targets):\n        bce_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduction='none')\n        pt = torch.exp(-bce_loss)\n        focal_loss = self.alpha * (1-pt)**self.gamma * bce_loss\n        return focal_loss.mean()\n\nclass WeightedMultiLabelLoss(nn.Module):\n    \"\"\"Weighted multi-label loss\"\"\"\n    def __init__(self, aneurysm_weight=3.0):\n        super(WeightedMultiLabelLoss, self).__init__()\n        self.weights = torch.ones(CFG.num_labels, device=device)\n        # self.weights[-1] = aneurysm_weight\n        \n    def forward(self, outputs, targets):\n        bce_loss = F.binary_cross_entropy_with_logits(outputs, targets, reduction='none')\n        weighted_loss = bce_loss * self.weights\n        return weighted_loss.mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.477225Z","iopub.execute_input":"2025-10-09T16:19:16.477404Z","iopub.status.idle":"2025-10-09T16:19:16.483545Z","shell.execute_reply.started":"2025-10-09T16:19:16.47739Z","shell.execute_reply":"2025-10-09T16:19:16.482817Z"}},"outputs":[],"execution_count":null},{"id":"cfc8fa44","cell_type":"code","source":"class ImprovedLoss(nn.Module):\n    \"\"\"Advanced combined loss function\"\"\"\n    def __init__(self, aneurysm_weight=3.0, focal_weight=0.3):\n        super(ImprovedLoss, self).__init__()\n        self.aneurysm_weight = aneurysm_weight\n        self.focal_weight = focal_weight\n        \n        self.weights = torch.ones(CFG.num_labels, device=device)\n        # self.weights[-1] = aneurysm_weight\n        \n        self.focal_loss = FocalLoss(alpha=1, gamma=2)\n        \n    def forward(self, outputs, targets):\n        # Weighted BCE\n        bce_loss = F.binary_cross_entropy_with_logits(outputs,\n                                                      targets,\n                                                      reduction='none'\n                                                     )\n        weighted_bce = (bce_loss * self.weights).mean()\n        \n        # Focal Loss\n        focal_loss = self.focal_loss(outputs, targets)\n        \n        # Combination\n        loss = (1 - self.focal_weight) * weighted_bce \\\n            + self.focal_weight * focal_loss\n            \n        return loss\n            ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.484328Z","iopub.execute_input":"2025-10-09T16:19:16.484575Z","iopub.status.idle":"2025-10-09T16:19:16.498277Z","shell.execute_reply.started":"2025-10-09T16:19:16.484552Z","shell.execute_reply":"2025-10-09T16:19:16.497693Z"}},"outputs":[],"execution_count":null},{"id":"206e8241","cell_type":"code","source":"# # Optimizer\n# optimizer = torch.optim.AdamW(model.parameters())\n\n# # Loss Function\n# criterion = nn.BCEWithLogitsLoss(pos_weight=CFG.pos_weight)\n\n# # Schedulers\n# scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n#     optimizer,\n#     T_max=CFG.num_epochs,\n#     eta_min=1e-6\n# )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.498976Z","iopub.execute_input":"2025-10-09T16:19:16.499162Z","iopub.status.idle":"2025-10-09T16:19:16.517118Z","shell.execute_reply.started":"2025-10-09T16:19:16.499147Z","shell.execute_reply":"2025-10-09T16:19:16.516421Z"}},"outputs":[],"execution_count":null},{"id":"2a91e30a","cell_type":"code","source":"def build_models():\n    \n    # Model\n    model = SwinWithMetaModel(model_name='swin_s', pretrained=CFG.pretrained)\n    model.to(CFG.device)\n    # Optimizer\n    optimizer = torch.optim.AdamW(model.parameters())\n    # Loss Function\n    # criterion = nn.BCEWithLogitsLoss(pos_weight=CFG.pos_weight)\n    criterion = ImprovedLoss(aneurysm_weight=3.0, focal_weight=0.3)\n    # Schedulers\n    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n        optimizer,\n        T_max=CFG.num_epochs,\n        eta_min=1e-6\n    )\n    \n    return model, optimizer, criterion, scheduler","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.517908Z","iopub.execute_input":"2025-10-09T16:19:16.518164Z","iopub.status.idle":"2025-10-09T16:19:16.530101Z","shell.execute_reply.started":"2025-10-09T16:19:16.518148Z","shell.execute_reply":"2025-10-09T16:19:16.529331Z"}},"outputs":[],"execution_count":null},{"id":"9314cd98","cell_type":"markdown","source":"# 6. Dataset","metadata":{}},{"id":"97f1c259","cell_type":"code","source":"# SeriesInstanceUID list\n# series_list = os.listdir(f'../series_npy/{CFG.image_size}-aggregated')\nseries_list = [\n    '1.2.826.0.1.3680043.8.498.10023411164590664678534044036963716636',\n    '1.2.826.0.1.3680043.8.498.10022796280698534221758473208024838831',\n    '1.2.826.0.1.3680043.8.498.10022688097731894079510930966432818105',\n    '1.2.826.0.1.3680043.8.498.10021411248005513321236647460239137906',\n    '1.2.826.0.1.3680043.8.498.10014757658335054766479957992112625961',\n    '1.2.826.0.1.3680043.8.498.10012790035410518400400834395242853657',\n    '1.2.826.0.1.3680043.8.498.10009383108068795488741533244914370182',\n    '1.2.826.0.1.3680043.8.498.10005158603912009425635473100344077317',\n    '1.2.826.0.1.3680043.8.498.10004684224894397679901841656954650085',\n    '1.2.826.0.1.3680043.8.498.10004044428023505108375152878107656647',\n    ]\n\n# .npy path DataFrame\nimage_path_df = pd.read_csv(\n    f'/kaggle/input/rsna-iad-csv/image_{CFG.image_size}-aggregated_path_df.csv'\n)\n\n# Meta DataFrame\nmeta_df = pd.read_csv('/kaggle/input/rsna-iad-csv/meta.csv')\n\n# Label DataFrame\nlabel_df = pd.read_csv('/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv')\nlabel_df = label_df[['SeriesInstanceUID'] + CFG.label_names]\nlabel_df = label_df.loc[label_df['SeriesInstanceUID'].isin(series_list)]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.530956Z","iopub.execute_input":"2025-10-09T16:19:16.531214Z","iopub.status.idle":"2025-10-09T16:19:16.598688Z","shell.execute_reply.started":"2025-10-09T16:19:16.531198Z","shell.execute_reply":"2025-10-09T16:19:16.597897Z"}},"outputs":[],"execution_count":null},{"id":"b3545415-745f-4001-ba00-873d39973d96","cell_type":"code","source":"series_list","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.599465Z","iopub.execute_input":"2025-10-09T16:19:16.599722Z","iopub.status.idle":"2025-10-09T16:19:16.604489Z","shell.execute_reply.started":"2025-10-09T16:19:16.599697Z","shell.execute_reply":"2025-10-09T16:19:16.603926Z"}},"outputs":[],"execution_count":null},{"id":"89b15778-408d-43f0-bd3c-1d462017f726","cell_type":"code","source":"label_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.605378Z","iopub.execute_input":"2025-10-09T16:19:16.605632Z","iopub.status.idle":"2025-10-09T16:19:16.623998Z","shell.execute_reply.started":"2025-10-09T16:19:16.605611Z","shell.execute_reply":"2025-10-09T16:19:16.623269Z"}},"outputs":[],"execution_count":null},{"id":"b4e6ade1","cell_type":"code","source":"# for training\ntrain_transform = A.Compose(\n    [   \n        # Rotation\n        A.Rotate(limit=(-3, 3), p=0.5, border_mode=cv2.BORDER_WRAP,  # cv2.BORDER_WRAP,\n                 seed=CFG.seed\n        ),\n        \n        # Normalization\n        A.Normalize(normalization='min_max'),\n        \n        # ToTensor\n        ToTensorV2(),\n    ]\n)\n\n# for inference\ninference_transform = A.Compose(\n    [\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2()\n    ]\n)\n    \n# for TTA\ntta_transform = A.Compose(\n    [\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n            \n        # Horizontal flip\n        A.HorizontalFlip(p=1.0),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        \n        # Vertical flip\n        A.VerticalFlip(p=1.0),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        \n        # 90 degree rotation\n        A.RandomRotate90(p=1.0),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        \n        # ↓ Original\n        # Sharpen\n        A.Sharpen(alpha=(0, 1.0), p=1.0),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        \n        ToTensorV2(),\n    ]\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.624796Z","iopub.execute_input":"2025-10-09T16:19:16.624991Z","iopub.status.idle":"2025-10-09T16:19:16.641715Z","shell.execute_reply.started":"2025-10-09T16:19:16.624972Z","shell.execute_reply":"2025-10-09T16:19:16.641066Z"}},"outputs":[],"execution_count":null},{"id":"7e1ffd20","cell_type":"code","source":"class BaseDataset(torch.utils.data.Dataset):\n    '''\n    Datasetの__getitem__()は、num_slicesの枚数分だけ画像を出力する。\n    \n    Arguments:\n    - series_list: 画像のSeriesInstanceUIDのリスト\n    - image_path_df: 画像のパスを含むDataFrame\n    - meta_df: 患者のメタデータが入ったDataFrame\n    - label_df: ラベルが入ったDataFrame\n    - num_slices: 1つのシリーズから抽出するスライス数\n    - transforms: 画像変換のためのAlbumentationsのComposeオブジェクト\n    '''\n    def __init__(self,\n                 series_list: list,\n                 image_path_df=image_path_df,\n                 meta_df=meta_df,\n                 label_df=label_df,\n                 transforms=None\n                ):\n        self.series_list = series_list\n        self.image_path_df = image_path_df\n        self.meta_df = meta_df\n        self.label_df = label_df\n        self.transforms = transforms\n        self.num_slices = CFG.num_slices\n        self.use_aggregated_slices = CFG.use_aggregated_slices\n\n    def __len__(self):\n        return len(self.series_list)\n\n    def __getitem__(self, index):\n        # Index to SeriesInstanceUID\n        series_id = self.series_list[index]\n        # Extract image paths from DataFrame\n        image_path_df = self.image_path_df.loc[\n            self.image_path_df['series_id'] == series_id\n        ].reset_index(drop=True)\n        \n        # Get image paths\n        mean_path = image_path_df.loc[0, 'mean_path']\n        std_path = image_path_df.loc[0, 'std_path']\n        kurt_path = image_path_df.loc[0, 'kurtosis_path']\n        \n        # Convert My Local Path to Kaggle Path\n        mean_path = mean_path.replace('../series_npy/384-aggregated', '/kaggle/input/rsna-iad-aggregated-npy-dataset')\n        std_path = std_path.replace('../series_npy/384-aggregated', '/kaggle/input/rsna-iad-aggregated-npy-dataset')\n        kurt_path = kurt_path.replace('../series_npy/384-aggregated', '/kaggle/input/rsna-iad-aggregated-npy-dataset')\n\n        # Stack Images\n        images = []\n        images.append(np.load(mean_path).astype(np.uint8))\n        images.append(np.load(std_path).astype(np.uint8))\n        images.append(np.load(kurt_path).astype(np.uint8))\n        images = np.stack(images, axis=-1)\n        \n        # Transform\n        if self.transforms:\n            # ToTensorV2はnumpy.ndarrayをtorch.Tensorに変換する\n            augmented = self.transforms(image=images)\n            images = augmented['image']\n        else:\n            images = torch.tensor(images, dtype=torch.float32)\n            images = torch.permute(images, (2, 0, 1))\n            # Min-Max Normalization\n            if torch.max(images) > 1.0:\n                max_value = torch.max(images)\n                min_value = torch.min(images)\n                images = (images - min_value) / (max_value - min_value)\n                \n        # Meta data\n        meta = self.meta_df.loc[\n            self.meta_df['SeriesInstanceUID'] == series_id, ['age', 'sex']\n        ]\n        age = min(meta['age'].values[0], 100)\n        age = age / 100\n        sex = meta['sex'].values[0]\n        meta = torch.tensor([age, sex], dtype=torch.float32)\n\n        # Labels\n        labels = self.label_df.loc[\n            self.label_df['SeriesInstanceUID']==series_id, \\\n                CFG.label_names].values\n        labels = torch.tensor(labels, dtype=torch.float32)\n        labels = torch.squeeze(labels, dim=0)\n        \n        return images, meta, labels, series_id\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.642502Z","iopub.execute_input":"2025-10-09T16:19:16.642764Z","iopub.status.idle":"2025-10-09T16:19:16.652731Z","shell.execute_reply.started":"2025-10-09T16:19:16.642743Z","shell.execute_reply":"2025-10-09T16:19:16.65211Z"}},"outputs":[],"execution_count":null},{"id":"15340c5a","cell_type":"markdown","source":"# 7. DataLoader","metadata":{}},{"id":"2119da08","cell_type":"code","source":"# Represent multi-label with a single number\nlabel_df['label_id'] = 0\nexponentiations = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]\nfor i in range(len(CFG.label_names)):\n    label_name = CFG.label_names[i]\n    label_df['label_id'] += label_df[label_name] * exponentiations[i]\n    \n# Stratified K-Fold\nskf = StratifiedKFold(\n    n_splits=CFG.num_folds,\n    shuffle=True,\n    random_state=CFG.seed\n)\nlabel_df['fold'] = -1\nfor fold, (train_idx, val_idx) in enumerate(\\\n    skf.split(X=label_df, y=label_df['label_id'])):\n    label_df.loc[val_idx, 'fold'] = fold","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.6535Z","iopub.execute_input":"2025-10-09T16:19:16.653769Z","iopub.status.idle":"2025-10-09T16:19:16.681088Z","shell.execute_reply.started":"2025-10-09T16:19:16.653746Z","shell.execute_reply":"2025-10-09T16:19:16.6804Z"}},"outputs":[],"execution_count":null},{"id":"c52a3a90","cell_type":"code","source":"def build_dataloaders(fold: int):\n\n    train_series = label_df.loc[\\\n        label_df['fold']!=fold, \"SeriesInstanceUID\"].values\n    val_series = label_df.loc[\\\n        label_df['fold']==fold, \"SeriesInstanceUID\"].values\n    train_labels = label_df.loc[label_df['fold']!=fold, CFG.label_names].values\n    val_labels = label_df.loc[label_df['fold']==fold, CFG.label_names].values\n\n    if CFG.test_run:\n        \n        train_series = train_series[:5]\n        val_series = val_series[:5]\n        train_labels = train_labels[:5]\n        val_labels = val_labels[:5]\n\n    # 2 dimensions -> 1 dimension\n    train_series, val_series = train_series.flatten(), val_series.flatten()\n    print(f\"Train size: {len(train_series)}, Val size: {len(val_series)}\")\n\n    # Datasets\n    train_dataset = BaseDataset(\n        series_list=train_series,\n        transforms=train_transform\n    )\n    val_dataset = BaseDataset(\n        series_list=val_series,\n        transforms=train_transform # or tta_transform\n    )\n    \n    # Dataloaders\n    train_dataloader = torch.utils.data.DataLoader(\n        train_dataset,\n        batch_size=CFG.batch_size,\n        shuffle=True,\n        num_workers=0\n    )\n    val_dataloader = torch.utils.data.DataLoader(\n        val_dataset,\n        batch_size=CFG.batch_size,\n        shuffle=False,\n        num_workers=0\n    )\n\n    return train_dataloader, val_dataloader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.681886Z","iopub.execute_input":"2025-10-09T16:19:16.682133Z","iopub.status.idle":"2025-10-09T16:19:16.699207Z","shell.execute_reply.started":"2025-10-09T16:19:16.682108Z","shell.execute_reply":"2025-10-09T16:19:16.698578Z"}},"outputs":[],"execution_count":null},{"id":"536ec2e5","cell_type":"code","source":"train_dataloaders = []\nval_dataloaders = []\n\nfor fold in range(CFG.num_folds):\n    print(f\"Fold {fold}\")\n    train_dataloader, val_dataloader = build_dataloaders(fold)\n    train_dataloaders.append(train_dataloader)\n    val_dataloaders.append(val_dataloader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.702112Z","iopub.execute_input":"2025-10-09T16:19:16.702333Z","iopub.status.idle":"2025-10-09T16:19:16.724835Z","shell.execute_reply.started":"2025-10-09T16:19:16.702307Z","shell.execute_reply":"2025-10-09T16:19:16.724012Z"}},"outputs":[],"execution_count":null},{"id":"a2732754","cell_type":"markdown","source":"# 8. Functions","metadata":{}},{"id":"1161cd00","cell_type":"code","source":"# count execution time for one epoch\ndef count_time(start:float) -> float:\n    \n    elapsed_time = time.time() - start\n    elapsed_time /= 60\n    \n    return elapsed_time\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.725446Z","iopub.execute_input":"2025-10-09T16:19:16.725669Z","iopub.status.idle":"2025-10-09T16:19:16.740164Z","shell.execute_reply.started":"2025-10-09T16:19:16.725653Z","shell.execute_reply":"2025-10-09T16:19:16.739511Z"}},"outputs":[],"execution_count":null},{"id":"7d4611cf","cell_type":"code","source":"# to save model, optimizer, scheduler\ndef save_checkpoint(model, optimizer, scheduler,\n                    fold=100, save_dir=CFG.save_dir):\n    \n    model.to('cpu')\n    \n    model_state_dict =  model.state_dict()\n    optimizer_state_dict = optimizer.state_dict()\n    scheduler_state_dict = scheduler.state_dict()\n    \n    model_path = save_dir + f'/model_fold{fold}.pth'\n    optimizer_path = save_dir + f'/optimizer_fold{fold}.pth'\n    scheduler_path = save_dir + f'/scheduler_fold{fold}.pth'\n        \n    torch.save(model_state_dict, model_path)\n    torch.save(optimizer_state_dict, optimizer_path)\n    torch.save(scheduler_state_dict, scheduler_path)\n    \n    model.to(device)\n    \n    print(f\"Model saved.\")\n\n# # to load model, optimizer, scheduler\n# def load_checkpoint(model, optimizer, scheduler, save_dir=''):\n    \n#     model.to('cpu')\n    \n#     model.load_state_dict(save_dir + '/model.pth')\n#     optimizer.load_state_dict(save_dir + '/optimizer.pth')\n#     scheduler.load_state_dict(save_dir + '/scheduler.pth')\n    \n#     model.to(device)\n    \n#     return model, optimizer, scheduler","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.740873Z","iopub.execute_input":"2025-10-09T16:19:16.741058Z","iopub.status.idle":"2025-10-09T16:19:16.753424Z","shell.execute_reply.started":"2025-10-09T16:19:16.74104Z","shell.execute_reply":"2025-10-09T16:19:16.752796Z"}},"outputs":[],"execution_count":null},{"id":"81e585ab","cell_type":"code","source":"def add_files_to_artifact(fold=100, save_dir=CFG.save_dir):\n    \n    artifact.add_file(save_dir + f'/model_fold{fold}.pth')\n    artifact.add_file(save_dir + f'/optimizer_fold{fold}.pth')\n    artifact.add_file(save_dir + f'/scheduler_fold{fold}.pth')\n    \n    print(\"Files added to the artifact.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.754144Z","iopub.execute_input":"2025-10-09T16:19:16.754329Z","iopub.status.idle":"2025-10-09T16:19:16.766002Z","shell.execute_reply.started":"2025-10-09T16:19:16.754314Z","shell.execute_reply":"2025-10-09T16:19:16.765261Z"}},"outputs":[],"execution_count":null},{"id":"634e7cf3","cell_type":"markdown","source":"# 9. Training","metadata":{}},{"id":"d6593236","cell_type":"code","source":"def train_one_epoch(model, optimizer, scheduler, criterion,\n                    train_dataloader, val_dataloader,\n                    epoch=100) -> Tuple[float, float, List, List]:\n    \n    print(f'---------- Epoch {epoch} ----------')\n    \n    # Training\n    train_losses = []\n    \n    for images, meta, labels, series_ids in tqdm(train_dataloader):    \n        images = images.to(CFG.device)\n        meta = meta.to(CFG.device)\n        labels = labels.to(CFG.device)\n        optimizer.zero_grad()\n        with autocast(device_type=CFG.device):\n            outputs = model(images, meta)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            train_losses.append(loss.item())\n    \n    mean_train_loss = np.mean(train_losses)\n    print(f'Inner Mean Train Loss: {mean_train_loss:.4f}')\n    \n    # Validation\n    val_losses = []\n    inner_series_ids_list = []\n    inner_predicted_list = []\n    model.eval()\n    \n    with torch.no_grad():\n        for images, meta, labels, series_ids in tqdm(val_dataloader):\n            images = images.to(CFG.device)\n            meta = meta.to(CFG.device)\n            labels = labels.to(CFG.device)\n            with autocast(device_type=CFG.device):\n                outputs = model(images, meta)\n                loss = criterion(outputs, labels)\n                val_losses.append(loss.item())\n                inner_series_ids_list.extend(series_ids)\n                inner_predicted_list.extend(outputs.cpu().numpy().tolist())\n        \n    mean_val_loss = np.mean(val_losses)\n    print(f'Inner Mean Validation Loss: {mean_val_loss:.8f}')\n    \n    scheduler.step()\n        \n    return mean_train_loss, mean_val_loss,\\\n        inner_series_ids_list, inner_predicted_list","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.7667Z","iopub.execute_input":"2025-10-09T16:19:16.766941Z","iopub.status.idle":"2025-10-09T16:19:16.780983Z","shell.execute_reply.started":"2025-10-09T16:19:16.766913Z","shell.execute_reply":"2025-10-09T16:19:16.780171Z"}},"outputs":[],"execution_count":null},{"id":"7148edc8","cell_type":"code","source":"def main():\n    \n    if not CFG.test_run:\n        os.makedirs(CFG.save_dir, exist_ok=True)\n    \n    fold_val_losses = []\n    outer_series_ids_list = []\n    outer_predicted_list = []\n    \n    for fold in range(CFG.num_folds):\n        print(f'======================== Fold {fold} ========================')\n        \n        # Model, Optimizer, Criterion, Scheduler\n        model, optimizer, criterion, scheduler = build_models()\n        \n        # Dataloaders\n        train_dataloader = train_dataloaders[fold]\n        val_dataloader = val_dataloaders[fold]\n        \n        best_predicted_list = []\n        \n        best_val_loss = np.inf\n    \n        for epoch in range(CFG.num_epochs):\n            \n            # Train & Validation\n            start_time = time.time()\n            train_loss, val_loss, inner_series_ids, inner_predicted_list \\\n                = train_one_epoch(model, optimizer, scheduler, criterion,\n                                  train_dataloader, val_dataloader,\n                                  epoch=epoch)\n            elapsed_time = count_time(start_time)\n            print(f'Elapsed time: {elapsed_time}')\n            \n            # Log Losses to W&B\n            if CFG.use_wandb:\n                losses = {\n                    f'train_loss_fold{fold}': train_loss,\n                    f'val_loss_fold{fold}': val_loss\n                }\n                wandb.log(losses)\n            \n            if CFG.test_run:\n                if val_loss < best_val_loss:\n                    best_val_loss = val_loss\n                    best_predicted_list = inner_predicted_list\n                print('Test run: Skip saving checkpoint.')\n            else:\n                # Save best checkpoint\n                if val_loss < best_val_loss:\n                    best_val_loss = val_loss\n                    save_checkpoint(model, optimizer, scheduler, fold=fold)\n                    print(f'Best checkpoint saved at {CFG.save_dir}')\n                    best_predicted_list = inner_predicted_list\n        \n        # Add model, optimizer, scheduler files to W&B\n        if CFG.use_wandb:\n            add_files_to_artifact(fold=fold)\n        \n        # Collect results for all folds\n        fold_val_losses.append(best_val_loss)\n        \n        # Collect results for all folds\n        outer_series_ids_list.extend(inner_series_ids)\n        outer_predicted_list.extend(best_predicted_list)\n        \n    print(f'====================== All folds completed ======================')\n    mean_fold_val_losses = np.mean(fold_val_losses)\n    print(f'Each Fold Validation Losses: {fold_val_losses}')\n    print(f'Mean Validation Losses: {mean_fold_val_losses:.8f}')\n        \n    if CFG.use_wandb:\n        # Log Mean Validation Loss to W&B\n        wandb.log({'mean_fold_val_losses': mean_fold_val_losses})\n        \n        # Log all files to W&B\n        run.log_artifact(artifact)\n        print('All artifacts were logged to W&B')\n            \n    return outer_series_ids_list, outer_predicted_list","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.781771Z","iopub.execute_input":"2025-10-09T16:19:16.782047Z","iopub.status.idle":"2025-10-09T16:19:16.798812Z","shell.execute_reply.started":"2025-10-09T16:19:16.782028Z","shell.execute_reply":"2025-10-09T16:19:16.798026Z"}},"outputs":[],"execution_count":null},{"id":"1fd99e87","cell_type":"code","source":"outer_series_ids_list, outer_predicted_list = main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:16.79964Z","iopub.execute_input":"2025-10-09T16:19:16.799882Z","iopub.status.idle":"2025-10-09T16:19:53.497028Z","shell.execute_reply.started":"2025-10-09T16:19:16.799856Z","shell.execute_reply":"2025-10-09T16:19:53.496406Z"}},"outputs":[],"execution_count":null},{"id":"9ddd56fc","cell_type":"code","source":"result_df = pd.DataFrame()\n\nresult_df['SeriesInstanceUID'] = outer_series_ids_list\nresult_df[CFG.label_names] = outer_predicted_list\n\nif not CFG.test_run:\n    result_df.to_csv(f'{CFG.save_dir}/predicted_labels.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:53.498203Z","iopub.execute_input":"2025-10-09T16:19:53.498404Z","iopub.status.idle":"2025-10-09T16:19:53.508324Z","shell.execute_reply.started":"2025-10-09T16:19:53.498389Z","shell.execute_reply":"2025-10-09T16:19:53.507734Z"}},"outputs":[],"execution_count":null},{"id":"b0c48e57","cell_type":"code","source":"result_df.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:53.509006Z","iopub.execute_input":"2025-10-09T16:19:53.509183Z","iopub.status.idle":"2025-10-09T16:19:53.555996Z","shell.execute_reply.started":"2025-10-09T16:19:53.509169Z","shell.execute_reply":"2025-10-09T16:19:53.555114Z"}},"outputs":[],"execution_count":null},{"id":"c104349c","cell_type":"markdown","source":"# 10. Finish","metadata":{}},{"id":"f94bd995","cell_type":"code","source":"if CFG.use_wandb:\n    run.finish()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-09T16:19:53.556783Z","iopub.execute_input":"2025-10-09T16:19:53.55706Z","iopub.status.idle":"2025-10-09T16:19:53.562574Z","shell.execute_reply.started":"2025-10-09T16:19:53.557037Z","shell.execute_reply":"2025-10-09T16:19:53.561572Z"}},"outputs":[],"execution_count":null}]}