{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":20270,"databundleVersionId":1222630},{"sourceType":"datasetVersion","sourceId":1353811,"datasetId":762203,"databundleVersionId":1386220}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"### Confirming paths","metadata":{}},{"cell_type":"code","source":"import os\n\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    print(dirname)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-17T15:37:04.471278Z","iopub.execute_input":"2026-05-17T15:37:04.471451Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport random\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport timm\n\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import StratifiedGroupKFold\n\nfrom sklearn.metrics import (\n    roc_auc_score,\n    roc_curve,\n    confusion_matrix,\n    accuracy_score,\n    precision_score,\n    recall_score,\n    f1_score,\n    ConfusionMatrixDisplay\n)\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nfrom tqdm.auto import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:42:39.925287Z","iopub.execute_input":"2026-05-18T17:42:39.925870Z","iopub.status.idle":"2026-05-18T17:42:53.976155Z","shell.execute_reply.started":"2026-05-18T17:42:39.925842Z","shell.execute_reply":"2026-05-18T17:42:53.975322Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Reproducibility","metadata":{}},{"cell_type":"code","source":"def seed_everything(seed=42):\n\n    random.seed(seed)\n    np.random.seed(seed)\n\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nseed_everything()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:48:58.478320Z","iopub.execute_input":"2026-05-18T17:48:58.478906Z","iopub.status.idle":"2026-05-18T17:48:58.484780Z","shell.execute_reply.started":"2026-05-18T17:48:58.478876Z","shell.execute_reply":"2026-05-18T17:48:58.484127Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load 2020 dataset","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(\n    \"/kaggle/input/competitions/siim-isic-melanoma-classification/train.csv\"\n)\n\nimg_dir = \"/kaggle/input/competitions/siim-isic-melanoma-classification/jpeg/train\"\n\ndf[\"image_path\"] = df[\"image_name\"].apply(\n    lambda x: os.path.join(img_dir, f\"{x}.jpg\")\n)\n\ndf.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:00.768975Z","iopub.execute_input":"2026-05-18T17:49:00.769750Z","iopub.status.idle":"2026-05-18T17:49:00.860023Z","shell.execute_reply.started":"2026-05-18T17:49:00.769717Z","shell.execute_reply":"2026-05-18T17:49:00.859380Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Missing Values Handling ","metadata":{}},{"cell_type":"code","source":"df[\"age_approx\"] = df[\"age_approx\"].fillna(\n    df[\"age_approx\"].median()\n)\n\ndf[\"sex\"] = df[\"sex\"].map({\n    \"male\": 1,\n    \"female\": 0\n})\n\ndf[\"sex\"] = df[\"sex\"].fillna(-1)\n\ndf[\"anatom_site_general_challenge\"] = (\n    df[\"anatom_site_general_challenge\"]\n    .fillna(\"unknown\")\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:04.239873Z","iopub.execute_input":"2026-05-18T17:49:04.240252Z","iopub.status.idle":"2026-05-18T17:49:04.252873Z","shell.execute_reply.started":"2026-05-18T17:49:04.240223Z","shell.execute_reply":"2026-05-18T17:49:04.252111Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Encode Anatomical Site","metadata":{}},{"cell_type":"code","source":"le = LabelEncoder()\n\ndf[\"site_encoded\"] = le.fit_transform(\n    df[\"anatom_site_general_challenge\"]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:06.667796Z","iopub.execute_input":"2026-05-18T17:49:06.668539Z","iopub.status.idle":"2026-05-18T17:49:06.677359Z","shell.execute_reply.started":"2026-05-18T17:49:06.668505Z","shell.execute_reply":"2026-05-18T17:49:06.676691Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(df[['image_path', 'age_approx', 'sex','site_encoded', 'target']].head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:07.358889Z","iopub.execute_input":"2026-05-18T17:49:07.359614Z","iopub.status.idle":"2026-05-18T17:49:07.369398Z","shell.execute_reply.started":"2026-05-18T17:49:07.359581Z","shell.execute_reply":"2026-05-18T17:49:07.368605Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## PATIENT-WISE SPLIT\nThis avoids leakage ","metadata":{}},{"cell_type":"code","source":"df[\"fold\"] = -1\n\nsgkf = StratifiedGroupKFold(\n    n_splits=5,\n    shuffle=True,\n    random_state=42\n)\n\nfor fold, (train_idx, val_idx) in enumerate(\n    sgkf.split(\n        df,\n        y=df[\"target\"],\n        groups=df[\"patient_id\"]\n    )\n):\n\n    df.loc[val_idx, \"fold\"] = fold\n\ntrain_df = df[df.fold != 0].reset_index(drop=True)\nval_df   = df[df.fold == 0].reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:09.638853Z","iopub.execute_input":"2026-05-18T17:49:09.639534Z","iopub.status.idle":"2026-05-18T17:49:10.271329Z","shell.execute_reply.started":"2026-05-18T17:49:09.639500Z","shell.execute_reply":"2026-05-18T17:49:10.270444Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Normalize Metadata","metadata":{}},{"cell_type":"code","source":"mean = train_df[\"age_approx\"].mean()\nstd  = train_df[\"age_approx\"].std()\n\ntrain_df[\"age_approx\"] = (\n    train_df[\"age_approx\"] - mean\n) / std\n\nval_df[\"age_approx\"] = (\n    val_df[\"age_approx\"] - mean\n) / std","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:11.942503Z","iopub.execute_input":"2026-05-18T17:49:11.942806Z","iopub.status.idle":"2026-05-18T17:49:11.949704Z","shell.execute_reply.started":"2026-05-18T17:49:11.942781Z","shell.execute_reply":"2026-05-18T17:49:11.948944Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Augmentation","metadata":{}},{"cell_type":"code","source":"def get_train_transforms():\n\n    return A.Compose([\n\n        A.Resize(512, 512),\n\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n\n        A.RandomRotate90(p=0.5),\n\n        A.Affine(\n            scale=(0.95, 1.05),\n            translate_percent=(0.05, 0.05),\n            rotate=(-20, 20),\n            p=0.5\n        ),\n\n        A.ColorJitter(\n            brightness=0.2,\n            contrast=0.2,\n            saturation=0.2,\n            hue=0.1,\n            p=0.5\n        ),\n\n        A.Normalize(),\n\n        ToTensorV2()\n    ])\n\n\ndef get_valid_transforms():\n\n    return A.Compose([\n\n        A.Resize(512, 512),\n\n        A.Normalize(),\n\n        ToTensorV2()\n    ])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:14.218711Z","iopub.execute_input":"2026-05-18T17:49:14.219347Z","iopub.status.idle":"2026-05-18T17:49:14.224805Z","shell.execute_reply.started":"2026-05-18T17:49:14.219317Z","shell.execute_reply":"2026-05-18T17:49:14.224025Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset","metadata":{}},{"cell_type":"code","source":"class ISICDataset(Dataset):\n\n    def __init__(self, df, transforms=None):\n\n        self.df = df.reset_index(drop=True)\n        self.transforms = transforms\n\n        self.meta_features = [\n            \"age_approx\",\n            \"sex\",\n            \"site_encoded\"\n        ]\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n\n        row = self.df.loc[idx]\n\n        image = cv2.imread(row.image_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n        if self.transforms:\n            image = self.transforms(image=image)[\"image\"]\n\n        meta = torch.tensor(\n            row[self.meta_features].values.astype(np.float32)\n        )\n\n        target = torch.tensor(row.target).float()\n\n        return image, meta, target","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:16.978715Z","iopub.execute_input":"2026-05-18T17:49:16.979457Z","iopub.status.idle":"2026-05-18T17:49:16.984982Z","shell.execute_reply.started":"2026-05-18T17:49:16.979427Z","shell.execute_reply":"2026-05-18T17:49:16.984139Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Hierarchical Spatial FiLM","metadata":{}},{"cell_type":"code","source":"class SpatialFiLM(nn.Module):\n\n    def __init__(self, channels, meta_features):\n\n        super().__init__()\n\n        self.gamma = nn.Linear(meta_features, channels)\n        self.beta  = nn.Linear(meta_features, channels)\n\n    def forward(self, x, meta):\n\n        gamma = self.gamma(meta)\n        beta  = self.beta(meta)\n\n        gamma = gamma.unsqueeze(-1).unsqueeze(-1)\n        beta  = beta.unsqueeze(-1).unsqueeze(-1)\n\n        return gamma * x + beta","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:19.341347Z","iopub.execute_input":"2026-05-18T17:49:19.342002Z","iopub.status.idle":"2026-05-18T17:49:19.346785Z","shell.execute_reply.started":"2026-05-18T17:49:19.341971Z","shell.execute_reply":"2026-05-18T17:49:19.345998Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Hierarchical FiLM Model","metadata":{}},{"cell_type":"code","source":"class HierarchicalFiLMModel(nn.Module):\n\n    def __init__(self, meta_features=3):\n\n        super().__init__()\n\n        self.backbone = timm.create_model(\n            \"efficientnet_b3\",\n            pretrained=True,\n            features_only=True\n        )\n\n        channels = [24, 32, 48, 136, 384]\n\n        # FiLM blocks\n        self.film1 = SpatialFiLM(channels[0], meta_features)\n        self.film2 = SpatialFiLM(channels[1], meta_features)\n        self.film3 = SpatialFiLM(channels[2], meta_features)\n        self.film4 = SpatialFiLM(channels[3], meta_features)\n        self.film5 = SpatialFiLM(channels[4], meta_features)\n\n        # Metadata branch\n        self.meta_branch = nn.Sequential(\n\n            nn.Linear(meta_features, 128),\n\n            nn.ReLU(),\n\n            nn.BatchNorm1d(128),\n\n            nn.Dropout(0.3)\n        )\n\n        # Total image channels:\n        # 24 + 32 + 48 + 136 + 384 = 624\n        # +128 metadata = 752\n\n        self.classifier = nn.Sequential(\n\n            nn.Linear(752, 256),\n\n            nn.ReLU(),\n\n            nn.Dropout(0.4),\n\n            nn.Linear(256, 1)\n        )\n\n    def forward(self, image, meta):\n\n        features = self.backbone(image)\n\n        # Hierarchical FiLM\n        f1 = self.film1(features[0], meta)\n        f2 = self.film2(features[1], meta)\n        f3 = self.film3(features[2], meta)\n        f4 = self.film4(features[3], meta)\n        f5 = self.film5(features[4], meta)\n\n        # Multiscale pooling\n        p1 = F.adaptive_avg_pool2d(f1, 1).flatten(1)\n        p2 = F.adaptive_avg_pool2d(f2, 1).flatten(1)\n        p3 = F.adaptive_avg_pool2d(f3, 1).flatten(1)\n        p4 = F.adaptive_avg_pool2d(f4, 1).flatten(1)\n        p5 = F.adaptive_avg_pool2d(f5, 1).flatten(1)\n\n        # Concatenate all scales\n        img_feat = torch.cat(\n            [p1, p2, p3, p4, p5],\n            dim=1\n        )\n\n        # Metadata embedding\n        meta_feat = self.meta_branch(meta)\n\n        # Final fusion\n        x = torch.cat(\n            [img_feat, meta_feat],\n            dim=1\n        )\n\n        out = self.classifier(x)\n\n        return out.squeeze(1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:21.491117Z","iopub.execute_input":"2026-05-18T17:49:21.491890Z","iopub.status.idle":"2026-05-18T17:49:21.500777Z","shell.execute_reply.started":"2026-05-18T17:49:21.491853Z","shell.execute_reply":"2026-05-18T17:49:21.499917Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Focal Loss Cell","metadata":{}},{"cell_type":"code","source":"class FocalLoss(nn.Module):\n\n    def __init__(self, alpha=1, gamma=2):\n\n        super().__init__()\n\n        self.alpha = alpha\n        self.gamma = gamma\n\n    def forward(self, logits, targets):\n\n        bce = F.binary_cross_entropy_with_logits(\n            logits,\n            targets,\n            reduction='none'\n        )\n\n        probs = torch.sigmoid(logits)\n\n        pt = torch.where(\n            targets == 1,\n            probs,\n            1 - probs\n        )\n\n        loss = self.alpha * (1 - pt) ** self.gamma * bce\n\n        return loss.mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:24.893295Z","iopub.execute_input":"2026-05-18T17:49:24.894022Z","iopub.status.idle":"2026-05-18T17:49:24.898840Z","shell.execute_reply.started":"2026-05-18T17:49:24.893988Z","shell.execute_reply":"2026-05-18T17:49:24.898049Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## DataLoaders\n\nUse num_workers=0","metadata":{}},{"cell_type":"code","source":"train_dataset = ISICDataset(\n    train_df,\n    transforms=get_train_transforms()\n)\n\nval_dataset = ISICDataset(\n    val_df,\n    transforms=get_valid_transforms()\n)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    shuffle=True,\n    num_workers=0\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=0\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:27.557384Z","iopub.execute_input":"2026-05-18T17:49:27.558043Z","iopub.status.idle":"2026-05-18T17:49:27.572203Z","shell.execute_reply.started":"2026-05-18T17:49:27.558011Z","shell.execute_reply":"2026-05-18T17:49:27.571344Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Validation Function","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import average_precision_score\n\n\ndef validate(model, loader, threshold=0.5):\n\n    model.eval()\n\n    preds = []\n    targets_list = []\n\n    with torch.no_grad():\n\n        for images, meta, targets in tqdm(\n            loader,\n            desc=\"Validation\",\n            leave=False\n        ):\n\n            images = images.to(device)\n            meta = meta.to(device)\n\n            outputs = model(images, meta)\n\n            probs = torch.sigmoid(outputs)\n\n            preds.extend(\n                probs.cpu().numpy().ravel()\n            )\n\n            targets_list.extend(\n                targets.numpy().ravel()\n            )\n\n    preds = np.array(preds)\n    targets_list = np.array(targets_list)\n\n    auc = roc_auc_score(\n        targets_list,\n        preds\n    )\n\n    pr_auc = average_precision_score(\n        targets_list,\n        preds\n    )\n\n    binary_preds = (\n        preds > threshold\n    ).astype(int)\n\n    accuracy = accuracy_score(\n        targets_list,\n        binary_preds\n    )\n\n    precision = precision_score(\n        targets_list,\n        binary_preds,\n        zero_division=0\n    )\n\n    recall = recall_score(\n        targets_list,\n        binary_preds,\n        zero_division=0\n    )\n\n    f1 = f1_score(\n        targets_list,\n        binary_preds,\n        zero_division=0\n    )\n\n    tn, fp, fn, tp = confusion_matrix(\n        targets_list,\n        binary_preds\n    ).ravel()\n\n    specificity = tn / (tn + fp)\n\n    fpr, tpr, _ = roc_curve(\n        targets_list,\n        preds\n    )\n\n    metrics = {\n\n        \"AUC\": auc,\n        \"PR_AUC\": pr_auc,\n        \"Accuracy\": accuracy,\n        \"Precision\": precision,\n        \"Recall\": recall,\n        \"F1\": f1,\n        \"Specificity\": specificity\n    }\n\n    return metrics, fpr, tpr, preds, targets_list","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:29.976373Z","iopub.execute_input":"2026-05-18T17:49:29.977142Z","iopub.status.idle":"2026-05-18T17:49:29.985968Z","shell.execute_reply.started":"2026-05-18T17:49:29.977060Z","shell.execute_reply":"2026-05-18T17:49:29.984880Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model Setup","metadata":{}},{"cell_type":"code","source":"device = torch.device(\n    \"cuda\" if torch.cuda.is_available() else \"cpu\"\n)\n\nmodel = HierarchicalFiLMModel().to(device)\n\ncriterion = FocalLoss(\n    alpha=1,\n    gamma=2\n)\n\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=1e-4,\n    weight_decay=1e-4\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:33.243992Z","iopub.execute_input":"2026-05-18T17:49:33.245056Z","iopub.status.idle":"2026-05-18T17:49:33.598456Z","shell.execute_reply.started":"2026-05-18T17:49:33.245016Z","shell.execute_reply":"2026-05-18T17:49:33.597752Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=5\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:35.780300Z","iopub.execute_input":"2026-05-18T17:49:35.780570Z","iopub.status.idle":"2026-05-18T17:49:35.786010Z","shell.execute_reply.started":"2026-05-18T17:49:35.780547Z","shell.execute_reply":"2026-05-18T17:49:35.785284Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training Function","metadata":{}},{"cell_type":"code","source":"def train_one_epoch(model, loader):\n\n    model.train()\n\n    running_loss = 0\n\n    progress_bar = tqdm(\n        loader,\n        desc=\"Training\",\n        leave=False\n    )\n\n    for images, meta, targets in progress_bar:\n\n        images = images.to(device)\n        meta = meta.to(device)\n        targets = targets.to(device)\n\n        optimizer.zero_grad()\n\n        outputs = model(images, meta)\n\n        loss = criterion(outputs, targets)\n\n        loss.backward()\n\n        torch.nn.utils.clip_grad_norm_(\n            model.parameters(),\n            max_norm=1.0\n        )\n\n        optimizer.step()\n\n        running_loss += loss.item()\n\n        progress_bar.set_postfix(\n            loss=f\"{loss.item():.4f}\"\n        )\n\n    return running_loss / len(loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:40.964308Z","iopub.execute_input":"2026-05-18T17:49:40.965233Z","iopub.status.idle":"2026-05-18T17:49:40.970666Z","shell.execute_reply.started":"2026-05-18T17:49:40.965201Z","shell.execute_reply":"2026-05-18T17:49:40.969815Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Training Loop","metadata":{}},{"cell_type":"code","source":"history = {\n    \"train_loss\": [],\n    \"auc\": [],\n    \"pr_auc\": [],\n    \"f1\": [],\n    \"recall\": [],\n    \"specificity\": []\n}\n\nbest_auc = 0\n\nEPOCHS = 5\n\nfor epoch in range(EPOCHS):\n\n    print(f\"\\n===== Epoch {epoch+1}/{EPOCHS} =====\")\n\n    train_loss = train_one_epoch(\n        model,\n        train_loader\n    )\n\n    metrics, fpr, tpr, preds, targets_list = validate(\n        model,\n        val_loader,\n        threshold=0.5\n    )\n\n    scheduler.step()\n\n    if metrics[\"AUC\"] > best_auc:\n\n        best_auc = metrics[\"AUC\"]\n\n        torch.save(\n            model.state_dict(),\n            \"best_hierarchical_film.pth\"\n        )\n\n        print(\"✅ Best model saved!\")\n\n    history[\"train_loss\"].append(train_loss)\n    history[\"auc\"].append(metrics[\"AUC\"])\n    history[\"pr_auc\"].append(metrics[\"PR_AUC\"])\n    history[\"f1\"].append(metrics[\"F1\"])\n    history[\"recall\"].append(metrics[\"Recall\"])\n    history[\"specificity\"].append(\n        metrics[\"Specificity\"]\n    )\n\n    print(f\"Loss        : {train_loss:.4f}\")\n    print(f\"AUC         : {metrics['AUC']:.4f}\")\n    print(f\"PR-AUC      : {metrics['PR_AUC']:.4f}\")\n    print(f\"F1          : {metrics['F1']:.4f}\")\n    print(f\"Recall      : {metrics['Recall']:.4f}\")\n    print(f\"Specificity : {metrics['Specificity']:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:49:43.606496Z","iopub.execute_input":"2026-05-18T17:49:43.607243Z","iopub.status.idle":"2026-05-18T17:52:48.894496Z","shell.execute_reply.started":"2026-05-18T17:49:43.607209Z","shell.execute_reply":"2026-05-18T17:52:48.893360Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## ROC Curve","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(7,7))\n\nplt.plot(\n    fpr,\n    tpr,\n    label=f\"AUC={metrics['AUC']:.4f}\"\n)\n\nplt.plot([0,1], [0,1], linestyle=\"--\")\n\nplt.xlabel(\"False Positive Rate\")\nplt.ylabel(\"True Positive Rate\")\n\nplt.title(\"ROC Curve\")\n\nplt.legend()\nplt.grid()\n\nplt.savefig(\"roc_curve.png\", dpi=300)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:47:58.379010Z","iopub.execute_input":"2026-05-18T17:47:58.379885Z","iopub.status.idle":"2026-05-18T17:47:58.395386Z","shell.execute_reply.started":"2026-05-18T17:47:58.379852Z","shell.execute_reply":"2026-05-18T17:47:58.394341Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Threshold Search ","metadata":{}},{"cell_type":"code","source":"thresholds = np.arange(0.1, 1.0, 0.05)\n\nbest_f1 = 0\nbest_threshold = 0.5\nbest_preds = None\nbest_targets = None\n\nfor t in thresholds:\n\n    metrics, _, _, preds, targets_list = validate(\n        model,\n        val_loader,\n        threshold=t\n    )\n\n    if metrics[\"F1\"] > best_f1:\n\n        best_f1 = metrics[\"F1\"]\n        best_threshold = t\n        best_preds = preds\n        best_targets = targets_list\n\nprint(f\"Best Threshold: {best_threshold:.2f}\")\nprint(f\"Best F1: {best_f1:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-10T21:15:19.948453Z","iopub.execute_input":"2026-05-10T21:15:19.949305Z","iopub.status.idle":"2026-05-10T21:54:25.795897Z","shell.execute_reply.started":"2026-05-10T21:15:19.949267Z","shell.execute_reply":"2026-05-10T21:54:25.795010Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Confusion Matrix","metadata":{}},{"cell_type":"code","source":"binary_preds = (\n    best_preds > best_threshold\n).astype(int)\n\ncm = confusion_matrix(\n    best_targets,\n    binary_preds\n)\n\ndisp = ConfusionMatrixDisplay(\n    confusion_matrix=cm,\n    display_labels=[\n        \"Benign\",\n        \"Melanoma\"\n    ]\n)\n\ndisp.plot(cmap=\"Blues\")\n\nplt.title(\n    f\"Confusion Matrix (t={best_threshold:.2f})\"\n)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-10T20:35:25.490649Z","iopub.execute_input":"2026-05-10T20:35:25.491001Z","iopub.status.idle":"2026-05-10T20:35:25.620347Z","shell.execute_reply.started":"2026-05-10T20:35:25.490973Z","shell.execute_reply":"2026-05-10T20:35:25.619762Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Metric Curves Across Epochs","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(10,5))\n\nplt.plot(history[\"auc\"], label=\"AUC\")\nplt.plot(history[\"pr_auc\"], label=\"PR-AUC\")\nplt.plot(history[\"f1\"], label=\"F1\")\nplt.plot(history[\"recall\"], label=\"Recall\")\nplt.plot(history[\"specificity\"], label=\"Specificity\")\n\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Score\")\n\nplt.title(\"Validation Metrics Across Epochs\")\n\nplt.legend()\nplt.grid()\n\nplt.savefig(\"metrics.png\", dpi=300)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T17:48:44.595179Z","iopub.execute_input":"2026-05-18T17:48:44.595897Z","iopub.status.idle":"2026-05-18T17:48:44.607640Z","shell.execute_reply.started":"2026-05-18T17:48:44.595864Z","shell.execute_reply":"2026-05-18T17:48:44.606744Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"results = {\n    \"best_auc\": best_auc,\n    \"history\": history\n}\n\ntorch.save(results, \"training_results.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-10T20:25:23.547613Z","iopub.execute_input":"2026-05-10T20:25:23.548066Z","iopub.status.idle":"2026-05-10T20:25:23.553825Z","shell.execute_reply.started":"2026-05-10T20:25:23.548035Z","shell.execute_reply":"2026-05-10T20:25:23.553009Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.savefig(\"roc_curve.png\", dpi=300)\n\nplt.savefig(\"metrics.png\", dpi=300)\n\nplt.savefig(\"confusion_matrix.png\", dpi=300)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-10T20:27:57.053003Z","iopub.execute_input":"2026-05-10T20:27:57.053818Z","iopub.status.idle":"2026-05-10T20:27:57.329948Z","shell.execute_reply.started":"2026-05-10T20:27:57.053773Z","shell.execute_reply":"2026-05-10T20:27:57.329206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}