{"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":[{"sourceType":"competition","sourceId":14774,"databundleVersionId":875431},{"sourceType":"datasetVersion","sourceId":2812287,"datasetId":1719146,"databundleVersionId":2858573},{"sourceType":"datasetVersion","sourceId":2819730,"datasetId":1723812,"databundleVersionId":2866107},{"sourceType":"modelInstanceVersion","sourceId":824195,"databundleVersionId":16544733,"modelInstanceId":626508,"modelId":638420},{"sourceType":"modelInstanceVersion","sourceId":750837,"databundleVersionId":15694577,"modelInstanceId":573492,"modelId":585836}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nfrom PIL import Image\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom sklearn.model_selection import train_test_split\n\n# -------------------------------------------------\n# Load dataframe\n# -------------------------------------------------\ndf = pd.read_csv(\"/kaggle/input/aptos2019-blindness-detection/train.csv\")\n\n# binary labels\ndf[\"binary_label\"] = df[\"diagnosis\"].apply(lambda x: 0 if x in [0,1] else 1)\n\n# -------------------------------------------------\n# Stratified split\n# -------------------------------------------------\ntrain_df, temp_df = train_test_split(\n    df,\n    test_size=0.30,\n    stratify=df[\"binary_label\"],\n    random_state=42\n)\n\nval_df, test_df = train_test_split(\n    temp_df,\n    test_size=0.50,\n    stratify=temp_df[\"binary_label\"],\n    random_state=42\n)\n\n# -------------------------------------------------\n# Dataset class (takes dataframe instead of csv)\n# -------------------------------------------------\nclass APTOSDataset(Dataset):\n    def __init__(self, dataframe, img_dir, transform=None):\n        self.data = dataframe.reset_index(drop=True)\n        self.img_dir = img_dir\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        img_name = self.data.iloc[idx][\"id_code\"] + \".png\"\n        img_path = os.path.join(self.img_dir, img_name)\n\n        image = Image.open(img_path).convert(\"RGB\")\n        label = self.data.iloc[idx][\"binary_label\"]\n\n        if self.transform:\n            image = self.transform(image)\n\n        return image, label\n\n# -------------------------------------------------\n# Transforms\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop(224, scale=(0.8, 1.0)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(15),\n    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.1),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\nval_test_transform = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\n# -------------------------------------------------\n# Datasets\n# -------------------------------------------------\nimg_dir = \"/kaggle/input/aptos2019-blindness-detection/train_images\"\n\ntrain_ds = APTOSDataset(train_df, img_dir, train_transform)\nval_ds   = APTOSDataset(val_df, img_dir, val_test_transform)\ntest_ds  = APTOSDataset(test_df, img_dir, val_test_transform)\n\n# -------------------------------------------------\n# Loaders\n# -------------------------------------------------\ntrain_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=2, pin_memory=True)\nval_loader   = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=2, pin_memory=True)\ntest_loader  = DataLoader(test_ds, batch_size=32, shuffle=False, num_workers=2, pin_memory=True)\n\nprint(\"Train:\", len(train_ds))\nprint(\"Val:\", len(val_ds))\nprint(\"Test:\", len(test_ds))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:07:09.485475Z","iopub.execute_input":"2026-04-09T04:07:09.485747Z","iopub.status.idle":"2026-04-09T04:07:19.062859Z","shell.execute_reply.started":"2026-04-09T04:07:09.485722Z","shell.execute_reply":"2026-04-09T04:07:19.061973Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# sanity check batch\nimages, labels = next(iter(train_loader))\nprint(\"labels unique:\", torch.unique(labels))\nprint(\"labels dtype:\", labels.dtype)\nprint(\"max label:\", labels.max())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:07:19.064436Z","iopub.execute_input":"2026-04-09T04:07:19.064651Z","iopub.status.idle":"2026-04-09T04:07:29.990192Z","shell.execute_reply.started":"2026-04-09T04:07:19.064631Z","shell.execute_reply":"2026-04-09T04:07:29.989383Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\n# ---------------------------------------------------\n# Residual Block\n# ---------------------------------------------------\nclass ResBlock(nn.Module):\n    def __init__(self, in_c, out_c):\n        super().__init__()\n\n        self.conv1 = nn.Conv2d(in_c, out_c, kernel_size=3, padding=1)\n        self.bn1 = nn.BatchNorm2d(out_c)\n\n        self.conv2 = nn.Conv2d(out_c, out_c, kernel_size=3, padding=1)\n        self.bn2 = nn.BatchNorm2d(out_c)\n\n        if in_c != out_c:\n            self.skip = nn.Conv2d(in_c, out_c, kernel_size=1)\n        else:\n            self.skip = nn.Identity()\n\n    def forward(self, x):\n        identity = self.skip(x)\n\n        x = F.relu(self.bn1(self.conv1(x)))\n        x = self.bn2(self.conv2(x))\n\n        x = F.relu(x + identity)\n        return x\n\n\n# ---------------------------------------------------\n# Channel Attention\n# ---------------------------------------------------\nclass ChannelAttention(nn.Module):\n    def __init__(self, channels, r=16):\n        super().__init__()\n\n        self.fc1 = nn.Conv2d(channels, channels // r, 1)\n        self.fc2 = nn.Conv2d(channels // r, channels, 1)\n\n    def forward(self, x):\n        avg = F.adaptive_avg_pool2d(x, 1)\n        mx  = F.adaptive_max_pool2d(x, 1)\n\n        avg = self.fc2(F.relu(self.fc1(avg)))\n        mx  = self.fc2(F.relu(self.fc1(mx)))\n\n        attn = torch.sigmoid(avg + mx)\n        return x * attn\n\n\n# ---------------------------------------------------\n# Spatial Attention\n# ---------------------------------------------------\nclass SpatialAttention(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.conv = nn.Conv2d(2, 1, kernel_size=7, padding=3)\n\n    def forward(self, x):\n        avg = torch.mean(x, dim=1, keepdim=True)\n        mx, _ = torch.max(x, dim=1, keepdim=True)\n\n        x_cat = torch.cat([avg, mx], dim=1)\n        attn = torch.sigmoid(self.conv(x_cat))\n\n        return x * attn\n\n\n# ---------------------------------------------------\n# Full Retina CNN Model\n# ---------------------------------------------------\nclass RetinaCNN(nn.Module):\n    def __init__(self, num_classes=2):\n        super().__init__()\n\n        # ---------- Stem ----------\n        self.stem = nn.Sequential(\n            nn.Conv2d(3, 32, 3, padding=1),\n            nn.BatchNorm2d(32),\n            nn.ReLU(),\n            nn.Conv2d(32, 32, 3, padding=1),\n            nn.BatchNorm2d(32),\n            nn.ReLU(),\n            nn.MaxPool2d(2)\n        )\n\n        # ---------- Stage 2 ----------\n        self.stage2 = nn.Sequential(\n            ResBlock(32, 64),\n            ResBlock(64, 64),\n            ChannelAttention(64),\n            nn.MaxPool2d(2)\n        )\n\n        # ---------- Stage 3 ----------\n        self.stage3 = nn.Sequential(\n            ResBlock(64, 128),\n            ResBlock(128, 128),\n            SpatialAttention(),\n            nn.MaxPool2d(2)\n        )\n\n        # ---------- Stage 4 ----------\n        self.stage4 = nn.Sequential(\n            ResBlock(128, 256),\n            ResBlock(256, 256),\n            ChannelAttention(256),\n            SpatialAttention()\n        )\n\n        # ---------- Classifier ----------\n        self.pool = nn.AdaptiveAvgPool2d(1)\n\n        self.fc = nn.Sequential(\n            nn.Linear(256, 128),\n            nn.ReLU(),\n            nn.Dropout(0.4),\n            nn.Linear(128, num_classes)\n        )\n\n    def forward(self, x):\n\n        x = self.stem(x)\n        x = self.stage2(x)\n        x = self.stage3(x)\n        x = self.stage4(x)\n\n        x = self.pool(x)\n        x = x.view(x.size(0), -1)\n\n        x = self.fc(x)\n        return x\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:10:10.910495Z","iopub.execute_input":"2026-04-09T04:10:10.911300Z","iopub.status.idle":"2026-04-09T04:10:10.928831Z","shell.execute_reply.started":"2026-04-09T04:10:10.911244Z","shell.execute_reply":"2026-04-09T04:10:10.927977Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device='cuda' if torch.cuda.is_available() else 'cpu'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:10:11.094404Z","iopub.execute_input":"2026-04-09T04:10:11.094616Z","iopub.status.idle":"2026-04-09T04:10:11.098570Z","shell.execute_reply.started":"2026-04-09T04:10:11.094596Z","shell.execute_reply":"2026-04-09T04:10:11.097887Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model=RetinaCNN().to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:10:13.736464Z","iopub.execute_input":"2026-04-09T04:10:13.736854Z","iopub.status.idle":"2026-04-09T04:10:13.782708Z","shell.execute_reply.started":"2026-04-09T04:10:13.736826Z","shell.execute_reply":"2026-04-09T04:10:13.782147Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load(\"/kaggle/input/models/rajdeepsahu05/normalized/pytorch/default/1/best_model (4).pth\"))\nmodel.eval() ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:11:37.441605Z","iopub.execute_input":"2026-04-09T04:11:37.442524Z","iopub.status.idle":"2026-04-09T04:11:37.724032Z","shell.execute_reply.started":"2026-04-09T04:11:37.442494Z","shell.execute_reply":"2026-04-09T04:11:37.723044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\nclass_counts = train_df[\"binary_label\"].value_counts().sort_index()\nprint(class_counts)\n\ntotal = class_counts.sum()\n\nweights = total / (2 * class_counts)   # inverse frequency\nclass_weights = torch.tensor(weights.values, dtype=torch.float)\n\nprint(\"Class weights:\", class_weights)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:13:35.325193Z","iopub.execute_input":"2026-04-09T04:13:35.325899Z","iopub.status.idle":"2026-04-09T04:13:35.346695Z","shell.execute_reply.started":"2026-04-09T04:13:35.325854Z","shell.execute_reply":"2026-04-09T04:13:35.345733Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = model.to(device)\n\ncriterion = torch.nn.CrossEntropyLoss(weight=class_weights.to(device))\n\noptimizer = optim.Adam(model.parameters(), lr=3e-4)\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=40\n)\n\nepochs = 40\npatience = 10\nbest_val_loss = float(\"inf\")\ncounter = 0\n\nfor epoch in range(epochs):\n\n    # ---------------- TRAIN ----------------\n    model.train()\n    train_loss = 0\n\n    for images, labels in train_loader:\n        images = images.to(device)\n        labels = labels.long().to(device)\n\n        optimizer.zero_grad()\n\n        outputs = model(images)             # [batch,2]\n        loss = criterion(outputs, labels)\n\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n\n    train_loss /= len(train_loader)\n\n    # ---------------- VALIDATION ----------------\n    model.eval()\n    val_loss = 0\n    correct = 0\n    total = 0\n\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images = images.to(device)\n            labels = labels.long().to(device)\n\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n            val_loss += loss.item()\n\n            preds = torch.argmax(outputs, dim=1)\n            correct += (preds == labels).sum().item()\n            total += labels.size(0)\n\n    val_loss /= len(val_loader)\n    val_acc = correct / total\n\n    scheduler.step()\n\n    print(f\"Epoch {epoch+1}\")\n    print(f\"Train Loss: {train_loss:.4f}  Val Loss: {val_loss:.4f}  Val Acc: {val_acc:.4f}\")\n\n    # ---------------- EARLY STOPPING ----------------\n    if val_loss < best_val_loss:\n        best_val_loss = val_loss\n        counter = 0\n        torch.save(model.state_dict(), \"best_model.pth\")\n    else:\n        counter += 1\n        if counter >= patience:\n            print(\"Early stopping triggered\")\n            break\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T11:37:32.357312Z","iopub.execute_input":"2026-02-22T11:37:32.358031Z","iopub.status.idle":"2026-02-22T13:50:05.521232Z","shell.execute_reply.started":"2026-02-22T11:37:32.358001Z","shell.execute_reply":"2026-02-22T13:50:05.520479Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, roc_curve, auc\nimport numpy as np\nimport torch\nimport matplotlib.pyplot as plt\n\nmodel.eval()\n\nall_labels = []\nall_preds  = []\nall_probs  = []   # for ROC\n\nwith torch.no_grad():\n    for images, labels in test_loader:\n        images = images.to(device)\n        labels = labels.to(device)\n\n        outputs = model(images)\n\n        probs = torch.softmax(outputs, dim=1)[:,1]   # probability of class 1\n        preds = torch.argmax(outputs, dim=1)\n\n        all_labels.extend(labels.cpu().numpy())\n        all_preds.extend(preds.cpu().numpy())\n        all_probs.extend(probs.cpu().numpy())\n\nall_labels = np.array(all_labels)\nall_preds  = np.array(all_preds)\nall_probs  = np.array(all_probs)\n\n# Accuracy\nacc = accuracy_score(all_labels, all_preds)\nprint(\"Test Accuracy:\", acc)\n\n# Classification report\nprint(\"\\nClassification Report:\")\nprint(classification_report(all_labels, all_preds))\n\n# Confusion matrix\nprint(\"\\nConfusion Matrix:\")\nprint(confusion_matrix(all_labels, all_preds))\n\n# ---------------- ROC Curve ----------------\nfpr, tpr, _ = roc_curve(all_labels, all_probs)\nroc_auc = auc(fpr, tpr)\n\nplt.figure(figsize=(6,5))\nplt.plot(fpr, tpr, label=f\"ROC curve (AUC = {roc_auc:.3f})\")\nplt.plot([0,1], [0,1], linestyle='--')\nplt.xlabel(\"False Positive Rate\")\nplt.ylabel(\"True Positive Rate\")\nplt.title(\"ROC Curve\")\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:11:49.315519Z","iopub.execute_input":"2026-04-09T04:11:49.315817Z","iopub.status.idle":"2026-04-09T04:12:32.589361Z","shell.execute_reply.started":"2026-04-09T04:11:49.315793Z","shell.execute_reply":"2026-04-09T04:12:32.588613Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nepochs = list(range(1,37))\n\ntrain_loss = [\n0.4655,0.4089,0.3872,0.3516,0.3549,0.3389,0.3294,0.3232,0.3227,\n0.2777,0.2546,0.2410,0.2665,0.2520,0.2238,0.2026,0.2055,0.2173,\n0.1996,0.2029,0.1869,0.2067,0.1684,0.1745,0.1657,0.1523,0.1582,\n0.1524,0.1494,0.1336,0.1457,0.1627,0.1317,0.1597,0.1272,0.1217\n]\n\nval_loss = [\n0.4494,0.3968,0.4134,0.3110,0.3148,0.4077,0.3273,0.3477,0.2919,\n0.2810,0.2641,0.2431,0.2845,0.3317,0.2170,0.2979,0.2674,0.2452,\n0.2217,0.2436,0.2118,0.2094,0.2420,0.2157,0.2200,0.2031,0.2132,\n0.2635,0.2501,0.2299,0.2108,0.2417,0.2308,0.2176,0.2190,0.2159\n]\n\nplt.figure(figsize=(8,5))\nplt.plot(epochs, train_loss, label=\"Train Loss\")\nplt.plot(epochs, val_loss, label=\"Validation Loss\")\n\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Training vs Validation Loss\")\nplt.legend()\nplt.grid(True)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T07:25:12.640866Z","iopub.execute_input":"2026-02-14T07:25:12.641496Z","iopub.status.idle":"2026-02-14T07:25:12.789775Z","shell.execute_reply.started":"2026-02-14T07:25:12.641462Z","shell.execute_reply":"2026-02-14T07:25:12.789182Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# choose last conv layer\ntarget_layer = model.stage4[1].conv2\n\nactivations = []\ngradients = []\n\ndef forward_hook(module, input, output):\n    activations.append(output)\n\ndef backward_hook(module, grad_input, grad_output):\n    gradients.append(grad_output[0])\n\n# keep your original hooks (no change)\ntarget_layer.register_forward_hook(forward_hook)\ntarget_layer.register_backward_hook(backward_hook)\n\n# -----------------------------------------------------\n# GradCAM generation\n# -----------------------------------------------------\ndef generate_gradcam(image_tensor):\n\n    model.eval()\n    activations.clear()\n    gradients.clear()\n\n    image_tensor = image_tensor.unsqueeze(0).to(device)\n\n    output = model(image_tensor)\n    pred_class = torch.argmax(output, dim=1)\n\n    model.zero_grad()\n    output[0, pred_class].backward()\n\n    grads = gradients[0]\n    acts  = activations[0]\n\n    weights = grads.mean(dim=[2,3], keepdim=True)\n    cam = (weights * acts).sum(dim=1)\n\n    cam = torch.relu(cam)\n    cam = cam.squeeze().detach().cpu().numpy()\n\n    cam = cv2.resize(cam, (224,224))\n    cam = (cam - cam.min()) / (cam.max() - cam.min() + 1e-8)\n\n    return cam, pred_class.item()\n\n# -----------------------------------------------------\n# Visualization\n# -----------------------------------------------------\ndef show_gradcam(img_tensor, label, class_names):\n\n    cam, pred_class = generate_gradcam(img_tensor)\n\n    # -------------------------------\n    # FIX: DENORMALIZE IMAGE\n    # -------------------------------\n    mean = [0.485, 0.456, 0.406]\n    std  = [0.229, 0.224, 0.225]\n\n    img = img_tensor.clone().cpu()\n    for i in range(3):\n        img[i] = img[i] * std[i] + mean[i]\n\n    original_img = (img.permute(1,2,0).numpy() * 255).astype(np.uint8)\n\n    # -------------------------------\n    # Heatmap\n    # -------------------------------\n    heatmap = cv2.applyColorMap(np.uint8(255*cam), cv2.COLORMAP_JET)\n    heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)\n\n    # -------------------------------\n    # Overlay\n    # -------------------------------\n    overlay = heatmap*0.35 + original_img*0.65\n    overlay = np.clip(overlay, 0, 255).astype(np.uint8)\n\n    # Save overlay\n    cv2.imwrite(\"gradcam_overlay.png\", cv2.cvtColor(overlay, cv2.COLOR_RGB2BGR))\n\n    # -------------------------------\n    # Plot\n    # -------------------------------\n    plt.figure(figsize=(12,4))\n\n    plt.subplot(1,3,1)\n    plt.imshow(original_img)\n    plt.title(f\"Original\\nTrue: {class_names[label]}\")\n\n    plt.subplot(1,3,2)\n    plt.imshow(heatmap)\n    plt.title(\"Heatmap\")\n\n    plt.subplot(1,3,3)\n    plt.imshow(overlay)\n    plt.title(f\"Predicted: {class_names[pred_class]}\")\n\n    plt.show()\n\n# -----------------------------------------------------\n# Example Run\n# -----------------------------------------------------\nclass_names = [\"Normal\", \"Diabetic Retinopathy\"]\n\ncount = 0\nfor i in range(len(test_ds)):\n    img, label = test_ds[i]\n    if label == 1:\n        show_gradcam(img, label, class_names)\n        count += 1\n    if count == 3:\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:28:59.771264Z","iopub.execute_input":"2026-04-09T04:28:59.771894Z","iopub.status.idle":"2026-04-09T04:29:02.167950Z","shell.execute_reply.started":"2026-04-09T04:28:59.771857Z","shell.execute_reply":"2026-04-09T04:29:02.167138Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_names = [\"Normal\", \"Diabetic Retinopathy\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:21:57.306769Z","iopub.execute_input":"2026-04-09T04:21:57.307438Z","iopub.status.idle":"2026-04-09T04:21:57.311264Z","shell.execute_reply.started":"2026-04-09T04:21:57.307404Z","shell.execute_reply":"2026-04-09T04:21:57.310451Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"total_params = sum(p.numel() for p in model.parameters())\nprint(\"Total parameters:\", total_params)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T07:39:22.854241Z","iopub.execute_input":"2026-02-14T07:39:22.854817Z","iopub.status.idle":"2026-02-14T07:39:22.859773Z","shell.execute_reply.started":"2026-02-14T07:39:22.854785Z","shell.execute_reply":"2026-02-14T07:39:22.859072Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\nprint(\"Trainable parameters:\", trainable_params)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-14T07:40:20.702850Z","iopub.execute_input":"2026-02-14T07:40:20.703574Z","iopub.status.idle":"2026-02-14T07:40:20.708171Z","shell.execute_reply.started":"2026-02-14T07:40:20.703546Z","shell.execute_reply":"2026-02-14T07:40:20.707374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#train time  was epochs 36 with 1 hr 43 mintutes","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T10:34:47.102374Z","iopub.execute_input":"2026-02-22T10:34:47.102627Z","iopub.status.idle":"2026-02-22T10:34:47.106550Z","shell.execute_reply.started":"2026-02-22T10:34:47.102596Z","shell.execute_reply":"2026-02-22T10:34:47.105839Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"External Validation","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\n\n# -------------------------------\n# Custom Dataset (ONLY test images)\n# -------------------------------\nclass IDRiDTestDataset(Dataset):\n    def __init__(self, csv_file, img_dir, transform=None):\n        self.df = pd.read_csv(csv_file)\n        self.img_dir = img_dir\n        self.transform = transform\n\n        # columns\n        self.image_col = self.df.columns[0]\n        self.label_col = self.df.columns[1]\n\n        # ✅ Keep only test images\n        self.df = self.df[self.df[self.image_col].str.contains(\"test\")].reset_index(drop=True)\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_name = self.df.iloc[idx][self.image_col]\n\n        # ensure extension\n        if not img_name.endswith(\".jpg\"):\n            img_name = img_name + \".jpg\"\n\n        img_path = os.path.join(self.img_dir, img_name)\n\n        image = Image.open(img_path).convert(\"RGB\")\n\n        # -------------------------------\n        # Convert to binary label\n        # -------------------------------\n        grade = int(self.df.iloc[idx][self.label_col])\n        label = 0 if grade == 0 else 1   # binary classification\n\n        if self.transform:\n            image = self.transform(image)\n\n        return image, label\n\n\n# -------------------------------\n# Transform (SAME AS TRAINING)\n# -------------------------------\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std =[0.229, 0.224, 0.225]\n    )\n])\n\n# -------------------------------\n# Paths (UPDATE THESE)\n# -------------------------------\ncsv_path = \"/kaggle/input/datasets/mariaherrerot/idrid-dataset/idrid_labels.csv\"\nimg_path = \"/kaggle/input/datasets/mariaherrerot/idrid-dataset/Imagenes/Imagenes\"\n\n# -------------------------------\n# Dataset & Loader\n# -------------------------------\ntest_dataset = IDRiDTestDataset(csv_path, img_path, transform=transform)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=32,\n    shuffle=False,\n    num_workers=2\n)\n\n# -------------------------------\n# Check\n# -------------------------------\nprint(\"Total test samples:\", len(test_dataset))\n\nfor images, labels in test_loader:\n    print(images.shape)\n    print(labels.shape)\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:35:12.253103Z","iopub.execute_input":"2026-04-09T04:35:12.253476Z","iopub.status.idle":"2026-04-09T04:35:21.241967Z","shell.execute_reply.started":"2026-04-09T04:35:12.253425Z","shell.execute_reply":"2026-04-09T04:35:21.240861Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torchvision.transforms as transforms","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:29:53.555738Z","iopub.execute_input":"2026-04-09T04:29:53.556463Z","iopub.status.idle":"2026-04-09T04:29:53.560248Z","shell.execute_reply.started":"2026-04-09T04:29:53.556428Z","shell.execute_reply":"2026-04-09T04:29:53.559454Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dataset = MessidorDataset(\n    df=df,\n    img_dir=\"/kaggle/input/datasets/mariaherrerot/messidor2preprocess/messidor-2/messidor-2/preprocess\",\n    transform=val_test_transform   # NO augmentation here\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:29:53.699044Z","iopub.execute_input":"2026-04-09T04:29:53.699715Z","iopub.status.idle":"2026-04-09T04:29:53.703463Z","shell.execute_reply.started":"2026-04-09T04:29:53.699692Z","shell.execute_reply":"2026-04-09T04:29:53.702769Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img, label = dataset[0]\nprint(img.shape, label)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:29:57.757002Z","iopub.execute_input":"2026-04-09T04:29:57.757731Z","iopub.status.idle":"2026-04-09T04:29:57.798742Z","shell.execute_reply.started":"2026-04-09T04:29:57.757699Z","shell.execute_reply":"2026-04-09T04:29:57.798109Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import DataLoader\n\ntest_loader = DataLoader(\n    dataset,\n    batch_size=32,\n    shuffle=False,\n    num_workers=2\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:29:58.443478Z","iopub.execute_input":"2026-04-09T04:29:58.444242Z","iopub.status.idle":"2026-04-09T04:29:58.448514Z","shell.execute_reply.started":"2026-04-09T04:29:58.444212Z","shell.execute_reply":"2026-04-09T04:29:58.447528Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load(\"/kaggle/input/models/rajdeepsahu05/rajdeepnet/pytorch/default/1/best_model (2).pth\", map_location=device))\nmodel.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T11:18:36.531472Z","iopub.execute_input":"2026-02-22T11:18:36.532213Z","iopub.status.idle":"2026-02-22T11:18:36.572798Z","shell.execute_reply.started":"2026-02-22T11:18:36.532183Z","shell.execute_reply":"2026-02-22T11:18:36.572211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device='cuda' if torch.cuda.is_available() else 'cpu'\nmodel.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:30:06.382855Z","iopub.execute_input":"2026-04-09T04:30:06.383322Z","iopub.status.idle":"2026-04-09T04:30:06.393431Z","shell.execute_reply.started":"2026-04-09T04:30:06.383291Z","shell.execute_reply":"2026-04-09T04:30:06.392597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, roc_curve, auc\nimport numpy as np\nimport torch\nimport matplotlib.pyplot as plt\n\nmodel.eval()\n\nall_labels = []\nall_preds  = []\nall_probs  = []   # for ROC\n\nwith torch.no_grad():\n    for images, labels in test_loader:\n        images = images.to(device)\n        labels = labels.to(device)\n\n        outputs = model(images)\n\n        probs = torch.softmax(outputs, dim=1)[:,1]   # probability of class 1\n        preds = torch.argmax(outputs, dim=1)\n\n        all_labels.extend(labels.cpu().numpy())\n        all_preds.extend(preds.cpu().numpy())\n        all_probs.extend(probs.cpu().numpy())\n\nall_labels = np.array(all_labels)\nall_preds  = np.array(all_preds)\nall_probs  = np.array(all_probs)\n\n# Accuracy\nacc = accuracy_score(all_labels, all_preds)\nprint(\"Test Accuracy:\", acc)\n\n# Classification report\nprint(\"\\nClassification Report:\")\nprint(classification_report(all_labels, all_preds))\n\n# Confusion matrix\nprint(\"\\nConfusion Matrix:\")\nprint(confusion_matrix(all_labels, all_preds))\n\n# ---------------- ROC Curve ----------------\nfpr, tpr, _ = roc_curve(all_labels, all_probs)\nroc_auc = auc(fpr, tpr)\n\nplt.figure(figsize=(6,5))\nplt.plot(fpr, tpr, label=f\"ROC curve (AUC = {roc_auc:.3f})\")\nplt.plot([0,1], [0,1], linestyle='--')\nplt.xlabel(\"False Positive Rate\")\nplt.ylabel(\"True Positive Rate\")\nplt.title(\"ROC Curve\")\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-09T04:35:44.502616Z","iopub.execute_input":"2026-04-09T04:35:44.503323Z","iopub.status.idle":"2026-04-09T04:35:52.654704Z","shell.execute_reply.started":"2026-04-09T04:35:44.503286Z","shell.execute_reply":"2026-04-09T04:35:52.654003Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\n\nprint(\"Pred distribution:\", np.bincount(all_preds))\nprint(\"True distribution:\", np.bincount(all_labels))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T10:46:35.800757Z","iopub.execute_input":"2026-02-22T10:46:35.801577Z","iopub.status.idle":"2026-02-22T10:46:35.806353Z","shell.execute_reply.started":"2026-02-22T10:46:35.801545Z","shell.execute_reply":"2026-02-22T10:46:35.805765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(df[\"diagnosis\"].value_counts())\nprint(df[\"binary_label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T10:46:55.903649Z","iopub.execute_input":"2026-02-22T10:46:55.903963Z","iopub.status.idle":"2026-02-22T10:46:55.911159Z","shell.execute_reply.started":"2026-02-22T10:46:55.903939Z","shell.execute_reply":"2026-02-22T10:46:55.910502Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}