{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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,"isSourceIdPinned":false}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\n\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:38:38.984989Z","iopub.execute_input":"2026-03-14T11:38:38.985325Z","iopub.status.idle":"2026-03-14T11:38:47.364648Z","shell.execute_reply.started":"2026-03-14T11:38:38.985294Z","shell.execute_reply":"2026-03-14T11:38:47.363965Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nos.environ[\"PYTORCH_CUDA_ALLOC_CONF\"] = \"expandable_segments:True\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:44:19.718081Z","iopub.execute_input":"2026-03-14T11:44:19.718668Z","iopub.status.idle":"2026-03-14T11:44:19.722017Z","shell.execute_reply.started":"2026-03-14T11:44:19.718639Z","shell.execute_reply":"2026-03-14T11:44:19.721322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install timm albumentations --quiet","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:44:20.371818Z","iopub.execute_input":"2026-03-14T11:44:20.37249Z","iopub.status.idle":"2026-03-14T11:44:23.764834Z","shell.execute_reply.started":"2026-03-14T11:44:20.372458Z","shell.execute_reply":"2026-03-14T11:44:23.763931Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nfrom sklearn.model_selection import train_test_split\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\nimport timm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nfrom PIL import Image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:44:23.766699Z","iopub.execute_input":"2026-03-14T11:44:23.767462Z","iopub.status.idle":"2026-03-14T11:44:23.771725Z","shell.execute_reply.started":"2026-03-14T11:44:23.767425Z","shell.execute_reply":"2026-03-14T11:44:23.771197Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR  = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\nIMG_DIR   = os.path.join(DATA_DIR, \"train_images\")\nCSV_PATH  = os.path.join(DATA_DIR, \"train.csv\")\n\ndf = pd.read_csv(CSV_PATH)\ndf[\"image_path\"] = df[\"id_code\"].apply(lambda x: os.path.join(IMG_DIR, x + \".png\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:44:23.772577Z","iopub.execute_input":"2026-03-14T11:44:23.772758Z","iopub.status.idle":"2026-03-14T11:44:23.796186Z","shell.execute_reply.started":"2026-03-14T11:44:23.77274Z","shell.execute_reply":"2026-03-14T11:44:23.795639Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df, temp_df = train_test_split(\n    df, test_size=0.30, stratify=df[\"diagnosis\"], random_state=42\n)\n\nval_df, test_df = train_test_split(\n    temp_df, test_size=0.50, stratify=temp_df[\"diagnosis\"], random_state=42\n)\n\nprint(\"Train:\", len(train_df))\nprint(\"Val  :\", len(val_df))\nprint(\"Test :\", len(test_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:44:23.79733Z","iopub.execute_input":"2026-03-14T11:44:23.797532Z","iopub.status.idle":"2026-03-14T11:44:23.809699Z","shell.execute_reply.started":"2026-03-14T11:44:23.797513Z","shell.execute_reply":"2026-03-14T11:44:23.809186Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ── EfficientNet-B4 uses 380×380 input for best performance ──\nIMG_SIZE = 380\n\ntrain_tfms = A.Compose([\n    A.RandomResizedCrop(\n        size=(IMG_SIZE, IMG_SIZE),\n        scale=(0.8, 1.0),\n        ratio=(0.75, 1.33),\n        p=1.0\n    ),\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.5),\n    A.Rotate(limit=30, p=0.5),\n    A.RandomBrightnessContrast(p=0.4),\n    A.Normalize(\n        mean=(0.485, 0.456, 0.406),\n        std=(0.229, 0.224, 0.225)\n    ),\n    ToTensorV2()\n])\n\nval_tfms = A.Compose([\n    A.Resize(IMG_SIZE, IMG_SIZE),\n    A.Normalize(\n        mean=(0.485, 0.456, 0.406),\n        std=(0.229, 0.224, 0.225)\n    ),\n    ToTensorV2()\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:44:29.333475Z","iopub.execute_input":"2026-03-14T11:44:29.334261Z","iopub.status.idle":"2026-03-14T11:44:29.343989Z","shell.execute_reply.started":"2026-03-14T11:44:29.334229Z","shell.execute_reply":"2026-03-14T11:44:29.343283Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class APTOSDataset(Dataset):\n    def __init__(self, df, transform):\n        self.df        = df.reset_index(drop=True)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img   = Image.open(self.df.loc[idx, \"image_path\"]).convert(\"RGB\")\n        label = self.df.loc[idx, \"diagnosis\"]\n        img   = self.transform(image=np.array(img))[\"image\"]\n        return img, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:44:32.131983Z","iopub.execute_input":"2026-03-14T11:44:32.132268Z","iopub.status.idle":"2026-03-14T11:44:32.137565Z","shell.execute_reply.started":"2026-03-14T11:44:32.132244Z","shell.execute_reply":"2026-03-14T11:44:32.136745Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_ds = APTOSDataset(train_df, train_tfms)\nval_ds   = APTOSDataset(val_df,   val_tfms)\ntest_ds  = APTOSDataset(test_df,  val_tfms)\n\ntrain_loader = DataLoader(train_ds, batch_size=8, shuffle=True, num_workers=2)\nval_loader   = DataLoader(val_ds,   batch_size=8, shuffle=False)\ntest_loader  = DataLoader(test_ds,  batch_size=8, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:44:33.097298Z","iopub.execute_input":"2026-03-14T11:44:33.097896Z","iopub.status.idle":"2026-03-14T11:44:33.10462Z","shell.execute_reply.started":"2026-03-14T11:44:33.097867Z","shell.execute_reply":"2026-03-14T11:44:33.103822Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class EfficientNet_ViT(nn.Module):\n    def __init__(self, num_classes=5):\n        super().__init__()\n\n        self.cnn = timm.create_model(\n            \"efficientnet_b4\",\n            pretrained=True,\n            num_classes=0\n        )\n        cnn_dim = self.cnn.num_features\n\n        self.vit = timm.create_model(\n            \"vit_tiny_patch16_224\",\n            pretrained=True,\n            num_classes=0,\n            img_size=224\n        )\n        vit_dim = self.vit.num_features\n\n        self.classifier = nn.Sequential(\n            nn.Linear(cnn_dim + vit_dim, 512),\n            nn.ReLU(),\n            nn.Dropout(0.4),\n            nn.Linear(512, num_classes)\n        )\n\n    def forward(self, x):\n        cnn_feat = self.cnn(x)\n        vit_feat = self.vit(x)\n        feat = torch.cat([cnn_feat, vit_feat], dim=1)\n        return self.classifier(feat)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:46:56.087043Z","iopub.execute_input":"2026-03-14T11:46:56.087584Z","iopub.status.idle":"2026-03-14T11:46:56.09319Z","shell.execute_reply.started":"2026-03-14T11:46:56.087556Z","shell.execute_reply":"2026-03-14T11:46:56.092558Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class FocalLoss(nn.Module):\n    def __init__(self, gamma=2):\n        super().__init__()\n        self.gamma = gamma\n\n    def forward(self, logits, targets):\n        ce  = F.cross_entropy(logits, targets, reduction=\"none\")\n        pt  = torch.exp(-ce)\n        return ((1 - pt) ** self.gamma * ce).mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:46:58.632104Z","iopub.execute_input":"2026-03-14T11:46:58.632839Z","iopub.status.idle":"2026-03-14T11:46:58.637147Z","shell.execute_reply.started":"2026-03-14T11:46:58.632803Z","shell.execute_reply":"2026-03-14T11:46:58.636391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device    = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)\n\nmodel     = EfficientNet_ViT().to(device)\ncriterion = FocalLoss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-2)\n\n# Cosine LR scheduler — reduces LR smoothly over training\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer, T_max=10, eta_min=1e-6\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:47:01.314259Z","iopub.execute_input":"2026-03-14T11:47:01.314901Z","iopub.status.idle":"2026-03-14T11:47:02.661934Z","shell.execute_reply.started":"2026-03-14T11:47:01.314873Z","shell.execute_reply":"2026-03-14T11:47:02.661308Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_epoch(loader):\n    model.train()\n    correct, total, loss_sum = 0, 0, 0\n\n    for x, y in loader:\n        x, y = x.to(device), y.to(device)\n        optimizer.zero_grad()\n        out  = model(x)\n        loss = criterion(out, y)\n        loss.backward()\n        optimizer.step()\n\n        loss_sum += loss.item()\n        correct  += (out.argmax(1) == y).sum().item()\n        total    += y.size(0)\n\n    return loss_sum / len(loader), correct / total","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:47:03.942433Z","iopub.execute_input":"2026-03-14T11:47:03.942941Z","iopub.status.idle":"2026-03-14T11:47:03.947889Z","shell.execute_reply.started":"2026-03-14T11:47:03.942912Z","shell.execute_reply":"2026-03-14T11:47:03.947133Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def eval_epoch(loader):\n    model.eval()\n    correct, total = 0, 0\n\n    with torch.no_grad():\n        for x, y in loader:\n            x, y    = x.to(device), y.to(device)\n            out     = model(x)\n            correct += (out.argmax(1) == y).sum().item()\n            total   += y.size(0)\n\n    return correct / total","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:47:07.972266Z","iopub.execute_input":"2026-03-14T11:47:07.973089Z","iopub.status.idle":"2026-03-14T11:47:07.977939Z","shell.execute_reply.started":"2026-03-14T11:47:07.973057Z","shell.execute_reply":"2026-03-14T11:47:07.977071Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 10\n\nfor epoch in range(EPOCHS):\n    train_loss, train_acc = train_epoch(train_loader)\n    val_acc               = eval_epoch(val_loader)\n    scheduler.step()       # update learning rate each epoch\n\n    print(f\"Epoch {epoch+1:02d}/{EPOCHS} \"\n          f\"| Loss: {train_loss:.4f} \"\n          f\"| Train Acc: {train_acc:.4f} \"\n          f\"| Val Acc: {val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:48:25.321826Z","iopub.execute_input":"2026-03-14T11:48:25.322506Z","iopub.status.idle":"2026-03-14T11:48:27.359271Z","shell.execute_reply.started":"2026-03-14T11:48:25.322477Z","shell.execute_reply":"2026-03-14T11:48:27.358265Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_acc = eval_epoch(test_loader)\nprint(\"Final Test Accuracy:\", round(test_acc, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:39:50.942311Z","iopub.status.idle":"2026-03-14T11:39:50.942587Z","shell.execute_reply.started":"2026-03-14T11:39:50.942455Z","shell.execute_reply":"2026-03-14T11:39:50.942473Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport torch\nfrom sklearn.metrics import (\n    accuracy_score,\n    precision_score,\n    recall_score,\n    f1_score,\n    confusion_matrix,\n    classification_report,\n    cohen_kappa_score,\n    roc_auc_score\n)\nfrom sklearn.preprocessing import label_binarize","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:39:50.943619Z","iopub.status.idle":"2026-03-14T11:39:50.943887Z","shell.execute_reply.started":"2026-03-14T11:39:50.943743Z","shell.execute_reply":"2026-03-14T11:39:50.943757Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_predictions(model, loader, device):\n    model.eval()\n    y_true, y_pred, y_prob = [], [], []\n\n    with torch.no_grad():\n        for images, labels in loader:\n            images  = images.to(device)\n            labels  = labels.to(device)\n            outputs = model(images)\n            probs   = torch.softmax(outputs, dim=1)\n            preds   = torch.argmax(outputs, dim=1)\n\n            y_true.extend(labels.cpu().numpy())\n            y_pred.extend(preds.cpu().numpy())\n            y_prob.extend(probs.cpu().numpy())\n\n    return np.array(y_true), np.array(y_pred), np.array(y_prob)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:39:50.945137Z","iopub.status.idle":"2026-03-14T11:39:50.945367Z","shell.execute_reply.started":"2026-03-14T11:39:50.945261Z","shell.execute_reply":"2026-03-14T11:39:50.945274Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def evaluate_all_metrics(model, loader, device, name=\"SET\"):\n    y_true, y_pred, y_prob = get_predictions(model, loader, device)\n\n    acc                = accuracy_score(y_true, y_pred)\n    precision_macro    = precision_score(y_true, y_pred, average=\"macro\",    zero_division=0)\n    precision_weighted = precision_score(y_true, y_pred, average=\"weighted\", zero_division=0)\n    recall_macro       = recall_score(y_true, y_pred,    average=\"macro\",    zero_division=0)\n    recall_weighted    = recall_score(y_true, y_pred,    average=\"weighted\", zero_division=0)\n    f1_macro           = f1_score(y_true, y_pred,        average=\"macro\",    zero_division=0)\n    f1_weighted        = f1_score(y_true, y_pred,        average=\"weighted\", zero_division=0)\n    qwk                = cohen_kappa_score(y_true, y_pred, weights=\"quadratic\")\n    cm                 = confusion_matrix(y_true, y_pred)\n\n    print(f\"\\n========== {name} RESULTS ==========\")\n    print(f\"Accuracy              : {acc:.4f}\")\n    print(f\"Precision (Macro)     : {precision_macro:.4f}\")\n    print(f\"Precision (Weighted)  : {precision_weighted:.4f}\")\n    print(f\"Recall/Sensitivity(M) : {recall_macro:.4f}\")\n    print(f\"Recall/Sensitivity(W) : {recall_weighted:.4f}\")\n    print(f\"F1-score (Macro)      : {f1_macro:.4f}\")\n    print(f\"F1-score (Weighted)   : {f1_weighted:.4f}\")\n    print(f\"QWK                   : {qwk:.4f}\")\n    print(\"\\nConfusion Matrix:\")\n    print(cm)\n    print(\"\\nClassification Report:\")\n    print(classification_report(y_true, y_pred, digits=4))\n\n    return y_true, y_pred, y_prob, cm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:39:50.946745Z","iopub.status.idle":"2026-03-14T11:39:50.947033Z","shell.execute_reply.started":"2026-03-14T11:39:50.946921Z","shell.execute_reply":"2026-03-14T11:39:50.946936Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def per_class_accuracy(cm):\n    class_acc = cm.diagonal() / cm.sum(axis=1)\n    for i, acc in enumerate(class_acc):\n        print(f\"Class {i} Accuracy: {acc:.4f}\")\n    return class_acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:39:52.401523Z","iopub.execute_input":"2026-03-14T11:39:52.4021Z","iopub.status.idle":"2026-03-14T11:39:52.406274Z","shell.execute_reply.started":"2026-03-14T11:39:52.402068Z","shell.execute_reply":"2026-03-14T11:39:52.405637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def specificity_per_class(cm):\n    spec = []\n    for i in range(len(cm)):\n        tn     = np.sum(cm) - (np.sum(cm[i,:]) + np.sum(cm[:,i]) - cm[i,i])\n        fp     = np.sum(cm[:,i]) - cm[i,i]\n        spec_i = tn / (tn + fp)\n        spec.append(spec_i)\n        print(f\"Class {i} Specificity: {spec_i:.4f}\")\n    return spec","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:39:52.677516Z","iopub.execute_input":"2026-03-14T11:39:52.677991Z","iopub.status.idle":"2026-03-14T11:39:52.682471Z","shell.execute_reply.started":"2026-03-14T11:39:52.677963Z","shell.execute_reply":"2026-03-14T11:39:52.681834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def multiclass_roc_auc(y_true, y_prob, n_classes=5):\n    y_true_bin = label_binarize(y_true, classes=list(range(n_classes)))\n    auc        = roc_auc_score(y_true_bin, y_prob, average=\"macro\", multi_class=\"ovr\")\n    print(f\"ROC-AUC (Macro, OVR): {auc:.4f}\")\n    return auc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:39:54.627282Z","iopub.execute_input":"2026-03-14T11:39:54.628049Z","iopub.status.idle":"2026-03-14T11:39:54.631927Z","shell.execute_reply.started":"2026-03-14T11:39:54.628019Z","shell.execute_reply":"2026-03-14T11:39:54.631174Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ── TRAIN EVALUATION ──\ny_t, y_p, y_prob, cm_train = evaluate_all_metrics(model, train_loader, device, name=\"TRAIN\")\nper_class_accuracy(cm_train)\nspecificity_per_class(cm_train)\nmulticlass_roc_auc(y_t, y_prob)\n\n# ── VALIDATION EVALUATION ──\ny_t, y_p, y_prob, cm_val = evaluate_all_metrics(model, val_loader, device, name=\"VALIDATION\")\nper_class_accuracy(cm_val)\nspecificity_per_class(cm_val)\nmulticlass_roc_auc(y_t, y_prob)\n\n# ── TEST EVALUATION ──\ny_t, y_p, y_prob, cm_test = evaluate_all_metrics(model, test_loader, device, name=\"TEST\")\nper_class_accuracy(cm_test)\nspecificity_per_class(cm_test)\nmulticlass_roc_auc(y_t, y_prob)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-14T11:39:55.012363Z","iopub.execute_input":"2026-03-14T11:39:55.013088Z","iopub.status.idle":"2026-03-14T11:39:55.020416Z","shell.execute_reply.started":"2026-03-14T11:39:55.013059Z","shell.execute_reply":"2026-03-14T11:39:55.019429Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}