{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":20270,"databundleVersionId":1222630,"sourceType":"competition"}],"dockerImageVersionId":31011,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport pandas as pd\nfrom PIL import Image\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\nimport torch\nimport numpy as np\nimport timm\nimport os\nfrom PIL import Image, ImageEnhance\nimport matplotlib.pyplot as plt\nimport torchvision.transforms.functional as TF\nimport torch\nimport torch.nn as nn\nimport torchvision.models as tm\nfrom torch.optim import AdamW\nfrom torch.utils.data import random_split\nfrom sklearn.metrics import roc_auc_score\nfrom tqdm.auto import tqdm\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-05T12:49:10.885612Z","iopub.execute_input":"2025-05-05T12:49:10.8859Z","iopub.status.idle":"2025-05-05T12:49:10.901845Z","shell.execute_reply.started":"2025-05-05T12:49:10.885876Z","shell.execute_reply":"2025-05-05T12:49:10.901148Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_ROOT = \"/kaggle/input/siim-isic-melanoma-classification/jpeg\"\nTRAIN_CSV = \"/kaggle/input/siim-isic-melanoma-classification/train.csv\"\nTEST_CSV  = \"/kaggle/input/siim-isic-melanoma-classification/test.csv\"\n\n# 2. Load CSVs\ntrain_df = pd.read_csv(TRAIN_CSV)\ntest_df  = pd.read_csv(TEST_CSV)\n\n# 3. Drop unwanted columns\ntrain_df = train_df.drop(columns=[\"patient_id\", \"diagnosis\", \"benign_malignant\"])\ntest_df  = test_df .drop(columns=[\"patient_id\"])\n\n# 4. Build full image paths\ntrain_df[\"image_path\"] = train_df[\"image_name\"].apply(\n    lambda fn: os.path.join(IMG_ROOT, \"train\", fn + \".jpg\")\n)\ntest_df[\"image_path\"]  = test_df [\"image_name\"].apply(\n    lambda fn: os.path.join(IMG_ROOT, \"test\", fn  + \".jpg\")\n)\ncat_cols = [\n    c for c in train_df.select_dtypes(include=[\"object\",\"category\"]).columns\n    if c not in (\"image_name\",\"image_path\",\"target\")\n]\n\n# Apply get_dummies to each DataFrame\ntrain_ohe = pd.get_dummies(train_df, columns=cat_cols, drop_first=False)\ntest_ohe  = pd.get_dummies(test_df,  columns=cat_cols, drop_first=False)\n\n# Align train/test so they have same set of columns (missing filled with 0)\ntrain_ohe, test_ohe = train_ohe.align(test_ohe, join=\"left\", axis=1, fill_value=0)\n\n# Re-attach target\ntrain_ohe[\"target\"] = train_df[\"target\"].values\n\n\n# 6) Transforms for images\nimage_transform = T.Compose([\n    T.Resize((224,224)),\n    T.RandomHorizontalFlip(),\n    T.ToTensor(),\n    T.Normalize(mean=[0.485,0.456,0.406],\n                std =[0.229,0.224,0.225]),\n])\n\n# 7) Dataset definition\nfrom PIL import ImageEnhance\n\nclass MelanomaDataset(Dataset):\n    def __init__(self, df, is_train=True, transform=None,\n                 zoom_factor=0.25, contrast_factor=1.5):\n        self.df             = df.reset_index(drop=True)\n        self.is_train       = is_train\n        self.transform      = transform\n        self.zoom_factor    = zoom_factor\n        self.contrast_factor = contrast_factor\n\n        drop_cols = [\"image_name\", \"image_path\"]\n        if is_train: drop_cols += [\"target\"]\n        numeric_df = self.df.drop(columns=drop_cols)\n        self.tab_cols = numeric_df.columns.tolist()\n        self.tab_data = numeric_df.values.astype(np.float32)\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.loc[idx]\n        # 1) Load PIL image\n        img = Image.open(row.image_path).convert(\"RGB\")\n\n        # 2) Enhance contrast\n        enhancer = ImageEnhance.Contrast(img)\n        img = enhancer.enhance(self.contrast_factor)\n\n        # 3) Center-zoom\n        w, h = img.size\n        keep = 1.0 - self.zoom_factor\n        new_w, new_h = int(w * keep), int(h * keep)\n        left = (w - new_w)//2; top = (h - new_h)//2\n        img = img.crop((left, top, left+new_w, top+new_h)).resize((w,h), Image.LANCZOS)\n\n        # 4) Usual transforms (resize, flip, normalize...)\n        if self.transform:\n            img = self.transform(img)\n\n        # 5) Tabular\n        tab = torch.from_numpy(self.tab_data[idx])\n\n        if self.is_train:\n            target = torch.tensor(row.target, dtype=torch.float32)\n            return img, tab, target\n        else:\n            return img, tab, row.image_name\n\n\n# 8) Instantiate datasets & loaders\ntrain_ds = MelanomaDataset(\n    train_ohe, is_train=True,\n    transform=image_transform,\n    zoom_factor=0.25,\n    contrast_factor=1.5\n)\ntest_ds = MelanomaDataset(\n    test_ohe,  is_train=False,\n    transform=image_transform,\n    zoom_factor=0.25,\n    contrast_factor=1.5\n)\n\ntrain_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=2)\ntest_loader  = DataLoader(test_ds,  batch_size=32, shuffle=False, num_workers=2)\n\n# 9) Sanity check\nimgs, tabs, targs = next(iter(train_loader))\nprint(\"Images:\", imgs.shape)        # (B,3,224,224)\nprint(\"Tabular:\", tabs.shape)       # (B, num_tab_features_with_ohe)\nprint(\"Targets:\", targs.shape)      # (B,)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-05T12:48:31.547452Z","iopub.execute_input":"2025-05-05T12:48:31.548042Z","iopub.status.idle":"2025-05-05T12:49:09.384966Z","shell.execute_reply.started":"2025-05-05T12:48:31.548022Z","shell.execute_reply":"2025-05-05T12:49:09.383643Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MultiModalSENet(nn.Module):\n    def __init__(self, num_tab_feats, pretrained=True, freeze_cnn=False, fusion_hidden=128):\n        super().__init__()\n        # — Image branch: SE-ResNeXt50 32×4d, feature-only\n        # (returns a (B,2048) vector per image)\n        self.cnn = timm.create_model(\n            'seresnext50_32x4d',\n            pretrained=pretrained,\n            num_classes=0,        # strip off classification head\n            global_pool='avg'     # returns the pooled feature\n        )\n        if freeze_cnn:\n            for p in self.cnn.parameters():\n                p.requires_grad = False\n\n        # timm sets this property to the feature size\n        self.cnn_out = self.cnn.num_features  # should be 2048\n\n        # — Tabular branch\n        self.tab_mlp = nn.Sequential(\n            nn.Linear(num_tab_feats, 64),\n            nn.ReLU(inplace=True),\n            nn.BatchNorm1d(64),\n            nn.Linear(64, 64),\n            nn.ReLU(inplace=True),\n        )\n\n        # — Fusion head\n        self.fusion = nn.Sequential(\n            nn.Linear(self.cnn_out + 64, fusion_hidden),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.2),\n            nn.Linear(fusion_hidden, 1),   # single logit\n        )\n\n    def forward(self, img, tab):\n        x1 = self.cnn(img)           # (B, 2048)\n        x2 = self.tab_mlp(tab)       # (B,   64)\n        x  = torch.cat([x1, x2], dim=1)\n        return self.fusion(x).squeeze(1)  # (B,) logits","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-05T12:49:09.386804Z","iopub.execute_input":"2025-05-05T12:49:09.387174Z","iopub.status.idle":"2025-05-05T12:49:09.401441Z","shell.execute_reply.started":"2025-05-05T12:49:09.387136Z","shell.execute_reply":"2025-05-05T12:49:09.400572Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"full_ds = MelanomaDataset(train_ohe, is_train=True, transform=image_transform)\n\n# 2) Compute sizes & split\nN = len(full_ds)\nn_val   = int(0.1 * N)\nn_train = N - n_val\n\ntrain_ds, val_ds = random_split(\n    full_ds,\n    [n_train, n_val],\n    generator=torch.Generator().manual_seed(42)\n)\n\n# 3) Now you can safely grab num_tabular_feats from full_ds\nnum_tabular_feats = len(full_ds.tab_cols)\n\n# 4) Build your DataLoaders\ntrain_loader = DataLoader(train_ds, batch_size=32, shuffle=True,  num_workers=2)\nval_loader   = DataLoader(val_ds,   batch_size=32, shuffle=False, num_workers=2)\n\n# 5) Instantiate model with the correct feature count\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = MultiModalSENet(\n    num_tab_feats=num_tabular_feats,\n    pretrained=True,\n    freeze_cnn=False\n).to(device)\n\n\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = AdamW(model.parameters(), lr=1e-4, weight_decay=1e-5)\n\n# ——————————————————————————————\n# 4) Train & validate loops\n# ——————————————————————————————\ndef train_epoch(dl, model, criterion, optimizer, device):\n    model.train()\n    running_loss = 0.0\n    running_correct = 0\n    running_total = 0\n\n    loop = tqdm(dl, desc=\"Train\", leave=False)\n    for imgs, tabs, targets in loop:\n        imgs, tabs, targets = imgs.to(device), tabs.to(device), targets.to(device)\n        print(imgs.device, tabs.device, targets.device)\n        optimizer.zero_grad()\n\n        logits = model(imgs, tabs)\n        loss = criterion(logits, targets)\n        loss.backward()\n        optimizer.step()\n\n        # accumulate loss\n        bs = imgs.size(0)\n        running_loss += loss.item() * bs\n\n        # compute accuracy\n        probs = torch.sigmoid(logits)\n        preds = (probs > 0.5).float()\n        running_correct += (preds == targets).sum().item()\n        running_total   += bs\n\n        # update tqdm bar\n        loop.set_postfix({\n            \"loss\": f\"{running_loss / running_total:.4f}\",\n            \"acc\":  f\"{running_correct / running_total:.4f}\",\n        })\n\n    epoch_loss = running_loss / running_total\n    epoch_acc  = running_correct / running_total\n    return epoch_loss, epoch_acc\n\n# ——————————————————————————————\n# 4b) Validation loop with tqdm + accuracy\n# ——————————————————————————————\ndef validate(dl, model, criterion, device):\n    model.eval()\n    running_loss = 0.0\n    running_correct = 0\n    running_total = 0\n    all_logits = []\n    all_targets = []\n\n    loop = tqdm(dl, desc=\"Valid\", leave=False)\n    with torch.no_grad():\n        for imgs, tabs, targets in loop:\n            imgs, tabs, targets = imgs.to(device), tabs.to(device), targets.to(device)\n\n            logits = model(imgs, tabs)\n            loss = criterion(logits, targets)\n\n            # accumulate loss\n            bs = imgs.size(0)\n            running_loss += loss.item() * bs\n\n            # accuracy\n            probs = torch.sigmoid(logits)\n            preds = (probs > 0.5).float()\n            running_correct += (preds == targets).sum().item()\n            running_total   += bs\n\n            all_logits.append(logits.cpu())\n            all_targets.append(targets.cpu())\n\n            # update tqdm bar\n            loop.set_postfix({\n                \"loss\": f\"{running_loss / running_total:.4f}\",\n                \"acc\":  f\"{running_correct / running_total:.4f}\",\n            })\n\n    epoch_loss = running_loss / running_total\n    epoch_acc  = running_correct / running_total\n    # compute AUC once at epoch end\n    probs_all = torch.sigmoid(torch.cat(all_logits)).numpy()\n    targs_all = torch.cat(all_targets).numpy()\n    epoch_auc  = roc_auc_score(targs_all, probs_all)\n\n    return epoch_loss, epoch_acc, epoch_auc\n\n# ——————————————————————————————\n# 5) Run training\n# ——————————————————————————————","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-05T12:49:09.402784Z","iopub.execute_input":"2025-05-05T12:49:09.403253Z","iopub.status.idle":"2025-05-05T12:49:10.855193Z","shell.execute_reply.started":"2025-05-05T12:49:09.403229Z","shell.execute_reply":"2025-05-05T12:49:10.854587Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!nvidia-smi\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"imgs, tabs, targets = next(iter(train_loader))\nimgs, tabs, targets = imgs.to(device), tabs.to(device), targets.to(device)\nlogits = model(imgs, tabs)\nprint(\"Logits on:\", logits.device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-05T12:49:42.204352Z","iopub.execute_input":"2025-05-05T12:49:42.204646Z","iopub.status.idle":"2025-05-05T12:50:20.897297Z","shell.execute_reply.started":"2025-05-05T12:49:42.204623Z","shell.execute_reply":"2025-05-05T12:50:20.896351Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_auc = 0.0\nfor epoch in range(1, 4):\n    print(f\"\\nEpoch {epoch}\")\n    train_loss, train_acc = train_epoch(train_loader, model, criterion, optimizer, device)\n    val_loss, val_acc, val_auc = validate(val_loader, model, criterion, device)\n    print(f\"Train ▶ loss: {train_loss:.4f}, acc: {train_acc:.4f}\")\n    print(f\"Valid ▶ loss: {val_loss:.4f}, acc: {val_acc:.4f}, AUC: {val_auc:.4f}\")\n    # if val_auc > best_auc:.io.....v\n    best_auc = val_auc\n    torch.save(model.state_dict(), \"best_model1.pth\")\n    print(\"  ↳ new best model saved\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-05T12:50:41.938589Z","iopub.execute_input":"2025-05-05T12:50:41.938962Z","iopub.status.idle":"2025-05-05T12:53:40.121401Z","shell.execute_reply.started":"2025-05-05T12:50:41.938935Z","shell.execute_reply":"2025-05-05T12:53:40.119899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load(\"best_model1.pth\"))\nmodel.eval()\n\nimport pandas as pd\nsubmission = pd.read_csv(\"/kaggle/input/siim-isic-melanoma-classification/test\")\nall_preds = []\n\nwith torch.no_grad():\n    for imgs, tabs, img_ids in test_loader:\n        imgs, tabs = imgs.to(device), tabs.to(device)\n        logits = model(imgs, tabs)\n        probs  = torch.sigmoid(logits).cpu().numpy()\n        all_preds.extend(probs.tolist())\n\nsubmission['target'] = all_preds\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"Wrote submission.csv\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def enhance_and_zoom(img: Image.Image, zoom_factor: float = 0.25, contrast_factor: float = 1.5):\n    \"\"\"\n    img             : PIL Image\n    zoom_factor     : fraction to zoom in (0.25 means crop 75% center)\n    contrast_factor : >1.0 increases contrast; <1.0 decreases\n    \"\"\"\n    # 1. Enhance contrast\n    enhancer = ImageEnhance.Contrast(img)\n    img_contrast = enhancer.enhance(contrast_factor)\n    \n    # 2. Center zoom (crop and resize back)\n    w, h = img_contrast.size\n    # fraction of size to KEEP\n    keep_frac = 1.0 - zoom_factor\n    new_w, new_h = int(w * keep_frac), int(h * keep_frac)\n    left = (w - new_w) // 2\n    top  = (h - new_h) // 2\n    right = left + new_w\n    bottom = top + new_h\n    img_cropped = img_contrast.crop((left, top, right, bottom))\n    img_zoomed = img_cropped.resize((w, h), resample=Image.LANCZOS)\n    \n    return img_zoomed\ndef compare_images(orig: Image.Image, proc: Image.Image, figsize=(10,5)):\n    plt.figure(figsize=figsize)\n    # Before\n    plt.subplot(1,2,1)\n    plt.imshow(orig)\n    plt.title('Original')\n    plt.axis('off')\n    # After\n    plt.subplot(1,2,2)\n    plt.imshow(proc)\n    plt.title('Enhanced + Zoomed')\n    plt.axis('off')\n    plt.show()\n# ——— Usage ———\n# Replace this with the path to any .jpg in your train folder\nIMG_PATH = \"/kaggle/input/siim-isic-melanoma-classification/jpeg/train/ISIC_0084270.jpg\"\n# Load\norig_img = Image.open(IMG_PATH).convert(\"RGB\")\n# Process\nproc_img = enhance_and_zoom(orig_img, zoom_factor=0.25, contrast_factor=1.5)\n# Compare\ncompare_images(orig_img, proc_img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-04T15:17:30.665836Z","iopub.execute_input":"2025-05-04T15:17:30.666625Z","iopub.status.idle":"2025-05-04T15:17:31.334997Z","shell.execute_reply.started":"2025-05-04T15:17:30.666601Z","shell.execute_reply":"2025-05-04T15:17:31.334111Z"}},"outputs":[],"execution_count":null}]}