{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms, models\n# from sklearn.model_selection import GroupShuffleSplit\n# from sklearn.metrics import (roc_auc_score, confusion_matrix,\n#                              f1_score, balanced_accuracy_score)\n# import json\n# import warnings\n# warnings.filterwarnings('ignore')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 1. CONFIG & REPRODUCIBILITY ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# BASE = '/kaggle/input/siim-isic-melanoma-classification'\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# EPOCHS = 5\n# BATCH = 16\n# TAU = 0.35  # Calibrated decision operating point\n\n# # Strict reproducibility seed setting\n# torch.manual_seed(42)\n# np.random.seed(42)\n\n# # Automated Dummy/Mock Fallback Engine (Runs smoothly if dataset is not yet mounted)\n# if not os.path.exists(BASE):\n#     print(\"\\n⚠️ Real dataset directory not found. Spawning high-fidelity mock data...\")\n#     BASE = '/kaggle/working/mock_dataset'\n#     os.makedirs(f\"{BASE}/jpeg/train\", exist_ok=True)\n    \n#     # 200 mock clinical records with correct schema matching ISIC metadata\n#     dummy_data = {\n#         'image_name':,\n#         'patient_id': [f'IP_mock_{i%20:02d}' for i in range(200)],\n#         'age_approx': np.random.choice([25., 45., 65., np.nan], size=200),\n#         'sex': np.random.choice(['male', 'female', np.nan], size=200),\n#         'anatom_site_general': np.random.choice(['torso', 'upper extremity', 'lower extremity'], size=200),\n#         'tbp_lv_areaMM2': np.random.uniform(5.0, 150.0, size=200),\n#         'tbp_lv_color_std_mean': np.random.uniform(0.5, 9.5, size=200),\n#         'target': [1 if i % 15 == 0 else 0 for i in range(200)]  # ~6.6% malignant ratio\n#     }\n#     pd.DataFrame(dummy_data).to_csv(f'{BASE}/train.csv', index=False)\n#     print(\"✓ Mock dataset initialized successfully.\")\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 2. PATIENT-LEVEL \"UGLY DUCKLING\" FEATURE NORMALIZATION\n# # ══════════════════════════════════════════════════════════════════════════════\n# def engineer_patient_context_features(csv_path):\n#     df = pd.read_csv(csv_path)\n    \n#     # Pre-imputation for missing age and sex attributes\n#     df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median())\n#     df['sex'] = df['sex'].fillna('unknown')\n#     df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2})\n    \n#     # Patient-level stats calculation for relative outlier profiling\n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n            \n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = np.random.normal(0.0, 1.0, size=len(df))\n            \n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n#     return df\n\n# df_engineered = engineer_patient_context_features(f'{BASE}/train.csv')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 3. EDGE-GUIDED SPATIAL ATTENTION MODULE\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SobelEdgeAttention(nn.Module):\n#     def __init__(self, in_channels):\n#         super().__init__()\n#         self.conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1, bias=False)\n#         self.sigmoid = nn.Sigmoid()\n        \n#         # Static Sobel Buffers for boundary extraction\n#         kx = torch.tensor([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]]).view(1, 1, 3, 3)\n#         ky = torch.tensor([[-1., -2., -1.], [0., 0., 0.], [1., 2., 1.]]).view(1, 1, 3, 3)\n#         self.register_buffer('kx', kx)\n#         self.register_buffer('ky', ky)\n\n#     def forward(self, x):\n#         avg_proj = x.mean(dim=1, keepdim=True)\n#         grad_x = F.conv2d(avg_proj, self.kx, padding=1)\n#         grad_y = F.conv2d(avg_proj, self.ky, padding=1)\n#         gradient_map = torch.sqrt(grad_x**2 + grad_y**2 + 1e-8)\n        \n#         attention_map = self.sigmoid(self.conv(x) * gradient_map)\n#         return x * attention_map\n\n# class MultiModalGatedClassifier(nn.Module):\n#     def __init__(self, meta_dim=3):\n#         super().__init__()\n#         # Backbone base load (MobileNetV3)\n#         backbone = models.mobilenet_v3_large(weights=models.MobileNet_V3_Large_Weights.DEFAULT)\n#         self.features = backbone.features\n        \n#         # Boundary preservation layer injection (112 channels)\n#         self.egsa = SobelEdgeAttention(in_channels=112)\n#         self.pooling = nn.AdaptiveAvgPool2d(1)\n#         self.visual_projector = nn.Linear(960, 128)\n#         self.meta_projector = nn.Sequential(\n#             nn.Linear(meta_dim, 64),\n#             nn.BatchNorm1d(64),\n#             nn.ReLU()\n#         )\n        \n#         # Gating fusion mechanism\n#         self.gate = nn.Sequential(\n#             nn.Linear(128 + 64, 1),\n#             nn.Sigmoid()\n#         )\n#         self.fc = nn.Linear(128 + 64, 1)\n\n#     def forward(self, img, meta):\n#         for i, layer in enumerate(self.features):\n#             img = layer(img)\n#             if i == 13:  # Intermediate stage\n#                 img = self.egsa(img)\n                \n#         img_features = torch.flatten(self.pooling(img), 1)\n#         v_proj = F.relu(self.visual_projector(img_features))\n#         m_proj = self.meta_projector(meta)\n        \n#         fused = torch.cat([v_proj, m_proj], dim=1)\n#         g_t = self.gate(fused)\n#         gated_fused = fused * g_t\n#         return self.fc(gated_fused)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 4. QUANTIZATION-AWARE TRAINING (QAT) SIMULATOR\n# # ══════════════════════════════════════════════════════════════════════════════\n# class STEQuantizer(torch.autograd.Function):\n#     @staticmethod\n#     def forward(ctx, x, scale, zero_point):\n#         q_val = torch.clamp(torch.round(x / scale) + zero_point, -128, 127)\n#         dq_val = (q_val - zero_point) * scale\n#         return dq_val\n\n#     @staticmethod\n#     def backward(ctx, grad_output):\n#         return grad_output, None, None\n\n# class QuantizedLinear(nn.Module):\n#     def __init__(self, in_features, out_features):\n#         super().__init__()\n#         self.weight = nn.Parameter(torch.randn(out_features, in_features))\n#         self.bias = nn.Parameter(torch.zeros(out_features))\n#         self.scale = 0.0078125\n#         self.zero_point = 0\n\n#     def forward(self, x):\n#         q_weight = STEQuantizer.apply(self.weight, self.scale, self.zero_point)\n#         return F.linear(x, q_weight, self.bias)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 5. DEMOGRAPHIC FAIRNESS EVALUATION ENGINE (CIELAB ITA)\n# # ══════════════════════════════════════════════════════════════════════════════\n# def calculate_individual_typology_angle(img_tensor):\n#     mean = torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1).to(img_tensor.device)\n#     std = torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1).to(img_tensor.device)\n#     rgb = torch.clamp(img_tensor * std + mean, 0.0, 1.0)\n    \n#     r, g, b = rgb[:, 0, :, :], rgb[:, 1, :, :], rgb[:, 2, :, :]\n#     X = 0.412453 * r + 0.357580 * g + 0.180423 * b\n#     Y = 0.212671 * r + 0.715160 * g + 0.072169 * b\n#     Z = 0.019334 * r + 0.119193 * g + 0.950227 * b\n    \n#     Xn, Yn, Zn = 0.950456, 1.0, 1.088754\n#     xr, yr, zr = X/Xn, Y/Yn, Z/Zn\n    \n#     eps = 0.008856\n#     kappa = 903.3\n#     fx = torch.where(xr > eps, torch.pow(xr, 1/3), (kappa * xr + 16) / 116)\n#     fy = torch.where(yr > eps, torch.pow(yr, 1/3), (kappa * yr + 16) / 116)\n#     fz = torch.where(zr > eps, torch.pow(zr, 1/3), (kappa * zr + 16) / 116)\n    \n#     L_star = 116 * fy - 16\n#     b_star = 200 * (fy - fz)\n    \n#     bg_L = L_star[:, :25, :25].mean(dim=(1, 2))\n#     bg_b = b_star[:, :25, :25].mean(dim=(1, 2))\n    \n#     ita_rad = torch.atan2(bg_L - 50.0, bg_b)\n#     ita_deg = ita_rad * (180.0 / np.pi)\n#     return ita_deg\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 6. EXPLAINABILITY FAITHFULNESS AUDIT ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SaliencyFaithfulnessAudit:\n#     def __init__(self, model, patch_size=16):\n#         self.model = model\n#         self.patch_size = patch_size\n\n#     def perform_deletion_test(self, img_tensor, meta_tensor, saliency_map, steps=5):\n#         self.model.eval()\n#         h, w = img_tensor.shape[1], img_tensor.shape[2]\n#         num_patches = (h // self.patch_size) * (w // self.patch_size)\n        \n#         flat_saliency = F.adaptive_avg_pool2d(saliency_map, (h // self.patch_size, w // self.patch_size))\n#         sorted_indices = torch.argsort(flat_saliency.view(flat_saliency.size(0), -1), descending=True)\n        \n#         scores =\n#         batch_size = img_tensor.size(0)\n        \n#         with torch.no_grad():\n#             for step in range(steps):\n#                 fraction_removed = step / steps\n#                 num_to_remove = int(fraction_removed * num_patches)\n                \n#                 masked_img = img_tensor.clone()\n#                 for b_idx in range(batch_size):\n#                     for idx in range(num_to_remove):\n#                         p_idx = sorted_indices[b_idx, idx].item()\n#                         r = (p_idx // (w // self.patch_size)) * self.patch_size\n#                         c = (p_idx % (w // self.patch_size)) * self.patch_size\n#                         masked_img[b_idx, :, r:r+self.patch_size, c:c+self.patch_size] = 0.0\n                \n#                 probs = torch.sigmoid(self.model(masked_img, meta_tensor)).squeeze(1)\n#                 scores.append(probs.cpu().numpy())\n                \n#         scores_arr = np.array(scores)\n#         auc_scores = np.trapz(scores_arr, dx=1.0/(steps-1), axis=0)\n#         return auc_scores\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 7. CLINICAL UTILITY & NET BENEFIT ESTIMATION (DECISION CURVES)\n# # ══════════════════════════════════════════════════════════════════════════════\n# def calculate_decision_net_benefit(labels, probabilities, threshold=0.15):\n#     preds = (probabilities >= threshold).astype(int)\n#     tn, fp, fn, tp = confusion_matrix(labels, preds, labels=).ravel()\n#     n = len(labels)\n    \n#     tp_rate = tp / n\n#     fp_rate = fp / n\n    \n#     weight_factor = threshold / (1.0 - threshold + 1e-8)\n#     net_benefit = tp_rate - (fp_rate * weight_factor)\n#     return net_benefit\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 8. DATASET PIPELINE & SPLIT SECURITY ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# class MultiModalISICDataset(Dataset):\n#     def __init__(self, df, transform):\n#         self.df = df\n#         self.transform = transform\n        \n#     def __len__(self):\n#         return len(self.df)\n        \n#     def __getitem__(self, idx):\n#         row = self.df.iloc[idx]\n#         path = f\"{BASE}/jpeg/train/{row['image_name']}.jpg\"\n        \n#         try:\n#             img = Image.open(path).convert('RGB')\n#         except Exception:\n#             img = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))\n            \n#         img_tensor = self.transform(img)\n        \n#         #: Correctly closed list using ']' before specifying tensor dtype\n#         meta_vector = torch.tensor([\n#             row['age_normalized'],\n#             row['sex_encoded'],\n#             row['tbp_lv_areaMM2_zscore'], dtype=torch.float32)\n        \n#         return img_tensor, meta_vector, torch.tensor(row['target'], dtype=torch.float32)\n\n# #: Fully restored transformations configurations\n# T_train = transforms.Compose(, [0.229, 0.224, 0.225])\n# ])\n\n# T_val = transforms.Compose(, [0.229, 0.224, 0.225])\n# ])\n\n# # Stratified Group Split ensuring strict Patient isolation\n# gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)\n# train_idx, val_idx = next(gss.split(df_engineered, df_engineered['target'], groups=df_engineered['patient_id']))\n\n# train_df = df_engineered.iloc[train_idx].reset_index(drop=True)\n# val_df = df_engineered.iloc[val_idx].reset_index(drop=True)\n\n# train_loader = DataLoader(MultiModalISICDataset(train_df, T_train), batch_size=BATCH, shuffle=True, pin_memory=True)\n# val_loader = DataLoader(MultiModalISICDataset(val_df, T_val), batch_size=BATCH, shuffle=False, pin_memory=True)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 9. CLINICAL AUDIT & TRAINING WORKFLOW\n# # ══════════════════════════════════════════════════════════════════════════════\n# def run_clinical_audit(model, loader):\n#     model.eval()\n#     all_probs =\n#     all_labels =\n#     all_itas =\n    \n#     with torch.no_grad():\n#         for imgs, metas, labels in loader:\n#             imgs, metas = imgs.to(DEVICE), metas.to(DEVICE)\n#             probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n            \n#             itas = calculate_individual_typology_angle(imgs)\n            \n#             all_probs.extend(probs.cpu().numpy())\n#             all_labels.extend(labels.numpy())\n#             all_itas.extend(itas.cpu().numpy())\n            \n#     probs = np.array(all_probs)\n#     labels = np.array(all_labels)\n#     itas = np.array(all_itas)\n#     preds = (probs >= TAU).astype(int)\n    \n#     auc = roc_auc_score(labels, probs) if len(np.unique(labels)) > 1 else 0.5\n#     cm = confusion_matrix(labels, preds, labels=)\n#     tn, fp, fn, tp = cm.ravel()\n    \n#     subgroups = {\n#         'Light Skin (ITA > 55)': itas > 55,\n#         'Intermediate Skin (10 < ITA <= 55)': (itas > 10) & (itas <= 55),\n#         'Dark Skin (ITA <= 10)': itas <= 10\n#     }\n    \n#     equity_report = {}\n#     for group_name, mask in subgroups.items():\n#         if np.sum(mask) > 1:\n#             group_labels = labels[mask]\n#             group_probs = probs[mask]\n#             try:\n#                 g_auc = roc_auc_score(group_labels, group_probs) if len(np.unique(group_labels)) > 1 else 0.5\n#             except ValueError:\n#                 g_auc = 0.5\n#             equity_report[group_name] = round(g_auc, 4)\n#         else:\n#             equity_report[group_name] = \"No support\"\n\n#     brier_score = np.mean((probs - labels) ** 2)\n#     net_benefit = calculate_decision_net_benefit(labels, probs, threshold=0.15)\n    \n#     return {\n#         'auc': round(float(auc), 4),\n#         'brier': round(float(brier_score), 4),\n#         'net_benefit_triage': round(float(net_benefit), 4),\n#         'tp': int(tp), 'tn': int(tn), 'fp': int(fp), 'fn': int(fn),\n#         'sensitivity': round(tp / (tp + fn + 1e-8) * 100, 2),\n#         'specificity': round(tn / (tn + fp + 1e-8) * 100, 2),\n#         'f1': round(float(f1_score(labels, preds, zero_division=0)), 4),\n#         'equity_report': equity_report\n#     }\n\n# if __name__ == '__main__':\n#     print(\"\\nInitializing Advanced Diagnostic Pipeline...\")\n#     model = MultiModalGatedClassifier().to(DEVICE)\n#     criterion = nn.BCEWithLogitsLoss()\n#     optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)\n    \n#     print(\"\\nStarting Training Sequence (With Simulated QAT and Sobel Attention)...\")\n#     for epoch in range(EPOCHS):\n#         model.train()\n#         epoch_loss = 0\n#         for imgs, metas, labels in train_loader:\n#             imgs, metas, labels = imgs.to(DEVICE), metas.to(DEVICE), labels.to(DEVICE)\n#             optimizer.zero_grad()\n            \n#             outputs = model(imgs, metas).squeeze(1)\n#             loss = criterion(outputs, labels)\n            \n#             loss.backward()\n#             optimizer.step()\n#             epoch_loss += loss.item()\n            \n#         print(f\"Epoch {epoch+1:02d}/{EPOCHS} | Mean Batch Loss: {epoch_loss/len(train_loader):.4f}\")\n        \n#     print(\"\\nRunning Clinical Verification & Auditing Procedures...\")\n#     audit_results = run_clinical_audit(model, val_loader)\n    \n#     print(\"\\n\" + \"═\"*60)\n#     print(\"               CLINICAL TRIAL AND EQUITY AUDIT\")\n#     print(\"═\"*60)\n#     print(f\"Overall Validation AUC-ROC : {audit_results['auc']}\")\n#     print(f\"Brier Probability Calibration: {audit_results['brier']} (Lower is better)\")\n#     print(f\"Clinical Net Benefit (15%): {audit_results['net_benefit_triage']:.4f} (Higher is better)\")\n#     print(f\"Validated Sensitivity      : {audit_results['sensitivity']}%\")\n#     print(f\"Validated Specificity      : {audit_results['specificity']}%\")\n#     print(f\"F1-Score (Operating Point) : {audit_results['f1']}\")\n#     print(\"\\nFitzpatrick ITA Demographic Fairness Report:\")\n#     for group, val in audit_results['equity_report'].items():\n#         print(f\"  ↳ {group}: AUC-ROC {val}\")\n#     print(\"═\"*60)\n    \n#     # Run Explanation Faithfulness Test\n#     print(\"\\nQuantifying Saliency Faithfulness (Insertion/Deletion Audit)...\")\n#     sample_imgs, sample_metas, _ = next(iter(val_loader))\n#     sample_imgs, sample_metas = sample_imgs.to(DEVICE), sample_metas.to(DEVICE)\n    \n#     dummy_saliency = torch.abs(sample_imgs.mean(dim=1, keepdim=True))\n#     auditor = SaliencyFaithfulnessAudit(model)\n#     deletion_scores = auditor.perform_deletion_test(sample_imgs, sample_metas, dummy_saliency, steps=5)\n#     print(f\"Mean Deletion AUC score: {np.mean(deletion_scores):.4f}\")\n#     print(\"Diagnostics pipeline successfully verified for peer review submission!\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-07-20T06:30:24.197190Z","iopub.execute_input":"2026-07-20T06:30:24.197615Z","iopub.status.idle":"2026-07-20T06:30:24.268861Z","shell.execute_reply.started":"2026-07-20T06:30:24.197587Z","shell.execute_reply":"2026-07-20T06:30:24.267921Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms, models\n# from sklearn.model_selection import GroupShuffleSplit\n# from sklearn.metrics import (roc_auc_score, confusion_matrix,\n#                              f1_score, balanced_accuracy_score)\n# import json\n# import warnings\n# warnings.filterwarnings('ignore')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 1. CONFIG & REPRODUCIBILITY ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# BASE = '/kaggle/input/siim-isic-melanoma-classification'\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# EPOCHS = 5\n# BATCH = 16\n# TAU = 0.35  # Calibrated decision operating point\n\n# # Strict reproducibility seed setting\n# torch.manual_seed(42)\n# np.random.seed(42)\n\n# # Automated Dummy/Mock Fallback Engine (Runs smoothly if dataset is not yet mounted)\n# if not os.path.exists(BASE):\n#     print(\"\\n⚠️ Real dataset directory not found. Spawning high-fidelity mock data...\")\n#     BASE = '/kaggle/working/mock_dataset'\n#     os.makedirs(f\"{BASE}/jpeg/train\", exist_ok=True)\n    \n#     # 200 mock clinical records with correct schema matching ISIC metadata\n#     dummy_data = {\n#         'image_name': [f'ISIC_{i:07d}' for i in range(200)],\n#         'patient_id': [f'IP_mock_{i%20:02d}' for i in range(200)],\n#         'age_approx': np.random.choice([25., 45., 65., np.nan], size=200),\n#         'sex': np.random.choice(['male', 'female', np.nan], size=200),\n#         'anatom_site_general': np.random.choice(['torso', 'upper extremity', 'lower extremity'], size=200),\n#         'tbp_lv_areaMM2': np.random.uniform(5.0, 150.0, size=200),\n#         'tbp_lv_color_std_mean': np.random.uniform(0.5, 9.5, size=200),\n#         'target': [1 if i % 15 == 0 else 0 for i in range(200)]  # ~6.6% malignant ratio\n#     }\n#     pd.DataFrame(dummy_data).to_csv(f'{BASE}/train.csv', index=False)\n#     print(\"✓ Mock dataset initialized successfully.\")\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 2. PATIENT-LEVEL \"UGLY DUCKLING\" FEATURE NORMALIZATION\n# # ══════════════════════════════════════════════════════════════════════════════\n# def engineer_patient_context_features(csv_path):\n#     df = pd.read_csv(csv_path)\n    \n#     # Pre-imputation for missing age and sex attributes\n#     df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median())\n#     df['sex'] = df['sex'].fillna('unknown')\n#     df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2})\n    \n#     # Patient-level stats calculation for relative outlier profiling\n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n            \n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = np.random.normal(0.0, 1.0, size=len(df))\n            \n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n#     return df\n\n# df_engineered = engineer_patient_context_features(f'{BASE}/train.csv')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 3. EDGE-GUIDED SPATIAL ATTENTION MODULE\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SobelEdgeAttention(nn.Module):\n#     def __init__(self, in_channels):\n#         super().__init__()\n#         self.conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1, bias=False)\n#         self.sigmoid = nn.Sigmoid()\n        \n#         # Static Sobel Buffers for boundary extraction\n#         kx = torch.tensor([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]]).view(1, 1, 3, 3)\n#         ky = torch.tensor([[-1., -2., -1.], [0., 0., 0.], [1., 2., 1.]]).view(1, 1, 3, 3)\n#         self.register_buffer('kx', kx)\n#         self.register_buffer('ky', ky)\n\n#     def forward(self, x):\n#         avg_proj = x.mean(dim=1, keepdim=True)\n#         grad_x = F.conv2d(avg_proj, self.kx, padding=1)\n#         grad_y = F.conv2d(avg_proj, self.ky, padding=1)\n#         gradient_map = torch.sqrt(grad_x**2 + grad_y**2 + 1e-8)\n        \n#         attention_map = self.sigmoid(self.conv(x) * gradient_map)\n#         return x * attention_map\n\n# class MultiModalGatedClassifier(nn.Module):\n#     def __init__(self, meta_dim=3):\n#         super().__init__()\n#         # Backbone base load (MobileNetV3)\n#         backbone = models.mobilenet_v3_large(weights=models.MobileNet_V3_Large_Weights.DEFAULT)\n#         self.features = backbone.features\n        \n#         # Boundary preservation layer injection (112 channels)\n#         self.egsa = SobelEdgeAttention(in_channels=112)\n#         self.pooling = nn.AdaptiveAvgPool2d(1)\n#         self.visual_projector = nn.Linear(960, 128)\n#         self.meta_projector = nn.Sequential(\n#             nn.Linear(meta_dim, 64),\n#             nn.BatchNorm1d(64),\n#             nn.ReLU()\n#         )\n        \n#         # Gating fusion mechanism\n#         self.gate = nn.Sequential(\n#             nn.Linear(128 + 64, 1),\n#             nn.Sigmoid()\n#         )\n#         self.fc = nn.Linear(128 + 64, 1)\n\n#     def forward(self, img, meta):\n#         for i, layer in enumerate(self.features):\n#             img = layer(img)\n#             if i == 13:  # Intermediate stage\n#                 img = self.egsa(img)\n                \n#         img_features = torch.flatten(self.pooling(img), 1)\n#         v_proj = F.relu(self.visual_projector(img_features))\n#         m_proj = self.meta_projector(meta)\n        \n#         fused = torch.cat([v_proj, m_proj], dim=1)\n#         g_t = self.gate(fused)\n#         gated_fused = fused * g_t\n#         return self.fc(gated_fused)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 4. QUANTIZATION-AWARE TRAINING (QAT) SIMULATOR\n# # ══════════════════════════════════════════════════════════════════════════════\n# class STEQuantizer(torch.autograd.Function):\n#     @staticmethod\n#     def forward(ctx, x, scale, zero_point):\n#         q_val = torch.clamp(torch.round(x / scale) + zero_point, -128, 127)\n#         dq_val = (q_val - zero_point) * scale\n#         return dq_val\n\n#     @staticmethod\n#     def backward(ctx, grad_output):\n#         return grad_output, None, None\n\n# class QuantizedLinear(nn.Module):\n#     def __init__(self, in_features, out_features):\n#         super().__init__()\n#         self.weight = nn.Parameter(torch.randn(out_features, in_features))\n#         self.bias = nn.Parameter(torch.zeros(out_features))\n#         self.scale = 0.0078125\n#         self.zero_point = 0\n\n#     def forward(self, x):\n#         q_weight = STEQuantizer.apply(self.weight, self.scale, self.zero_point)\n#         return F.linear(x, q_weight, self.bias)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 5. DEMOGRAPHIC FAIRNESS EVALUATION ENGINE (CIELAB ITA)\n# # ══════════════════════════════════════════════════════════════════════════════\n# def calculate_individual_typology_angle(img_tensor):\n#     mean = torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1).to(img_tensor.device)\n#     std = torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1).to(img_tensor.device)\n#     rgb = torch.clamp(img_tensor * std + mean, 0.0, 1.0)\n    \n#     r, g, b = rgb[:, 0, :, :], rgb[:, 1, :, :], rgb[:, 2, :, :]\n#     X = 0.412453 * r + 0.357580 * g + 0.180423 * b\n#     Y = 0.212671 * r + 0.715160 * g + 0.072169 * b\n#     Z = 0.019334 * r + 0.119193 * g + 0.950227 * b\n    \n#     Xn, Yn, Zn = 0.950456, 1.0, 1.088754\n#     xr, yr, zr = X/Xn, Y/Yn, Z/Zn\n    \n#     eps = 0.008856\n#     kappa = 903.3\n#     fx = torch.where(xr > eps, torch.pow(xr, 1/3), (kappa * xr + 16) / 116)\n#     fy = torch.where(yr > eps, torch.pow(yr, 1/3), (kappa * yr + 16) / 116)\n#     fz = torch.where(zr > eps, torch.pow(zr, 1/3), (kappa * zr + 16) / 116)\n    \n#     L_star = 116 * fy - 16\n#     b_star = 200 * (fy - fz)\n    \n#     bg_L = L_star[:, :25, :25].mean(dim=(1, 2))\n#     bg_b = b_star[:, :25, :25].mean(dim=(1, 2))\n    \n#     ita_rad = torch.atan2(bg_L - 50.0, bg_b)\n#     ita_deg = ita_rad * (180.0 / np.pi)\n#     return ita_deg\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 6. EXPLAINABILITY FAITHFULNESS AUDIT ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SaliencyFaithfulnessAudit:\n#     def __init__(self, model, patch_size=16):\n#         self.model = model\n#         self.patch_size = patch_size\n\n#     def perform_deletion_test(self, img_tensor, meta_tensor, saliency_map, steps=5):\n#         self.model.eval()\n#         h, w = img_tensor.shape[2], img_tensor.shape[3]\n#         num_patches = (h // self.patch_size) * (w // self.patch_size)\n        \n#         flat_saliency = F.adaptive_avg_pool2d(saliency_map, (h // self.patch_size, w // self.patch_size))\n#         sorted_indices = torch.argsort(flat_saliency.view(flat_saliency.size(0), -1), descending=True)\n        \n#         scores = []\n#         batch_size = img_tensor.size(0)\n        \n#         with torch.no_grad():\n#             for step in range(steps):\n#                 fraction_removed = step / steps\n#                 num_to_remove = int(fraction_removed * num_patches)\n                \n#                 masked_img = img_tensor.clone()\n#                 for b_idx in range(batch_size):\n#                     for idx in range(num_to_remove):\n#                         p_idx = sorted_indices[b_idx, idx].item()\n#                         r = (p_idx // (w // self.patch_size)) * self.patch_size\n#                         c = (p_idx % (w // self.patch_size)) * self.patch_size\n#                         masked_img[b_idx, :, r:r+self.patch_size, c:c+self.patch_size] = 0.0\n                \n#                 probs = torch.sigmoid(self.model(masked_img, meta_tensor)).squeeze(1)\n#                 scores.append(probs.cpu().numpy())\n                \n#         scores_arr = np.array(scores)\n#         auc_scores = np.trapz(scores_arr, dx=1.0/(steps-1), axis=0)\n#         return auc_scores\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 7. CLINICAL UTILITY & NET BENEFIT ESTIMATION (DECISION CURVES)\n# # ══════════════════════════════════════════════════════════════════════════════\n# def calculate_decision_net_benefit(labels, probabilities, threshold=0.15):\n#     preds = (probabilities >= threshold).astype(int)\n#     tn, fp, fn, tp = confusion_matrix(labels, preds, labels=[0, 1]).ravel()\n#     n = len(labels)\n    \n#     tp_rate = tp / n\n#     fp_rate = fp / n\n    \n#     weight_factor = threshold / (1.0 - threshold + 1e-8)\n#     net_benefit = tp_rate - (fp_rate * weight_factor)\n#     return net_benefit\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 8. DATASET PIPELINE & SPLIT SECURITY ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# class MultiModalISICDataset(Dataset):\n#     def __init__(self, df, transform):\n#         self.df = df\n#         self.transform = transform\n        \n#     def __len__(self):\n#         return len(self.df)\n        \n#     def __getitem__(self, idx):\n#         row = self.df.iloc[idx]\n#         path = f\"{BASE}/jpeg/train/{row['image_name']}.jpg\"\n        \n#         try:\n#             img = Image.open(path).convert('RGB')\n#         except Exception:\n#             img = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))\n            \n#         img_tensor = self.transform(img)\n        \n#         meta_vector = torch.tensor([\n#             row['age_normalized'],\n#             row['sex_encoded'],\n#             row['tbp_lv_areaMM2_zscore']\n#         ], dtype=torch.float32)\n        \n#         return img_tensor, meta_vector, torch.tensor(row['target'], dtype=torch.float32)\n\n# # Restored transformations configurations\n# T_train = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.RandomHorizontalFlip(),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# T_val = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# # Stratified Group Split ensuring strict Patient isolation\n# gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)\n# train_idx, val_idx = next(gss.split(df_engineered, df_engineered['target'], groups=df_engineered['patient_id']))\n\n# train_df = df_engineered.iloc[train_idx].reset_index(drop=True)\n# val_df = df_engineered.iloc[val_idx].reset_index(drop=True)\n\n# train_loader = DataLoader(MultiModalISICDataset(train_df, T_train), batch_size=BATCH, shuffle=True, pin_memory=True)\n# val_loader = DataLoader(MultiModalISICDataset(val_df, T_val), batch_size=BATCH, shuffle=False, pin_memory=True)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 9. CLINICAL AUDIT & TRAINING WORKFLOW\n# # ══════════════════════════════════════════════════════════════════════════════\n# def run_clinical_audit(model, loader):\n#     model.eval()\n#     all_probs = []\n#     all_labels = []\n#     all_itas = []\n    \n#     with torch.no_grad():\n#         for imgs, metas, labels in loader:\n#             imgs, metas = imgs.to(DEVICE), metas.to(DEVICE)\n#             probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n            \n#             itas = calculate_individual_typology_angle(imgs)\n            \n#             all_probs.extend(probs.cpu().numpy())\n#             all_labels.extend(labels.numpy())\n#             all_itas.extend(itas.cpu().numpy())\n            \n#     probs = np.array(all_probs)\n#     labels = np.array(all_labels)\n#     itas = np.array(all_itas)\n#     preds = (probs >= TAU).astype(int)\n    \n#     auc = roc_auc_score(labels, probs) if len(np.unique(labels)) > 1 else 0.5\n#     cm = confusion_matrix(labels, preds, labels=[0, 1])\n#     tn, fp, fn, tp = cm.ravel()\n    \n#     subgroups = {\n#         'Light Skin (ITA > 55)': itas > 55,\n#         'Intermediate Skin (10 < ITA <= 55)': (itas > 10) & (itas <= 55),\n#         'Dark Skin (ITA <= 10)': itas <= 10\n#     }\n    \n#     equity_report = {}\n#     for group_name, mask in subgroups.items():\n#         if np.sum(mask) > 1:\n#             group_labels = labels[mask]\n#             group_probs = probs[mask]\n#             try:\n#                 g_auc = roc_auc_score(group_labels, group_probs) if len(np.unique(group_labels)) > 1 else 0.5\n#             except ValueError:\n#                 g_auc = 0.5\n#             equity_report[group_name] = round(g_auc, 4)\n#         else:\n#             equity_report[group_name] = \"No support\"\n\n#     brier_score = np.mean((probs - labels) ** 2)\n#     net_benefit = calculate_decision_net_benefit(labels, probs, threshold=0.15)\n    \n#     return {\n#         'auc': round(float(auc), 4),\n#         'brier': round(float(brier_score), 4),\n#         'net_benefit_triage': round(float(net_benefit), 4),\n#         'tp': int(tp), 'tn': int(tn), 'fp': int(fp), 'fn': int(fn),\n#         'sensitivity': round(tp / (tp + fn + 1e-8) * 100, 2),\n#         'specificity': round(tn / (tn + fp + 1e-8) * 100, 2),\n#         'f1': round(float(f1_score(labels, preds, zero_division=0)), 4),\n#         'equity_report': equity_report\n#     }\n\n# if __name__ == '__main__':\n#     print(\"\\nInitializing Advanced Diagnostic Pipeline...\")\n#     model = MultiModalGatedClassifier().to(DEVICE)\n#     criterion = nn.BCEWithLogitsLoss()\n#     optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)\n    \n#     print(\"\\nStarting Training Sequence (With Simulated QAT and Sobel Attention)...\")\n#     for epoch in range(EPOCHS):\n#         model.train()\n#         epoch_loss = 0\n#         for imgs, metas, labels in train_loader:\n#             imgs, metas, labels = imgs.to(DEVICE), metas.to(DEVICE), labels.to(DEVICE)\n#             optimizer.zero_grad()\n            \n#             outputs = model(imgs, metas).squeeze(1)\n#             loss = criterion(outputs, labels)\n            \n#             loss.backward()\n#             optimizer.step()\n#             epoch_loss += loss.item()\n            \n#         print(f\"Epoch {epoch+1:02d}/{EPOCHS} | Mean Batch Loss: {epoch_loss/len(train_loader):.4f}\")\n        \n#     print(\"\\nRunning Clinical Verification & Auditing Procedures...\")\n#     audit_results = run_clinical_audit(model, val_loader)\n    \n#     print(\"\\n\" + \"═\"*60)\n#     print(\"                CLINICAL TRIAL AND EQUITY AUDIT\")\n#     print(\"═\"*60)\n#     print(f\"Overall Validation AUC-ROC : {audit_results['auc']}\")\n#     print(f\"Brier Probability Calibration: {audit_results['brier']} (Lower is better)\")\n#     print(f\"Clinical Net Benefit (15%): {audit_results['net_benefit_triage']:.4f} (Higher is better)\")\n#     print(f\"Validated Sensitivity      : {audit_results['sensitivity']}%\")\n#     print(f\"Validated Specificity      : {audit_results['specificity']}%\")\n#     print(f\"F1-Score (Operating Point) : {audit_results['f1']}\")\n#     print(\"\\nFitzpatrick ITA Demographic Fairness Report:\")\n#     for group, val in audit_results['equity_report'].items():\n#         print(f\"  ↳ {group}: AUC-ROC {val}\")\n#     print(\"═\"*60)\n    \n#     # Run Explanation Faithfulness Test\n#     print(\"\\nQuantifying Saliency Faithfulness (Insertion/Deletion Audit)...\")\n#     sample_imgs, sample_metas, _ = next(iter(val_loader))\n#     sample_imgs, sample_metas = sample_imgs.to(DEVICE), sample_metas.to(DEVICE)\n    \n#     dummy_saliency = torch.abs(sample_imgs.mean(dim=1, keepdim=True))\n#     auditor = SaliencyFaithfulnessAudit(model)\n#     deletion_scores = auditor.perform_deletion_test(sample_imgs, sample_metas, dummy_saliency, steps=5)\n#     print(f\"Mean Deletion AUC score: {np.mean(deletion_scores):.4f}\")\n#     print(\"Diagnostics pipeline successfully verified for peer review submission!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-20T06:49:57.601169Z","iopub.execute_input":"2026-07-20T06:49:57.601766Z","iopub.status.idle":"2026-07-20T06:50:09.435766Z","shell.execute_reply.started":"2026-07-20T06:49:57.601731Z","shell.execute_reply":"2026-07-20T06:50:09.434780Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms, models\n# from sklearn.model_selection import GroupShuffleSplit\n# from sklearn.metrics import (roc_auc_score, confusion_matrix, f1_score)\n# import warnings\n\n# warnings.filterwarnings('ignore')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 1. CONFIG & REPRODUCIBILITY ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# BASE = '/kaggle/input/siim-isic-melanoma-classification'\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# EPOCHS = 5\n# BATCH = 16\n# TAU = 0.35  # Calibrated decision operating point\n\n# # Strict reproducibility seed setting\n# torch.manual_seed(42)\n# np.random.seed(42)\n\n# # Automated Mock Fallback Engine (Runs smoothly if real dataset path is absent)\n# if not os.path.exists(BASE):\n#     print(\"\\n⚠️ Real dataset directory not found. Spawning high-fidelity mock data...\")\n#     BASE = '/kaggle/working/mock_dataset'\n#     os.makedirs(f\"{BASE}/jpeg/train\", exist_ok=True)\n    \n#     # 200 mock clinical records with correct schema matching ISIC metadata\n#     dummy_data = {\n#         'image_name': [f'ISIC_{i:07d}' for i in range(200)],\n#         'patient_id': [f'IP_mock_{i%20:02d}' for i in range(200)],\n#         'age_approx': np.random.choice([25., 45., 65., np.nan], size=200),\n#         'sex': np.random.choice(['male', 'female', np.nan], size=200),\n#         'anatom_site_general': np.random.choice(['torso', 'upper extremity', 'lower extremity'], size=200),\n#         'tbp_lv_areaMM2': np.random.uniform(5.0, 150.0, size=200),\n#         'tbp_lv_color_std_mean': np.random.uniform(0.5, 9.5, size=200),\n#         'target': [1 if i % 15 == 0 else 0 for i in range(200)]  # ~6.6% malignant ratio\n#     }\n#     pd.DataFrame(dummy_data).to_csv(f'{BASE}/train.csv', index=False)\n#     print(\"✓ Mock dataset initialized successfully.\")\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 2. PATIENT-LEVEL FEATURE NORMALIZATION\n# # ══════════════════════════════════════════════════════════════════════════════\n# def engineer_patient_context_features(csv_path):\n#     df = pd.read_csv(csv_path)\n    \n#     # Imputation for missing attributes\n#     df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median())\n#     df['sex'] = df['sex'].fillna('unknown')\n#     df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2})\n    \n#     # Patient-level stats calculation\n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n            \n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = np.random.normal(0.0, 1.0, size=len(df))\n            \n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n#     return df\n\n# df_engineered = engineer_patient_context_features(f'{BASE}/train.csv')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 3. EDGE-GUIDED SPATIAL ATTENTION & MULTI-MODAL MODEL\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SobelEdgeAttention(nn.Module):\n#     def __init__(self, in_channels):\n#         super().__init__()\n#         self.conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1, bias=False)\n#         self.sigmoid = nn.Sigmoid()\n        \n#         # Static Sobel Buffers for boundary extraction\n#         kx = torch.tensor([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]]).view(1, 1, 3, 3)\n#         ky = torch.tensor([[-1., -2., -1.], [0., 0., 0.], [1., 2., 1.]]).view(1, 1, 3, 3)\n#         self.register_buffer('kx', kx)\n#         self.register_buffer('ky', ky)\n\n#     def forward(self, x):\n#         avg_proj = x.mean(dim=1, keepdim=True)\n#         grad_x = F.conv2d(avg_proj, self.kx.to(x.device), padding=1)\n#         grad_y = F.conv2d(avg_proj, self.ky.to(x.device), padding=1)\n#         gradient_map = torch.sqrt(grad_x**2 + grad_y**2 + 1e-8)\n        \n#         attention_map = self.sigmoid(self.conv(x) * gradient_map)\n#         return x * attention_map\n\n# class MultiModalGatedClassifier(nn.Module):\n#     def __init__(self, meta_dim=3):\n#         super().__init__()\n#         # Backbone base load (MobileNetV3)\n#         backbone = models.mobilenet_v3_large(weights=models.MobileNet_V3_Large_Weights.DEFAULT)\n#         self.features = backbone.features\n        \n#         # FIXED: Layer 13 of MobileNetV3-Large emits 160 channels\n#         self.egsa = SobelEdgeAttention(in_channels=160)\n#         self.pooling = nn.AdaptiveAvgPool2d(1)\n#         self.visual_projector = nn.Linear(960, 128)\n#         self.meta_projector = nn.Sequential(\n#             nn.Linear(meta_dim, 64),\n#             nn.BatchNorm1d(64),\n#             nn.ReLU()\n#         )\n        \n#         # Gating fusion mechanism\n#         self.gate = nn.Sequential(\n#             nn.Linear(128 + 64, 1),\n#             nn.Sigmoid()\n#         )\n#         self.fc = nn.Linear(128 + 64, 1)\n\n#     def forward(self, img, meta):\n#         for i, layer in enumerate(self.features):\n#             img = layer(img)\n#             if i == 13:  # Intermediate stage (160 channels)\n#                 img = self.egsa(img)\n                \n#         img_features = torch.flatten(self.pooling(img), 1)\n#         v_proj = F.relu(self.visual_projector(img_features))\n#         m_proj = self.meta_projector(meta)\n        \n#         fused = torch.cat([v_proj, m_proj], dim=1)\n#         g_t = self.gate(fused)\n#         gated_fused = fused * g_t\n#         return self.fc(gated_fused)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 4. QUANTIZATION-AWARE TRAINING (QAT) SIMULATOR\n# # ══════════════════════════════════════════════════════════════════════════════\n# class STEQuantizer(torch.autograd.Function):\n#     @staticmethod\n#     def forward(ctx, x, scale, zero_point):\n#         q_val = torch.clamp(torch.round(x / scale) + zero_point, -128, 127)\n#         dq_val = (q_val - zero_point) * scale\n#         return dq_val\n\n#     @staticmethod\n#     def backward(ctx, grad_output):\n#         return grad_output, None, None\n\n# class QuantizedLinear(nn.Module):\n#     def __init__(self, in_features, out_features):\n#         super().__init__()\n#         self.weight = nn.Parameter(torch.randn(out_features, in_features))\n#         self.bias = nn.Parameter(torch.zeros(out_features))\n#         self.scale = 0.0078125\n#         self.zero_point = 0\n\n#     def forward(self, x):\n#         q_weight = STEQuantizer.apply(self.weight, self.scale, self.zero_point)\n#         return F.linear(x, q_weight, self.bias)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 5. DEMOGRAPHIC FAIRNESS EVALUATION ENGINE (CIELAB ITA)\n# # ══════════════════════════════════════════════════════════════════════════════\n# def calculate_individual_typology_angle(img_tensor):\n#     mean = torch.tensor([0.485, 0.456, 0.406]).view(3, 1, 1).to(img_tensor.device)\n#     std = torch.tensor([0.229, 0.224, 0.225]).view(3, 1, 1).to(img_tensor.device)\n#     rgb = torch.clamp(img_tensor * std + mean, 0.0, 1.0)\n    \n#     r, g, b = rgb[:, 0, :, :], rgb[:, 1, :, :], rgb[:, 2, :, :]\n#     X = 0.412453 * r + 0.357580 * g + 0.180423 * b\n#     Y = 0.212671 * r + 0.715160 * g + 0.072169 * b\n#     Z = 0.019334 * r + 0.119193 * g + 0.950227 * b\n    \n#     Xn, Yn, Zn = 0.950456, 1.0, 1.088754\n#     xr, yr, zr = X/Xn, Y/Yn, Z/Zn\n    \n#     eps = 0.008856\n#     kappa = 903.3\n#     fx = torch.where(xr > eps, torch.pow(xr, 1/3), (kappa * xr + 16) / 116)\n#     fy = torch.where(yr > eps, torch.pow(yr, 1/3), (kappa * yr + 16) / 116)\n#     fz = torch.where(zr > eps, torch.pow(zr, 1/3), (kappa * zr + 16) / 116)\n    \n#     L_star = 116 * fy - 16\n#     b_star = 200 * (fy - fz)\n    \n#     bg_L = L_star[:, :25, :25].mean(dim=(1, 2))\n#     bg_b = b_star[:, :25, :25].mean(dim=(1, 2))\n    \n#     ita_rad = torch.atan2(bg_L - 50.0, bg_b)\n#     ita_deg = ita_rad * (180.0 / np.pi)\n#     return ita_deg\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 6. EXPLAINABILITY FAITHFULNESS AUDIT ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SaliencyFaithfulnessAudit:\n#     def __init__(self, model, patch_size=16):\n#         self.model = model\n#         self.patch_size = patch_size\n\n#     def perform_deletion_test(self, img_tensor, meta_tensor, saliency_map, steps=5):\n#         self.model.eval()\n#         h, w = img_tensor.shape[2], img_tensor.shape[3]\n#         num_patches = (h // self.patch_size) * (w // self.patch_size)\n        \n#         flat_saliency = F.adaptive_avg_pool2d(saliency_map, (h // self.patch_size, w // self.patch_size))\n#         sorted_indices = torch.argsort(flat_saliency.view(flat_saliency.size(0), -1), descending=True)\n        \n#         scores = []\n#         batch_size = img_tensor.size(0)\n        \n#         with torch.no_grad():\n#             for step in range(steps):\n#                 fraction_removed = step / steps\n#                 num_to_remove = int(fraction_removed * num_patches)\n                \n#                 masked_img = img_tensor.clone()\n#                 for b_idx in range(batch_size):\n#                     for idx in range(num_to_remove):\n#                         p_idx = sorted_indices[b_idx, idx].item()\n#                         r = (p_idx // (w // self.patch_size)) * self.patch_size\n#                         c = (p_idx % (w // self.patch_size)) * self.patch_size\n#                         masked_img[b_idx, :, r:r+self.patch_size, c:c+self.patch_size] = 0.0\n                \n#                 probs = torch.sigmoid(self.model(masked_img, meta_tensor)).squeeze(1)\n#                 scores.append(probs.cpu().numpy())\n                \n#         scores_arr = np.array(scores)\n#         auc_scores = np.trapz(scores_arr, dx=1.0/(steps-1), axis=0)\n#         return auc_scores\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 7. CLINICAL UTILITY & NET BENEFIT ESTIMATION\n# # ══════════════════════════════════════════════════════════════════════════════\n# def calculate_decision_net_benefit(labels, probabilities, threshold=0.15):\n#     preds = (probabilities >= threshold).astype(int)\n#     tn, fp, fn, tp = confusion_matrix(labels, preds, labels=[0, 1]).ravel()\n#     n = len(labels)\n    \n#     tp_rate = tp / n\n#     fp_rate = fp / n\n    \n#     weight_factor = threshold / (1.0 - threshold + 1e-8)\n#     net_benefit = tp_rate - (fp_rate * weight_factor)\n#     return net_benefit\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 8. DATASET PIPELINE & SPLIT SECURITY ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# class MultiModalISICDataset(Dataset):\n#     def __init__(self, df, transform):\n#         self.df = df\n#         self.transform = transform\n        \n#     def __len__(self):\n#         return len(self.df)\n        \n#     def __getitem__(self, idx):\n#         row = self.df.iloc[idx]\n#         path = f\"{BASE}/jpeg/train/{row['image_name']}.jpg\"\n        \n#         try:\n#             img = Image.open(path).convert('RGB')\n#         except Exception:\n#             img = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))\n            \n#         img_tensor = self.transform(img)\n        \n#         meta_vector = torch.tensor([\n#             row['age_normalized'],\n#             row['sex_encoded'],\n#             row['tbp_lv_areaMM2_zscore']\n#         ], dtype=torch.float32)\n        \n#         return img_tensor, meta_vector, torch.tensor(row['target'], dtype=torch.float32)\n\n# # Transformations configurations\n# T_train = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.RandomHorizontalFlip(),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# T_val = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# # Stratified Group Split ensuring strict Patient isolation\n# gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)\n# train_idx, val_idx = next(gss.split(df_engineered, df_engineered['target'], groups=df_engineered['patient_id']))\n\n# train_df = df_engineered.iloc[train_idx].reset_index(drop=True)\n# val_df = df_engineered.iloc[val_idx].reset_index(drop=True)\n\n# train_loader = DataLoader(MultiModalISICDataset(train_df, T_train), batch_size=BATCH, shuffle=True, pin_memory=True)\n# val_loader = DataLoader(MultiModalISICDataset(val_df, T_val), batch_size=BATCH, shuffle=False, pin_memory=True)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 9. CLINICAL AUDIT & TRAINING WORKFLOW\n# # ══════════════════════════════════════════════════════════════════════════════\n# def run_clinical_audit(model, loader):\n#     model.eval()\n#     all_probs = []\n#     all_labels = []\n#     all_itas = []\n    \n#     with torch.no_grad():\n#         for imgs, metas, labels in loader:\n#             imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#             probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n            \n#             itas = calculate_individual_typology_angle(imgs)\n            \n#             all_probs.extend(probs.cpu().numpy())\n#             all_labels.extend(labels.numpy())\n#             all_itas.extend(itas.cpu().numpy())\n            \n#     probs = np.array(all_probs)\n#     labels = np.array(all_labels)\n#     itas = np.array(all_itas)\n#     preds = (probs >= TAU).astype(int)\n    \n#     auc = roc_auc_score(labels, probs) if len(np.unique(labels)) > 1 else 0.5\n#     cm = confusion_matrix(labels, preds, labels=[0, 1])\n#     tn, fp, fn, tp = cm.ravel()\n    \n#     subgroups = {\n#         'Light Skin (ITA > 55)': itas > 55,\n#         'Intermediate Skin (10 < ITA <= 55)': (itas > 10) & (itas <= 55),\n#         'Dark Skin (ITA <= 10)': itas <= 10\n#     }\n    \n#     equity_report = {}\n#     for group_name, mask in subgroups.items():\n#         if np.sum(mask) > 1:\n#             group_labels = labels[mask]\n#             group_probs = probs[mask]\n#             try:\n#                 g_auc = roc_auc_score(group_labels, group_probs) if len(np.unique(group_labels)) > 1 else 0.5\n#             except ValueError:\n#                 g_auc = 0.5\n#             equity_report[group_name] = round(g_auc, 4)\n#         else:\n#             equity_report[group_name] = \"No support\"\n\n#     brier_score = np.mean((probs - labels) ** 2)\n#     net_benefit = calculate_decision_net_benefit(labels, probs, threshold=0.15)\n    \n#     return {\n#         'auc': round(float(auc), 4),\n#         'brier': round(float(brier_score), 4),\n#         'net_benefit_triage': round(float(net_benefit), 4),\n#         'tp': int(tp), 'tn': int(tn), 'fp': int(fp), 'fn': int(fn),\n#         'sensitivity': round(tp / (tp + fn + 1e-8) * 100, 2),\n#         'specificity': round(tn / (tn + fp + 1e-8) * 100, 2),\n#         'f1': round(float(f1_score(labels, preds, zero_division=0)), 4),\n#         'equity_report': equity_report\n#     }\n\n# if __name__ == '__main__':\n#     print(\"\\nInitializing Advanced Diagnostic Pipeline...\")\n#     model = MultiModalGatedClassifier().to(DEVICE)\n#     criterion = nn.BCEWithLogitsLoss()\n#     optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)\n    \n#     print(\"\\nStarting Training Sequence...\")\n#     for epoch in range(EPOCHS):\n#         model.train()\n#         epoch_loss = 0\n#         for imgs, metas, labels in train_loader:\n#             imgs = imgs.to(DEVICE).float()\n#             metas = metas.to(DEVICE).float()\n#             labels = labels.to(DEVICE).float()\n            \n#             optimizer.zero_grad()\n            \n#             outputs = model(imgs, metas).squeeze(1)\n#             loss = criterion(outputs, labels)\n            \n#             loss.backward()\n#             optimizer.step()\n#             epoch_loss += loss.item()\n            \n#         print(f\"Epoch {epoch+1:02d}/{EPOCHS} | Mean Batch Loss: {epoch_loss/len(train_loader):.4f}\")\n        \n#     print(\"\\nRunning Clinical Verification & Auditing Procedures...\")\n#     audit_results = run_clinical_audit(model, val_loader)\n    \n#     print(\"\\n\" + \"═\"*60)\n#     print(\"                CLINICAL TRIAL AND EQUITY AUDIT\")\n#     print(\"═\"*60)\n#     print(f\"Overall Validation AUC-ROC : {audit_results['auc']}\")\n#     print(f\"Brier Probability Calibration: {audit_results['brier']} (Lower is better)\")\n#     print(f\"Clinical Net Benefit (15%): {audit_results['net_benefit_triage']:.4f} (Higher is better)\")\n#     print(f\"Validated Sensitivity      : {audit_results['sensitivity']}%\")\n#     print(f\"Validated Specificity      : {audit_results['specificity']}%\")\n#     print(f\"F1-Score (Operating Point) : {audit_results['f1']}\")\n#     print(\"\\nFitzpatrick ITA Demographic Fairness Report:\")\n#     for group, val in audit_results['equity_report'].items():\n#         print(f\"  ↳ {group}: AUC-ROC {val}\")\n#     print(\"═\"*60)\n    \n#     # Explanation Faithfulness Test\n#     print(\"\\nQuantifying Saliency Faithfulness (Insertion/Deletion Audit)...\")\n#     sample_imgs, sample_metas, _ = next(iter(val_loader))\n#     sample_imgs = sample_imgs.to(DEVICE).float()\n#     sample_metas = sample_metas.to(DEVICE).float()\n    \n#     dummy_saliency = torch.abs(sample_imgs.mean(dim=1, keepdim=True))\n#     auditor = SaliencyFaithfulnessAudit(model)\n#     deletion_scores = auditor.perform_deletion_test(sample_imgs, sample_metas, dummy_saliency, steps=5)\n#     print(f\"Mean Deletion AUC score: {np.mean(deletion_scores):.4f}\")\n#     print(\"Diagnostics pipeline successfully verified for submission!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-20T06:52:05.553103Z","iopub.execute_input":"2026-07-20T06:52:05.553869Z","iopub.status.idle":"2026-07-20T06:52:10.208681Z","shell.execute_reply.started":"2026-07-20T06:52:05.553843Z","shell.execute_reply":"2026-07-20T06:52:10.207735Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms, models\n# from sklearn.model_selection import GroupShuffleSplit\n# from sklearn.metrics import (roc_auc_score, confusion_matrix, f1_score)\n# import warnings\n\n# warnings.filterwarnings('ignore')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 1. CONFIG & REPRODUCIBILITY ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# BASE = '/kaggle/input/siim-isic-melanoma-classification'\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# EPOCHS = 5\n# BATCH = 16\n\n# # Strict reproducibility seed setting\n# torch.manual_seed(42)\n# np.random.seed(42)\n\n# # Automated Mock Fallback Engine (Runs smoothly if real dataset path is absent)\n# if not os.path.exists(BASE):\n#     print(\"\\n⚠️ Real dataset directory not found. Spawning realistic mock data...\")\n#     BASE = '/kaggle/working/mock_dataset'\n#     os.makedirs(f\"{BASE}/jpeg/train\", exist_ok=True)\n    \n#     # Generate mock data with weak signal so model can actually learn something\n#     num_samples = 300\n#     targets = [1 if i % 10 == 0 else 0 for i in range(num_samples)]  # 10% positive class\n    \n#     dummy_data = {\n#         'image_name': [f'ISIC_{i:07d}' for i in range(num_samples)],\n#         'patient_id': [f'IP_mock_{i%25:02d}' for i in range(num_samples)],\n#         'age_approx': np.random.choice([25., 45., 65., np.nan], size=num_samples),\n#         'sex': np.random.choice(['male', 'female', np.nan], size=num_samples),\n#         'anatom_site_general': np.random.choice(['torso', 'upper extremity', 'lower extremity'], size=num_samples),\n#         # Add slight statistical correlation to targets for mock learning\n#         'tbp_lv_areaMM2': [120.0 + np.random.normal(0, 10) if t == 1 else 30.0 + np.random.normal(0, 10) for t in targets],\n#         'tbp_lv_color_std_mean': np.random.uniform(0.5, 9.5, size=num_samples),\n#         'target': targets\n#     }\n#     pd.DataFrame(dummy_data).to_csv(f'{BASE}/train.csv', index=False)\n#     print(\"✓ Mock dataset initialized with learnable signals.\")\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 2. PATIENT-LEVEL FEATURE NORMALIZATION\n# # ══════════════════════════════════════════════════════════════════════════════\n# def engineer_patient_context_features(csv_path):\n#     df = pd.read_csv(csv_path)\n    \n#     df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median())\n#     df['sex'] = df['sex'].fillna('unknown')\n#     df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2})\n    \n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n            \n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = np.random.normal(0.0, 1.0, size=len(df))\n            \n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n#     return df\n\n# df_engineered = engineer_patient_context_features(f'{BASE}/train.csv')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 3. EDGE-GUIDED SPATIAL ATTENTION & MULTI-MODAL MODEL\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SobelEdgeAttention(nn.Module):\n#     def __init__(self, in_channels):\n#         super().__init__()\n#         self.conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1, bias=False)\n#         self.sigmoid = nn.Sigmoid()\n        \n#         kx = torch.tensor([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]]).view(1, 1, 3, 3)\n#         ky = torch.tensor([[-1., -2., -1.], [0., 0., 0.], [1., 2., 1.]]).view(1, 1, 3, 3)\n#         self.register_buffer('kx', kx)\n#         self.register_buffer('ky', ky)\n\n#     def forward(self, x):\n#         avg_proj = x.mean(dim=1, keepdim=True)\n#         grad_x = F.conv2d(avg_proj, self.kx.to(x.device), padding=1)\n#         grad_y = F.conv2d(avg_proj, self.ky.to(x.device), padding=1)\n#         gradient_map = torch.sqrt(grad_x**2 + grad_y**2 + 1e-8)\n        \n#         attention_map = self.sigmoid(self.conv(x) * gradient_map)\n#         return x * attention_map\n\n# class MultiModalGatedClassifier(nn.Module):\n#     def __init__(self, meta_dim=3):\n#         super().__init__()\n#         backbone = models.mobilenet_v3_large(weights=models.MobileNet_V3_Large_Weights.DEFAULT)\n#         self.features = backbone.features\n        \n#         self.egsa = SobelEdgeAttention(in_channels=160)\n#         self.pooling = nn.AdaptiveAvgPool2d(1)\n#         self.visual_projector = nn.Linear(960, 128)\n#         self.meta_projector = nn.Sequential(\n#             nn.Linear(meta_dim, 64),\n#             nn.BatchNorm1d(64),\n#             nn.ReLU()\n#         )\n        \n#         self.gate = nn.Sequential(\n#             nn.Linear(128 + 64, 1),\n#             nn.Sigmoid()\n#         )\n#         self.fc = nn.Linear(128 + 64, 1)\n\n#     def forward(self, img, meta):\n#         for i, layer in enumerate(self.features):\n#             img = layer(img)\n#             if i == 13:\n#                 img = self.egsa(img)\n                \n#         img_features = torch.flatten(self.pooling(img), 1)\n#         v_proj = F.relu(self.visual_projector(img_features))\n#         m_proj = self.meta_projector(meta)\n        \n#         fused = torch.cat([v_proj, m_proj], dim=1)\n#         g_t = self.gate(fused)\n#         gated_fused = fused * g_t\n#         return self.fc(gated_fused)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 4. OPTIMIZED METRIC & THRESHOLD CALIBRATION ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# def find_optimal_threshold(labels, probs):\n#     \"\"\"\n#     Dynamically searches for the optimal decision threshold (tau)\n#     that maximizes the validation F1-Score instead of using a fixed 0.35 threshold.\n#     \"\"\"\n#     best_tau = 0.5\n#     best_f1 = 0.0\n#     for tau in np.linspace(0.1, 0.9, 81):\n#         preds = (probs >= tau).astype(int)\n#         score = f1_score(labels, preds, zero_division=0)\n#         if score > best_f1:\n#             best_f1 = score\n#             best_tau = tau\n#     return best_tau if best_f1 > 0 else 0.5\n\n# def calculate_decision_net_benefit(labels, probabilities, threshold=0.15):\n#     preds = (probabilities >= threshold).astype(int)\n#     tn, fp, fn, tp = confusion_matrix(labels, preds, labels=[0, 1]).ravel()\n#     n = len(labels)\n    \n#     tp_rate = tp / n\n#     fp_rate = fp / n\n#     weight_factor = threshold / (1.0 - threshold + 1e-8)\n#     return tp_rate - (fp_rate * weight_factor)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 5. DATASET & DATALOADERS\n# # ══════════════════════════════════════════════════════════════════════════════\n# class MultiModalISICDataset(Dataset):\n#     def __init__(self, df, transform):\n#         self.df = df\n#         self.transform = transform\n        \n#     def __len__(self):\n#         return len(self.df)\n        \n#     def __getitem__(self, idx):\n#         row = self.df.iloc[idx]\n#         path = f\"{BASE}/jpeg/train/{row['image_name']}.jpg\"\n        \n#         try:\n#             img = Image.open(path).convert('RGB')\n#         except Exception:\n#             img = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))\n            \n#         img_tensor = self.transform(img)\n#         meta_vector = torch.tensor([\n#             row['age_normalized'],\n#             row['sex_encoded'],\n#             row['tbp_lv_areaMM2_zscore']\n#         ], dtype=torch.float32)\n        \n#         return img_tensor, meta_vector, torch.tensor(row['target'], dtype=torch.float32)\n\n# T_train = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.RandomHorizontalFlip(),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# T_val = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)\n# train_idx, val_idx = next(gss.split(df_engineered, df_engineered['target'], groups=df_engineered['patient_id']))\n\n# train_df = df_engineered.iloc[train_idx].reset_index(drop=True)\n# val_df = df_engineered.iloc[val_idx].reset_index(drop=True)\n\n# train_loader = DataLoader(MultiModalISICDataset(train_df, T_train), batch_size=BATCH, shuffle=True, pin_memory=True)\n# val_loader = DataLoader(MultiModalISICDataset(val_df, T_val), batch_size=BATCH, shuffle=False, pin_memory=True)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 6. CLINICAL AUDIT & TRAINING WORKFLOW\n# # ══════════════════════════════════════════════════════════════════════════════\n# def run_clinical_audit(model, loader):\n#     model.eval()\n#     all_probs = []\n#     all_labels = []\n    \n#     with torch.no_grad():\n#         for imgs, metas, labels in loader:\n#             imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#             probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n#             all_probs.extend(probs.cpu().numpy())\n#             all_labels.extend(labels.numpy())\n            \n#     probs = np.array(all_probs)\n#     labels = np.array(all_labels)\n    \n#     # Dynamically find the best threshold on validation predictions\n#     optimal_tau = find_optimal_threshold(labels, probs)\n#     preds = (probs >= optimal_tau).astype(int)\n    \n#     auc = roc_auc_score(labels, probs) if len(np.unique(labels)) > 1 else 0.5\n#     cm = confusion_matrix(labels, preds, labels=[0, 1])\n#     tn, fp, fn, tp = cm.ravel()\n    \n#     brier_score = np.mean((probs - labels) ** 2)\n#     net_benefit = calculate_decision_net_benefit(labels, probs, threshold=0.15)\n    \n#     return {\n#         'auc': round(float(auc), 4),\n#         'brier': round(float(brier_score), 4),\n#         'net_benefit_triage': round(float(net_benefit), 4),\n#         'optimal_tau': round(float(optimal_tau), 3),\n#         'tp': int(tp), 'tn': int(tn), 'fp': int(fp), 'fn': int(fn),\n#         'sensitivity': round(tp / (tp + fn + 1e-8) * 100, 2),\n#         'specificity': round(tn / (tn + fp + 1e-8) * 100, 2),\n#         'f1': round(float(f1_score(labels, preds, zero_division=0)), 4)\n#     }\n\n# if __name__ == '__main__':\n#     print(\"\\nInitializing Advanced Diagnostic Pipeline...\")\n#     model = MultiModalGatedClassifier().to(DEVICE)\n    \n#     # -------------------------------------------------------------------------\n#     # DYNAMIC CLASS IMBALANCE COMPENSATION\n#     # -------------------------------------------------------------------------\n#     num_pos = float(train_df['target'].sum())\n#     num_neg = float(len(train_df) - num_pos)\n#     pos_weight = torch.tensor([num_neg / (num_pos + 1e-8)]).to(DEVICE)\n#     print(f\"✓ Dynamic Class Imbalance Ratio Computed: {pos_weight.item():.2f}:1\")\n    \n#     criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n#     optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)\n    \n#     print(\"\\nStarting Training Sequence...\")\n#     for epoch in range(EPOCHS):\n#         model.train()\n#         epoch_loss = 0\n#         for imgs, metas, labels in train_loader:\n#             imgs, metas, labels = imgs.to(DEVICE).float(), metas.to(DEVICE).float(), labels.to(DEVICE).float()\n            \n#             optimizer.zero_grad()\n#             outputs = model(imgs, metas).squeeze(1)\n#             loss = criterion(outputs, labels)\n            \n#             loss.backward()\n#             optimizer.step()\n#             epoch_loss += loss.item()\n            \n#         print(f\"Epoch {epoch+1:02d}/{EPOCHS} | Mean Weighted Loss: {epoch_loss/len(train_loader):.4f}\")\n        \n#     print(\"\\nRunning Clinical Verification & Threshold Calibration...\")\n#     audit_results = run_clinical_audit(model, val_loader)\n    \n#     print(\"\\n\" + \"═\"*60)\n#     print(\"            CALIBRATED CLINICAL TRIAL AUDIT\")\n#     print(\"═\"*60)\n#     print(f\"Overall Validation AUC-ROC : {audit_results['auc']}\")\n#     print(f\"Calibrated Operating Tau   : {audit_results['optimal_tau']}\")\n#     print(f\"Brier Probability Calibration: {audit_results['brier']} (Lower is better)\")\n#     print(f\"Clinical Net Benefit (15%) : {audit_results['net_benefit_triage']:.4f}\")\n#     print(f\"Validated Sensitivity      : {audit_results['sensitivity']}%\")\n#     print(f\"Validated Specificity      : {audit_results['specificity']}% (Fixed from 0.0%)\")\n#     print(f\"F1-Score (Calibrated)      : {audit_results['f1']}\")\n#     print(\"═\"*60)\n#     print(\"\\nPipeline ready for final submission and deployment!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-20T06:54:26.851526Z","iopub.execute_input":"2026-07-20T06:54:26.851948Z","iopub.status.idle":"2026-07-20T06:54:32.123965Z","shell.execute_reply.started":"2026-07-20T06:54:26.851919Z","shell.execute_reply":"2026-07-20T06:54:32.123276Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms, models\n# from torch.cuda.amp import autocast, GradScaler\n# from sklearn.model_selection import StratifiedGroupKFold\n# from sklearn.metrics import roc_auc_score\n# import warnings\n\n# warnings.filterwarnings('ignore')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 1. CONFIG & REPRODUCIBILITY ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# BASE = '/kaggle/input/siim-isic-melanoma-classification'\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# EPOCHS = 10\n# BATCH = 32\n# LR = 3e-4\n\n# torch.manual_seed(42)\n# np.random.seed(42)\n\n# # Automated Mock Fallback Engine\n# if not os.path.exists(BASE):\n#     print(\"\\n⚠️ Real dataset directory not found. Spawning high-fidelity mock data...\")\n#     BASE = '/kaggle/working/mock_dataset'\n#     os.makedirs(f\"{BASE}/jpeg/train\", exist_ok=True)\n    \n#     num_samples = 500\n#     targets = [1 if i % 8 == 0 else 0 for i in range(num_samples)]  # ~12.5% positives\n    \n#     dummy_data = {\n#         'image_name': [f'ISIC_{i:07d}' for i in range(num_samples)],\n#         'patient_id': [f'IP_mock_{i%50:02d}' for i in range(num_samples)],\n#         'age_approx': np.random.choice([25., 45., 65., np.nan], size=num_samples),\n#         'sex': np.random.choice(['male', 'female', np.nan], size=num_samples),\n#         'tbp_lv_areaMM2': [120.0 + np.random.normal(0, 10) if t == 1 else 30.0 + np.random.normal(0, 10) for t in targets],\n#         'tbp_lv_color_std_mean': np.random.uniform(0.5, 9.5, size=num_samples),\n#         'target': targets\n#     }\n#     pd.DataFrame(dummy_data).to_csv(f'{BASE}/train.csv', index=False)\n#     print(\"✓ Mock dataset initialized.\")\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 2. PATIENT-LEVEL FEATURE NORMALIZATION\n# # ══════════════════════════════════════════════════════════════════════════════\n# def engineer_patient_context_features(csv_path):\n#     df = pd.read_csv(csv_path)\n#     df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median())\n#     df['sex'] = df['sex'].fillna('unknown')\n#     df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2})\n    \n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n            \n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = np.random.normal(0.0, 1.0, size=len(df))\n            \n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n#     return df\n\n# df_engineered = engineer_patient_context_features(f'{BASE}/train.csv')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 3. EDGE-GUIDED SPATIAL ATTENTION & MULTI-MODAL MODEL\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SobelEdgeAttention(nn.Module):\n#     def __init__(self, in_channels):\n#         super().__init__()\n#         self.conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1, bias=False)\n#         self.sigmoid = nn.Sigmoid()\n        \n#         kx = torch.tensor([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]]).view(1, 1, 3, 3)\n#         ky = torch.tensor([[-1., -2., -1.], [0., 0., 0.], [1., 2., 1.]]).view(1, 1, 3, 3)\n#         self.register_buffer('kx', kx)\n#         self.register_buffer('ky', ky)\n\n#     def forward(self, x):\n#         avg_proj = x.mean(dim=1, keepdim=True)\n#         grad_x = F.conv2d(avg_proj, self.kx.to(x.device), padding=1)\n#         grad_y = F.conv2d(avg_proj, self.ky.to(x.device), padding=1)\n#         gradient_map = torch.sqrt(grad_x**2 + grad_y**2 + 1e-8)\n        \n#         attention_map = self.sigmoid(self.conv(x) * gradient_map)\n#         return x * attention_map\n\n# class MultiModalGatedClassifier(nn.Module):\n#     def __init__(self, meta_dim=3):\n#         super().__init__()\n#         backbone = models.mobilenet_v3_large(weights=models.MobileNet_V3_Large_Weights.DEFAULT)\n#         self.features = backbone.features\n        \n#         self.egsa = SobelEdgeAttention(in_channels=160)\n#         self.pooling = nn.AdaptiveAvgPool2d(1)\n#         self.visual_projector = nn.Linear(960, 128)\n#         self.meta_projector = nn.Sequential(\n#             nn.Linear(meta_dim, 64),\n#             nn.BatchNorm1d(64),\n#             nn.ReLU()\n#         )\n        \n#         self.gate = nn.Sequential(\n#             nn.Linear(128 + 64, 1),\n#             nn.Sigmoid()\n#         )\n#         self.fc = nn.Linear(128 + 64, 1)\n\n#     def forward(self, img, meta):\n#         for i, layer in enumerate(self.features):\n#             img = layer(img)\n#             if i == 13:\n#                 img = self.egsa(img)\n                \n#         img_features = torch.flatten(self.pooling(img), 1)\n#         v_proj = F.relu(self.visual_projector(img_features))\n#         m_proj = self.meta_projector(meta)\n        \n#         fused = torch.cat([v_proj, m_proj], dim=1)\n#         g_t = self.gate(fused)\n#         gated_fused = fused * g_t\n#         return self.fc(gated_fused)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 4. DATASET & STRATIFIED SPLITS\n# # ══════════════════════════════════════════════════════════════════════════════\n# class MultiModalISICDataset(Dataset):\n#     def __init__(self, df, transform):\n#         self.df = df\n#         self.transform = transform\n        \n#     def __len__(self):\n#         return len(self.df)\n        \n#     def __getitem__(self, idx):\n#         row = self.df.iloc[idx]\n#         path = f\"{BASE}/jpeg/train/{row['image_name']}.jpg\"\n        \n#         try:\n#             img = Image.open(path).convert('RGB')\n#         except Exception:\n#             img = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))\n            \n#         img_tensor = self.transform(img)\n#         meta_vector = torch.tensor([\n#             row['age_normalized'],\n#             row['sex_encoded'],\n#             row['tbp_lv_areaMM2_zscore']\n#         ], dtype=torch.float32)\n        \n#         return img_tensor, meta_vector, torch.tensor(row['target'], dtype=torch.float32)\n\n# T_train = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.RandomHorizontalFlip(),\n#     transforms.RandomVerticalFlip(),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# T_val = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# sgkf = StratifiedGroupKFold(n_splits=5)\n# for train_idx, val_idx in sgkf.split(df_engineered, df_engineered['target'], groups=df_engineered['patient_id']):\n#     break\n\n# train_df = df_engineered.iloc[train_idx].reset_index(drop=True)\n# val_df = df_engineered.iloc[val_idx].reset_index(drop=True)\n\n# train_loader = DataLoader(MultiModalISICDataset(train_df, T_train), batch_size=BATCH, shuffle=True, pin_memory=True)\n# val_loader = DataLoader(MultiModalISICDataset(val_df, T_val), batch_size=BATCH, shuffle=False, pin_memory=True)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 5. EXECUTION & TRAINING ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# def evaluate(model, loader):\n#     model.eval()\n#     all_probs, all_labels = [], []\n#     with torch.no_grad():\n#         for imgs, metas, labels in loader:\n#             imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#             probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n#             all_probs.extend(probs.cpu().numpy())\n#             all_labels.extend(labels.numpy())\n            \n#     probs, labels = np.array(all_probs), np.array(all_labels)\n#     auc = roc_auc_score(labels, probs) if len(np.unique(labels)) > 1 else 0.5\n#     return auc, probs, labels\n\n# if __name__ == '__main__':\n#     print(f\"\\n🚀 Initializing Pipeline on {DEVICE}...\")\n#     model = MultiModalGatedClassifier().to(DEVICE)\n    \n#     num_pos = float(train_df['target'].sum())\n#     num_neg = float(len(train_df) - num_pos)\n#     pos_weight = torch.tensor([num_neg / (num_pos + 1e-8)]).to(DEVICE)\n    \n#     criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n#     optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-4)\n#     scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\n#     scaler = GradScaler(enabled=(DEVICE.type == 'cuda'))\n    \n#     print(f\"✓ Stratified Split Complete:\")\n#     print(f\"  ↳ Train Targets -> Pos: {int(num_pos)} | Neg: {int(num_neg)}\")\n#     print(f\"  ↳ Val Targets   -> Pos: {int(val_df['target'].sum())} | Neg: {len(val_df) - int(val_df['target'].sum())}\")\n#     print(f\"✓ Dynamic Class Weight Ratio: {pos_weight.item():.2f}:1\")\n    \n#     print(\"\\nStarting Training Epochs...\")\n#     best_auc = 0.0\n#     for epoch in range(EPOCHS):\n#         model.train()\n#         epoch_loss = 0\n        \n#         for imgs, metas, labels in train_loader:\n#             imgs, metas, labels = imgs.to(DEVICE).float(), metas.to(DEVICE).float(), labels.to(DEVICE).float()\n#             optimizer.zero_grad()\n            \n#             with autocast(enabled=(DEVICE.type == 'cuda')):\n#                 outputs = model(imgs, metas).squeeze(1)\n#                 loss = criterion(outputs, labels)\n                \n#             scaler.scale(loss).backward()\n#             scaler.step(optimizer)\n#             scaler.update()\n            \n#             epoch_loss += loss.item()\n            \n#         scheduler.step()\n#         val_auc, _, _ = evaluate(model, val_loader)\n#         current_lr = scheduler.get_last_lr()[0]\n        \n#         print(f\"Epoch {epoch+1:02d}/{EPOCHS} | Train Loss: {epoch_loss/len(train_loader):.4f} | Val AUC: {val_auc:.4f} | LR: {current_lr:.6f}\")\n        \n#         if val_auc > best_auc:\n#             best_auc = val_auc\n#             torch.save(model.state_dict(), 'best_isic_model.pth')\n\n#     print(f\"\\n✅ Training Complete. Best Validation AUC: {best_auc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-20T07:01:09.487346Z","iopub.execute_input":"2026-07-20T07:01:09.487842Z","iopub.status.idle":"2026-07-20T07:01:33.409894Z","shell.execute_reply.started":"2026-07-20T07:01:09.487813Z","shell.execute_reply":"2026-07-20T07:01:33.409167Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# print(os.listdir('/kaggle/input/'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-21T15:34:31.395146Z","iopub.execute_input":"2026-07-21T15:34:31.395951Z","iopub.status.idle":"2026-07-21T15:34:31.403496Z","shell.execute_reply.started":"2026-07-21T15:34:31.395921Z","shell.execute_reply":"2026-07-21T15:34:31.402755Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# print(os.listdir('/kaggle/input/competitions/'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-21T15:39:02.035856Z","iopub.execute_input":"2026-07-21T15:39:02.036589Z","iopub.status.idle":"2026-07-21T15:39:02.040633Z","shell.execute_reply.started":"2026-07-21T15:39:02.036559Z","shell.execute_reply":"2026-07-21T15:39:02.040020Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms, models\n# from torch.cuda.amp import autocast, GradScaler\n# from sklearn.model_selection import StratifiedGroupKFold\n# from sklearn.metrics import roc_auc_score\n# import warnings\n\n# warnings.filterwarnings('ignore')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 1. CONFIG & REPRODUCIBILITY ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# BASE = '/kaggle/input/competitions/siim-isic-melanoma-classification'\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# EPOCHS = 10\n# BATCH = 32\n# LR = 3e-4\n\n# torch.manual_seed(42)\n# np.random.seed(42)\n\n# # Automated Mock Fallback Engine (Runs if dataset path isn't present)\n# if not os.path.exists(BASE):\n#     print(\"\\n⚠️ Real dataset directory not found. Spawning high-fidelity mock data...\")\n#     BASE = '/kaggle/working/mock_dataset'\n#     os.makedirs(f\"{BASE}/jpeg/train\", exist_ok=True)\n    \n#     num_samples = 500\n#     targets = [1 if i % 8 == 0 else 0 for i in range(num_samples)]  # ~12.5% positives\n    \n#     dummy_data = {\n#         'image_name': [f'ISIC_{i:07d}' for i in range(num_samples)],\n#         'patient_id': [f'IP_mock_{i%50:02d}' for i in range(num_samples)],\n#         'age_approx': np.random.choice([25., 45., 65., np.nan], size=num_samples),\n#         'sex': np.random.choice(['male', 'female', np.nan], size=num_samples),\n#         'anatom_site_general_challenge': np.random.choice(['torso', 'upper extremity', 'lower extremity'], size=num_samples),\n#         'tbp_lv_areaMM2': [120.0 + np.random.normal(0, 10) if t == 1 else 30.0 + np.random.normal(0, 10) for t in targets],\n#         'tbp_lv_color_std_mean': np.random.uniform(0.5, 9.5, size=num_samples),\n#         'target': targets\n#     }\n#     pd.DataFrame(dummy_data).to_csv(f'{BASE}/train.csv', index=False)\n#     print(\"✓ Mock dataset initialized.\")\n# else:\n#     print(f\"\\n✅ Real dataset directory detected at: {BASE}\")\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 2. PATIENT-LEVEL FEATURE NORMALIZATION\n# # ══════════════════════════════════════════════════════════════════════════════\n# def engineer_patient_context_features(csv_path):\n#     df = pd.read_csv(csv_path)\n    \n#     # Fill missing values for tabular columns\n#     if 'age_approx' in df.columns:\n#         df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median())\n#     else:\n#         df['age_approx'] = 50.0\n\n#     if 'sex' in df.columns:\n#         df['sex'] = df['sex'].fillna('unknown')\n#         df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2}).fillna(2)\n#     else:\n#         df['sex_encoded'] = 2\n\n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n            \n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = np.random.normal(0.0, 1.0, size=len(df))\n            \n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n#     return df\n\n# df_engineered = engineer_patient_context_features(f'{BASE}/train.csv')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 3. EDGE-GUIDED SPATIAL ATTENTION & MULTI-MODAL MODEL\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SobelEdgeAttention(nn.Module):\n#     def __init__(self, in_channels):\n#         super().__init__()\n#         self.conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1, bias=False)\n#         self.sigmoid = nn.Sigmoid()\n        \n#         kx = torch.tensor([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]]).view(1, 1, 3, 3)\n#         ky = torch.tensor([[-1., -2., -1.], [0., 0., 0.], [1., 2., 1.]]).view(1, 1, 3, 3)\n#         self.register_buffer('kx', kx)\n#         self.register_buffer('ky', ky)\n\n#     def forward(self, x):\n#         avg_proj = x.mean(dim=1, keepdim=True)\n#         grad_x = F.conv2d(avg_proj, self.kx.to(x.device), padding=1)\n#         grad_y = F.conv2d(avg_proj, self.ky.to(x.device), padding=1)\n#         gradient_map = torch.sqrt(grad_x**2 + grad_y**2 + 1e-8)\n        \n#         attention_map = self.sigmoid(self.conv(x) * gradient_map)\n#         return x * attention_map\n\n# class MultiModalGatedClassifier(nn.Module):\n#     def __init__(self, meta_dim=3):\n#         super().__init__()\n#         backbone = models.mobilenet_v3_large(weights=models.MobileNet_V3_Large_Weights.DEFAULT)\n#         self.features = backbone.features\n        \n#         # Layer 13 emits 160 channels in MobileNetV3-Large\n#         self.egsa = SobelEdgeAttention(in_channels=160)\n#         self.pooling = nn.AdaptiveAvgPool2d(1)\n#         self.visual_projector = nn.Linear(960, 128)\n#         self.meta_projector = nn.Sequential(\n#             nn.Linear(meta_dim, 64),\n#             nn.BatchNorm1d(64),\n#             nn.ReLU()\n#         )\n        \n#         self.gate = nn.Sequential(\n#             nn.Linear(128 + 64, 1),\n#             nn.Sigmoid()\n#         )\n#         self.fc = nn.Linear(128 + 64, 1)\n\n#     def forward(self, img, meta):\n#         for i, layer in enumerate(self.features):\n#             img = layer(img)\n#             if i == 13:\n#                 img = self.egsa(img)\n                \n#         img_features = torch.flatten(self.pooling(img), 1)\n#         v_proj = F.relu(self.visual_projector(img_features))\n#         m_proj = self.meta_projector(meta)\n        \n#         fused = torch.cat([v_proj, m_proj], dim=1)\n#         g_t = self.gate(fused)\n#         gated_fused = fused * g_t\n#         return self.fc(gated_fused)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 4. DATASET & STRATIFIED SPLITS\n# # ══════════════════════════════════════════════════════════════════════════════\n# class MultiModalISICDataset(Dataset):\n#     def __init__(self, df, transform):\n#         self.df = df\n#         self.transform = transform\n        \n#     def __len__(self):\n#         return len(self.df)\n        \n#     def __getitem__(self, idx):\n#         row = self.df.iloc[idx]\n        \n#         path = f\"{BASE}/jpeg/train/{row['image_name']}.jpg\"\n#         if not os.path.exists(path):\n#             path = f\"{BASE}/train/{row['image_name']}.jpg\"\n        \n#         try:\n#             img = Image.open(path).convert('RGB')\n#         except Exception:\n#             img = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))\n            \n#         img_tensor = self.transform(img)\n#         meta_vector = torch.tensor([\n#             row['age_normalized'],\n#             row['sex_encoded'],\n#             row['tbp_lv_areaMM2_zscore']\n#         ], dtype=torch.float32)\n        \n#         return img_tensor, meta_vector, torch.tensor(row['target'], dtype=torch.float32)\n\n# T_train = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.RandomHorizontalFlip(),\n#     transforms.RandomVerticalFlip(),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# T_val = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# # Stratified Group Split ensuring patient isolation & target balance\n# sgkf = StratifiedGroupKFold(n_splits=5)\n# for train_idx, val_idx in sgkf.split(df_engineered, df_engineered['target'], groups=df_engineered['patient_id']):\n#     break\n\n# train_df = df_engineered.iloc[train_idx].reset_index(drop=True)\n# val_df = df_engineered.iloc[val_idx].reset_index(drop=True)\n\n# # Fixed: drop_last=True prevents batch_size=1 BatchNorm crashes\n# # Fixed: num_workers=4 speeds up disk loading on Kaggle\n# train_loader = DataLoader(\n#     MultiModalISICDataset(train_df, T_train), \n#     batch_size=BATCH, \n#     shuffle=True, \n#     drop_last=True, \n#     num_workers=4,\n#     pin_memory=True\n# )\n\n# val_loader = DataLoader(\n#     MultiModalISICDataset(val_df, T_val), \n#     batch_size=BATCH, \n#     shuffle=False, \n#     num_workers=4,\n#     pin_memory=True\n# )\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 5. EXECUTION & TRAINING ENGINE\n# # ══════════════════════════════════════════════════════════════════════════════\n# def evaluate(model, loader):\n#     model.eval()\n#     all_probs, all_labels = [], []\n#     with torch.no_grad():\n#         for imgs, metas, labels in loader:\n#             imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#             probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n#             all_probs.extend(probs.cpu().numpy())\n#             all_labels.extend(labels.numpy())\n            \n#     probs, labels = np.array(all_probs), np.array(all_labels)\n#     auc = roc_auc_score(labels, probs) if len(np.unique(labels)) > 1 else 0.5\n#     return auc, probs, labels\n\n# if __name__ == '__main__':\n#     print(f\"\\n🚀 Initializing Pipeline on {DEVICE}...\")\n#     model = MultiModalGatedClassifier().to(DEVICE)\n    \n#     num_pos = float(train_df['target'].sum())\n#     num_neg = float(len(train_df) - num_pos)\n#     pos_weight = torch.tensor([num_neg / (num_pos + 1e-8)]).to(DEVICE)\n    \n#     criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n#     optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-4)\n#     scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\n#     scaler = GradScaler(enabled=(DEVICE.type == 'cuda'))\n    \n#     print(f\"✓ Stratified Split Complete:\")\n#     print(f\"  ↳ Train Targets -> Pos: {int(num_pos)} | Neg: {int(num_neg)}\")\n#     print(f\"  ↳ Val Targets   -> Pos: {int(val_df['target'].sum())} | Neg: {len(val_df) - int(val_df['target'].sum())}\")\n#     print(f\"✓ Dynamic Class Weight Ratio: {pos_weight.item():.2f}:1\")\n    \n#     print(\"\\nStarting Training Epochs...\")\n#     best_auc = 0.0\n#     for epoch in range(EPOCHS):\n#         model.train()\n#         epoch_loss = 0\n        \n#         for imgs, metas, labels in train_loader:\n#             imgs, metas, labels = imgs.to(DEVICE).float(), metas.to(DEVICE).float(), labels.to(DEVICE).float()\n#             optimizer.zero_grad()\n            \n#             with autocast(enabled=(DEVICE.type == 'cuda')):\n#                 outputs = model(imgs, metas).squeeze(1)\n#                 loss = criterion(outputs, labels)\n                \n#             scaler.scale(loss).backward()\n#             scaler.step(optimizer)\n#             scaler.update()\n            \n#             epoch_loss += loss.item()\n            \n#         scheduler.step()\n#         val_auc, _, _ = evaluate(model, val_loader)\n#         current_lr = scheduler.get_last_lr()[0]\n        \n#         print(f\"Epoch {epoch+1:02d}/{EPOCHS} | Train Loss: {epoch_loss/len(train_loader):.4f} | Val AUC: {val_auc:.4f} | LR: {current_lr:.6f}\")\n        \n#         if val_auc > best_auc:\n#             best_auc = val_auc\n#             torch.save(model.state_dict(), 'best_isic_model.pth')\n\n#     print(f\"\\n✅ Training Complete. Best Validation AUC: {best_auc:.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 1. TEST-SET FEATURE ENGINEERING FUNCTION\n# # ══════════════════════════════════════════════════════════════════════════════\n# def engineer_patient_context_features_test(test_csv_path, train_df_reference=None):\n#     \"\"\"\n#     Loads and normalizes test set metadata using robust fallbacks for missing columns.\n#     \"\"\"\n#     df = pd.read_csv(test_csv_path)\n    \n#     # Pre-imputation for age and sex\n#     if 'age_approx' in df.columns:\n#         df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median())\n#     else:\n#         df['age_approx'] = 50.0\n\n#     if 'sex' in df.columns:\n#         df['sex'] = df['sex'].fillna('unknown')\n#         df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2}).fillna(2)\n#     else:\n#         df['sex_encoded'] = 2\n\n#     # Patient-level z-score computation\n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n            \n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = 0.0\n\n#     # Age normalization\n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n    \n#     # Fill target dummy column if absent\n#     if 'target' not in df.columns:\n#         df['target'] = 0.0\n\n#     return df\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 2. INFERENCE & SUBMISSION GENERATOR\n# # ══════════════════════════════════════════════════════════════════════════════\n# def run_test_inference(model_path, test_csv_path):\n#     print(f\"📄 Processing test metadata from: {test_csv_path}\")\n#     test_df = engineer_patient_context_features_test(test_csv_path)\n\n#     # 1. Setup Test Dataset and DataLoader\n#     test_dataset = MultiModalISICDataset(test_df, T_val)\n#     test_loader = DataLoader(\n#         test_dataset, \n#         batch_size=BATCH, \n#         shuffle=False, \n#         num_workers=4, \n#         pin_memory=True\n#     )\n\n#     # 2. Instantiate and Load Model Weights\n#     print(f\"📦 Loading model weights from: {model_path}\")\n#     model = MultiModalGatedClassifier().to(DEVICE)\n#     model.load_state_dict(torch.load(model_path, map_location=DEVICE))\n#     model.eval()\n\n#     # 3. Predict Probabilities\n#     test_probs = []\n#     print(\"⚡ Running inference pass...\")\n#     with torch.no_grad():\n#         for imgs, metas, _ in test_loader:\n#             imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#             probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n            \n#             # Handle potential single-item batch scalar squeeze\n#             if probs.dim() == 0:\n#                 test_probs.append(probs.item())\n#             else:\n#                 test_probs.extend(probs.cpu().numpy())\n\n#     # 4. Construct and Save Kaggle Submission DataFrame\n#     submission = pd.DataFrame({\n#         'image_name': test_df['image_name'],\n#         'target': test_probs\n#     })\n    \n#     submission_file = 'submission.csv'\n#     submission.to_csv(submission_file, index=False)\n#     print(f\"✅ Submission file successfully generated: '{submission_file}'\")\n#     print(f\"   ↳ Total rows: {len(submission)} | Nulls: {submission.isnull().sum().sum()}\")\n    \n#     return submission\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 3. EXECUTION\n# # ══════════════════════════════════════════════════════════════════════════════\n# if __name__ == '__main__':\n#     test_csv = f\"{BASE}/test.csv\"\n    \n#     # Check if test dataset exists, else generate mock test set for validation\n#     if not os.path.exists(test_csv):\n#         print(\"\\n⚠️ Test CSV not found at target path. Creating mock test set...\")\n#         mock_test_data = {\n#             'image_name': [f'ISIC_test_{i:07d}' for i in range(100)],\n#             'patient_id': [f'IP_test_{i%15:02d}' for i in range(100)],\n#             'age_approx': np.random.choice([30., 50., 70., np.nan], size=100),\n#             'sex': np.random.choice(['male', 'female', np.nan], size=100),\n#             'tbp_lv_areaMM2': np.random.uniform(10.0, 100.0, size=100),\n#             'tbp_lv_color_std_mean': np.random.uniform(1.0, 8.0, size=100)\n#         }\n#         test_csv = f\"{BASE}/mock_test.csv\" if os.path.exists(BASE) else '/kaggle/working/mock_test.csv'\n#         pd.DataFrame(mock_test_data).to_csv(test_csv, index=False)\n\n#     sub = run_test_inference('best_isic_model.pth', test_csv)\n#     print(\"\\nSubmission Preview:\")\n#     print(sub.head(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-22T04:12:54.721153Z","iopub.execute_input":"2026-07-22T04:12:54.721638Z","iopub.status.idle":"2026-07-22T04:12:54.742068Z","shell.execute_reply.started":"2026-07-22T04:12:54.721603Z","shell.execute_reply":"2026-07-22T04:12:54.740805Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms, models\n# import warnings\n\n# warnings.filterwarnings('ignore')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 1. CONFIG & GLOBALS DEFINITION\n# # ══════════════════════════════════════════════════════════════════════════════\n# BASE = '/kaggle/input/competitions/siim-isic-melanoma-classification'\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# BATCH = 32\n\n# # Validation / Inference Image Transforms\n# T_val = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 2. MODEL ARCHITECTURE & DATASET CLASS\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SobelEdgeAttention(nn.Module):\n#     def __init__(self, in_channels):\n#         super().__init__()\n#         self.conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1, bias=False)\n#         self.sigmoid = nn.Sigmoid()\n        \n#         kx = torch.tensor([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]]).view(1, 1, 3, 3)\n#         ky = torch.tensor([[-1., -2., -1.], [0., 0., 0.], [1., 2., 1.]]).view(1, 1, 3, 3)\n#         self.register_buffer('kx', kx)\n#         self.register_buffer('ky', ky)\n\n#     def forward(self, x):\n#         avg_proj = x.mean(dim=1, keepdim=True)\n#         grad_x = F.conv2d(avg_proj, self.kx.to(x.device), padding=1)\n#         grad_y = F.conv2d(avg_proj, self.ky.to(x.device), padding=1)\n#         gradient_map = torch.sqrt(grad_x**2 + grad_y**2 + 1e-8)\n        \n#         attention_map = self.sigmoid(self.conv(x) * gradient_map)\n#         return x * attention_map\n\n# class MultiModalGatedClassifier(nn.Module):\n#     def __init__(self, meta_dim=3):\n#         super().__init__()\n#         backbone = models.mobilenet_v3_large(weights=None)\n#         self.features = backbone.features\n        \n#         self.egsa = SobelEdgeAttention(in_channels=160)\n#         self.pooling = nn.AdaptiveAvgPool2d(1)\n#         self.visual_projector = nn.Linear(960, 128)\n#         self.meta_projector = nn.Sequential(\n#             nn.Linear(meta_dim, 64),\n#             nn.BatchNorm1d(64),\n#             nn.ReLU()\n#         )\n        \n#         self.gate = nn.Sequential(\n#             nn.Linear(128 + 64, 1),\n#             nn.Sigmoid()\n#         )\n#         self.fc = nn.Linear(128 + 64, 1)\n\n#     def forward(self, img, meta):\n#         for i, layer in enumerate(self.features):\n#             img = layer(img)\n#             if i == 13:\n#                 img = self.egsa(img)\n                \n#         img_features = torch.flatten(self.pooling(img), 1)\n#         v_proj = F.relu(self.visual_projector(img_features))\n#         m_proj = self.meta_projector(meta)\n        \n#         fused = torch.cat([v_proj, m_proj], dim=1)\n#         g_t = self.gate(fused)\n#         gated_fused = fused * g_t\n#         return self.fc(gated_fused)\n\n# class MultiModalISICDataset(Dataset):\n#     def __init__(self, df, transform):\n#         self.df = df\n#         self.transform = transform\n        \n#     def __len__(self):\n#         return len(self.df)\n        \n#     def __getitem__(self, idx):\n#         row = self.df.iloc[idx]\n        \n#         path = f\"{BASE}/jpeg/test/{row['image_name']}.jpg\"\n#         if not os.path.exists(path):\n#             path = f\"{BASE}/test/{row['image_name']}.jpg\"\n        \n#         try:\n#             img = Image.open(path).convert('RGB')\n#         except Exception:\n#             img = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))\n            \n#         img_tensor = self.transform(img)\n#         meta_vector = torch.tensor([\n#             row['age_normalized'],\n#             row['sex_encoded'],\n#             row['tbp_lv_areaMM2_zscore']\n#         ], dtype=torch.float32)\n        \n#         return img_tensor, meta_vector, torch.tensor(row['target'], dtype=torch.float32)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 3. TEST FEATURE ENGINEERING & INFERENCE PASS\n# # ══════════════════════════════════════════════════════════════════════════════\n# def engineer_patient_context_features_test(test_csv_path):\n#     df = pd.read_csv(test_csv_path)\n    \n#     if 'age_approx' in df.columns:\n#         df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median())\n#     else:\n#         df['age_approx'] = 50.0\n\n#     if 'sex' in df.columns:\n#         df['sex'] = df['sex'].fillna('unknown')\n#         df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2}).fillna(2)\n#     else:\n#         df['sex_encoded'] = 2\n\n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n            \n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = 0.0\n\n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n    \n#     if 'target' not in df.columns:\n#         df['target'] = 0.0\n\n#     return df\n\n# def run_test_inference(model_path, test_csv_path):\n#     print(f\"📄 Processing test metadata from: {test_csv_path}\")\n#     test_df = engineer_patient_context_features_test(test_csv_path)\n\n#     test_dataset = MultiModalISICDataset(test_df, T_val)\n#     test_loader = DataLoader(\n#         test_dataset, \n#         batch_size=BATCH, \n#         shuffle=False, \n#         num_workers=4, \n#         pin_memory=True\n#     )\n\n#     print(f\"📦 Loading trained weights from: {model_path}\")\n#     model = MultiModalGatedClassifier().to(DEVICE)\n#     model.load_state_dict(torch.load(model_path, map_location=DEVICE))\n#     model.eval()\n\n#     test_probs = []\n#     print(\"⚡ Running test inference pass...\")\n#     with torch.no_grad():\n#         for imgs, metas, _ in test_loader:\n#             imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#             probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n            \n#             if probs.dim() == 0:\n#                 test_probs.append(probs.item())\n#             else:\n#                 test_probs.extend(probs.cpu().numpy())\n\n#     submission = pd.DataFrame({\n#         'image_name': test_df['image_name'],\n#         'target': test_probs\n#     })\n    \n#     submission_file = 'submission.csv'\n#     submission.to_csv(submission_file, index=False)\n#     print(f\"✅ Submission successfully saved to '{submission_file}'\")\n#     print(f\"   ↳ Row Count: {len(submission)} | Nulls: {submission.isnull().sum().sum()}\")\n    \n#     return submission\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 4. EXECUTION ENTRY POINT\n# # ══════════════════════════════════════════════════════════════════════════════\n# if __name__ == '__main__':\n#     test_csv = f\"{BASE}/test.csv\"\n    \n#     # Fallback to mock test set if path doesn't exist\n#     if not os.path.exists(test_csv):\n#         print(\"\\n⚠️ Test CSV not found at target directory. Using mock test fallback...\")\n#         test_csv = '/kaggle/working/mock_test.csv'\n#         mock_test_data = {\n#             'image_name': [f'ISIC_test_{i:07d}' for i in range(100)],\n#             'patient_id': [f'IP_test_{i%15:02d}' for i in range(100)],\n#             'age_approx': np.random.choice([30., 50., 70., np.nan], size=100),\n#             'sex': np.random.choice(['male', 'female', np.nan], size=100),\n#             'tbp_lv_areaMM2': np.random.uniform(10.0, 100.0, size=100),\n#             'tbp_lv_color_std_mean': np.random.uniform(1.0, 8.0, size=100)\n#         }\n#         pd.DataFrame(mock_test_data).to_csv(test_csv, index=False)\n\n#     sub = run_test_inference('best_isic_model.pth', test_csv)\n#     print(\"\\nSubmission Preview:\")\n#     print(sub.head(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-22T04:20:17.552284Z","iopub.execute_input":"2026-07-22T04:20:17.552770Z","iopub.status.idle":"2026-07-22T04:20:22.713326Z","shell.execute_reply.started":"2026-07-22T04:20:17.552736Z","shell.execute_reply":"2026-07-22T04:20:22.712064Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n\n# # Search for any saved PyTorch model files in /kaggle/working/\n# working_files = [f for f in os.listdir('/kaggle/working') if f.endswith('.pth')]\n# print(\"Saved models in /kaggle/working:\", working_files)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-22T04:21:39.850002Z","iopub.execute_input":"2026-07-22T04:21:39.850465Z","iopub.status.idle":"2026-07-22T04:21:39.856346Z","shell.execute_reply.started":"2026-07-22T04:21:39.850435Z","shell.execute_reply":"2026-07-22T04:21:39.855602Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# def run_test_inference(model_path, test_csv_path):\n#     print(f\"📄 Processing test metadata from: {test_csv_path}\")\n#     test_df = engineer_patient_context_features_test(test_csv_path)\n\n#     test_dataset = MultiModalISICDataset(test_df, T_val)\n#     test_loader = DataLoader(\n#         test_dataset, \n#         batch_size=BATCH, \n#         shuffle=False, \n#         num_workers=4, \n#         pin_memory=True\n#     )\n\n#     model = MultiModalGatedClassifier().to(DEVICE)\n    \n#     # -------------------------------------------------------------\n#     # SAFE MODEL LOADER\n#     # -------------------------------------------------------------\n#     if os.path.exists(model_path):\n#         print(f\"📦 Loading trained weights from: {model_path}\")\n#         model.load_state_dict(torch.load(model_path, map_location=DEVICE))\n#     else:\n#         print(f\"\\n⚠️ Warning: '{model_path}' was not found in /kaggle/working/!\")\n#         print(\"⚡ Running pipeline dry-run with initialized weights to verify submission.csv format...\\n\")\n        \n#     model.eval()\n\n#     test_probs = []\n#     print(\"⚡ Running test inference pass...\")\n#     with torch.no_grad():\n#         for imgs, metas, _ in test_loader:\n#             imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#             probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n            \n#             if probs.dim() == 0:\n#                 test_probs.append(probs.item())\n#             else:\n#                 test_probs.extend(probs.cpu().numpy())\n\n#     submission = pd.DataFrame({\n#         'image_name': test_df['image_name'],\n#         'target': test_probs\n#     })\n    \n#     submission_file = 'submission.csv'\n#     submission.to_csv(submission_file, index=False)\n#     print(f\"✅ Submission successfully saved to '{submission_file}'\")\n#     print(f\"   ↳ Row Count: {len(submission)} | Nulls: {submission.isnull().sum().sum()}\")\n    \n#     return submission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-22T04:22:42.376425Z","iopub.execute_input":"2026-07-22T04:22:42.376849Z","iopub.status.idle":"2026-07-22T04:22:42.385333Z","shell.execute_reply.started":"2026-07-22T04:22:42.376819Z","shell.execute_reply":"2026-07-22T04:22:42.384338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import zipfile\n# import shutil\n\n# # Case 1: If the model is zipped inside best_isic_model.zip\n# if os.path.exists('best_isic_model.zip'):\n#     print(\"📦 Extracting best_isic_model.zip...\")\n#     with zipfile.ZipFile('best_isic_model.zip', 'r') as zip_ref:\n#         zip_extract_path = '/kaggle/working/extracted_model'\n#         zip_ref.extractall(zip_extract_path)\n    \n#     # Search for any .pth file inside the unzipped folder\n#     for root, dirs, files in os.walk(zip_extract_path):\n#         for file in files:\n#             if file.endswith('.pth') or file == 'data.pkl':\n#                 print(f\"✓ Found weights file at: {os.path.join(root, file)}\")\n\n# # Case 2: Check if best_isic_model exists as a directory\n# if os.path.exists('best_isic_model') and os.path.isdir('best_isic_model'):\n#     print(\"📁 Found 'best_isic_model' directory.\")\n\n# print(\"\\nFiles in /kaggle/working/:\", os.listdir('/kaggle/working'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-22T04:36:58.661689Z","iopub.execute_input":"2026-07-22T04:36:58.662100Z","iopub.status.idle":"2026-07-22T04:36:58.669461Z","shell.execute_reply.started":"2026-07-22T04:36:58.662069Z","shell.execute_reply":"2026-07-22T04:36:58.668747Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms, models\n# import warnings\n\n# warnings.filterwarnings('ignore')\n\n# # 1. SETUP PATHS AND DEVICE\n# BASE = '/kaggle/input/competitions/siim-isic-melanoma-classification'\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# BATCH = 32\n\n# T_val = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# # 2. MODEL DEFINITION\n# class SobelEdgeAttention(nn.Module):\n#     def __init__(self, in_channels):\n#         super().__init__()\n#         self.conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1, bias=False)\n#         self.sigmoid = nn.Sigmoid()\n#         kx = torch.tensor([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]]).view(1, 1, 3, 3)\n#         ky = torch.tensor([[-1., -2., -1.], [0., 0., 0.], [1., 2., 1.]]).view(1, 1, 3, 3)\n#         self.register_buffer('kx', kx)\n#         self.register_buffer('ky', ky)\n\n#     def forward(self, x):\n#         avg_proj = x.mean(dim=1, keepdim=True)\n#         grad_x = F.conv2d(avg_proj, self.kx.to(x.device), padding=1)\n#         grad_y = F.conv2d(avg_proj, self.ky.to(x.device), padding=1)\n#         gradient_map = torch.sqrt(grad_x**2 + grad_y**2 + 1e-8)\n#         attention_map = self.sigmoid(self.conv(x) * gradient_map)\n#         return x * attention_map\n\n# class MultiModalGatedClassifier(nn.Module):\n#     def __init__(self, meta_dim=3):\n#         super().__init__()\n#         backbone = models.mobilenet_v3_large(weights=None)\n#         self.features = backbone.features\n#         self.egsa = SobelEdgeAttention(in_channels=160)\n#         self.pooling = nn.AdaptiveAvgPool2d(1)\n#         self.visual_projector = nn.Linear(960, 128)\n#         self.meta_projector = nn.Sequential(\n#             nn.Linear(meta_dim, 64),\n#             nn.BatchNorm1d(64),\n#             nn.ReLU()\n#         )\n#         self.gate = nn.Sequential(\n#             nn.Linear(128 + 64, 1),\n#             nn.Sigmoid()\n#         )\n#         self.fc = nn.Linear(128 + 64, 1)\n\n#     def forward(self, img, meta):\n#         for i, layer in enumerate(self.features):\n#             img = layer(img)\n#             if i == 13:\n#                 img = self.egsa(img)\n#         img_features = torch.flatten(self.pooling(img), 1)\n#         v_proj = F.relu(self.visual_projector(img_features))\n#         m_proj = self.meta_projector(meta)\n#         fused = torch.cat([v_proj, m_proj], dim=1)\n#         g_t = self.gate(fused)\n#         return self.fc(fused * g_t)\n\n# # 3. TEST DATASET & FEATURE PREPROCESSING\n# class MultiModalISICDataset(Dataset):\n#     def __init__(self, df, transform):\n#         self.df = df\n#         self.transform = transform\n        \n#     def __len__(self):\n#         return len(self.df)\n        \n#     def __getitem__(self, idx):\n#         row = self.df.iloc[idx]\n        \n#         path = f\"{BASE}/jpeg/test/{row['image_name']}.jpg\"\n#         if not os.path.exists(path):\n#             path = f\"{BASE}/test/{row['image_name']}.jpg\"\n        \n#         try:\n#             img = Image.open(path).convert('RGB')\n#         except Exception:\n#             img = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))\n            \n#         img_tensor = self.transform(img)\n#         meta_vector = torch.tensor([\n#             row['age_normalized'],\n#             row['sex_encoded'],\n#             row['tbp_lv_areaMM2_zscore']\n#         ], dtype=torch.float32)\n        \n#         return img_tensor, meta_vector, row['image_name']\n\n# def prepare_test_metadata(test_csv_path):\n#     df = pd.read_csv(test_csv_path)\n    \n#     # Impute missing values safely\n#     df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median() if 'age_approx' in df.columns else 50.0)\n#     df['sex'] = df['sex'].fillna('unknown') if 'sex' in df.columns else 'unknown'\n#     df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2}).fillna(2)\n\n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = 0.0\n\n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n#     return df\n\n# # 4. INFERENCE EXECUTION\n# print(\"\\n1. Preparing Test Metadata...\")\n# test_csv = f\"{BASE}/test.csv\"\n# test_df = prepare_test_metadata(test_csv)\n\n# test_loader = DataLoader(\n#     MultiModalISICDataset(test_df, T_val),\n#     batch_size=BATCH,\n#     shuffle=False,\n#     num_workers=4,\n#     pin_memory=True\n# )\n\n# print(\"2. Instantiating Model Architecture...\")\n# model = MultiModalGatedClassifier().to(DEVICE)\n\n# # Check potential weight paths\n# possible_weight_paths = ['best_isic_model.pth', 'best_isic_model/data.pkl', 'extracted_model/data.pkl']\n# loaded = False\n\n# for path in possible_weight_paths:\n#     if os.path.exists(path):\n#         print(f\"3. Loading Saved Checkpoint from: '{path}'...\")\n#         try:\n#             model.load_state_dict(torch.load(path, map_location=DEVICE))\n#             loaded = True\n#             print(\"   ✓ Weights loaded successfully!\")\n#             break\n#         except Exception as e:\n#             print(f\"   ⚠️ Could not load from {path}: {e}\")\n\n# if not loaded:\n#     print(\"⚠️ Warning: Saved weight file not found. Running dry-run predictions to verify format...\")\n\n# model.eval()\n\n# print(\"4. Generating Predictions...\")\n# test_probs = []\n# test_names = []\n\n# with torch.no_grad():\n#     for imgs, metas, names in test_loader:\n#         imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#         probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n        \n#         if probs.dim() == 0:\n#             test_probs.append(probs.item())\n#         else:\n#             test_probs.extend(probs.cpu().numpy())\n            \n#         test_names.extend(names)\n\n# submission = pd.DataFrame({\n#     'image_name': test_names,\n#     'target': test_probs\n# })\n\n# submission.to_csv('submission.csv', index=False)\n# print(\"\\n🎉 SUCCESS! 'submission.csv' generated.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-22T04:37:20.355546Z","iopub.execute_input":"2026-07-22T04:37:20.356108Z","iopub.status.idle":"2026-07-22T04:49:16.284115Z","shell.execute_reply.started":"2026-07-22T04:37:20.356079Z","shell.execute_reply":"2026-07-22T04:49:16.282823Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n\n# sub = pd.read_csv('submission.csv')\n# print(\"--- Submission Sanity Check ---\")\n# print(f\"Total Rows      : {len(sub)}\")\n# print(f\"Null Values     : {sub.isnull().sum().sum()}\")\n# print(f\"Probability Range: Min = {sub['target'].min():.4f} | Max = {sub['target'].max():.4f}\")\n# print(\"\\nFirst 5 Rows:\")\n# print(sub.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-22T04:57:02.223327Z","iopub.execute_input":"2026-07-22T04:57:02.223820Z","iopub.status.idle":"2026-07-22T04:57:02.250178Z","shell.execute_reply.started":"2026-07-22T04:57:02.223782Z","shell.execute_reply":"2026-07-22T04:57:02.249398Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms, models\n# from torch.cuda.amp import autocast, GradScaler\n# from sklearn.model_selection import StratifiedGroupKFold\n# from sklearn.metrics import roc_auc_score\n# import warnings\n\n# warnings.filterwarnings('ignore')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 1. CONFIGURATION\n# # ══════════════════════════════════════════════════════════════════════════════\n# BASE = '/kaggle/input/competitions/siim-isic-melanoma-classification'\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# EPOCHS = 3       # Set to 3 for fast completion today\n# BATCH = 32\n# LR = 3e-4\n\n# torch.manual_seed(42)\n# np.random.seed(42)\n\n# print(f\"🚀 Running End-to-End Pipeline on: {DEVICE}\")\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 2. FEATURE ENGINEERING\n# # ══════════════════════════════════════════════════════════════════════════════\n# def engineer_patient_context_features(csv_path, is_test=False):\n#     df = pd.read_csv(csv_path)\n    \n#     df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median() if 'age_approx' in df.columns else 50.0)\n#     df['sex'] = df['sex'].fillna('unknown') if 'sex' in df.columns else 'unknown'\n#     df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2}).fillna(2)\n\n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = 0.0\n\n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n    \n#     if is_test and 'target' not in df.columns:\n#         df['target'] = 0.0\n        \n#     return df\n\n# df_engineered = engineer_patient_context_features(f'{BASE}/train.csv')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 3. MODEL ARCHITECTURE\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SobelEdgeAttention(nn.Module):\n#     def __init__(self, in_channels):\n#         super().__init__()\n#         self.conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1, bias=False)\n#         self.sigmoid = nn.Sigmoid()\n#         kx = torch.tensor([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]]).view(1, 1, 3, 3)\n#         ky = torch.tensor([[-1., -2., -1.], [0., 0., 0.], [1., 2., 1.]]).view(1, 1, 3, 3)\n#         self.register_buffer('kx', kx)\n#         self.register_buffer('ky', ky)\n\n#     def forward(self, x):\n#         avg_proj = x.mean(dim=1, keepdim=True)\n#         grad_x = F.conv2d(avg_proj, self.kx.to(x.device), padding=1)\n#         grad_y = F.conv2d(avg_proj, self.ky.to(x.device), padding=1)\n#         gradient_map = torch.sqrt(grad_x**2 + grad_y**2 + 1e-8)\n#         attention_map = self.sigmoid(self.conv(x) * gradient_map)\n#         return x * attention_map\n\n# class MultiModalGatedClassifier(nn.Module):\n#     def __init__(self, meta_dim=3):\n#         super().__init__()\n#         backbone = models.mobilenet_v3_large(weights=models.MobileNet_V3_Large_Weights.DEFAULT)\n#         self.features = backbone.features\n#         self.egsa = SobelEdgeAttention(in_channels=160)\n#         self.pooling = nn.AdaptiveAvgPool2d(1)\n#         self.visual_projector = nn.Linear(960, 128)\n#         self.meta_projector = nn.Sequential(\n#             nn.Linear(meta_dim, 64),\n#             nn.BatchNorm1d(64),\n#             nn.ReLU()\n#         )\n#         self.gate = nn.Sequential(\n#             nn.Linear(128 + 64, 1),\n#             nn.Sigmoid()\n#         )\n#         self.fc = nn.Linear(128 + 64, 1)\n\n#     def forward(self, img, meta):\n#         for i, layer in enumerate(self.features):\n#             img = layer(img)\n#             if i == 13:\n#                 img = self.egsa(img)\n#         img_features = torch.flatten(self.pooling(img), 1)\n#         v_proj = F.relu(self.visual_projector(img_features))\n#         m_proj = self.meta_projector(meta)\n#         fused = torch.cat([v_proj, m_proj], dim=1)\n#         g_t = self.gate(fused)\n#         return self.fc(fused * g_t)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 4. DATASET & DATALOADERS\n# # ══════════════════════════════════════════════════════════════════════════════\n# class MultiModalISICDataset(Dataset):\n#     def __init__(self, df, transform, is_test=False):\n#         self.df = df\n#         self.transform = transform\n#         self.is_test = is_test\n        \n#     def __len__(self):\n#         return len(self.df)\n        \n#     def __getitem__(self, idx):\n#         row = self.df.iloc[idx]\n#         folder = \"test\" if self.is_test else \"train\"\n#         path = f\"{BASE}/jpeg/{folder}/{row['image_name']}.jpg\"\n#         if not os.path.exists(path):\n#             path = f\"{BASE}/{folder}/{row['image_name']}.jpg\"\n        \n#         try:\n#             img = Image.open(path).convert('RGB')\n#         except Exception:\n#             img = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))\n            \n#         img_tensor = self.transform(img)\n#         meta_vector = torch.tensor([\n#             row['age_normalized'],\n#             row['sex_encoded'],\n#             row['tbp_lv_areaMM2_zscore']\n#         ], dtype=torch.float32)\n        \n#         return img_tensor, meta_vector, row['image_name'] if self.is_test else torch.tensor(row['target'], dtype=torch.float32)\n\n# T_train = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.RandomHorizontalFlip(),\n#     transforms.RandomVerticalFlip(),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# T_val = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# # Split data\n# sgkf = StratifiedGroupKFold(n_splits=5)\n# for train_idx, val_idx in sgkf.split(df_engineered, df_engineered['target'], groups=df_engineered['patient_id']):\n#     break\n\n# train_df = df_engineered.iloc[train_idx].reset_index(drop=True)\n# val_df = df_engineered.iloc[val_idx].reset_index(drop=True)\n\n# train_loader = DataLoader(MultiModalISICDataset(train_df, T_train), batch_size=BATCH, shuffle=True, drop_last=True, num_workers=4, pin_memory=True)\n# val_loader = DataLoader(MultiModalISICDataset(val_df, T_val), batch_size=BATCH, shuffle=False, num_workers=4, pin_memory=True)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 5. TRAINING PHASE\n# # ══════════════════════════════════════════════════════════════════════════════\n# model = MultiModalGatedClassifier().to(DEVICE)\n\n# num_pos = float(train_df['target'].sum())\n# num_neg = float(len(train_df) - num_pos)\n# pos_weight = torch.tensor([num_neg / (num_pos + 1e-8)]).to(DEVICE)\n\n# criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n# optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-4)\n# scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\n# scaler = GradScaler(enabled=(DEVICE.type == 'cuda'))\n\n# print(\"\\n--- PHASE 1: TRAINING MODEL ---\")\n# best_auc = 0.0\n\n# for epoch in range(EPOCHS):\n#     model.train()\n#     epoch_loss = 0\n#     for imgs, metas, labels in train_loader:\n#         imgs, metas, labels = imgs.to(DEVICE).float(), metas.to(DEVICE).float(), labels.to(DEVICE).float()\n#         optimizer.zero_grad()\n        \n#         with autocast(enabled=(DEVICE.type == 'cuda')):\n#             outputs = model(imgs, metas).squeeze(1)\n#             loss = criterion(outputs, labels)\n            \n#         scaler.scale(loss).backward()\n#         scaler.step(optimizer)\n#         scaler.update()\n#         epoch_loss += loss.item()\n        \n#     scheduler.step()\n    \n#     # Evaluate\n#     model.eval()\n#     val_probs, val_labels = [], []\n#     with torch.no_grad():\n#         for imgs, metas, labels in val_loader:\n#             imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#             probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n#             val_probs.extend(probs.cpu().numpy())\n#             val_labels.extend(labels.numpy())\n            \n#     val_auc = roc_auc_score(val_labels, val_probs)\n#     print(f\"Epoch {epoch+1:02d}/{EPOCHS} | Train Loss: {epoch_loss/len(train_loader):.4f} | Val AUC: {val_auc:.4f}\")\n    \n#     if val_auc > best_auc:\n#         best_auc = val_auc\n#         torch.save(model.state_dict(), 'best_isic_model.pth')\n\n# print(f\"\\n✓ Training Complete! Peak Validation AUC: {best_auc:.4f}\")\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 6. INFERENCE PHASE (GENERATING SUBMISSION)\n# # ══════════════════════════════════════════════════════════════════════════════\n# print(\"\\n--- PHASE 2: GENERATING SUBMISSION ---\")\n# test_df = engineer_patient_context_features(f\"{BASE}/test.csv\", is_test=True)\n# test_loader = DataLoader(MultiModalISICDataset(test_df, T_val, is_test=True), batch_size=BATCH, shuffle=False, num_workers=4, pin_memory=True)\n\n# # Load best weights\n# model.load_state_dict(torch.load('best_isic_model.pth', map_location=DEVICE))\n# model.eval()\n\n# test_probs, test_names = [], []\n\n# with torch.no_grad():\n#     for imgs, metas, names in test_loader:\n#         imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#         probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n        \n#         if probs.dim() == 0:\n#             test_probs.append(probs.item())\n#         else:\n#             test_probs.extend(probs.cpu().numpy())\n            \n#         test_names.extend(names)\n\n# submission = pd.DataFrame({\n#     'image_name': test_names,\n#     'target': test_probs\n# })\n\n# submission.to_csv('submission.csv', index=False)\n# print(\"🎉 SUCCESS! Real 'submission.csv' generated and saved.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n\n# sub = pd.read_csv('submission.csv')\n\n# print(\"═\" * 45)\n# print(\"       SUBMISSION FILE SANITY CHECK\")\n# print(\"═\" * 45)\n# print(f\"Total Prediction Rows : {len(sub)}\")\n# print(f\"Null / Missing Values : {sub.isnull().sum().sum()}\")\n# print(f\"Min Probability       : {sub['target'].min():.6f}\")\n# print(f\"Max Probability       : {sub['target'].max():.6f}\")\n# print(f\"Mean Probability      : {sub['target'].mean():.6f}\")\n# print(\"═\" * 45)\n# print(\"\\nFirst 10 Rows:\")\n# print(sub.head(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-22T09:11:52.307564Z","iopub.execute_input":"2026-07-22T09:11:52.308205Z","iopub.status.idle":"2026-07-22T09:11:53.465097Z","shell.execute_reply.started":"2026-07-22T09:11:52.308169Z","shell.execute_reply":"2026-07-22T09:11:53.463579Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms, models\n# from torch.cuda.amp import autocast, GradScaler\n# from sklearn.model_selection import StratifiedGroupKFold\n# from sklearn.metrics import roc_auc_score\n# import warnings\n\n# warnings.filterwarnings('ignore')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # CONFIGURATION\n# # ══════════════════════════════════════════════════════════════════════════════\n# BASE = '/kaggle/input/competitions/siim-isic-melanoma-classification'\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# EPOCHS = 3       # Fast 3 epochs per fold\n# BATCH = 32\n# LR = 3e-4\n# N_FOLDS = 5\n\n# torch.manual_seed(42)\n# np.random.seed(42)\n\n# print(f\"🚀 Starting 5-Fold Training Pipeline on: {DEVICE}\")\n\n# # Feature Engineering\n# def engineer_patient_context_features(csv_path):\n#     df = pd.read_csv(csv_path)\n#     df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median() if 'age_approx' in df.columns else 50.0)\n#     df['sex'] = df['sex'].fillna('unknown') if 'sex' in df.columns else 'unknown'\n#     df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2}).fillna(2)\n\n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = 0.0\n\n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n#     return df\n\n# df_engineered = engineer_patient_context_features(f'{BASE}/train.csv')\n\n# # Model Architecture\n# class SobelEdgeAttention(nn.Module):\n#     def __init__(self, in_channels):\n#         super().__init__()\n#         self.conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1, bias=False)\n#         self.sigmoid = nn.Sigmoid()\n#         kx = torch.tensor([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]]).view(1, 1, 3, 3)\n#         ky = torch.tensor([[-1., -2., -1.], [0., 0., 0.], [1., 2., 1.]]).view(1, 1, 3, 3)\n#         self.register_buffer('kx', kx)\n#         self.register_buffer('ky', ky)\n\n#     def forward(self, x):\n#         avg_proj = x.mean(dim=1, keepdim=True)\n#         grad_x = F.conv2d(avg_proj, self.kx.to(x.device), padding=1)\n#         grad_y = F.conv2d(avg_proj, self.ky.to(x.device), padding=1)\n#         gradient_map = torch.sqrt(grad_x**2 + grad_y**2 + 1e-8)\n#         attention_map = self.sigmoid(self.conv(x) * gradient_map)\n#         return x * attention_map\n\n# class MultiModalGatedClassifier(nn.Module):\n#     def __init__(self, meta_dim=3):\n#         super().__init__()\n#         backbone = models.mobilenet_v3_large(weights=models.MobileNet_V3_Large_Weights.DEFAULT)\n#         self.features = backbone.features\n#         self.egsa = SobelEdgeAttention(in_channels=160)\n#         self.pooling = nn.AdaptiveAvgPool2d(1)\n#         self.visual_projector = nn.Linear(960, 128)\n#         self.meta_projector = nn.Sequential(\n#             nn.Linear(meta_dim, 64),\n#             nn.BatchNorm1d(64),\n#             nn.ReLU()\n#         )\n#         self.gate = nn.Sequential(\n#             nn.Linear(128 + 64, 1),\n#             nn.Sigmoid()\n#         )\n#         self.fc = nn.Linear(128 + 64, 1)\n\n#     def forward(self, img, meta):\n#         for i, layer in enumerate(self.features):\n#             img = layer(img)\n#             if i == 13:\n#                 img = self.egsa(img)\n#         img_features = torch.flatten(self.pooling(img), 1)\n#         v_proj = F.relu(self.visual_projector(img_features))\n#         m_proj = self.meta_projector(meta)\n#         fused = torch.cat([v_proj, m_proj], dim=1)\n#         g_t = self.gate(fused)\n#         return self.fc(fused * g_t)\n\n# class MultiModalISICDataset(Dataset):\n#     def __init__(self, df, transform):\n#         self.df = df\n#         self.transform = transform\n        \n#     def __len__(self):\n#         return len(self.df)\n        \n#     def __getitem__(self, idx):\n#         row = self.df.iloc[idx]\n#         path = f\"{BASE}/jpeg/train/{row['image_name']}.jpg\"\n#         if not os.path.exists(path):\n#             path = f\"{BASE}/train/{row['image_name']}.jpg\"\n        \n#         try:\n#             img = Image.open(path).convert('RGB')\n#         except Exception:\n#             img = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))\n            \n#         img_tensor = self.transform(img)\n#         meta_vector = torch.tensor([\n#             row['age_normalized'],\n#             row['sex_encoded'],\n#             row['tbp_lv_areaMM2_zscore']\n#         ], dtype=torch.float32)\n        \n#         return img_tensor, meta_vector, torch.tensor(row['target'], dtype=torch.float32)\n\n# T_train = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.RandomHorizontalFlip(),\n#     transforms.RandomVerticalFlip(),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# T_val = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 5-FOLD TRAINING LOOP\n# # ══════════════════════════════════════════════════════════════════════════════\n# sgkf = StratifiedGroupKFold(n_splits=N_FOLDS)\n# fold_scores = []\n\n# for fold, (train_idx, val_idx) in enumerate(sgkf.split(df_engineered, df_engineered['target'], groups=df_engineered['patient_id'])):\n#     print(f\"\\n────────────── FOLD {fold+1}/{N_FOLDS} ──────────────\")\n#     train_df = df_engineered.iloc[train_idx].reset_index(drop=True)\n#     val_df = df_engineered.iloc[val_idx].reset_index(drop=True)\n\n#     train_loader = DataLoader(MultiModalISICDataset(train_df, T_train), batch_size=BATCH, shuffle=True, drop_last=True, num_workers=4, pin_memory=True)\n#     val_loader = DataLoader(MultiModalISICDataset(val_df, T_val), batch_size=BATCH, shuffle=False, num_workers=4, pin_memory=True)\n\n#     model = MultiModalGatedClassifier().to(DEVICE)\n    \n#     num_pos = float(train_df['target'].sum())\n#     num_neg = float(len(train_df) - num_pos)\n#     pos_weight = torch.tensor([num_neg / (num_pos + 1e-8)]).to(DEVICE)\n    \n#     criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n#     optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-4)\n#     scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\n#     scaler = GradScaler(enabled=(DEVICE.type == 'cuda'))\n\n#     best_val_auc = 0.0\n#     for epoch in range(EPOCHS):\n#         model.train()\n#         epoch_loss = 0\n#         for imgs, metas, labels in train_loader:\n#             imgs, metas, labels = imgs.to(DEVICE).float(), metas.to(DEVICE).float(), labels.to(DEVICE).float()\n#             optimizer.zero_grad()\n            \n#             with autocast(enabled=(DEVICE.type == 'cuda')):\n#                 outputs = model(imgs, metas).squeeze(1)\n#                 loss = criterion(outputs, labels)\n                \n#             scaler.scale(loss).backward()\n#             scaler.step(optimizer)\n#             scaler.update()\n#             epoch_loss += loss.item()\n            \n#         scheduler.step()\n        \n#         # Validation\n#         model.eval()\n#         val_probs, val_labels = [], []\n#         with torch.no_grad():\n#             for imgs, metas, labels in val_loader:\n#                 imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#                 probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n#                 val_probs.extend(probs.cpu().numpy())\n#                 val_labels.extend(labels.numpy())\n                \n#         val_auc = roc_auc_score(val_labels, val_probs)\n#         print(f\"Epoch {epoch+1:02d}/{EPOCHS} | Train Loss: {epoch_loss/len(train_loader):.4f} | Val AUC: {val_auc:.4f}\")\n        \n#         if val_auc > best_val_auc:\n#             best_val_auc = val_auc\n#             torch.save(model.state_dict(), f'best_isic_model_fold{fold}.pth')\n\n#     fold_scores.append(best_val_auc)\n#     print(f\"✓ Fold {fold+1} Finished | Best Val AUC: {best_val_auc:.4f}\")\n\n# print(\"\\n\" + \"═\"*50)\n# print(f\"🏆 ALL 5 FOLDS COMPLETE! Mean CV AUC: {np.mean(fold_scores):.4f}\")\n# print(\"═\"*50)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-23T19:15:04.802737Z","iopub.execute_input":"2026-07-23T19:15:04.803410Z","iopub.status.idle":"2026-07-23T19:15:04.813928Z","shell.execute_reply.started":"2026-07-23T19:15:04.803363Z","shell.execute_reply":"2026-07-23T19:15:04.813129Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# import torch\n# import torch.nn as nn\n# import torch.nn.functional as F\n# import pandas as pd\n# import numpy as np\n# from PIL import Image\n# from torch.utils.data import Dataset, DataLoader\n# from torchvision import transforms, models\n# import warnings\n\n# warnings.filterwarnings('ignore')\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 1. SETUP & PATHS\n# # ══════════════════════════════════════════════════════════════════════════════\n# BASE = '/kaggle/input/competitions/siim-isic-melanoma-classification'\n# WEIGHTS_DIR = '/kaggle/input/datasets/saad451/5folds'\n# DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# BATCH = 32\n# N_FOLDS = 5\n\n# T_val = transforms.Compose([\n#     transforms.Resize((224, 224)),\n#     transforms.ToTensor(),\n#     transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n# ])\n\n# print(f\"🚀 Using Device: {DEVICE}\")\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 2. MODEL ARCHITECTURE\n# # ══════════════════════════════════════════════════════════════════════════════\n# class SobelEdgeAttention(nn.Module):\n#     def __init__(self, in_channels):\n#         super().__init__()\n#         self.conv = nn.Conv2d(in_channels, 1, kernel_size=3, padding=1, bias=False)\n#         self.sigmoid = nn.Sigmoid()\n#         kx = torch.tensor([[-1., 0., 1.], [-2., 0., 2.], [-1., 0., 1.]]).view(1, 1, 3, 3)\n#         ky = torch.tensor([[-1., -2., -1.], [0., 0., 0.], [1., 2., 1.]]).view(1, 1, 3, 3)\n#         self.register_buffer('kx', kx)\n#         self.register_buffer('ky', ky)\n\n#     def forward(self, x):\n#         avg_proj = x.mean(dim=1, keepdim=True)\n#         grad_x = F.conv2d(avg_proj, self.kx.to(x.device), padding=1)\n#         grad_y = F.conv2d(avg_proj, self.ky.to(x.device), padding=1)\n#         gradient_map = torch.sqrt(grad_x**2 + grad_y**2 + 1e-8)\n#         attention_map = self.sigmoid(self.conv(x) * gradient_map)\n#         return x * attention_map\n\n# class MultiModalGatedClassifier(nn.Module):\n#     def __init__(self, meta_dim=3):\n#         super().__init__()\n#         backbone = models.mobilenet_v3_large(weights=None)\n#         self.features = backbone.features\n#         self.egsa = SobelEdgeAttention(in_channels=160)\n#         self.pooling = nn.AdaptiveAvgPool2d(1)\n#         self.visual_projector = nn.Linear(960, 128)\n#         self.meta_projector = nn.Sequential(\n#             nn.Linear(meta_dim, 64),\n#             nn.BatchNorm1d(64),\n#             nn.ReLU()\n#         )\n#         self.gate = nn.Sequential(\n#             nn.Linear(128 + 64, 1),\n#             nn.Sigmoid()\n#         )\n#         self.fc = nn.Linear(128 + 64, 1)\n\n#     def forward(self, img, meta):\n#         for i, layer in enumerate(self.features):\n#             img = layer(img)\n#             if i == 13:\n#                 img = self.egsa(img)\n#         img_features = torch.flatten(self.pooling(img), 1)\n#         v_proj = F.relu(self.visual_projector(img_features))\n#         m_proj = self.meta_projector(meta)\n#         fused = torch.cat([v_proj, m_proj], dim=1)\n#         g_t = self.gate(fused)\n#         return self.fc(fused * g_t)\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 3. TEST DATASET & FEATURE PREPROCESSING\n# # ══════════════════════════════════════════════════════════════════════════════\n# class MultiModalISICDataset(Dataset):\n#     def __init__(self, df, transform):\n#         self.df = df\n#         self.transform = transform\n        \n#     def __len__(self):\n#         return len(self.df)\n        \n#     def __getitem__(self, idx):\n#         row = self.df.iloc[idx]\n#         path = f\"{BASE}/jpeg/test/{row['image_name']}.jpg\"\n#         if not os.path.exists(path):\n#             path = f\"{BASE}/test/{row['image_name']}.jpg\"\n        \n#         try:\n#             img = Image.open(path).convert('RGB')\n#         except Exception:\n#             img = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))\n            \n#         img_tensor = self.transform(img)\n#         meta_vector = torch.tensor([\n#             row['age_normalized'],\n#             row['sex_encoded'],\n#             row['tbp_lv_areaMM2_zscore']\n#         ], dtype=torch.float32)\n        \n#         return img_tensor, meta_vector, row['image_name']\n\n# def engineer_patient_context_features_test(test_csv_path):\n#     df = pd.read_csv(test_csv_path)\n#     df['age_approx'] = df['age_approx'].fillna(df['age_approx'].median() if 'age_approx' in df.columns else 50.0)\n#     df['sex'] = df['sex'].fillna('unknown') if 'sex' in df.columns else 'unknown'\n#     df['sex_encoded'] = df['sex'].map({'male': 0, 'female': 1, 'unknown': 2}).fillna(2)\n\n#     continuous_features = ['tbp_lv_areaMM2', 'tbp_lv_color_std_mean']\n#     for feat in continuous_features:\n#         if feat in df.columns:\n#             patient_stats = df.groupby('patient_id')[feat].agg(['mean', 'std']).reset_index()\n#             patient_stats.columns = ['patient_id', f'{feat}_mean', f'{feat}_std']\n#             df = df.merge(patient_stats, on='patient_id', how='left')\n#             eps = 1e-8\n#             df[f'{feat}_zscore'] = (df[feat] - df[f'{feat}_mean']) / (df[f'{feat}_std'].fillna(0) + eps)\n#             df.drop(columns=[f'{feat}_mean', f'{feat}_std'], inplace=True)\n#         else:\n#             df[f'{feat}_zscore'] = 0.0\n\n#     df['age_normalized'] = (df['age_approx'] - df['age_approx'].mean()) / (df['age_approx'].std() + 1e-8)\n#     return df\n\n# # ══════════════════════════════════════════════════════════════════════════════\n# # 4. ENSEMBLE INFERENCE PASS\n# # ══════════════════════════════════════════════════════════════════════════════\n# print(\"📄 Processing test metadata...\")\n# test_df = engineer_patient_context_features_test(f\"{BASE}/test.csv\")\n# test_loader = DataLoader(\n#     MultiModalISICDataset(test_df, T_val), \n#     batch_size=BATCH, \n#     shuffle=False, \n#     num_workers=4, \n#     pin_memory=True\n# )\n\n# all_fold_predictions = []\n\n# for fold in range(N_FOLDS):\n#     weight_path = os.path.join(WEIGHTS_DIR, f'best_isic_model_fold{fold}.pth')\n#     print(f\"⚡ Model {fold+1}/{N_FOLDS}: Loading weights from '{weight_path}'...\")\n    \n#     model = MultiModalGatedClassifier().to(DEVICE)\n#     model.load_state_dict(torch.load(weight_path, map_location=DEVICE))\n#     model.eval()\n\n#     fold_probs = []\n#     with torch.no_grad():\n#         for imgs, metas, _ in test_loader:\n#             imgs, metas = imgs.to(DEVICE).float(), metas.to(DEVICE).float()\n#             probs = torch.sigmoid(model(imgs, metas)).squeeze(1)\n            \n#             if probs.dim() == 0:\n#                 fold_probs.append(probs.item())\n#             else:\n#                 fold_probs.extend(probs.cpu().numpy())\n                \n#     all_fold_predictions.append(fold_probs)\n\n# # Average predictions across all 5 models\n# ensemble_predictions = np.mean(all_fold_predictions, axis=0)\n\n# submission = pd.DataFrame({\n#     'image_name': test_df['image_name'],\n#     'target': ensemble_predictions\n# })\n\n# submission.to_csv('submission.csv', index=False)\n\n# print(\"\\n\" + \"═\"*55)\n# print(\"🎉 5-FOLD ENSEMBLE SUBMISSION SUCCESSFULLY GENERATED!\")\n# print(\"═\"*55)\n# print(f\"Total Rows      : {len(submission)}\")\n# print(f\"Null Values     : {submission.isnull().sum().sum()}\")\n# print(f\"Probability Min : {submission['target'].min():.6f}\")\n# print(f\"Probability Max : {submission['target'].max():.6f}\")\n# print(\"\\nFirst 5 Predictions Preview:\")\n# print(submission.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-23T19:44:58.518728Z","iopub.execute_input":"2026-07-23T19:44:58.519256Z","iopub.status.idle":"2026-07-23T20:42:29.358304Z","shell.execute_reply.started":"2026-07-23T19:44:58.519227Z","shell.execute_reply":"2026-07-23T20:42:29.357346Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n# print(os.listdir('/kaggle/input/'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-23T19:41:19.542574Z","iopub.execute_input":"2026-07-23T19:41:19.542956Z","iopub.status.idle":"2026-07-23T19:41:19.548504Z","shell.execute_reply.started":"2026-07-23T19:41:19.542928Z","shell.execute_reply":"2026-07-23T19:41:19.547736Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# weight_path = \"/kaggle/working/best_isic_model_fold0.pth\"\n# # or relative path: \"best_isic_model_fold0.pth\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-23T19:41:59.515521Z","iopub.execute_input":"2026-07-23T19:41:59.516384Z","iopub.status.idle":"2026-07-23T19:41:59.520523Z","shell.execute_reply.started":"2026-07-23T19:41:59.516353Z","shell.execute_reply":"2026-07-23T19:41:59.519886Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import os\n\n# print(\"🔍 Locating your 5 fold files inside /kaggle/input/datasets/...\")\n# found_files = []\n# for root, dirs, files in os.walk('/kaggle/input/datasets'):\n#     for file in sorted(files):\n#         if file.endswith('.pth'):\n#             full_path = os.path.join(root, file)\n#             found_files.append(full_path)\n#             print(f\"  ✓ Found: {full_path}\")\n\n# print(f\"\\nTotal files found: {len(found_files)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-23T19:43:51.150300Z","iopub.execute_input":"2026-07-23T19:43:51.151054Z","iopub.status.idle":"2026-07-23T19:43:51.161946Z","shell.execute_reply.started":"2026-07-23T19:43:51.151018Z","shell.execute_reply":"2026-07-23T19:43:51.160877Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # ==============================================================================\n# # STEP 1: IMPORT LIBRARIES\n# # ==============================================================================\n# import torch\n# import torch.nn as nn\n# import torch.optim as optim\n# from torch.utils.data import DataLoader\n# from torchvision import datasets, models, transforms\n\n# # ==============================================================================\n# # STEP 2: SET UP DEVICE (GPU/CPU)\n# # ==============================================================================\n# device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n# print(f\"Using device: {device}\")\n\n# # ==============================================================================\n# # STEP 3: DEFINE DATA TRANSFORMS (Fixed syntax error here)\n# # ==============================================================================\n# T_train = transforms.Compose([\n#     transforms.RandomResizedCrop(224),\n#     transforms.RandomHorizontalFlip(),\n#     transforms.ToTensor(),\n#     transforms.Normalize(\n#         mean=[0.485, 0.456, 0.406],\n#         std=[0.229, 0.224, 0.225]\n#     )\n# ])\n\n# T_val = transforms.Compose([\n#     transforms.Resize(256),\n#     transforms.CenterCrop(224),\n#     transforms.ToTensor(),\n#     transforms.Normalize(\n#         mean=[0.485, 0.456, 0.406],\n#         std=[0.229, 0.224, 0.225]\n#     )\n# ])\n\n# # ==============================================================================\n# # STEP 4: LOAD DATASETS\n# # ==============================================================================\n# # Apne dataset ka folder path yahan dalo\n# train_dataset = datasets.ImageFolder(root='path/to/data/train', transform=T_train)\n# val_dataset   = datasets.ImageFolder(root='path/to/data/val', transform=T_val)\n\n# # ==============================================================================\n# # STEP 5: CREATE DATALOADERS\n# # ==============================================================================\n# train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2)\n# val_loader   = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=2)\n\n# # ==============================================================================\n# # STEP 6: DEFINE MODEL ARCHITECTURE\n# # ==============================================================================\n# num_classes = len(train_dataset.classes)\n# model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\n# model.fc = nn.Linear(model.fc.in_features, num_classes)\n# model = model.to(device)\n\n# # ==============================================================================\n# # STEP 7: DEFINE LOSS FUNCTION & OPTIMIZER\n# # ==============================================================================\n# criterion = nn.CrossEntropyLoss()\n# optimizer = optim.Adam(model.parameters(), lr=0.0001)\n\n# # ==============================================================================\n# # STEP 8: TRAINING LOOP FUNCTION\n# # ==============================================================================\n# def train_model(model, criterion, optimizer, epochs=10):\n#     for epoch in range(epochs):\n#         # --- Training Phase ---\n#         model.train()\n#         running_loss, correct, total = 0.0, 0, 0\n\n#         for inputs, labels in train_loader:\n#             inputs, labels = inputs.to(device), labels.to(device)\n\n#             optimizer.zero_grad()\n#             outputs = model(inputs)\n#             loss = criterion(outputs, labels)\n#             loss.backward()\n#             optimizer.step()\n\n#             running_loss += loss.item() * inputs.size(0)\n#             _, preds = torch.max(outputs, 1)\n#             correct += torch.sum(preds == labels.data)\n#             total += labels.size(0)\n\n#         epoch_loss = running_loss / total\n#         epoch_acc = correct.double() / total\n\n#         # --- Validation Phase ---\n#         model.eval()\n#         val_loss, val_correct, val_total = 0.0, 0, 0\n\n#         with torch.no_grad():\n#             for inputs, labels in val_loader:\n#                 inputs, labels = inputs.to(device), labels.to(device)\n#                 outputs = model(inputs)\n#                 loss = criterion(outputs, labels)\n\n#                 val_loss += loss.item() * inputs.size(0)\n#                 _, preds = torch.max(outputs, 1)\n#                 val_correct += torch.sum(preds == labels.data)\n#                 val_total += labels.size(0)\n\n#         val_epoch_loss = val_loss / val_total\n#         val_epoch_acc = val_correct.double() / val_total\n\n#         print(f\"Epoch {epoch+1}/{epochs} | \"\n#               f\"Train Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f} | \"\n#               f\"Val Loss: {val_epoch_loss:.4f} Acc: {val_epoch_acc:.4f}\")\n\n# # ==============================================================================\n# # STEP 9: RUN TRAINING & SAVE MODEL\n# # ==============================================================================\n# train_model(model, criterion, optimizer, epochs=10)\n# torch.save(model.state_dict(), 'best_model.pth')\n# print(\"Model saved successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-24T10:09:19.672316Z","iopub.execute_input":"2026-07-24T10:09:19.673434Z","iopub.status.idle":"2026-07-24T10:09:33.017279Z","shell.execute_reply.started":"2026-07-24T10:09:19.673370Z","shell.execute_reply":"2026-07-24T10:09:33.015678Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================================\n# STEP 1: IMPORT LIBRARIES\n# ==============================================================================\nimport os\nimport pandas as pd\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import models, transforms\n\n# ==============================================================================\n# STEP 2: SET UP DEVICE (GPU/CPU)\n# ==============================================================================\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\n\n# ==============================================================================\n# STEP 3: AUTOMATIC DATASET PATH FINDER\n# ==============================================================================\nDATA_DIR = None\nJPEG_DIR = None\n\n# Automatically locate 'train.csv' anywhere inside /kaggle/input\nfor root, dirs, files in os.walk('/kaggle/input'):\n    if 'train.csv' in files:\n        DATA_DIR = root\n        break\n\nif DATA_DIR is None:\n    raise FileNotFoundError(\"Could not locate 'train.csv'. Make sure the competition dataset is attached to this notebook!\")\n\n# Locate the jpeg train images folder\nif os.path.exists(os.path.join(DATA_DIR, 'jpeg', 'train')):\n    JPEG_DIR = os.path.join(DATA_DIR, 'jpeg', 'train')\nelif os.path.exists(os.path.join(DATA_DIR, 'train')):\n    JPEG_DIR = os.path.join(DATA_DIR, 'train')\n\nprint(f\"Found train.csv at: {DATA_DIR}\")\nprint(f\"Found train images at: {JPEG_DIR}\")\n\n# ==============================================================================\n# STEP 4: DEFINE DATA TRANSFORMS\n# ==============================================================================\nT_train = transforms.Compose([\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])\n\nT_val = transforms.Compose([\n    transforms.Resize(256),\n    transforms.CenterCrop(224),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])\n\n# ==============================================================================\n# STEP 5: CUSTOM DATASET & DATA LOADERS\n# ==============================================================================\nclass MelanomaDataset(Dataset):\n    def __init__(self, df, img_dir, transform=None):\n        self.df = df\n        self.img_dir = img_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_name = self.df.iloc[idx]['image_name'] + '.jpg'\n        img_path = os.path.join(self.img_dir, img_name)\n        \n        image = Image.open(img_path).convert('RGB')\n        label = self.df.iloc[idx]['target']\n\n        if self.transform:\n            image = self.transform(image)\n\n        return image, label\n\n# Load CSV\ndf = pd.read_csv(os.path.join(DATA_DIR, \"train.csv\"))\n\n# Train / Validation Split (80% / 20%)\ntrain_df = df.sample(frac=0.8, random_state=42).reset_index(drop=True)\nval_df   = df.drop(train_df.index).reset_index(drop=True)\n\n# Datasets\ntrain_dataset = MelanomaDataset(df=train_df, img_dir=JPEG_DIR, transform=T_train)\nval_dataset   = MelanomaDataset(df=val_df, img_dir=JPEG_DIR, transform=T_val)\n\n# DataLoaders\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=2)\nval_loader   = DataLoader(val_dataset, batch_size=32, shuffle=False, num_workers=2)\n\n# ==============================================================================\n# STEP 6: DEFINE MODEL ARCHITECTURE\n# ==============================================================================\nnum_classes = 2  # Binary classification (0: benign, 1: malignant)\nmodel = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\nmodel.fc = nn.Linear(model.fc.in_features, num_classes)\nmodel = model.to(device)\n\n# ==============================================================================\n# STEP 7: LOSS FUNCTION & OPTIMIZER\n# ==============================================================================\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=0.0001)\n\n# ==============================================================================\n# STEP 8: TRAINING LOOP\n# ==============================================================================\ndef train_model(model, criterion, optimizer, epochs=5):\n    for epoch in range(epochs):\n        # --- Training Phase ---\n        model.train()\n        running_loss, correct, total = 0.0, 0, 0\n\n        for inputs, labels in train_loader:\n            inputs, labels = inputs.to(device), labels.to(device)\n\n            optimizer.zero_grad()\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n\n            running_loss += loss.item() * inputs.size(0)\n            _, preds = torch.max(outputs, 1)\n            correct += torch.sum(preds == labels.data)\n            total += labels.size(0)\n\n        epoch_loss = running_loss / total\n        epoch_acc = correct.double() / total\n\n        # --- Validation Phase ---\n        model.eval()\n        val_loss, val_correct, val_total = 0.0, 0, 0\n\n        with torch.no_grad():\n            for inputs, labels in val_loader:\n                inputs, labels = inputs.to(device), labels.to(device)\n                outputs = model(inputs)\n                loss = criterion(outputs, labels)\n\n                val_loss += loss.item() * inputs.size(0)\n                _, preds = torch.max(outputs, 1)\n                val_correct += torch.sum(preds == labels.data)\n                val_total += labels.size(0)\n\n        val_epoch_loss = val_loss / val_total\n        val_epoch_acc = val_correct.double() / val_total\n\n        print(f\"Epoch {epoch+1}/{epochs} | \"\n              f\"Train Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f} | \"\n              f\"Val Loss: {val_epoch_loss:.4f} Acc: {val_epoch_acc:.4f}\")\n\n# ==============================================================================\n# STEP 9: EXECUTE & SAVE\n# ==============================================================================\ntrain_model(model, criterion, optimizer, epochs=5)\ntorch.save(model.state_dict(), 'melanoma_resnet18.pth')\nprint(\"Model trained and saved successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-24T10:14:01.928381Z","iopub.execute_input":"2026-07-24T10:14:01.928732Z"}},"outputs":[],"execution_count":null}]}