{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","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":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, gc, random\nimport cv2\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, Subset\nfrom torchvision import models, transforms\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\ndef seed_everything(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\nseed_everything()\n\nROOT = \"/kaggle/input/aptos2019-blindness-detection\"\nNUM_CLASSES = 5\nBATCH_SIZE = 16\nEPOCHS = 100\nLR = 3e-4\nWD = 5e-4\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:42:59.390232Z","iopub.execute_input":"2026-01-20T18:42:59.391004Z","iopub.status.idle":"2026-01-20T18:43:12.413007Z","shell.execute_reply.started":"2026-01-20T18:42:59.390973Z","shell.execute_reply":"2026-01-20T18:43:12.412194Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def apply_clahe_rgb(img):\n    img = np.array(img)\n    lab = cv2.cvtColor(img, cv2.COLOR_RGB2LAB)\n    l, a, b = cv2.split(lab)\n    clahe = cv2.createCLAHE(2.0, (8,8))\n    l = clahe.apply(l)\n    lab = cv2.merge((l,a,b))\n    return Image.fromarray(cv2.cvtColor(lab, cv2.COLOR_LAB2RGB))\n\n\nclass APTOSDataset(Dataset):\n    def __init__(self, root, csv, transform=None):\n        self.df = pd.read_csv(os.path.join(root, csv))\n        self.img_dir = os.path.join(root, \"train_images\")\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        # 🔧 FIX: force idx to int\n        idx = int(idx)\n\n        img_id = self.df.iloc[idx, 0]\n        label = int(self.df.iloc[idx, 1])\n\n        img = Image.open(\n            os.path.join(self.img_dir, img_id + \".png\")\n        ).convert(\"RGB\")\n\n        img = apply_clahe_rgb(img)\n\n        if self.transform:\n            img = self.transform(img)\n\n        return img, torch.tensor(label)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:43:12.414425Z","iopub.execute_input":"2026-01-20T18:43:12.414817Z","iopub.status.idle":"2026-01-20T18:43:12.421932Z","shell.execute_reply.started":"2026-01-20T18:43:12.414793Z","shell.execute_reply":"2026-01-20T18:43:12.421364Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_tf = transforms.Compose([\n    transforms.Resize((256,256)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(15),\n    transforms.ColorJitter(0.1,0.1,0.1,0.05),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n\nval_tf = transforms.Compose([\n    transforms.Resize((256,256)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])\n])\n\nbase_ds = APTOSDataset(ROOT, \"train.csv\", transform=None)\nidx = torch.randperm(len(base_ds))\n\nval_len = int(0.1 * len(base_ds))\ntrain_idx, val_idx = idx[val_len:], idx[:val_len]\n\ntrain_ds = Subset(base_ds, train_idx)\nval_ds   = Subset(base_ds, val_idx)\n\ntrain_ds.dataset.transform = train_tf\nval_ds.dataset.transform   = val_tf\n\ntrain_loader = DataLoader(train_ds, BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader   = DataLoader(val_ds, BATCH_SIZE, shuffle=False, num_workers=2)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:43:12.422710Z","iopub.execute_input":"2026-01-20T18:43:12.422994Z","iopub.status.idle":"2026-01-20T18:43:12.497424Z","shell.execute_reply.started":"2026-01-20T18:43:12.422974Z","shell.execute_reply":"2026-01-20T18:43:12.496928Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = models.regnet_y_400mf(\n    weights=models.RegNet_Y_400MF_Weights.IMAGENET1K_V1\n)\n\nmodel.fc = nn.Sequential(\n    nn.Dropout(0.3),\n    nn.Linear(model.fc.in_features, NUM_CLASSES)\n)\n\nmodel.to(device)\n\n# Class weights (FIXED)\nlabels = base_ds.df.iloc[train_idx.numpy(), 1].values\ncounts = np.bincount(labels, minlength=NUM_CLASSES)\n\nweights = 1.0 / torch.tensor(counts, dtype=torch.float)\nweights = weights / weights.sum() * NUM_CLASSES\nweights = weights.to(device)\n\ncriterion = nn.CrossEntropyLoss(weight=weights)\n\n# Freeze backbone\nfor p in model.parameters():\n    p.requires_grad = False\nfor p in model.fc.parameters():\n    p.requires_grad = True\n\noptimizer = optim.Adam(model.fc.parameters(), lr=LR, weight_decay=WD)\n\n# ✅ REMOVE verbose\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer, mode=\"max\", factor=0.5, patience=5\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:43:12.498646Z","iopub.execute_input":"2026-01-20T18:43:12.498842Z","iopub.status.idle":"2026-01-20T18:43:13.172738Z","shell.execute_reply.started":"2026-01-20T18:43:12.498824Z","shell.execute_reply":"2026-01-20T18:43:13.171945Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_val = 0.0\n\nfor epoch in range(EPOCHS):\n    model.train()\n    corr, tot = 0, 0\n\n    for x, y in train_loader:\n        x, y = x.to(device), y.to(device)\n\n        optimizer.zero_grad()\n        out = model(x)\n        loss = criterion(out, y)\n        loss.backward()\n        optimizer.step()\n\n        corr += (out.argmax(1) == y).sum().item()\n        tot += y.size(0)\n\n    train_acc = corr / tot\n\n    model.eval()\n    vc, vt = 0, 0\n    with torch.no_grad():\n        for x, y in val_loader:\n            x, y = x.to(device), y.to(device)\n            vc += (model(x).argmax(1) == y).sum().item()\n            vt += y.size(0)\n\n    val_acc = vc / vt\n    scheduler.step(val_acc)\n\n    if val_acc > best_val:\n        best_val = val_acc\n        torch.save(model.state_dict(), \"best_regnet_aptos.pth\")\n\n    print(f\"Epoch [{epoch+1:03}/{EPOCHS}] | Train Acc: {train_acc:.4f} | Val Acc: {val_acc:.4f}\")\n\n    # 🔧 Unfreeze after 10 epochs (NO verbose here)\n    if epoch == 9:\n        for p in model.parameters():\n            p.requires_grad = True\n\n        optimizer = optim.Adam(model.parameters(), lr=LR * 0.1, weight_decay=WD)\n        scheduler = optim.lr_scheduler.ReduceLROnPlateau(\n            optimizer, mode=\"max\", factor=0.5, patience=5\n        )\n\n    torch.cuda.empty_cache()\n    gc.collect()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-20T18:43:13.173674Z","iopub.execute_input":"2026-01-20T18:43:13.173928Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import f1_score, confusion_matrix, classification_report\nimport numpy as np\nimport torch\n\n# Load best saved model\nmodel.load_state_dict(torch.load(\"best_regnet_aptos.pth\", map_location=device))\nmodel.eval()\n\nall_preds = []\nall_labels = []\n\nwith torch.no_grad():\n    for imgs, labels in val_loader:\n        imgs = imgs.to(device)\n        labels = labels.to(device)\n\n        outputs = model(imgs)\n        preds = outputs.argmax(1)\n\n        all_preds.extend(preds.cpu().numpy())\n        all_labels.extend(labels.cpu().numpy())\n\n# Convert to numpy\nall_preds = np.array(all_preds)\nall_labels = np.array(all_labels)\n\n# Metrics\naccuracy = (all_preds == all_labels).mean()\nmacro_f1 = f1_score(all_labels, all_preds, average=\"macro\")\nweighted_f1 = f1_score(all_labels, all_preds, average=\"weighted\")\ncm = confusion_matrix(all_labels, all_preds)\n\nprint(\"\\n📊 FINAL EVALUATION (BEST MODEL)\")\nprint(f\"Overall Accuracy : {accuracy:.4f}\")\nprint(f\"Macro F1-score   : {macro_f1:.4f}\")\nprint(f\"Weighted F1-score: {weighted_f1:.4f}\")\n\nprint(\"\\n🧩 Confusion Matrix:\")\nprint(cm)\n\nprint(\"\\n📋 Classification Report:\")\nprint(classification_report(all_labels, all_preds))\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}