{"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":"code","source":"# backbone_vgg16.py\n# ================================================================\n# EKSPLORASI BACKBONE: VGG16\n# Dikembangkan dari CNN_Baseline.py (Salsabila Amalia Harjanto)\n#\n# Perubahan dari asli:\n#   - Backbone  : EfficientNet-B0 → VGG16 pretrained (torchvision)\n#   - IMG_SIZE  : 512 → 224\n#   - Optimizer : Adam → AdamW\n#   - Scheduler : ReduceLROnPlateau → CosineAnnealingLR\n#   - Epochs    : 15 → 20\n#\n# Yang TIDAK berubah (identik dengan asli):\n#   - Preprocessing: crop_black + Green Channel + CLAHE\n#   - 5-Fold Stratified CV (seed=42)\n#   - Class weights balanced (CrossEntropyLoss)\n#   - Dual checkpoint: best_byvalloss & best_bymild\n#   - Format output JSON (kompatibel dengan CNN_GNN.py & CNN-GNN-MTL.py)\n#   - Metrik utama: Mild Recall (label=1)\n# ================================================================\n\nimport os, time, json, random, glob\nfrom pathlib import Path\nimport numpy as np, pandas as pd\nimport cv2\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import (\n    recall_score, confusion_matrix, ConfusionMatrixDisplay,\n    classification_report, roc_auc_score\n)\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom sklearn.preprocessing import label_binarize\nimport torch, torch.nn as nn, torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import models\nimport timm  # dipertahankan untuk kompatibilitas\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport matplotlib.pyplot as plt\n\n# ================================================================\n# CONFIG\n# ================================================================\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)\nif torch.cuda.is_available(): torch.cuda.manual_seed_all(SEED)\ntorch.backends.cudnn.deterministic = True\ntorch.backends.cudnn.benchmark = False\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Device : {DEVICE}\")\nprint(f\"Backbone: VGG16\")\n\n# --- Paths (Kaggle format) ---\nDATA_DIR    = Path(\"/kaggle/input/competitions/aptos2019-blindness-detection\")\nCSV_PATH    = DATA_DIR / \"train.csv\"\nIMG_DIR     = DATA_DIR / \"train_images\"\n\nBACKBONE_NAME = \"vgg16\"\nOUT_DIR      = Path(f\"./backbone_{BACKBONE_NAME}\")\nPREPROC_DIR  = OUT_DIR / \"preprocessed\"\nMODEL_DIR    = OUT_DIR / \"models\"\nfor d in [OUT_DIR, PREPROC_DIR, MODEL_DIR]:\n    d.mkdir(parents=True, exist_ok=True)\n\n# --- Hyperparameters ---\nIMG_SIZE    = 224       # VGG16 efisien di 224; Swin-Tiny native 224\nNUM_CLASSES = 5\nEPOCHS      = 20\nBATCH_SIZE  = 16\nLR          = 1e-4\nWEIGHT_DECAY = 1e-4\nPATIENCE    = 5\n\nCLASS_NAMES = ['No DR', 'Mild', 'Moderate', 'Severe', 'Proliferative DR']\nMILD_IDX    = 1  # indeks kelas Mild — METRIK UTAMA\n\nprint(f\"IMG_SIZE    : {IMG_SIZE}\")\nprint(f\"EPOCHS      : {EPOCHS}\")\nprint(f\"BATCH_SIZE  : {BATCH_SIZE}\")\nprint(f\"LR          : {LR}\")\n\n# ================================================================\n# PREPROCESSING — identik dengan proyek asli (CNN_Baseline.py)\n# ================================================================\ndef crop_black(img, tol=7):\n    \"\"\"Crop black border. Identik dengan fungsi asli.\"\"\"\n    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n    mask = gray > tol\n    if mask.any():\n        coords = np.argwhere(mask)\n        y0, x0 = coords.min(axis=0)\n        y1, x1 = coords.max(axis=0) + 1\n        img = img[y0:y1, x0:x1]\n    return img\n\ndef preprocess_green_clahe(img_bgr):\n    \"\"\"\n    Green Channel + CLAHE + crop black.\n    Identik dengan fungsi asli di CNN_Baseline.py dan app.py.\n    Output: BGR image ukuran (IMG_SIZE, IMG_SIZE).\n    \"\"\"\n    img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)\n    img_rgb = crop_black(img_rgb)\n    if img_rgb is None or img_rgb.size == 0:\n        img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)\n    g = img_rgb[:, :, 1]\n    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))\n    g2 = clahe.apply(g)\n    merged = np.stack([g2, g2, g2], axis=2)\n    out = cv2.resize(merged.astype(np.uint8), (IMG_SIZE, IMG_SIZE))\n    return cv2.cvtColor(out, cv2.COLOR_RGB2BGR)\n\n# ================================================================\n# LOAD & PREPROCESS DATASET\n# ================================================================\ndf = pd.read_csv(CSV_PATH)\ndf['id_code'] = df['id_code'].astype(str)\n\nprint(f\"\\nTotal data : {len(df)}\")\nprint(\"Distribusi kelas:\")\nfor cls, cnt in df['diagnosis'].value_counts().sort_index().items():\n    marker = \" ← Mild (target)\" if cls == MILD_IDX else \"\"\n    print(f\"  Class {cls} ({CLASS_NAMES[cls]}): {cnt} ({cnt/len(df)*100:.1f}%){marker}\")\n\nprint(\"\\nPreprocessing images (Green+CLAHE+autocrop)...\")\nskipped = 0\nfor _, row in df.iterrows():\n    fid = str(row.id_code)\n    src = IMG_DIR / f\"{fid}.png\"\n    dst = PREPROC_DIR / f\"{fid}.png\"\n    if dst.exists():\n        continue\n    if not src.exists():\n        skipped += 1\n        continue\n    try:\n        img = cv2.imread(str(src))\n        proc = preprocess_green_clahe(img)\n        cv2.imwrite(str(dst), proc)\n    except Exception as e:\n        print(f\"  Preproc error {fid}: {e}\")\n        skipped += 1\n\nprint(f\"Preprocessing done. Skipped: {skipped}\")\n\n# ================================================================\n# AUGMENTATIONS — sama dengan asli\n# ================================================================\ntrain_tf = A.Compose([\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.5),\n    A.Rotate(limit=15, p=0.5),\n    A.RandomBrightnessContrast(0.2, 0.2, p=0.5),\n    A.ShiftScaleRotate(0.02, 0.08, 0.0, p=0.4),\n    A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n    ToTensorV2()\n])\n\nval_tf = A.Compose([\n    A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n    ToTensorV2()\n])\n\n# ================================================================\n# DATASET — sama dengan asli\n# ================================================================\nclass FundusDS(Dataset):\n    def __init__(self, ids, labels, transform):\n        self.ids       = ids\n        self.labels    = labels\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.ids)\n\n    def __getitem__(self, idx):\n        fid = self.ids[idx]\n        img_path = PREPROC_DIR / f\"{fid}.png\"\n        img = cv2.imread(str(img_path))\n        if img is None:\n            # fallback: baca asli lalu preprocess on-the-fly\n            img = cv2.imread(str(IMG_DIR / f\"{fid}.png\"))\n            if img is None:\n                img = np.zeros((IMG_SIZE, IMG_SIZE, 3), dtype=np.uint8)\n            else:\n                img = preprocess_green_clahe(img)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = self.transform(image=img)['image']\n        return img, int(self.labels[idx])\n\n# ================================================================\n# MODEL — VGG16\n# ================================================================\ndef build_vgg16(num_classes=NUM_CLASSES, dropout=0.5):\n    \"\"\"\n    VGG16 pretrained ImageNet.\n    Hanya lapisan klasifikasi terakhir (Linear) yang diganti\n    untuk menyesuaikan num_classes=5.\n    \"\"\"\n    model = models.vgg16(weights=models.VGG16_Weights.IMAGENET1K_V1)\n    # Ganti head terakhir: 4096 → num_classes\n    model.classifier[-1] = nn.Linear(4096, num_classes)\n    # Tambah dropout lebih agresif untuk regularisasi VGG yang besar\n    model.classifier[2] = nn.Dropout(dropout)\n    model.classifier[5] = nn.Dropout(dropout)\n    return model\n\ntotal_params = sum(p.numel() for p in build_vgg16().parameters()) / 1e6\nprint(f\"\\nVGG16 total params: {total_params:.1f}M\")\n\n# ================================================================\n# 5-FOLD STRATIFIED CV — sama dengan asli\n# ================================================================\nskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=SEED)\nfolds = []\nfor fold, (train_idx, val_idx) in enumerate(skf.split(df['id_code'], df['diagnosis'])):\n    folds.append({\n        \"fold\" : fold,\n        \"train\": df.iloc[train_idx]['id_code'].tolist(),\n        \"val\"  : df.iloc[val_idx]['id_code'].tolist()\n    })\n\n# Simpan folds.json (sama dengan asli — untuk kompatibilitas dengan CNN_GNN.py)\nwith open(OUT_DIR / \"folds.json\", \"w\") as f:\n    json.dump(folds, f, indent=2)\nprint(f\"\\nFolds: {len(folds)} folds, train/val per fold: {len(folds[0]['train'])}/{len(folds[0]['val'])}\")\n\n# ================================================================\n# EVALUATION HELPER — ditambah AUC sebagai metrik pendukung\n# ================================================================\ndef evaluate_and_report(model, loader, criterion, fold, label):\n    \"\"\"\n    Evaluasi model → simpan classification report JSON & confusion matrix PNG.\n    Return dict: {val_acc, val_mild, val_auc, report, cm}\n    \"\"\"\n    model.eval()\n    all_preds, all_labels, all_probs = [], [], []\n\n    with torch.no_grad():\n        for imgs, labels in loader:\n            imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)\n            out   = model(imgs)\n            probs = F.softmax(out, dim=1)\n            preds = out.argmax(1)\n            all_preds.append(preds.cpu().numpy())\n            all_labels.append(labels.cpu().numpy())\n            all_probs.append(probs.cpu().numpy())\n\n    all_preds  = np.concatenate(all_preds)\n    all_labels = np.concatenate(all_labels)\n    all_probs  = np.concatenate(all_probs)\n\n    val_acc  = float((all_preds == all_labels).mean())\n    val_mild = float(recall_score(all_labels, all_preds,\n                                  labels=[MILD_IDX], average='macro', zero_division=0))\n\n    # AUC (one-vs-rest macro)\n    try:\n        y_bin = label_binarize(all_labels, classes=list(range(NUM_CLASSES)))\n        val_auc = float(roc_auc_score(y_bin, all_probs, multi_class='ovr', average='macro'))\n    except Exception:\n        val_auc = 0.0\n\n    rep_dict = classification_report(all_labels, all_preds,\n                                     labels=list(range(NUM_CLASSES)),\n                                     target_names=CLASS_NAMES,\n                                     digits=4, output_dict=True)\n    rep_text = classification_report(all_labels, all_preds,\n                                     labels=list(range(NUM_CLASSES)),\n                                     target_names=CLASS_NAMES, digits=4)\n    cm = confusion_matrix(all_labels, all_preds, labels=list(range(NUM_CLASSES)))\n\n    # Simpan classification report JSON\n    with open(OUT_DIR / f\"classification_report_fold{fold}_{label}.json\", \"w\") as f:\n        json.dump(rep_dict, f, indent=2)\n\n    # Simpan confusion matrix PNG (normalized per baris = recall per kelas)\n    cm_norm = cm.astype(float) / (cm.sum(axis=1, keepdims=True) + 1e-9)\n    fig, ax = plt.subplots(figsize=(6, 5))\n    ConfusionMatrixDisplay(confusion_matrix=cm_norm,\n                           display_labels=CLASS_NAMES).plot(ax=ax, cmap='Blues', colorbar=False)\n    ax.set_title(f\"Normalized ConfMat — Fold {fold} ({label})\\n\"\n                 f\"Mild Recall = {val_mild:.4f}\")\n    plt.tight_layout()\n    plt.savefig(OUT_DIR / f\"confmat_fold{fold}_{label}.png\", dpi=100)\n    plt.close()\n\n    print(f\"\\n  Fold {fold} [{label}] → acc={val_acc:.4f} | mild_recall={val_mild:.4f} | auc={val_auc:.4f}\")\n    print(rep_text)\n\n    return {\n        \"val_acc\"  : val_acc,\n        \"val_mild\" : val_mild,\n        \"val_auc\"  : val_auc,\n        \"report\"   : rep_dict,\n        \"cm\"       : cm.tolist()\n    }\n\n# ================================================================\n# TRAINING LOOP — 5-Fold CV\n# ================================================================\ncv_results_by_loss = []\ncv_results_by_mild = []\nreports_by_fold    = {}\n\nfor fold_data in folds:\n    fold = fold_data['fold']\n    print(f\"\\n{'='*60}\")\n    print(f\"FOLD {fold + 1}/5  —  Backbone: VGG16\")\n    print(f\"{'='*60}\")\n\n    train_df = df[df['id_code'].isin(fold_data['train'])].copy()\n    val_df   = df[df['id_code'].isin(fold_data['val'])].copy()\n    print(f\"Train: {len(train_df)} | Val: {len(val_df)}\")\n\n    # Class weights per fold (sama dengan asli)\n    labels_train  = train_df['diagnosis'].values\n    cw = compute_class_weight('balanced', classes=np.arange(NUM_CLASSES), y=labels_train)\n    class_weights = torch.tensor(cw, dtype=torch.float32).to(DEVICE)\n    print(f\"Class weights: {np.round(cw, 4)}\")\n\n    # DataLoader\n    train_ds = FundusDS(train_df['id_code'].values, train_df['diagnosis'].values, train_tf)\n    val_ds   = FundusDS(val_df['id_code'].values,   val_df['diagnosis'].values,   val_tf)\n    train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True,\n                              num_workers=2, pin_memory=True)\n    val_loader   = DataLoader(val_ds,   batch_size=BATCH_SIZE, shuffle=False,\n                              num_workers=2, pin_memory=True)\n\n    # Model, Loss, Optimizer, Scheduler\n    model     = build_vgg16().to(DEVICE)\n    criterion = nn.CrossEntropyLoss(weight=class_weights)\n    optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)\n    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS, eta_min=1e-6)\n\n    best_val_loss = float(\"inf\"); wait_loss = 0\n    best_val_mild = -1.0;         wait_mild = 0\n    history = []\n\n    for ep in range(1, EPOCHS + 1):\n        t0 = time.time()\n\n        # --- Training ---\n        model.train()\n        total = correct = 0\n        running_loss = 0.0\n        for imgs, labels in train_loader:\n            imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)\n            optimizer.zero_grad()\n            out  = model(imgs)\n            loss = criterion(out, labels)\n            loss.backward()\n            optimizer.step()\n            running_loss += loss.item() * imgs.size(0)\n            total        += imgs.size(0)\n            correct      += (out.argmax(1) == labels).sum().item()\n\n        train_loss = running_loss / total\n        train_acc  = correct / total\n\n        # --- Validation ---\n        model.eval()\n        val_loss_acc = 0.0; v_total = v_correct = 0\n        all_preds_ep = []; all_labels_ep = []\n        with torch.no_grad():\n            for imgs, labels in val_loader:\n                imgs, labels = imgs.to(DEVICE), labels.to(DEVICE)\n                out  = model(imgs)\n                lossv = criterion(out, labels)\n                val_loss_acc += lossv.item() * imgs.size(0)\n                v_total      += imgs.size(0)\n                preds = out.argmax(1)\n                v_correct += (preds == labels).sum().item()\n                all_preds_ep.append(preds.cpu().numpy())\n                all_labels_ep.append(labels.cpu().numpy())\n\n        val_loss = val_loss_acc / v_total\n        val_acc  = v_correct / v_total\n        all_preds_ep  = np.concatenate(all_preds_ep)\n        all_labels_ep = np.concatenate(all_labels_ep)\n        val_mild_recall = recall_score(all_labels_ep, all_preds_ep,\n                                       labels=[MILD_IDX], average='macro', zero_division=0)\n        scheduler.step()\n\n        elapsed = time.time() - t0\n        print(f\"  Ep{ep:02d}: train_loss={train_loss:.4f} val_loss={val_loss:.4f} \"\n              f\"val_acc={val_acc:.4f} val_mild={val_mild_recall:.4f} ({elapsed:.1f}s)\")\n\n        history.append({\n            \"epoch\": ep, \"train_loss\": train_loss, \"val_loss\": val_loss,\n            \"val_acc\": val_acc, \"val_mild_recall\": float(val_mild_recall)\n        })\n\n        # Checkpoint by val_loss\n        if val_loss < best_val_loss - 1e-6:\n            best_val_loss = val_loss; wait_loss = 0\n            torch.save(model.state_dict(),\n                       MODEL_DIR / f\"best_{BACKBONE_NAME}_fold{fold}_byvalloss.pth\")\n        else:\n            wait_loss += 1\n\n        # Checkpoint by val_mild_recall (METRIK UTAMA)\n        if val_mild_recall > best_val_mild + 1e-6:\n            best_val_mild = val_mild_recall; wait_mild = 0\n            torch.save(model.state_dict(),\n                       MODEL_DIR / f\"best_{BACKBONE_NAME}_fold{fold}_bymild.pth\")\n            print(f\"  ⭐ Best Mild Recall: {best_val_mild:.4f} — disimpan!\")\n        else:\n            wait_mild += 1\n\n        # Early stopping (sama dengan asli: tunggu keduanya tidak improve)\n        if wait_loss >= PATIENCE and wait_mild >= PATIENCE:\n            print(\"  Early stopping triggered.\")\n            break\n\n    # Plot loss curve\n    plt.figure(figsize=(6, 4))\n    plt.plot([h['epoch'] for h in history], [h['train_loss'] for h in history], label='Train Loss')\n    plt.plot([h['epoch'] for h in history], [h['val_loss'] for h in history], label='Val Loss')\n    plt.plot([h['epoch'] for h in history], [h['val_mild_recall'] for h in history],\n             label='Val Mild Recall', linestyle='--')\n    plt.xlabel(\"Epoch\"); plt.ylabel(\"Value\"); plt.legend()\n    plt.title(f\"Training Curve — VGG16 Fold {fold}\")\n    plt.tight_layout()\n    plt.savefig(OUT_DIR / f\"loss_curve_fold{fold}.png\", dpi=100)\n    plt.close()\n\n    # Evaluasi kedua checkpoint\n    path_loss = MODEL_DIR / f\"best_{BACKBONE_NAME}_fold{fold}_byvalloss.pth\"\n    path_mild = MODEL_DIR / f\"best_{BACKBONE_NAME}_fold{fold}_bymild.pth\"\n\n    res_by_loss = res_by_mild = None\n\n    if path_loss.exists():\n        model.load_state_dict(torch.load(path_loss, map_location=DEVICE))\n        res_by_loss = evaluate_and_report(model, val_loader, criterion, fold, \"byvalloss\")\n\n    if path_mild.exists():\n        model.load_state_dict(torch.load(path_mild, map_location=DEVICE))\n        res_by_mild = evaluate_and_report(model, val_loader, criterion, fold, \"bymild\")\n\n    cv_results_by_loss.append({\n        \"fold\": fold,\n        \"val_acc\" : res_by_loss[\"val_acc\"]  if res_by_loss else None,\n        \"val_mild\": res_by_loss[\"val_mild\"] if res_by_loss else None,\n        \"val_auc\" : res_by_loss[\"val_auc\"]  if res_by_loss else None,\n    })\n    cv_results_by_mild.append({\n        \"fold\": fold,\n        \"val_acc\" : res_by_mild[\"val_acc\"]  if res_by_mild else None,\n        \"val_mild\": res_by_mild[\"val_mild\"] if res_by_mild else None,\n        \"val_auc\" : res_by_mild[\"val_auc\"]  if res_by_mild else None,\n    })\n    reports_by_fold[f\"fold{fold}\"] = {\n        \"byvalloss\": res_by_loss[\"report\"] if res_by_loss else None,\n        \"bymild\"   : res_by_mild[\"report\"] if res_by_mild else None,\n    }\n\n    print(f\"\\nFold {fold} selesai.\")\n    torch.cuda.empty_cache()\n\n# ================================================================\n# SUMMARY — format kompatibel dengan CNN_GNN.py & CNN-GNN-MTL.py\n# ================================================================\ndef aggregate(cv_list, key):\n    vals = np.array([r[key] for r in cv_list if r.get(key) is not None], dtype=float)\n    return (float(vals.mean()), float(vals.std())) if vals.size > 0 else (None, None)\n\ndef mean_report(reps):\n    \"\"\"Rata-rata numerik dari list classification report dicts.\"\"\"\n    reps = [r for r in reps if r is not None]\n    if not reps:\n        return {}\n    keys = list(reps[0].keys())\n    out = {}\n    for k in keys:\n        if isinstance(reps[0][k], dict):\n            out[k] = {}\n            for sk in reps[0][k]:\n                vals = [r[k][sk] for r in reps if k in r and sk in r[k]]\n                out[k][sk] = float(np.mean(vals)) if vals else None\n        elif isinstance(reps[0][k], (int, float)):\n            vals = [r[k] for r in reps if k in r]\n            out[k] = float(np.mean(vals)) if vals else None\n    return out\n\n# Hitung mean/std semua metrik\nm_acc_l,  s_acc_l  = aggregate(cv_results_by_loss, 'val_acc')\nm_mild_l, s_mild_l = aggregate(cv_results_by_loss, 'val_mild')\nm_auc_l,  s_auc_l  = aggregate(cv_results_by_loss, 'val_auc')\nm_acc_m,  s_acc_m  = aggregate(cv_results_by_mild, 'val_acc')\nm_mild_m, s_mild_m = aggregate(cv_results_by_mild, 'val_mild')\nm_auc_m,  s_auc_m  = aggregate(cv_results_by_mild, 'val_auc')\n\nprint(f\"\\n{'='*60}\")\nprint(f\"CV SUMMARY — VGG16\")\nprint(f\"{'='*60}\")\nprint(f\"\\n[Best by Val Loss]\")\nprint(f\"  Mean Accuracy    : {m_acc_l:.4f} ± {s_acc_l:.4f}\")\nprint(f\"  Mean Mild Recall : {m_mild_l:.4f} ± {s_mild_l:.4f}  ← METRIK UTAMA\")\nprint(f\"  Mean AUC (macro) : {m_auc_l:.4f} ± {s_auc_l:.4f}\")\nprint(f\"\\n[Best by Val Mild] ← checkpoint untuk laporan\")\nprint(f\"  Mean Accuracy    : {m_acc_m:.4f} ± {s_acc_m:.4f}\")\nprint(f\"  Mean Mild Recall : {m_mild_m:.4f} ± {s_mild_m:.4f}  ← METRIK UTAMA\")\nprint(f\"  Mean AUC (macro) : {m_auc_m:.4f} ± {s_auc_m:.4f}\")\n\n# Simpan summary JSON (format sama dengan asli + tambahan AUC)\nsummary_json = {\n    \"backbone\"      : \"VGG16\",\n    \"img_size\"      : IMG_SIZE,\n    \"epochs\"        : EPOCHS,\n    \"optimizer\"     : \"AdamW\",\n    \"scheduler\"     : \"CosineAnnealingLR\",\n    \"best_by_val_loss\": {\n        \"mean_acc\" : m_acc_l,  \"std_acc\" : s_acc_l,\n        \"mean_mild\": m_mild_l, \"std_mild\": s_mild_l,\n        \"mean_auc\" : m_auc_l,  \"std_auc\" : s_auc_l,\n        \"n_folds\"  : len(cv_results_by_loss)\n    },\n    \"best_by_val_mild\": {\n        \"mean_acc\" : m_acc_m,  \"std_acc\" : s_acc_m,\n        \"mean_mild\": m_mild_m, \"std_mild\": s_mild_m,\n        \"mean_auc\" : m_auc_m,  \"std_auc\" : s_auc_m,\n        \"n_folds\"  : len(cv_results_by_mild)\n    }\n}\n\nwith open(OUT_DIR / \"cv_summary_byvalloss.json\", \"w\") as f:\n    json.dump(cv_results_by_loss, f, indent=2)\nwith open(OUT_DIR / \"cv_summary_bymild.json\", \"w\") as f:\n    json.dump(cv_results_by_mild, f, indent=2)\n\n# Format final_summary_with_reports.json IDENTIK dengan asli\n# agar kompatibel jika nanti di-feed ke CNN_GNN.py / CNN-GNN-MTL.py\nmean_reps = {\n    \"byvalloss\": mean_report([reports_by_fold.get(f\"fold{i}\", {}).get(\"byvalloss\") for i in range(5)]),\n    \"bymild\"   : mean_report([reports_by_fold.get(f\"fold{i}\", {}).get(\"bymild\")    for i in range(5)]),\n}\n\nfinal_summary = {\n    \"backbone\"         : \"VGG16\",\n    \"cv_summary\"       : {\"best_by_val_loss\": summary_json[\"best_by_val_loss\"],\n                           \"best_by_val_mild\": summary_json[\"best_by_val_mild\"]},\n    \"mean_reports\"     : mean_reps,\n    \"per_fold_reports\" : reports_by_fold,\n}\n\nwith open(OUT_DIR / \"final_summary_with_reports.json\", \"w\") as f:\n    json.dump(final_summary, f, indent=2)\n\nprint(f\"\\n{'='*60}\")\nprint(f\"OUTPUT FILES (di /kaggle/working/backbone_vgg16/):\")\nfor fp in sorted(OUT_DIR.iterdir()):\n    if fp.is_file():\n        print(f\"  {fp.name} ({fp.stat().st_size/1024:.1f} KB)\")\nprint(f\"{'='*60}\")\nprint(\"\\n✅ backbone_vgg16.py selesai!\")\nprint(f\"   Mean Mild Recall (best_by_val_mild): {m_mild_m:.4f} ± {s_mild_m:.4f}\")","metadata":{"_uuid":"8181c3c5-4fe1-458f-89c7-6d6b353ef127","_cell_guid":"feae1f4a-86c6-4249-bd7c-6ad4738be509","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2026-08-26T04:51:53.42989Z","iopub.execute_input":"2026-08-26T04:51:53.430529Z","iopub.status.idle":"2026-08-26T06:26:31.982067Z","shell.execute_reply.started":"2026-08-26T04:51:53.430496Z","shell.execute_reply":"2026-08-26T06:26:31.980762Z"}},"outputs":[{"name":"stdout","text":"Device : cuda\nBackbone: VGG16\nIMG_SIZE    : 224\nEPOCHS      : 20\nBATCH_SIZE  : 16\nLR          : 0.0001\n\nTotal data : 3662\nDistribusi kelas:\n  Class 0 (No DR): 1805 (49.3%)\n  Class 1 (Mild): 370 (10.1%) ← Mild (target)\n  Class 2 (Moderate): 999 (27.3%)\n  Class 3 (Severe): 193 (5.3%)\n  Class 4 (Proliferative DR): 295 (8.1%)\n\nPreprocessing images (Green+CLAHE+autocrop)...\nPreprocessing done. Skipped: 0\n","output_type":"stream"},{"name":"stderr","text":"/usr/local/lib/python3.12/dist-packages/albumentations/core/validation.py:114: UserWarning: ShiftScaleRotate is a special case of Affine transform. Please use Affine transform instead.\n  original_init(self, **validated_kwargs)\n","output_type":"stream"},{"name":"stdout","text":"Downloading: \"https://download.pytorch.org/models/vgg16-397923af.pth\" to /root/.cache/torch/hub/checkpoints/vgg16-397923af.pth\n","output_type":"stream"},{"name":"stderr","text":"100%|██████████| 528M/528M [00:03<00:00, 171MB/s]  \n","output_type":"stream"},{"name":"stdout","text":"\nVGG16 total params: 134.3M\n\nFolds: 5 folds, train/val per fold: 2929/733\n\n============================================================\nFOLD 1/5  —  Backbone: VGG16\n============================================================\nTrain: 2929 | Val: 733\nClass weights: [0.4057 1.9791 0.7332 3.8039 2.4822]\n  Ep01: train_loss=1.2518 val_loss=1.3110 val_acc=0.5034 val_mild=0.0405 (52.4s)\n  ⭐ Best Mild Recall: 0.0405 — disimpan!\n  Ep02: train_loss=1.0478 val_loss=0.9469 val_acc=0.6903 val_mild=0.5811 (54.8s)\n  ⭐ Best Mild Recall: 0.5811 — disimpan!\n  Ep03: train_loss=0.9526 val_loss=0.9201 val_acc=0.7585 val_mild=0.5946 (56.3s)\n  ⭐ Best Mild Recall: 0.5946 — disimpan!\n  Ep04: train_loss=0.9598 val_loss=0.9821 val_acc=0.7790 val_mild=0.6892 (56.7s)\n  ⭐ Best Mild Recall: 0.6892 — disimpan!\n  Ep05: train_loss=0.8520 val_loss=0.8931 val_acc=0.7094 val_mild=0.6757 (56.1s)\n  Ep06: train_loss=0.8326 val_loss=0.8498 val_acc=0.7640 val_mild=0.4459 (56.4s)\n  Ep07: train_loss=0.7963 val_loss=0.8358 val_acc=0.7517 val_mild=0.4595 (56.4s)\n  Ep08: train_loss=0.7987 val_loss=0.8477 val_acc=0.7053 val_mild=0.5000 (56.4s)\n  Ep09: train_loss=0.7614 val_loss=0.8066 val_acc=0.8172 val_mild=0.7027 (56.4s)\n  ⭐ Best Mild Recall: 0.7027 — disimpan!\n  Ep10: train_loss=0.7212 val_loss=0.8154 val_acc=0.7312 val_mild=0.6081 (56.4s)\n  Ep11: train_loss=0.6701 val_loss=0.7626 val_acc=0.8063 val_mild=0.6757 (56.3s)\n  Ep12: train_loss=0.6496 val_loss=0.7730 val_acc=0.7885 val_mild=0.5811 (56.4s)\n  Ep13: train_loss=0.5963 val_loss=0.7900 val_acc=0.7681 val_mild=0.5811 (56.4s)\n  Ep14: train_loss=0.5601 val_loss=0.7764 val_acc=0.7790 val_mild=0.7973 (56.3s)\n  ⭐ Best Mild Recall: 0.7973 — disimpan!\n  Ep15: train_loss=0.5095 val_loss=0.8379 val_acc=0.7790 val_mild=0.6351 (56.6s)\n  Ep16: train_loss=0.4701 val_loss=0.8447 val_acc=0.7844 val_mild=0.5946 (56.3s)\n  Ep17: train_loss=0.4468 val_loss=0.8659 val_acc=0.7885 val_mild=0.5946 (56.5s)\n  Ep18: train_loss=0.4229 val_loss=0.8540 val_acc=0.7926 val_mild=0.6081 (56.5s)\n  Ep19: train_loss=0.3961 val_loss=0.8526 val_acc=0.7872 val_mild=0.5946 (56.4s)\n  Early stopping triggered.\n\n  Fold 0 [byvalloss] → acc=0.8063 | mild_recall=0.6757 | auc=0.9433\n                  precision    recall  f1-score   support\n\n           No DR     0.9972    0.9778    0.9874       361\n            Mild     0.5882    0.6757    0.6289        74\n        Moderate     0.7513    0.7250    0.7379       200\n          Severe     0.2714    0.4872    0.3486        39\nProliferative DR     0.7742    0.4068    0.5333        59\n\n        accuracy                         0.8063       733\n       macro avg     0.6765    0.6545    0.6472       733\n    weighted avg     0.8322    0.8063    0.8126       733\n\n\n  Fold 0 [bymild] → acc=0.7790 | mild_recall=0.7973 | auc=0.9424\n                  precision    recall  f1-score   support\n\n           No DR     0.9916    0.9778    0.9847       361\n            Mild     0.5086    0.7973    0.6211        74\n        Moderate     0.7931    0.5750    0.6667       200\n          Severe     0.2500    0.5128    0.3361        39\nProliferative DR     0.6667    0.4068    0.5053        59\n\n        accuracy                         0.7790       733\n       macro avg     0.6420    0.6539    0.6228       733\n    weighted avg     0.8231    0.7790    0.7881       733\n\n\nFold 0 selesai.\n\n============================================================\nFOLD 2/5  —  Backbone: VGG16\n============================================================\nTrain: 2929 | Val: 733\nClass weights: [0.4057 1.9791 0.7332 3.8039 2.4822]\n  Ep01: train_loss=1.2007 val_loss=1.0698 val_acc=0.5935 val_mild=0.8649 (57.1s)\n  ⭐ Best Mild Recall: 0.8649 — disimpan!\n  Ep02: train_loss=1.0133 val_loss=0.8999 val_acc=0.6917 val_mild=0.7838 (57.0s)\n  Ep03: train_loss=0.9382 val_loss=0.8564 val_acc=0.8035 val_mild=0.7027 (56.4s)\n  Ep04: train_loss=0.8867 val_loss=0.8861 val_acc=0.7490 val_mild=0.7162 (56.4s)\n  Ep05: train_loss=0.8741 val_loss=0.8515 val_acc=0.7708 val_mild=0.6757 (56.5s)\n  Ep06: train_loss=0.8840 val_loss=0.9365 val_acc=0.7176 val_mild=0.6216 (56.7s)\n  Ep07: train_loss=0.8405 val_loss=0.7894 val_acc=0.7694 val_mild=0.7297 (56.3s)\n  Ep08: train_loss=0.7979 val_loss=0.9089 val_acc=0.6835 val_mild=0.8649 (56.3s)\n  Ep09: train_loss=0.7421 val_loss=0.7524 val_acc=0.7831 val_mild=0.7432 (56.6s)\n  Ep10: train_loss=0.7132 val_loss=0.7991 val_acc=0.7462 val_mild=0.7432 (56.4s)\n  Ep11: train_loss=0.6978 val_loss=0.8106 val_acc=0.8035 val_mild=0.7297 (56.5s)\n  Ep12: train_loss=0.6444 val_loss=0.8110 val_acc=0.7244 val_mild=0.6622 (56.4s)\n  Ep13: train_loss=0.6105 val_loss=0.9001 val_acc=0.7626 val_mild=0.6622 (56.4s)\n  Ep14: train_loss=0.5948 val_loss=0.8585 val_acc=0.7681 val_mild=0.7162 (56.6s)\n  Early stopping triggered.\n\n  Fold 1 [byvalloss] → acc=0.7831 | mild_recall=0.7432 | auc=0.9424\n                  precision    recall  f1-score   support\n\n           No DR     0.9916    0.9834    0.9875       361\n            Mild     0.5000    0.7432    0.5978        74\n        Moderate     0.7346    0.5950    0.6575       200\n          Severe     0.3125    0.5128    0.3883        39\nProliferative DR     0.6410    0.4237    0.5102        59\n\n        accuracy                         0.7831       733\n       macro avg     0.6359    0.6516    0.6283       733\n    weighted avg     0.8075    0.7831    0.7878       733\n\n","output_type":"stream"},{"name":"stderr","text":"/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n","output_type":"stream"},{"name":"stdout","text":"\n  Fold 1 [bymild] → acc=0.5935 | mild_recall=0.8649 | auc=0.9160\n                  precision    recall  f1-score   support\n\n           No DR     0.9790    0.9058    0.9410       361\n            Mild     0.2807    0.8649    0.4238        74\n        Moderate     0.0000    0.0000    0.0000       200\n          Severe     0.0000    0.0000    0.0000        39\nProliferative DR     0.2573    0.7458    0.3826        59\n\n        accuracy                         0.5935       733\n       macro avg     0.3034    0.5033    0.3495       733\n    weighted avg     0.5312    0.5935    0.5370       733\n\n\nFold 1 selesai.\n\n============================================================\nFOLD 3/5  —  Backbone: VGG16\n============================================================\nTrain: 2930 | Val: 732\nClass weights: [0.4058 1.9797 0.7334 3.7806 2.4831]\n  Ep01: train_loss=1.1933 val_loss=1.3909 val_acc=0.5792 val_mild=0.8784 (57.3s)\n  ⭐ Best Mild Recall: 0.8784 — disimpan!\n  Ep02: train_loss=1.0274 val_loss=0.9440 val_acc=0.7172 val_mild=0.7973 (56.6s)\n  Ep03: train_loss=0.9681 val_loss=0.9011 val_acc=0.7664 val_mild=0.6622 (56.5s)\n  Ep04: train_loss=0.9217 val_loss=0.8653 val_acc=0.6885 val_mild=0.7297 (56.5s)\n  Ep05: train_loss=0.8592 val_loss=0.8749 val_acc=0.6967 val_mild=0.5135 (56.6s)\n  Ep06: train_loss=0.8319 val_loss=0.8409 val_acc=0.7117 val_mild=0.6351 (56.2s)\n  Ep07: train_loss=0.8309 val_loss=0.7995 val_acc=0.7760 val_mild=0.7297 (56.9s)\n  Ep08: train_loss=0.7914 val_loss=0.7895 val_acc=0.7596 val_mild=0.7568 (56.5s)\n  Ep09: train_loss=0.7260 val_loss=0.8487 val_acc=0.7773 val_mild=0.7568 (56.3s)\n  Ep10: train_loss=0.7094 val_loss=0.8398 val_acc=0.7787 val_mild=0.6486 (56.3s)\n  Ep11: train_loss=0.6848 val_loss=0.8281 val_acc=0.7801 val_mild=0.7703 (56.4s)\n  Ep12: train_loss=0.6172 val_loss=0.7932 val_acc=0.7746 val_mild=0.6216 (56.7s)\n  Ep13: train_loss=0.6273 val_loss=0.7847 val_acc=0.7732 val_mild=0.7703 (56.4s)\n  Ep14: train_loss=0.5625 val_loss=0.8322 val_acc=0.7910 val_mild=0.7568 (56.8s)\n  Ep15: train_loss=0.5147 val_loss=0.7849 val_acc=0.8019 val_mild=0.7432 (56.4s)\n  Ep16: train_loss=0.4800 val_loss=0.8761 val_acc=0.7801 val_mild=0.5676 (56.6s)\n  Ep17: train_loss=0.4800 val_loss=0.8206 val_acc=0.8046 val_mild=0.7162 (56.7s)\n  Ep18: train_loss=0.4484 val_loss=0.8097 val_acc=0.8005 val_mild=0.7027 (56.5s)\n  Early stopping triggered.\n\n  Fold 2 [byvalloss] → acc=0.7732 | mild_recall=0.7703 | auc=0.9419\n                  precision    recall  f1-score   support\n\n           No DR     0.9942    0.9557    0.9746       361\n            Mild     0.4597    0.7703    0.5758        74\n        Moderate     0.7763    0.5900    0.6705       200\n          Severe     0.2987    0.6053    0.4000        38\nProliferative DR     0.7188    0.3898    0.5055        59\n\n        accuracy                         0.7732       732\n       macro avg     0.6495    0.6622    0.6253       732\n    weighted avg     0.8223    0.7732    0.7835       732\n\n","output_type":"stream"},{"name":"stderr","text":"/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n","output_type":"stream"},{"name":"stdout","text":"\n  Fold 2 [bymild] → acc=0.5792 | mild_recall=0.8784 | auc=0.8927\n                  precision    recall  f1-score   support\n\n           No DR     0.9677    0.9141    0.9402       361\n            Mild     0.2050    0.8784    0.3325        74\n        Moderate     0.6316    0.0600    0.1096       200\n          Severe     0.0000    0.0000    0.0000        38\nProliferative DR     0.3091    0.2881    0.2982        59\n\n        accuracy                         0.5792       732\n       macro avg     0.4227    0.4281    0.3361       732\n    weighted avg     0.6955    0.5792    0.5513       732\n\n\nFold 2 selesai.\n\n============================================================\nFOLD 4/5  —  Backbone: VGG16\n============================================================\nTrain: 2930 | Val: 732\nClass weights: [0.4058 1.9797 0.7334 3.7806 2.4831]\n  Ep01: train_loss=1.2284 val_loss=1.0424 val_acc=0.5820 val_mild=0.7973 (56.7s)\n  ⭐ Best Mild Recall: 0.7973 — disimpan!\n  Ep02: train_loss=1.0206 val_loss=0.9140 val_acc=0.7322 val_mild=0.7703 (56.6s)\n  Ep03: train_loss=0.9263 val_loss=0.8615 val_acc=0.7199 val_mild=0.7973 (56.5s)\n  Ep04: train_loss=0.9358 val_loss=1.0645 val_acc=0.6448 val_mild=0.9730 (56.4s)\n  ⭐ Best Mild Recall: 0.9730 — disimpan!\n  Ep05: train_loss=0.8900 val_loss=0.8517 val_acc=0.7773 val_mild=0.5946 (56.3s)\n  Ep06: train_loss=0.8516 val_loss=0.8451 val_acc=0.7760 val_mild=0.4054 (56.6s)\n  Ep07: train_loss=0.8533 val_loss=0.8735 val_acc=0.7609 val_mild=0.4459 (56.4s)\n  Ep08: train_loss=0.8118 val_loss=0.7848 val_acc=0.7760 val_mild=0.6892 (56.3s)\n  Ep09: train_loss=0.7682 val_loss=0.8057 val_acc=0.7568 val_mild=0.5676 (56.6s)\n  Ep10: train_loss=0.7509 val_loss=0.7838 val_acc=0.7801 val_mild=0.8784 (56.5s)\n  Ep11: train_loss=0.7128 val_loss=0.7829 val_acc=0.7650 val_mild=0.7297 (56.4s)\n  Ep12: train_loss=0.6874 val_loss=0.9255 val_acc=0.7678 val_mild=0.8378 (56.6s)\n  Ep13: train_loss=0.6669 val_loss=0.7809 val_acc=0.7910 val_mild=0.5811 (56.2s)\n  Ep14: train_loss=0.6291 val_loss=0.7850 val_acc=0.8033 val_mild=0.6622 (56.5s)\n  Ep15: train_loss=0.5965 val_loss=0.8063 val_acc=0.7937 val_mild=0.7432 (56.7s)\n  Ep16: train_loss=0.5622 val_loss=0.7636 val_acc=0.7855 val_mild=0.6351 (56.5s)\n  Ep17: train_loss=0.5549 val_loss=0.8003 val_acc=0.7951 val_mild=0.7432 (56.5s)\n  Ep18: train_loss=0.5099 val_loss=0.7999 val_acc=0.8005 val_mild=0.6622 (56.4s)\n  Ep19: train_loss=0.4966 val_loss=0.8152 val_acc=0.8019 val_mild=0.6622 (56.5s)\n  Ep20: train_loss=0.4835 val_loss=0.8191 val_acc=0.7964 val_mild=0.6351 (56.5s)\n\n  Fold 3 [byvalloss] → acc=0.7855 | mild_recall=0.6351 | auc=0.9457\n                  precision    recall  f1-score   support\n\n           No DR     0.9860    0.9723    0.9791       361\n            Mild     0.5109    0.6351    0.5663        74\n        Moderate     0.7616    0.6550    0.7043       200\n          Severe     0.2931    0.4474    0.3542        38\nProliferative DR     0.5370    0.4915    0.5133        59\n\n        accuracy                         0.7855       732\n       macro avg     0.6177    0.6403    0.6234       732\n    weighted avg     0.8045    0.7855    0.7923       732\n\n","output_type":"stream"},{"name":"stderr","text":"/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n/usr/local/lib/python3.12/dist-packages/sklearn/metrics/_classification.py:1565: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 in labels with no predicted samples. Use `zero_division` parameter to control this behavior.\n  _warn_prf(average, modifier, f\"{metric.capitalize()} is\", len(result))\n","output_type":"stream"},{"name":"stdout","text":"\n  Fold 3 [bymild] → acc=0.6448 | mild_recall=0.9730 | auc=0.9331\n                  precision    recall  f1-score   support\n\n           No DR     0.9914    0.9584    0.9746       361\n            Mild     0.2791    0.9730    0.4337        74\n        Moderate     0.6111    0.0550    0.1009       200\n          Severe     0.0000    0.0000    0.0000        38\nProliferative DR     0.4019    0.7288    0.5181        59\n\n        accuracy                         0.6448       732\n       macro avg     0.4567    0.5430    0.4055       732\n    weighted avg     0.7165    0.6448    0.5938       732\n\n\nFold 3 selesai.\n\n============================================================\nFOLD 5/5  —  Backbone: VGG16\n============================================================\nTrain: 2930 | Val: 732\nClass weights: [0.4058 1.9797 0.7325 3.8052 2.4831]\n  Ep01: train_loss=1.2250 val_loss=1.0743 val_acc=0.6844 val_mild=0.5811 (57.1s)\n  ⭐ Best Mild Recall: 0.5811 — disimpan!\n  Ep02: train_loss=0.9823 val_loss=0.9249 val_acc=0.6844 val_mild=0.6351 (56.5s)\n  ⭐ Best Mild Recall: 0.6351 — disimpan!\n  Ep03: train_loss=0.9083 val_loss=0.9595 val_acc=0.6899 val_mild=0.8919 (56.6s)\n  ⭐ Best Mild Recall: 0.8919 — disimpan!\n  Ep04: train_loss=0.8627 val_loss=0.9113 val_acc=0.7459 val_mild=0.3649 (56.4s)\n  Ep05: train_loss=0.8485 val_loss=1.0371 val_acc=0.6694 val_mild=0.2973 (56.4s)\n  Ep06: train_loss=0.8510 val_loss=0.9698 val_acc=0.6844 val_mild=0.2162 (56.5s)\n  Ep07: train_loss=0.7950 val_loss=0.8675 val_acc=0.7131 val_mild=0.7432 (56.0s)\n  Ep08: train_loss=0.7613 val_loss=0.8287 val_acc=0.7158 val_mild=0.7162 (56.5s)\n  Ep09: train_loss=0.7414 val_loss=0.7893 val_acc=0.7773 val_mild=0.6351 (56.6s)\n  Ep10: train_loss=0.7012 val_loss=0.8106 val_acc=0.7432 val_mild=0.7027 (56.3s)\n  Ep11: train_loss=0.6739 val_loss=0.8292 val_acc=0.7623 val_mild=0.6216 (56.3s)\n  Ep12: train_loss=0.6546 val_loss=0.8142 val_acc=0.7910 val_mild=0.6216 (56.5s)\n  Ep13: train_loss=0.6084 val_loss=0.8298 val_acc=0.7732 val_mild=0.5811 (56.4s)\n  Ep14: train_loss=0.5766 val_loss=0.8330 val_acc=0.7842 val_mild=0.6757 (56.5s)\n  Early stopping triggered.\n\n  Fold 4 [byvalloss] → acc=0.7773 | mild_recall=0.6351 | auc=0.9370\n                  precision    recall  f1-score   support\n\n           No DR     0.9860    0.9751    0.9805       361\n            Mild     0.5529    0.6351    0.5912        74\n        Moderate     0.7651    0.5729    0.6552       199\n          Severe     0.2576    0.4359    0.3238        39\nProliferative DR     0.5200    0.6610    0.5821        59\n\n        accuracy                         0.7773       732\n       macro avg     0.6163    0.6560    0.6266       732\n    weighted avg     0.8058    0.7773    0.7856       732\n\n\n  Fold 4 [bymild] → acc=0.6899 | mild_recall=0.8919 | auc=0.9246\n                  precision    recall  f1-score   support\n\n           No DR     0.9859    0.9695    0.9777       361\n            Mild     0.3251    0.8919    0.4765        74\n        Moderate     0.6905    0.2915    0.4099       199\n          Severe     0.2969    0.4872    0.3689        39\nProliferative DR     0.4615    0.2034    0.2824        59\n\n        accuracy                         0.6899       732\n       macro avg     0.5520    0.5687    0.5031       732\n    weighted avg     0.7598    0.6899    0.6842       732\n\n\nFold 4 selesai.\n\n============================================================\nCV SUMMARY — VGG16\n============================================================\n\n[Best by Val Loss]\n  Mean Accuracy    : 0.7851 ± 0.0114\n  Mean Mild Recall : 0.6919 ± 0.0557  ← METRIK UTAMA\n  Mean AUC (macro) : 0.9421 ± 0.0028\n\n[Best by Val Mild] ← checkpoint untuk laporan\n  Mean Accuracy    : 0.6573 ± 0.0724\n  Mean Mild Recall : 0.8811 ± 0.0563  ← METRIK UTAMA\n  Mean AUC (macro) : 0.9218 ± 0.0170\n\n============================================================\nOUTPUT FILES (di /kaggle/working/backbone_vgg16/):\n  classification_report_fold0_bymild.json (1.0 KB)\n  classification_report_fold0_byvalloss.json (1.0 KB)\n  classification_report_fold1_bymild.json (1.0 KB)\n  classification_report_fold1_byvalloss.json (1.0 KB)\n  classification_report_fold2_bymild.json (1.0 KB)\n  classification_report_fold2_byvalloss.json (1.0 KB)\n  classification_report_fold3_bymild.json (1.0 KB)\n  classification_report_fold3_byvalloss.json (1.0 KB)\n  classification_report_fold4_bymild.json (1.0 KB)\n  classification_report_fold4_byvalloss.json (1.0 KB)\n  confmat_fold0_bymild.png (37.0 KB)\n  confmat_fold0_byvalloss.png (36.2 KB)\n  confmat_fold1_bymild.png (34.8 KB)\n  confmat_fold1_byvalloss.png (37.1 KB)\n  confmat_fold2_bymild.png (37.7 KB)\n  confmat_fold2_byvalloss.png (39.4 KB)\n  confmat_fold3_bymild.png (35.9 KB)\n  confmat_fold3_byvalloss.png (38.4 KB)\n  confmat_fold4_bymild.png (38.0 KB)\n  confmat_fold4_byvalloss.png (38.7 KB)\n  cv_summary_bymild.json (0.6 KB)\n  cv_summary_byvalloss.json (0.6 KB)\n  final_summary_with_reports.json (16.2 KB)\n  folds.json (393.7 KB)\n  loss_curve_fold0.png (35.4 KB)\n  loss_curve_fold1.png (39.0 KB)\n  loss_curve_fold2.png (36.7 KB)\n  loss_curve_fold3.png (42.6 KB)\n  loss_curve_fold4.png (36.1 KB)\n============================================================\n\n✅ backbone_vgg16.py selesai!\n   Mean Mild Recall (best_by_val_mild): 0.8811 ± 0.0563\n","output_type":"stream"}],"execution_count":1}]}