{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":14774,"databundleVersionId":875431,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================ CELL 1: IMPORTS + CONFIG ============================\nimport os, random, warnings\nimport numpy as np, pandas as pd\nimport cv2, torch, timm\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import cohen_kappa_score, classification_report, confusion_matrix\nfrom tqdm import tqdm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport matplotlib.pyplot as plt, seaborn as sns\n\nwarnings.filterwarnings(\"ignore\")\n\n# ---- SEED + DEVICE ----\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED); torch.cuda.manual_seed_all(SEED)\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"✅ Device:\", DEVICE)\n\n# ---- GLOBAL PARAMS ----\nIMG_SIZE = 512\nBATCH_SIZE = 16\nEPOCHS = 12\nNUM_CLASSES = 5\nDATA_DIR = \"/kaggle/input/aptos2019-blindness-detection\"\n\n# ============================ CELL 2: LOAD CSV + SPLIT ============================\ndf = pd.read_csv(f\"{DATA_DIR}/train.csv\")\ndf[\"id_code\"] = df[\"id_code\"].astype(str)\ndf[\"image_path\"] = df[\"id_code\"].apply(lambda x: f\"{DATA_DIR}/train_images/{x}.png\")\n\ntrain_df, val_df = train_test_split(df, test_size=0.15, stratify=df[\"diagnosis\"], random_state=SEED)\nval_df, test_df = train_test_split(val_df, test_size=0.5, stratify=val_df[\"diagnosis\"], random_state=SEED)\nprint(f\"Train: {len(train_df)} | Val: {len(val_df)} | Test: {len(test_df)}\")\n\n# ============================ CELL 3: IMAGE PREPROCESSING ============================\ndef circular_crop(img):\n    h, w, _ = img.shape\n    x, y, r = w//2, h//2, min(w//2, h//2)\n    mask = np.zeros((h, w), np.uint8)\n    cv2.circle(mask, (x, y), r, 1, -1)\n    img = cv2.bitwise_and(img, img, mask=mask)\n    img = img[y-r:y+r, x-r:x+r]\n    return img\n\n# ============================ CELL 4: TRANSFORMS ============================\ntrain_transforms = A.Compose([\n    A.RandomResizedCrop(size=(IMG_SIZE, IMG_SIZE), scale=(0.8, 1.0), ratio=(0.9, 1.1), p=0.6),\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.2),\n    A.RandomBrightnessContrast(p=0.5),\n    A.ShiftScaleRotate(shift_limit=0.06, scale_limit=0.06, rotate_limit=15, p=0.4),\n    A.OneOf([A.GaussNoise(), A.ISONoise()], p=0.2),\n    A.Normalize(mean=(0.485,0.456,0.406), std=(0.229,0.224,0.225)),\n    ToTensorV2(),\n])\n\nvalid_transforms = A.Compose([\n    A.Resize(height=IMG_SIZE, width=IMG_SIZE),\n    A.Normalize(mean=(0.485,0.456,0.406), std=(0.229,0.224,0.225)),\n    ToTensorV2(),\n])\n\n# ============================ CELL 5: DATASET CLASS ============================\nclass APTOSDataset(Dataset):\n    def __init__(self, df, transforms=None, img_size=IMG_SIZE):\n        self.df = df.reset_index(drop=True)\n        self.transforms = transforms\n        self.img_size = img_size\n\n    def __len__(self): return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img = cv2.imread(row.image_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = circular_crop(img)\n        img = cv2.resize(img, (self.img_size, self.img_size))\n        if self.transforms: img = self.transforms(image=img)[\"image\"]\n        label = int(row.diagnosis)\n        return img, label\n\n# ============================ CELL 6: DATALOADERS ============================\ntrain_ds = APTOSDataset(train_df, transforms=train_transforms)\nval_ds   = APTOSDataset(val_df,   transforms=valid_transforms)\ntest_ds  = APTOSDataset(test_df,  transforms=valid_transforms)\n\ntrain_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=2, pin_memory=True)\nval_loader   = DataLoader(val_ds,   batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)\ntest_loader  = DataLoader(test_ds,  batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)\n\n# ============================ CELL 7: MODEL (DENSENET121) ============================\nmodel = timm.create_model(\"densenet121\", pretrained=True)\nin_features = model.classifier.in_features\nmodel.classifier = nn.Sequential(\n    nn.Linear(in_features, 256),\n    nn.ReLU(),\n    nn.Dropout(0.3),\n    nn.Linear(256, NUM_CLASSES)\n)\nmodel = model.to(DEVICE)\nprint(\"✅ DenseNet121 loaded.\")\n\n# ============================ CELL 8: OPTIMIZER + LOSS ============================\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-5)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\nscaler = torch.cuda.amp.GradScaler()\n\n# ============================ CELL 9: EVALUATION FUNCTION ============================\n@torch.no_grad()\ndef evaluate(model, loader, criterion):\n    model.eval()\n    total_loss, correct, total = 0, 0, 0\n    all_true, all_pred = [], []\n    device_type = \"cuda\" if DEVICE.type == \"cuda\" else \"cpu\"\n    with torch.amp.autocast(device_type=device_type):\n        for imgs, targets in loader:\n            imgs, targets = imgs.to(DEVICE), targets.to(DEVICE)\n            logits = model(imgs)\n            loss = criterion(logits, targets)\n            total_loss += loss.item() * imgs.size(0)\n            preds = logits.softmax(1).argmax(1)\n            correct += (preds == targets).sum().item()\n            total += imgs.size(0)\n            all_true.extend(targets.cpu().numpy())\n            all_pred.extend(preds.cpu().numpy())\n    acc = correct / total\n    qwk = cohen_kappa_score(all_true, all_pred, weights='quadratic')\n    return total_loss/len(loader.dataset), acc, qwk, all_true, all_pred\n\n# ============================ CELL 10: TRAIN LOOP ============================\nhistory = {'train_loss':[], 'train_acc':[], 'val_loss':[], 'val_acc':[], 'val_qwk':[]}\nbest_qwk = -999.0\n\nfor epoch in range(EPOCHS):\n    model.train()\n    running_loss, correct, total = 0.0, 0, 0\n    pbar = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS}\", leave=False)\n    device_type = \"cuda\" if DEVICE.type == \"cuda\" else \"cpu\"\n\n    for imgs, targets in pbar:\n        imgs, targets = imgs.to(DEVICE), targets.to(DEVICE)\n        optimizer.zero_grad()\n        with torch.amp.autocast(device_type=device_type):\n            logits = model(imgs)\n            loss = criterion(logits, targets)\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        running_loss += loss.item() * imgs.size(0)\n        preds = logits.softmax(1).argmax(1)\n        correct += (preds == targets).sum().item()\n        total += imgs.size(0)\n        pbar.set_postfix({'loss': f'{running_loss/total:.4f}', 'acc': f'{correct/total:.4f}'})\n\n    train_loss = running_loss / len(train_loader.dataset)\n    train_acc = correct / total\n    val_loss, val_acc, val_qwk, _, _ = evaluate(model, val_loader, criterion)\n\n    history['train_loss'].append(train_loss)\n    history['train_acc'].append(train_acc)\n    history['val_loss'].append(val_loss)\n    history['val_acc'].append(val_acc)\n    history['val_qwk'].append(val_qwk)\n\n    print(f\"Epoch {epoch+1}: train_loss={train_loss:.4f} train_acc={train_acc:.4f} val_loss={val_loss:.4f} val_acc={val_acc:.4f} val_qwk={val_qwk:.4f}\")\n\n    if val_qwk > best_qwk:\n        best_qwk = val_qwk\n        torch.save(model.state_dict(), \"densenet121_best.pth\")\n        print(f\"✅ Saved best model (val_qwk={best_qwk:.4f})\")\n    scheduler.step()\n\ntorch.save(model.state_dict(), \"densenet121_last.pth\")\nprint(\"Training finished.\")\n\n# ============================ CELL 11: PLOT METRICS ============================\nplt.figure(figsize=(14,5))\nplt.subplot(1,2,1)\nplt.plot(history['train_loss'], label='train_loss'); plt.plot(history['val_loss'], label='val_loss')\nplt.legend(); plt.title('Loss')\n\nplt.subplot(1,2,2)\nplt.plot(history['train_acc'], label='train_acc')\nplt.plot(history['val_acc'], label='val_acc')\nplt.plot(history['val_qwk'], label='val_qwk')\nplt.legend(); plt.title('Accuracy / QWK')\nplt.show()\n\n# ============================ CELL 12: TEST EVALUATION ============================\nckpt_path = \"densenet121_best.pth\"\nassert os.path.exists(ckpt_path), f\"Checkpoint not found: {ckpt_path}\"\n\nmodel.load_state_dict(torch.load(ckpt_path, map_location=DEVICE))\nmodel.to(DEVICE); model.eval()\nprint(f\"✅ Loaded: {ckpt_path}\")\n\ntest_loss, test_acc, test_qwk, y_true, y_pred = evaluate(model, test_loader, criterion)\nprint(f\"\\n📊 Test Loss: {test_loss:.4f}\\nTest Acc: {test_acc:.4f}\\nTest QWK: {test_qwk:.4f}\")\n\nprint(\"\\nClassification Report:\\n\", classification_report(y_true, y_pred, digits=4))\ncm = confusion_matrix(y_true, y_pred)\nplt.figure(figsize=(6,5))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False)\nplt.title(\"DenseNet121 – Confusion Matrix\")\nplt.xlabel(\"Predicted\"); plt.ylabel(\"True\"); plt.tight_layout(); plt.show()\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-11T03:03:55.876281Z","iopub.execute_input":"2025-11-11T03:03:55.876490Z","iopub.status.idle":"2025-11-11T03:58:14.466941Z","shell.execute_reply.started":"2025-11-11T03:03:55.876472Z","shell.execute_reply":"2025-11-11T03:58:14.466119Z"}},"outputs":[],"execution_count":null}]}