{"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":"gpu","dataSources":[{"sourceType":"competition","sourceId":14774,"databundleVersionId":875431}],"dockerImageVersionId":31287,"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,"execution":{"iopub.status.busy":"2026-03-14T11:55:32.892011Z","iopub.execute_input":"2026-03-14T11:55:32.892636Z","iopub.status.idle":"2026-03-14T11:55:37.504493Z","shell.execute_reply.started":"2026-03-14T11:55:32.892597Z","shell.execute_reply":"2026-03-14T11:55:37.503724Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install timm albumentations --quiet","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T12:38:50.352227Z","iopub.status.idle":"2026-05-27T12:38:50.352605Z","shell.execute_reply.started":"2026-05-27T12:38:50.352406Z","shell.execute_reply":"2026-05-27T12:38:50.352428Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import (\n    accuracy_score, precision_score, recall_score,\n    f1_score, confusion_matrix, classification_report,\n    cohen_kappa_score, roc_auc_score\n)\nfrom sklearn.preprocessing import label_binarize\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\nfrom PIL import Image\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T12:38:50.353354Z","iopub.status.idle":"2026-05-27T12:38:50.353727Z","shell.execute_reply.started":"2026-05-27T12:38:50.353531Z","shell.execute_reply":"2026-05-27T12:38:50.353553Z"}},"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\"))\n\nprint(\"Total images:\", len(df))\nprint(df[\"diagnosis\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T12:38:50.355352Z","iopub.status.idle":"2026-05-27T12:38:50.355642Z","shell.execute_reply.started":"2026-05-27T12:38:50.355512Z","shell.execute_reply":"2026-05-27T12:38:50.355536Z"}},"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-05-27T12:38:50.356821Z","iopub.status.idle":"2026-05-27T12:38:50.357704Z","shell.execute_reply.started":"2026-05-27T12:38:50.357507Z","shell.execute_reply":"2026-05-27T12:38:50.357529Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_tfms = A.Compose([\n    A.RandomResizedCrop(size=(224, 224), scale=(0.8, 1.0), p=1.0),\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(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n    ToTensorV2()\n])\n\nval_tfms = A.Compose([\n    A.Resize(224, 224),\n    A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n    ToTensorV2()\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T12:38:50.358586Z","iopub.status.idle":"2026-05-27T12:38:50.358976Z","shell.execute_reply.started":"2026-05-27T12:38:50.358803Z","shell.execute_reply":"2026-05-27T12:38:50.358828Z"}},"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 = int(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-05-27T12:38:50.360021Z","iopub.status.idle":"2026-05-27T12:38:50.360380Z","shell.execute_reply.started":"2026-05-27T12:38:50.360205Z","shell.execute_reply":"2026-05-27T12:38:50.360227Z"}},"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=16, shuffle=True,  num_workers=2)\nval_loader   = DataLoader(val_ds,   batch_size=16, shuffle=False, num_workers=2)\ntest_loader  = DataLoader(test_ds,  batch_size=16, shuffle=False, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T12:38:50.361744Z","iopub.status.idle":"2026-05-27T12:38:50.362493Z","shell.execute_reply.started":"2026-05-27T12:38:50.362355Z","shell.execute_reply":"2026-05-27T12:38:50.362381Z"}},"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-05-27T12:38:50.363474Z","iopub.status.idle":"2026-05-27T12:38:50.363844Z","shell.execute_reply.started":"2026-05-27T12:38:50.363622Z","shell.execute_reply":"2026-05-27T12:38:50.363665Z"}},"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-05-27T12:38:50.444129Z","iopub.execute_input":"2026-05-27T12:38:50.444477Z","iopub.status.idle":"2026-05-27T12:38:50.452322Z","shell.execute_reply.started":"2026-05-27T12:38:50.444451Z","shell.execute_reply":"2026-05-27T12:38:50.451095Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", device)\n\nmodel     = EfficientNet_ViT(num_classes=5).to(device)\ncriterion = FocalLoss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)\nscheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T12:38:50.453466Z","iopub.status.idle":"2026-05-27T12:38:50.453944Z","shell.execute_reply.started":"2026-05-27T12:38:50.453653Z","shell.execute_reply":"2026-05-27T12:38:50.453676Z"}},"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-05-27T12:38:50.455114Z","iopub.status.idle":"2026-05-27T12:38:50.455535Z","shell.execute_reply.started":"2026-05-27T12:38:50.455378Z","shell.execute_reply":"2026-05-27T12:38:50.455394Z"}},"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-05-27T12:38:50.456963Z","iopub.status.idle":"2026-05-27T12:38:50.457357Z","shell.execute_reply.started":"2026-05-27T12:38:50.457171Z","shell.execute_reply":"2026-05-27T12:38:50.457191Z"}},"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()\n    print(f\"Epoch {epoch+1:02d}/{EPOCHS} | Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f} | Val Acc: {val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T12:38:50.458945Z","iopub.status.idle":"2026-05-27T12:38:50.459306Z","shell.execute_reply.started":"2026-05-27T12:38:50.459132Z","shell.execute_reply":"2026-05-27T12:38:50.459152Z"}},"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-05-27T12:38:51.557115Z","iopub.execute_input":"2026-05-27T12:38:51.557389Z","iopub.status.idle":"2026-05-27T12:38:51.565648Z","shell.execute_reply.started":"2026-05-27T12:38:51.557366Z","shell.execute_reply":"2026-05-27T12:38:51.564587Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_predictions(loader):\n    model.eval()\n    y_true, y_pred, y_prob = [], [], []\n\n    with torch.no_grad():\n        for x, y in loader:\n            x = x.to(device)\n            out   = model(x)\n            probs = torch.softmax(out, dim=1)\n            preds = torch.argmax(out, dim=1)\n\n            y_true.extend(y.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-05-27T12:38:51.959730Z","iopub.execute_input":"2026-05-27T12:38:51.960388Z","iopub.status.idle":"2026-05-27T12:38:51.966344Z","shell.execute_reply.started":"2026-05-27T12:38:51.960357Z","shell.execute_reply":"2026-05-27T12:38:51.965678Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def evaluate_all(loader, name=\"TEST\"):\n    y_true, y_pred, y_prob = get_predictions(loader)\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_weight = 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_weight    = 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_weight        = 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    y_true_bin = label_binarize(y_true, classes=[0,1,2,3,4])\n    auc = roc_auc_score(y_true_bin, y_prob, average=\"macro\", multi_class=\"ovr\")\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_weight:.4f}\")\n    print(f\"Recall    (Macro)     : {recall_macro:.4f}\")\n    print(f\"Recall    (Weighted)  : {recall_weight:.4f}\")\n    print(f\"F1-Score  (Macro)     : {f1_macro:.4f}\")\n    print(f\"F1-Score  (Weighted)  : {f1_weight:.4f}\")\n    print(f\"QWK                   : {qwk:.4f}\")\n    print(f\"ROC-AUC  (Macro)      : {auc:.4f}\")\n    print(\"\\nConfusion Matrix:\")\n    print(cm)\n    print(\"\\nClassification Report:\")\n    print(classification_report(y_true, y_pred, digits=4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T12:38:53.798842Z","iopub.execute_input":"2026-05-27T12:38:53.799596Z","iopub.status.idle":"2026-05-27T12:38:53.806812Z","shell.execute_reply.started":"2026-05-27T12:38:53.799565Z","shell.execute_reply":"2026-05-27T12:38:53.805926Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"evaluate_all(train_loader, name=\"TRAIN\")\nevaluate_all(val_loader,   name=\"VALIDATION\")\nevaluate_all(test_loader,  name=\"TEST\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T12:38:57.986437Z","iopub.execute_input":"2026-05-27T12:38:57.987017Z","iopub.status.idle":"2026-05-27T12:38:57.992948Z","shell.execute_reply.started":"2026-05-27T12:38:57.986989Z","shell.execute_reply":"2026-05-27T12:38:57.991819Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}