{"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"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# **SIIM**\n\n\nThe Society for Imaging Informatics in Medicine (SIIM) is the leading healthcare professional organization for those interested in the current and future use of informatics in medical imaging. The society's mission is to advance medical imaging informatics across the enterprise through education, research, and innovation in a multi-disciplinary community","metadata":{}},{"cell_type":"markdown","source":"# **ISIC :**\n\nThe International Skin Imaging Collaboration: Melanoma Project is an academia and industry partnership designed to facilitate the application of digital skin imaging to help reduce melanoma mortality. When recognized and treated in its earliest stages, melanoma is readily curable. Digital images of skin lesions can be used to educate professionals and the public in melanoma recognition as well as directly aid in the diagnosis of melanoma through teledermatology, clinical decision support, and automated diagnosis.\n","metadata":{}},{"cell_type":"markdown","source":"# **Skin Cancer :**\n\nSkin cancer is the most prevalent type of cancer. Melanoma, specifically, is responsible for 75% of skin cancer deaths, despite being the least common skin cancer. The American Cancer Society estimates over 100,000 new melanoma cases will be diagnosed in 2020. It's also expected that almost 7,000 people will die from the disease. As with other cancers, early and accurate detection—potentially aided by data science—can make treatment more effective.\n","metadata":{}},{"cell_type":"markdown","source":"# **Problem Statement :**\n\nIn this workshop , you’ll identify melanoma in images of skin lesions and you’ll use images within the same patient and determine which are likely to represent a melanoma. Using patient-level contextual information may help the development of image analysis tools, which could better support clinical dermatologists.\n","metadata":{}},{"cell_type":"code","source":"import torch\nprint(\"PyTorch:\", torch.__version__)\nprint(\"CUDA:\", torch.cuda.is_available())\nprint(\"GPU:\", torch.cuda.get_device_name(0))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-25T14:26:28.639825Z","iopub.execute_input":"2026-06-25T14:26:28.640835Z","iopub.status.idle":"2026-06-25T14:26:28.64627Z","shell.execute_reply.started":"2026-06-25T14:26:28.640792Z","shell.execute_reply":"2026-06-25T14:26:28.645058Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import subprocess\nimport sys\n\n# Install all packages at once before anything else\nsubprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"timm\", \"albumentations\", \"-q\"])\n\nimport torch\nprint(\"PyTorch:\", torch.__version__)\nprint(\"CUDA:\", torch.cuda.is_available())\nprint(\"GPU:\", torch.cuda.get_device_name(0))\nprint(\"All packages ready ✓\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-25T14:26:28.648003Z","iopub.execute_input":"2026-06-25T14:26:28.648477Z","iopub.status.idle":"2026-06-25T14:26:34.552194Z","shell.execute_reply.started":"2026-06-25T14:26:28.648426Z","shell.execute_reply":"2026-06-25T14:26:34.551275Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nimport cv2\nimport matplotlib.pyplot as plt\n\n# Load CSVs\ntrain_df = pd.read_csv(\"/kaggle/input/siim-isic-melanoma-classification/train.csv\")\ntest_df  = pd.read_csv(\"/kaggle/input/siim-isic-melanoma-classification/test.csv\")\n\nprint(\"Train shape:\", train_df.shape)\nprint(\"Test shape: \", test_df.shape)\nprint(\"\\nColumns:\", train_df.columns.tolist())\nprint(\"\\nFirst 5 rows:\")\nprint(train_df.head())\nprint(\"\\nTarget distribution:\")\nprint(train_df[\"target\"].value_counts())\nprint(f\"\\nMelanoma %: {train_df['target'].mean()*100:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-25T14:26:34.553355Z","iopub.execute_input":"2026-06-25T14:26:34.553693Z","iopub.status.idle":"2026-06-25T14:26:35.215806Z","shell.execute_reply.started":"2026-06-25T14:26:34.553658Z","shell.execute_reply":"2026-06-25T14:26:35.214978Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_IMG_DIR = \"/kaggle/input/siim-isic-melanoma-classification/jpeg/train\"\n\nfig, axes = plt.subplots(2, 6, figsize=(20, 8))\nfig.suptitle(\"Melanoma Dataset — Sample Images\", fontsize=14, fontweight='bold')\n\n# 6 melanoma + 6 non-melanoma\nmelanoma     = train_df[train_df[\"target\"] == 1].sample(6, random_state=42)\nnon_melanoma = train_df[train_df[\"target\"] == 0].sample(6, random_state=42)\n\nfor col, (_, row) in enumerate(melanoma.iterrows()):\n    img_path = os.path.join(TRAIN_IMG_DIR, row[\"image_name\"] + \".jpg\")\n    img = cv2.imread(img_path)\n    if img is not None:\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = cv2.resize(img, (224, 224))\n        axes[0][col].imshow(img)\n        axes[0][col].set_title(\"Melanoma ✓\", color='red', fontsize=9)\n    axes[0][col].axis(\"off\")\n\nfor col, (_, row) in enumerate(non_melanoma.iterrows()):\n    img_path = os.path.join(TRAIN_IMG_DIR, row[\"image_name\"] + \".jpg\")\n    img = cv2.imread(img_path)\n    if img is not None:\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = cv2.resize(img, (224, 224))\n        axes[1][col].imshow(img)\n        axes[1][col].set_title(\"Non-Melanoma ✗\", color='green', fontsize=9)\n    axes[1][col].axis(\"off\")\n\nplt.tight_layout()\nplt.savefig(\"sample_images.png\", dpi=150, bbox_inches='tight')\nplt.show()\nprint(\"Saved: sample_images.png ✓\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-25T14:26:35.217535Z","iopub.execute_input":"2026-06-25T14:26:35.21816Z","iopub.status.idle":"2026-06-25T14:26:40.113681Z","shell.execute_reply.started":"2026-06-25T14:26:35.218135Z","shell.execute_reply":"2026-06-25T14:26:40.112947Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import StratifiedKFold\n\n# Dataset is heavily imbalanced (~1.7% melanoma)\n# Use stratified split to maintain ratio\nskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n\ntrain_df[\"fold\"] = -1\nfor fold, (train_idx, val_idx) in enumerate(skf.split(train_df, train_df[\"target\"])):\n    train_df.loc[val_idx, \"fold\"] = fold\n\n# Use fold 0 for train/val\nFOLD = 0\ntrain_data = train_df[train_df[\"fold\"] != FOLD].reset_index(drop=True)\nval_data   = train_df[train_df[\"fold\"] == FOLD].reset_index(drop=True)\n\nprint(f\"Train: {len(train_data)} | Val: {len(val_data)}\")\nprint(f\"Train melanoma: {train_data['target'].sum()} ({train_data['target'].mean()*100:.2f}%)\")\nprint(f\"Val melanoma:   {val_data['target'].sum()} ({val_data['target'].mean()*100:.2f}%)\")\n\n# Class weights to handle imbalance\nneg = (train_data[\"target\"] == 0).sum()\npos = (train_data[\"target\"] == 1).sum()\npos_weight = torch.tensor([neg / pos], dtype=torch.float32)\nprint(f\"\\nPositive weight for loss: {pos_weight.item():.2f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-25T14:26:40.115459Z","iopub.execute_input":"2026-06-25T14:26:40.115738Z","iopub.status.idle":"2026-06-25T14:26:40.907938Z","shell.execute_reply.started":"2026-06-25T14:26:40.115716Z","shell.execute_reply":"2026-06-25T14:26:40.907183Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom torch.utils.data import Dataset, DataLoader\n\nIMG_SIZE = 224\n\ntrain_transform = A.Compose([\n    A.Resize(IMG_SIZE, IMG_SIZE),\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.3),\n    A.RandomRotate90(p=0.3),\n    A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.1, rotate_limit=30, p=0.4),\n    A.RandomBrightnessContrast(p=0.3),\n    A.HueSaturationValue(p=0.3),\n    A.CoarseDropout(max_holes=8, max_height=32, max_width=32, p=0.3),\n    A.Normalize(mean=(0.485, 0.456, 0.406),\n                std=(0.229, 0.224, 0.225)),\n    ToTensorV2()\n])\n\nval_transform = A.Compose([\n    A.Resize(IMG_SIZE, IMG_SIZE),\n    A.Normalize(mean=(0.485, 0.456, 0.406),\n                std=(0.229, 0.224, 0.225)),\n    ToTensorV2()\n])\n\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        row      = self.df.iloc[idx]\n        img_path = os.path.join(self.img_dir, row[\"image_name\"] + \".jpg\")\n        \n        img = cv2.imread(img_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        \n        if self.transform:\n            img = self.transform(image=img)[\"image\"]\n        \n        label = torch.tensor(row[\"target\"], dtype=torch.float32)\n        return img, label\n\n\n# ── Create datasets ──\ntrain_dataset = MelanomaDataset(train_data, TRAIN_IMG_DIR, train_transform)\nval_dataset   = MelanomaDataset(val_data,   TRAIN_IMG_DIR, val_transform)\n\n# Replace the DataLoader lines at the bottom of Cell 5 with these:\n\ntrain_loader = DataLoader(train_dataset, batch_size=16, shuffle=True,\n                          num_workers=2,      # ← 0 instead of 2\n                          pin_memory=False)   # ← False instead of True\n\nval_loader   = DataLoader(val_dataset,   batch_size=16, shuffle=False,\n                          num_workers=2,      # ← 0 instead of 2\n                          pin_memory=False)   # ← False instead of True\n\nprint(f\"Train batches: {len(train_loader)} | Val batches: {len(val_loader)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-25T14:26:40.908812Z","iopub.execute_input":"2026-06-25T14:26:40.909253Z","iopub.status.idle":"2026-06-25T14:26:42.685109Z","shell.execute_reply.started":"2026-06-25T14:26:40.909228Z","shell.execute_reply":"2026-06-25T14:26:42.684266Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torchvision.models as models\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", DEVICE)\n\nclass MelanomaModel(torch.nn.Module):\n    def __init__(self):\n        super().__init__()\n        # Use ResNet50 — already in torchvision, no install needed!\n        self.backbone = models.resnet50(pretrained=True)\n        \n        # Replace final layer for binary classification\n        n_features = self.backbone.fc.in_features\n        self.backbone.fc = torch.nn.Sequential(\n            torch.nn.Linear(n_features, 256),\n            torch.nn.ReLU(),\n            torch.nn.Dropout(0.3),\n            torch.nn.Linear(256, 1)\n        )\n    \n    def forward(self, x):\n        return self.backbone(x)\n\nmodel = MelanomaModel().to(DEVICE)\nprint(f\"Total params     : {sum(p.numel() for p in model.parameters()):,}\")\nprint(f\"Trainable params : {sum(p.numel() for p in model.parameters() if p.requires_grad):,}\")\nprint(\"Model ready ✓\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-25T14:26:42.685937Z","iopub.execute_input":"2026-06-25T14:26:42.686145Z","iopub.status.idle":"2026-06-25T14:26:47.896274Z","shell.execute_reply.started":"2026-06-25T14:26:42.686124Z","shell.execute_reply":"2026-06-25T14:26:47.895301Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import roc_auc_score, accuracy_score\nimport os\n\n# Weighted BCE for class imbalance\ncriterion = torch.nn.BCEWithLogitsLoss(pos_weight=pos_weight.to(DEVICE))\noptimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-5)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=20, eta_min=1e-6)\n\ndef compute_metrics(preds_logits, labels, threshold=0.5):\n    probs  = torch.sigmoid(preds_logits).cpu().numpy()\n    labels = labels.cpu().numpy()\n    preds  = (probs > threshold).astype(int)\n    acc    = accuracy_score(labels, preds)\n    try:\n        auc = roc_auc_score(labels, probs)\n    except:\n        auc = 0.0\n    return acc, auc\n\nprint(\"Loss, optimizer, scheduler ready ✓\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-25T14:26:47.897412Z","iopub.execute_input":"2026-06-25T14:26:47.898003Z","iopub.status.idle":"2026-06-25T14:26:47.906138Z","shell.execute_reply.started":"2026-06-25T14:26:47.897979Z","shell.execute_reply":"2026-06-25T14:26:47.905298Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import time\n\nEPOCHS          = 20\nCHECKPOINT_BEST = \"/kaggle/working/melanoma_best.pth\"\nCHECKPOINT_LAST = \"/kaggle/working/melanoma_last.pth\"\nRESUME_FROM     = None  # ← Change when resuming\n\nhistory = {\"train_loss\": [], \"val_loss\": [],\n           \"train_acc\":  [], \"val_acc\":  [],\n           \"train_auc\":  [], \"val_auc\":  []}\n\nbest_val_auc  = 0.0\nSTART_EPOCH   = 1\n\n# ── Resume if checkpoint exists ──\nif RESUME_FROM and os.path.exists(RESUME_FROM):\n    ckpt = torch.load(RESUME_FROM, map_location=DEVICE)\n    model.load_state_dict(ckpt[\"model_state_dict\"])\n    optimizer.load_state_dict(ckpt[\"optimizer_state_dict\"])\n    scheduler.load_state_dict(ckpt[\"scheduler_state_dict\"])\n    START_EPOCH  = ckpt[\"epoch\"] + 1\n    best_val_auc = ckpt[\"best_val_auc\"]\n    history      = ckpt[\"history\"]\n    print(f\"✓ Resumed from Epoch {ckpt['epoch']} | Best AUC: {best_val_auc:.4f}\")\nelse:\n    print(\"Starting fresh from Epoch 1\")\n\n\ndef train_one_epoch(model, loader, criterion, optimizer):\n    model.train()\n    total_loss, all_preds, all_labels = 0, [], []\n    for imgs, labels in loader:\n        imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)\n        optimizer.zero_grad()\n        logits = model(imgs).squeeze(1)\n        loss   = criterion(logits, labels)\n        loss.backward()\n        optimizer.step()\n        total_loss += loss.item()\n        all_preds.append(logits.detach())\n        all_labels.append(labels.detach())\n    all_preds  = torch.cat(all_preds)\n    all_labels = torch.cat(all_labels)\n    acc, auc   = compute_metrics(all_preds, all_labels)\n    return total_loss / len(loader), acc, auc\n\n\ndef validate(model, loader, criterion):\n    model.eval()\n    total_loss, all_preds, all_labels = 0, [], []\n    with torch.no_grad():\n        for imgs, labels in loader:\n            imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)\n            logits = model(imgs).squeeze(1)\n            loss   = criterion(logits, labels)\n            total_loss += loss.item()\n            all_preds.append(logits)\n            all_labels.append(labels)\n    all_preds  = torch.cat(all_preds)\n    all_labels = torch.cat(all_labels)\n    acc, auc   = compute_metrics(all_preds, all_labels)\n    return total_loss / len(loader), acc, auc\n\n\n# ── Main Loop ──\nprint(f\"\\n{'Epoch':>6} | {'Tr Loss':>8} | {'Tr Acc':>7} | {'Tr AUC':>7} | \"\n      f\"{'Va Loss':>8} | {'Va Acc':>7} | {'Va AUC':>7} | {'Time':>6}\")\nprint(\"-\" * 80)\n\nfor epoch in range(START_EPOCH, START_EPOCH + EPOCHS):\n    t0 = time.time()\n    \n    tr_loss, tr_acc, tr_auc = train_one_epoch(model, train_loader, criterion, optimizer)\n    va_loss, va_acc, va_auc = validate(model, val_loader, criterion)\n    scheduler.step()\n    \n    history[\"train_loss\"].append(tr_loss)\n    history[\"val_loss\"].append(va_loss)\n    history[\"train_acc\"].append(tr_acc)\n    history[\"val_acc\"].append(va_acc)\n    history[\"train_auc\"].append(tr_auc)\n    history[\"val_auc\"].append(va_auc)\n    \n    elapsed = time.time() - t0\n    \n    # Save LAST always\n    torch.save({\n        \"epoch\": epoch,\n        \"model_state_dict\": model.state_dict(),\n        \"optimizer_state_dict\": optimizer.state_dict(),\n        \"scheduler_state_dict\": scheduler.state_dict(),\n        \"best_val_auc\": best_val_auc,\n        \"history\": history\n    }, CHECKPOINT_LAST)\n    \n    # Save BEST on AUC improvement\n    if va_auc > best_val_auc:\n        best_val_auc = va_auc\n        torch.save({\n            \"epoch\": epoch,\n            \"model_state_dict\": model.state_dict(),\n            \"optimizer_state_dict\": optimizer.state_dict(),\n            \"scheduler_state_dict\": scheduler.state_dict(),\n            \"best_val_auc\": best_val_auc,\n            \"history\": history\n        }, CHECKPOINT_BEST)\n        marker = \" ✓ BEST SAVED\"\n    else:\n        marker = \"\"\n    \n    print(f\"{epoch:>6} | {tr_loss:>8.4f} | {tr_acc:>7.4f} | {tr_auc:>7.4f} | \"\n          f\"{va_loss:>8.4f} | {va_acc:>7.4f} | {va_auc:>7.4f} | {elapsed:>5.1f}s{marker}\")\n\nprint(f\"\\nBest Val AUC: {best_val_auc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-25T14:26:47.907348Z","iopub.execute_input":"2026-06-25T14:26:47.907677Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, axes = plt.subplots(1, 3, figsize=(18, 5))\nfig.suptitle(\"EfficientNetB3 — Melanoma Classification Training History\",\n             fontsize=13, fontweight='bold')\n\nep = range(1, len(history[\"train_loss\"]) + 1)\n\naxes[0].plot(ep, history[\"train_loss\"], label=\"Train\", color=\"#e74c3c\")\naxes[0].plot(ep, history[\"val_loss\"],   label=\"Val\",   color=\"#3498db\")\naxes[0].set_title(\"Loss\")\naxes[0].set_xlabel(\"Epoch\")\naxes[0].legend()\naxes[0].grid(True, alpha=0.3)\n\naxes[1].plot(ep, history[\"train_acc\"], label=\"Train\", color=\"#e74c3c\")\naxes[1].plot(ep, history[\"val_acc\"],   label=\"Val\",   color=\"#3498db\")\naxes[1].set_title(\"Accuracy\")\naxes[1].set_xlabel(\"Epoch\")\naxes[1].legend()\naxes[1].grid(True, alpha=0.3)\n\naxes[2].plot(ep, history[\"train_auc\"], label=\"Train\", color=\"#e74c3c\")\naxes[2].plot(ep, history[\"val_auc\"],   label=\"Val\",   color=\"#3498db\")\naxes[2].set_title(\"AUC-ROC\")\naxes[2].set_xlabel(\"Epoch\")\naxes[2].legend()\naxes[2].grid(True, alpha=0.3)\n\nplt.tight_layout()\nplt.savefig(\"training_curves.png\", dpi=150, bbox_inches='tight')\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import classification_report, confusion_matrix\nimport seaborn as sns\n\n# Load best model\nckpt = torch.load(CHECKPOINT_BEST, map_location=DEVICE)\nmodel.load_state_dict(ckpt[\"model_state_dict\"])\nmodel.eval()\nprint(f\"Loaded best model — Epoch {ckpt['epoch']} | AUC: {ckpt['best_val_auc']:.4f}\")\n\n# Get all val predictions\nall_probs, all_labels = [], []\nwith torch.no_grad():\n    for imgs, labels in val_loader:\n        imgs   = imgs.to(DEVICE)\n        logits = model(imgs).squeeze(1)\n        probs  = torch.sigmoid(logits).cpu().numpy()\n        all_probs.extend(probs)\n        all_labels.extend(labels.numpy())\n\nall_probs  = np.array(all_probs)\nall_labels = np.array(all_labels)\nall_preds  = (all_probs > 0.5).astype(int)\n\n# ── Confusion Matrix ──\ncm = confusion_matrix(all_labels, all_preds)\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n            xticklabels=[\"Non-Melanoma\", \"Melanoma\"],\n            yticklabels=[\"Non-Melanoma\", \"Melanoma\"], ax=axes[0])\naxes[0].set_title(\"Confusion Matrix\", fontweight='bold')\naxes[0].set_ylabel(\"Actual\")\naxes[0].set_xlabel(\"Predicted\")\n\n# ── ROC Curve ──\nfrom sklearn.metrics import roc_curve\nfpr, tpr, _ = roc_curve(all_labels, all_probs)\nauc_score   = roc_auc_score(all_labels, all_probs)\naxes[1].plot(fpr, tpr, color=\"#e74c3c\", lw=2, label=f\"AUC = {auc_score:.4f}\")\naxes[1].plot([0,1],[0,1], \"k--\", lw=1)\naxes[1].set_title(\"ROC Curve\", fontweight='bold')\naxes[1].set_xlabel(\"False Positive Rate\")\naxes[1].set_ylabel(\"True Positive Rate\")\naxes[1].legend()\naxes[1].grid(True, alpha=0.3)\n\nplt.tight_layout()\nplt.savefig(\"evaluation.png\", dpi=150, bbox_inches='tight')\nplt.show()\n\n# ── Final Report ──\nprint(\"=\" * 55)\nprint(\"   MELANOMA CLASSIFICATION — FINAL REPORT\")\nprint(\"=\" * 55)\nprint(f\"  Architecture : EfficientNetB3 (pretrained ImageNet)\")\nprint(f\"  Input size   : {IMG_SIZE}x{IMG_SIZE}\")\nprint(f\"  Loss         : Weighted BCEWithLogitsLoss\")\nprint(f\"  Optimizer    : AdamW (lr=1e-4)\")\nprint(f\"  Epochs       : {len(history['train_loss'])}\")\nprint(\"-\" * 55)\nprint(f\"  Val AUC      : {auc_score:.4f}\")\nprint(f\"  Val Accuracy : {accuracy_score(all_labels, all_preds):.4f}\")\nprint(\"-\" * 55)\nprint(\"\\nClassification Report:\")\nprint(classification_report(all_labels, all_preds,\n      target_names=[\"Non-Melanoma\", \"Melanoma\"]))\nprint(\"=\" * 55)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}