{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:42:59.552984Z","iopub.execute_input":"2026-08-26T03:42:59.553270Z","iopub.status.idle":"2026-08-26T03:43:10.965921Z","shell.execute_reply.started":"2026-08-26T03:42:59.553232Z","shell.execute_reply":"2026-08-26T03:43:10.965359Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(os.listdir(\"/kaggle/input\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:10.967057Z","iopub.execute_input":"2026-08-26T03:43:10.967577Z","iopub.status.idle":"2026-08-26T03:43:10.972089Z","shell.execute_reply.started":"2026-08-26T03:43:10.967551Z","shell.execute_reply":"2026-08-26T03:43:10.971448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor root, folders, files in os.walk(\"/kaggle/input/competitions\"):\n    print(root)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:10.972943Z","iopub.execute_input":"2026-08-26T03:43:10.973221Z","iopub.status.idle":"2026-08-26T03:43:12.615336Z","shell.execute_reply.started":"2026-08-26T03:43:10.973198Z","shell.execute_reply":"2026-08-26T03:43:12.614707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\ndataset_path = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\n\ntrain_csv = pd.read_csv(dataset_path + \"/train.csv\")\n\nprint(train_csv.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:12.616388Z","iopub.execute_input":"2026-08-26T03:43:12.616844Z","iopub.status.idle":"2026-08-26T03:43:12.652863Z","shell.execute_reply.started":"2026-08-26T03:43:12.616815Z","shell.execute_reply":"2026-08-26T03:43:12.652214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(train_csv[\"diagnosis\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:12.654622Z","iopub.execute_input":"2026-08-26T03:43:12.654829Z","iopub.status.idle":"2026-08-26T03:43:12.677016Z","shell.execute_reply.started":"2026-08-26T03:43:12.654809Z","shell.execute_reply":"2026-08-26T03:43:12.676219Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom PIL import Image\nimport os\n\nimage_folder = dataset_path + \"/train_images\"\n\nimage_name = train_csv.iloc[0][\"id_code\"]\n\nimage_path = os.path.join(image_folder, image_name + \".png\")\n\nimage = Image.open(image_path)\n\nplt.figure(figsize=(8, 8))\nplt.imshow(image)\nplt.axis(\"off\")\nplt.show()\n\nprint(\"Image name:\", image_name)\nprint(\"DR diagnosis:\", train_csv.iloc[0][\"diagnosis\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:12.677942Z","iopub.execute_input":"2026-08-26T03:43:12.678233Z","iopub.status.idle":"2026-08-26T03:43:13.515340Z","shell.execute_reply.started":"2026-08-26T03:43:12.678201Z","shell.execute_reply":"2026-08-26T03:43:13.514741Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom PIL import Image\nimport os\n\nfig, axes = plt.subplots(1, 5, figsize=(20, 4))\n\nfor class_no in range(5):\n\n    # Find one image belonging to this class\n    row = train_csv[train_csv[\"diagnosis\"] == class_no].iloc[0]\n\n    image_name = row[\"id_code\"]\n    image_path = os.path.join(\n        image_folder,\n        image_name + \".png\"\n    )\n\n    image = Image.open(image_path)\n\n    axes[class_no].imshow(image)\n    axes[class_no].set_title(f\"Class {class_no}\")\n    axes[class_no].axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:13.516198Z","iopub.execute_input":"2026-08-26T03:43:13.516594Z","iopub.status.idle":"2026-08-26T03:43:15.960397Z","shell.execute_reply.started":"2026-08-26T03:43:13.516558Z","shell.execute_reply":"2026-08-26T03:43:15.959407Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Image size:\", image.size)\nprint(\"Image mode:\", image.mode)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:15.961585Z","iopub.execute_input":"2026-08-26T03:43:15.961916Z","iopub.status.idle":"2026-08-26T03:43:15.966880Z","shell.execute_reply.started":"2026-08-26T03:43:15.961881Z","shell.execute_reply":"2026-08-26T03:43:15.966292Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nresized_image = image.resize((512, 512))\n\nplt.figure(figsize=(6, 6))\nplt.imshow(resized_image)\nplt.axis(\"off\")\nplt.show()\n\nprint(\"Original size:\", image.size)\nprint(\"New size:\", resized_image.size)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:15.967786Z","iopub.execute_input":"2026-08-26T03:43:15.968195Z","iopub.status.idle":"2026-08-26T03:43:16.178992Z","shell.execute_reply.started":"2026-08-26T03:43:15.968163Z","shell.execute_reply":"2026-08-26T03:43:16.178468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\n\nimg = np.array(image)\n\n# Convert RGB to grayscale\ngray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)\n\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.imshow(image)\nplt.title(\"Original\")\nplt.axis(\"off\")\n\nplt.subplot(1, 2, 2)\nplt.imshow(gray, cmap=\"gray\")\nplt.title(\"Grayscale\")\nplt.axis(\"off\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:16.179770Z","iopub.execute_input":"2026-08-26T03:43:16.180010Z","iopub.status.idle":"2026-08-26T03:43:18.234871Z","shell.execute_reply.started":"2026-08-26T03:43:16.179988Z","shell.execute_reply":"2026-08-26T03:43:18.234244Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create a mask\n_, mask = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY)\n\nplt.figure(figsize=(6, 6))\nplt.imshow(mask, cmap=\"gray\")\nplt.title(\"Retina Mask\")\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:18.235899Z","iopub.execute_input":"2026-08-26T03:43:18.236147Z","iopub.status.idle":"2026-08-26T03:43:19.374371Z","shell.execute_reply.started":"2026-08-26T03:43:18.236124Z","shell.execute_reply":"2026-08-26T03:43:19.373701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Find the area where the retina exists\npoints = cv2.findNonZero(mask)\n\n# Find the smallest rectangle around the retina\nx, y, w, h = cv2.boundingRect(points)\n\n# Crop the original image\ncropped_image = image.crop((x, y, x + w, y + h))\n\n# Show the result\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.imshow(image)\nplt.title(\"Original\")\nplt.axis(\"off\")\n\nplt.subplot(1, 2, 2)\nplt.imshow(cropped_image)\nplt.title(\"Cropped Retina\")\nplt.axis(\"off\")\n\nplt.show()\n\nprint(\"Original size:\", image.size)\nprint(\"Cropped size:\", cropped_image.size)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:19.375272Z","iopub.execute_input":"2026-08-26T03:43:19.375609Z","iopub.status.idle":"2026-08-26T03:43:20.327213Z","shell.execute_reply.started":"2026-08-26T03:43:19.375581Z","shell.execute_reply":"2026-08-26T03:43:20.326429Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert cropped image to OpenCV format\ncropped_array = np.array(cropped_image)\n\n# Convert RGB to LAB color space\nlab_image = cv2.cvtColor(cropped_array, cv2.COLOR_RGB2LAB)\n\nprint(\"LAB image shape:\", lab_image.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:20.328139Z","iopub.execute_input":"2026-08-26T03:43:20.328342Z","iopub.status.idle":"2026-08-26T03:43:20.485802Z","shell.execute_reply.started":"2026-08-26T03:43:20.328320Z","shell.execute_reply":"2026-08-26T03:43:20.485106Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Split LAB image into 3 channels\nl_channel, a_channel, b_channel = cv2.split(lab_image)\n\n# Create CLAHE\nclahe = cv2.createCLAHE(\n    clipLimit=2.0,\n    tileGridSize=(8, 8)\n)\n\n# Improve the brightness/contrast channel\nl_enhanced = clahe.apply(l_channel)\n\n# Put the channels back together\nlab_enhanced = cv2.merge(\n    (l_enhanced, a_channel, b_channel)\n)\n\n# Convert back to RGB\nenhanced_array = cv2.cvtColor(\n    lab_enhanced,\n    cv2.COLOR_LAB2RGB\n)\n\nenhanced_image = Image.fromarray(enhanced_array)\n\n# Show before and after\nplt.figure(figsize=(14, 6))\n\nplt.subplot(1, 2, 1)\nplt.imshow(cropped_image)\nplt.title(\"Before CLAHE\")\nplt.axis(\"off\")\n\nplt.subplot(1, 2, 2)\nplt.imshow(enhanced_image)\nplt.title(\"After CLAHE\")\nplt.axis(\"off\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:20.487998Z","iopub.execute_input":"2026-08-26T03:43:20.488282Z","iopub.status.idle":"2026-08-26T03:43:21.484196Z","shell.execute_reply.started":"2026-08-26T03:43:20.488258Z","shell.execute_reply":"2026-08-26T03:43:21.483324Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert image to NumPy array\nenhanced_array = np.array(enhanced_image)\n\n# Convert pixel values from 0-255 to 0-1\nnormalized_image = enhanced_array / 255.0\n\nprint(\"Minimum pixel value:\", normalized_image.min())\nprint(\"Maximum pixel value:\", normalized_image.max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:21.485092Z","iopub.execute_input":"2026-08-26T03:43:21.485392Z","iopub.status.idle":"2026-08-26T03:43:21.566757Z","shell.execute_reply.started":"2026-08-26T03:43:21.485369Z","shell.execute_reply":"2026-08-26T03:43:21.565927Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\ntrain_df, val_df = train_test_split(\n    train_csv,\n    test_size=0.20,\n    random_state=42,\n    stratify=train_csv[\"diagnosis\"]\n)\n\nprint(\"Training images:\", len(train_df))\nprint(\"Validation images:\", len(val_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:21.567758Z","iopub.execute_input":"2026-08-26T03:43:21.568142Z","iopub.status.idle":"2026-08-26T03:43:22.443912Z","shell.execute_reply.started":"2026-08-26T03:43:21.568108Z","shell.execute_reply":"2026-08-26T03:43:22.443250Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Training class distribution:\")\nprint(train_df[\"diagnosis\"].value_counts().sort_index())\n\nprint(\"\\nValidation class distribution:\")\nprint(val_df[\"diagnosis\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:22.444803Z","iopub.execute_input":"2026-08-26T03:43:22.445219Z","iopub.status.idle":"2026-08-26T03:43:22.451808Z","shell.execute_reply.started":"2026-08-26T03:43:22.445192Z","shell.execute_reply":"2026-08-26T03:43:22.450932Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\nprint(\"PyTorch version:\", torch.__version__)\nprint(\"GPU available:\", torch.cuda.is_available())\n\nif torch.cuda.is_available():\n    print(\"GPU:\", torch.cuda.get_device_name(0))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:22.452732Z","iopub.execute_input":"2026-08-26T03:43:22.453008Z","iopub.status.idle":"2026-08-26T03:43:29.840208Z","shell.execute_reply.started":"2026-08-26T03:43:22.452977Z","shell.execute_reply":"2026-08-26T03:43:29.839553Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom PIL import Image\nimport os","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:29.841058Z","iopub.execute_input":"2026-08-26T03:43:29.841580Z","iopub.status.idle":"2026-08-26T03:43:29.845682Z","shell.execute_reply.started":"2026-08-26T03:43:29.841551Z","shell.execute_reply":"2026-08-26T03:43:29.844813Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class APTOSDataset(Dataset):\n\n    def __init__(self, dataframe, image_folder, transform=None):\n\n        self.dataframe = dataframe.reset_index(drop=True)\n        self.image_folder = image_folder\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.dataframe)\n\n    def __getitem__(self, index):\n\n        # Get image name\n        image_name = self.dataframe.iloc[index][\"id_code\"]\n\n        # Get label\n        label = self.dataframe.iloc[index][\"diagnosis\"]\n\n        # Create image path\n        image_path = os.path.join(\n            self.image_folder,\n            image_name + \".png\"\n        )\n\n        # Open image\n        image = Image.open(image_path).convert(\"RGB\")\n\n        # Apply transformations\n        if self.transform:\n            image = self.transform(image)\n\n        return image, torch.tensor(label, dtype=torch.long)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:29.846479Z","iopub.execute_input":"2026-08-26T03:43:29.846812Z","iopub.status.idle":"2026-08-26T03:43:29.860238Z","shell.execute_reply.started":"2026-08-26T03:43:29.846779Z","shell.execute_reply":"2026-08-26T03:43:29.859467Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision import transforms\n\ntransform = transforms.Compose([\n    transforms.Resize((512, 512)),\n    transforms.ToTensor()\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:29.861126Z","iopub.execute_input":"2026-08-26T03:43:29.861814Z","iopub.status.idle":"2026-08-26T03:43:34.205090Z","shell.execute_reply.started":"2026-08-26T03:43:29.861790Z","shell.execute_reply":"2026-08-26T03:43:34.204141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_folder = dataset_path + \"/train_images\"\n\ntrain_dataset = APTOSDataset(\n    dataframe=train_df,\n    image_folder=image_folder,\n    transform=transform\n)\n\nval_dataset = APTOSDataset(\n    dataframe=val_df,\n    image_folder=image_folder,\n    transform=transform\n)\n\nprint(\"Training dataset:\", len(train_dataset))\nprint(\"Validation dataset:\", len(val_dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:34.207138Z","iopub.execute_input":"2026-08-26T03:43:34.207666Z","iopub.status.idle":"2026-08-26T03:43:34.217141Z","shell.execute_reply.started":"2026-08-26T03:43:34.207623Z","shell.execute_reply":"2026-08-26T03:43:34.215140Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_tensor, label = train_dataset[0]\n\nprint(\"Image tensor shape:\", image_tensor.shape)\nprint(\"Label:\", label)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:34.218377Z","iopub.execute_input":"2026-08-26T03:43:34.218740Z","iopub.status.idle":"2026-08-26T03:43:34.404498Z","shell.execute_reply.started":"2026-08-26T03:43:34.218701Z","shell.execute_reply":"2026-08-26T03:43:34.403614Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nprint(\"Training batches:\", len(train_loader))\nprint(\"Validation batches:\", len(val_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:43:34.405543Z","iopub.execute_input":"2026-08-26T03:43:34.406216Z","iopub.status.idle":"2026-08-26T03:43:34.410612Z","shell.execute_reply.started":"2026-08-26T03:43:34.406187Z","shell.execute_reply":"2026-08-26T03:43:34.410006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images, labels = next(iter(train_loader))\n\nprint(\"Images shape:\", images.shape)\nprint(\"Labels shape:\", labels.shape)\nprint(\"Labels:\", labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:44:18.392673Z","iopub.execute_input":"2026-08-26T03:44:18.393175Z","iopub.status.idle":"2026-08-26T03:44:23.030481Z","shell.execute_reply.started":"2026-08-26T03:44:18.393146Z","shell.execute_reply":"2026-08-26T03:44:23.029590Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torchvision.models import efficientnet_v2_s, EfficientNet_V2_S_Weights\n\nprint(\"EfficientNetV2-S imported successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:45:09.913163Z","iopub.execute_input":"2026-08-26T03:45:09.914031Z","iopub.status.idle":"2026-08-26T03:45:09.918861Z","shell.execute_reply.started":"2026-08-26T03:45:09.913992Z","shell.execute_reply":"2026-08-26T03:45:09.917970Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"weights = EfficientNet_V2_S_Weights.DEFAULT\n\nmodel = efficientnet_v2_s(weights=weights)\n\nprint(\"Model loaded!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:45:31.927168Z","iopub.execute_input":"2026-08-26T03:45:31.927804Z","iopub.status.idle":"2026-08-26T03:45:32.986467Z","shell.execute_reply.started":"2026-08-26T03:45:31.927771Z","shell.execute_reply":"2026-08-26T03:45:32.985830Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(model.classifier)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:45:48.925955Z","iopub.execute_input":"2026-08-26T03:45:48.926375Z","iopub.status.idle":"2026-08-26T03:45:48.930448Z","shell.execute_reply.started":"2026-08-26T03:45:48.926345Z","shell.execute_reply":"2026-08-26T03:45:48.929696Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.classifier[1] = nn.Linear(\n    model.classifier[1].in_features,\n    5\n)\n\nprint(model.classifier)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:46:07.246803Z","iopub.execute_input":"2026-08-26T03:46:07.247245Z","iopub.status.idle":"2026-08-26T03:46:07.253253Z","shell.execute_reply.started":"2026-08-26T03:46:07.247212Z","shell.execute_reply":"2026-08-26T03:46:07.252265Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = model.to(device)\n\nprint(\"Using device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:46:23.987612Z","iopub.execute_input":"2026-08-26T03:46:23.988156Z","iopub.status.idle":"2026-08-26T03:46:24.061880Z","shell.execute_reply.started":"2026-08-26T03:46:23.988092Z","shell.execute_reply":"2026-08-26T03:46:24.060985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss()\n\nprint(\"Loss function created!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:47:33.346733Z","iopub.execute_input":"2026-08-26T03:47:33.347486Z","iopub.status.idle":"2026-08-26T03:47:33.351538Z","shell.execute_reply.started":"2026-08-26T03:47:33.347456Z","shell.execute_reply":"2026-08-26T03:47:33.350865Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=1e-4,\n    weight_decay=1e-4\n)\n\nprint(\"Optimizer created!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:47:43.306959Z","iopub.execute_input":"2026-08-26T03:47:43.307351Z","iopub.status.idle":"2026-08-26T03:47:43.314033Z","shell.execute_reply.started":"2026-08-26T03:47:43.307321Z","shell.execute_reply":"2026-08-26T03:47:43.313228Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=10\n)\n\nprint(\"Learning-rate scheduler created!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:48:01.406730Z","iopub.execute_input":"2026-08-26T03:48:01.407321Z","iopub.status.idle":"2026-08-26T03:48:01.411798Z","shell.execute_reply.started":"2026-08-26T03:48:01.407288Z","shell.execute_reply":"2026-08-26T03:48:01.410920Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\n\nclass_counts = train_df[\"diagnosis\"].value_counts().sort_index().values\n\nclass_weights = len(train_df) / (5 * class_counts)\n\nprint(\"Class counts:\")\nprint(class_counts)\n\nprint(\"\\nClass weights:\")\nprint(class_weights)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:49:22.706897Z","iopub.execute_input":"2026-08-26T03:49:22.707327Z","iopub.status.idle":"2026-08-26T03:49:22.715110Z","shell.execute_reply.started":"2026-08-26T03:49:22.707294Z","shell.execute_reply":"2026-08-26T03:49:22.714237Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_weights = torch.tensor(\n    class_weights,\n    dtype=torch.float32\n).to(device)\n\nprint(\"Class weights on:\", class_weights.device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:49:50.677322Z","iopub.execute_input":"2026-08-26T03:49:50.678100Z","iopub.status.idle":"2026-08-26T03:49:50.684311Z","shell.execute_reply.started":"2026-08-26T03:49:50.678067Z","shell.execute_reply":"2026-08-26T03:49:50.683668Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss(\n    weight=class_weights\n)\n\nprint(\"Weighted loss function created!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:49:55.135949Z","iopub.execute_input":"2026-08-26T03:49:55.136574Z","iopub.status.idle":"2026-08-26T03:49:55.140841Z","shell.execute_reply.started":"2026-08-26T03:49:55.136545Z","shell.execute_reply":"2026-08-26T03:49:55.139978Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(model, loader, criterion, optimizer, device):\n\n    model.train()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in loader:\n\n        # Move images and labels to GPU\n        images = images.to(device, non_blocking=True)\n        labels = labels.to(device, non_blocking=True)\n\n        # Remove old gradients\n        optimizer.zero_grad()\n\n        # Model makes predictions\n        outputs = model(images)\n\n        # Calculate error\n        loss = criterion(outputs, labels)\n\n        # Calculate gradients\n        loss.backward()\n\n        # Update model\n        optimizer.step()\n\n        # Keep track of loss\n        running_loss += loss.item() * images.size(0)\n\n        # Find predicted class\n        _, predicted = torch.max(outputs, 1)\n\n        total += labels.size(0)\n        correct += (predicted == labels).sum().item()\n\n    epoch_loss = running_loss / total\n    epoch_accuracy = correct / total\n\n    return epoch_loss, epoch_accuracy","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:52:38.167296Z","iopub.execute_input":"2026-08-26T03:52:38.167715Z","iopub.status.idle":"2026-08-26T03:52:38.173572Z","shell.execute_reply.started":"2026-08-26T03:52:38.167684Z","shell.execute_reply":"2026-08-26T03:52:38.172698Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def validate_one_epoch(model, loader, criterion, device):\n\n    model.eval()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    with torch.no_grad():\n\n        for images, labels in loader:\n\n            images = images.to(device, non_blocking=True)\n            labels = labels.to(device, non_blocking=True)\n\n            outputs = model(images)\n\n            loss = criterion(outputs, labels)\n\n            running_loss += loss.item() * images.size(0)\n\n            _, predicted = torch.max(outputs, 1)\n\n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n\n    epoch_loss = running_loss / total\n    epoch_accuracy = correct / total\n\n    return epoch_loss, epoch_accuracy","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:52:48.157121Z","iopub.execute_input":"2026-08-26T03:52:48.157654Z","iopub.status.idle":"2026-08-26T03:52:48.163325Z","shell.execute_reply.started":"2026-08-26T03:52:48.157622Z","shell.execute_reply":"2026-08-26T03:52:48.162558Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 10\n\nfor epoch in range(num_epochs):\n\n    train_loss, train_acc = train_one_epoch(\n        model,\n        train_loader,\n        criterion,\n        optimizer,\n        device\n    )\n\n    val_loss, val_acc = validate_one_epoch(\n        model,\n        val_loader,\n        criterion,\n        device\n    )\n\n    scheduler.step()\n\n    print(\n        f\"Epoch {epoch + 1}/{num_epochs} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Train Acc: {train_acc:.4f} | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc:.4f}\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T03:53:35.147140Z","iopub.execute_input":"2026-08-26T03:53:35.147935Z","iopub.status.idle":"2026-08-26T04:37:44.619581Z","shell.execute_reply.started":"2026-08-26T03:53:35.147902Z","shell.execute_reply":"2026-08-26T04:37:44.618808Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_transform = transforms.Compose([\n    transforms.Resize((512, 512)),\n\n    transforms.RandomHorizontalFlip(p=0.5),\n\n    transforms.RandomRotation(degrees=10),\n\n    transforms.ColorJitter(\n        brightness=0.15,\n        contrast=0.15\n    ),\n\n    transforms.ToTensor()\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T04:40:47.233227Z","iopub.execute_input":"2026-08-26T04:40:47.233740Z","iopub.status.idle":"2026-08-26T04:40:47.239210Z","shell.execute_reply.started":"2026-08-26T04:40:47.233697Z","shell.execute_reply":"2026-08-26T04:40:47.238496Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_transform = transforms.Compose([\n    transforms.Resize((512, 512)),\n    transforms.ToTensor()\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T04:41:02.422213Z","iopub.execute_input":"2026-08-26T04:41:02.422773Z","iopub.status.idle":"2026-08-26T04:41:02.427184Z","shell.execute_reply.started":"2026-08-26T04:41:02.422740Z","shell.execute_reply":"2026-08-26T04:41:02.426494Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = APTOSDataset(\n    dataframe=train_df,\n    image_folder=image_folder,\n    transform=train_transform\n)\n\nval_dataset = APTOSDataset(\n    dataframe=val_df,\n    image_folder=image_folder,\n    transform=val_transform\n)\n\nprint(\"Training dataset:\", len(train_dataset))\nprint(\"Validation dataset:\", len(val_dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T04:41:18.162225Z","iopub.execute_input":"2026-08-26T04:41:18.162611Z","iopub.status.idle":"2026-08-26T04:41:18.168594Z","shell.execute_reply.started":"2026-08-26T04:41:18.162580Z","shell.execute_reply":"2026-08-26T04:41:18.167689Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nprint(\"Training batches:\", len(train_loader))\nprint(\"Validation batches:\", len(val_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T04:41:44.771840Z","iopub.execute_input":"2026-08-26T04:41:44.772676Z","iopub.status.idle":"2026-08-26T04:41:44.778320Z","shell.execute_reply.started":"2026-08-26T04:41:44.772637Z","shell.execute_reply":"2026-08-26T04:41:44.777501Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.models import efficientnet_v2_s, EfficientNet_V2_S_Weights\nimport torch.nn as nn\n\n# Load pretrained EfficientNetV2-S\nweights = EfficientNet_V2_S_Weights.DEFAULT\n\nmodel = efficientnet_v2_s(weights=weights)\n\n# Change the final layer to 5 DR classes\nmodel.classifier[1] = nn.Linear(\n    model.classifier[1].in_features,\n    5\n)\n\n# Move model to Tesla T4\nmodel = model.to(device)\n\nprint(model.classifier)\nprint(\"Using device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T04:42:35.041391Z","iopub.execute_input":"2026-08-26T04:42:35.041755Z","iopub.status.idle":"2026-08-26T04:42:35.563229Z","shell.execute_reply.started":"2026-08-26T04:42:35.041728Z","shell.execute_reply":"2026-08-26T04:42:35.562322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss(\n    weight=class_weights\n)\n\nprint(\"Weighted loss ready!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T04:42:54.281474Z","iopub.execute_input":"2026-08-26T04:42:54.281872Z","iopub.status.idle":"2026-08-26T04:42:54.286310Z","shell.execute_reply.started":"2026-08-26T04:42:54.281845Z","shell.execute_reply":"2026-08-26T04:42:54.285753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=1e-4,\n    weight_decay=1e-4\n)\n\nprint(\"New optimizer ready!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T04:43:09.726221Z","iopub.execute_input":"2026-08-26T04:43:09.726681Z","iopub.status.idle":"2026-08-26T04:43:09.733810Z","shell.execute_reply.started":"2026-08-26T04:43:09.726650Z","shell.execute_reply":"2026-08-26T04:43:09.733006Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=10\n)\n\nprint(\"New scheduler ready!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T04:43:20.421661Z","iopub.execute_input":"2026-08-26T04:43:20.421916Z","iopub.status.idle":"2026-08-26T04:43:20.432460Z","shell.execute_reply.started":"2026-08-26T04:43:20.421895Z","shell.execute_reply":"2026-08-26T04:43:20.431814Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_val_loss = float(\"inf\")\n\nprint(\"Best validation loss:\", best_val_loss)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T04:43:45.981958Z","iopub.execute_input":"2026-08-26T04:43:45.982905Z","iopub.status.idle":"2026-08-26T04:43:45.987480Z","shell.execute_reply.started":"2026-08-26T04:43:45.982871Z","shell.execute_reply":"2026-08-26T04:43:45.986577Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 10\n\nbest_val_loss = float(\"inf\")\npatience = 3\npatience_counter = 0\n\nfor epoch in range(num_epochs):\n\n    # -------------------------\n    # TRAINING\n    # -------------------------\n    train_loss, train_acc = train_one_epoch(\n        model,\n        train_loader,\n        criterion,\n        optimizer,\n        device\n    )\n\n    # -------------------------\n    # VALIDATION\n    # -------------------------\n    val_loss, val_acc = validate_one_epoch(\n        model,\n        val_loader,\n        criterion,\n        device\n    )\n\n    # Update learning rate\n    scheduler.step()\n\n    # -------------------------\n    # SAVE BEST MODEL\n    # -------------------------\n    if val_loss < best_val_loss:\n\n        best_val_loss = val_loss\n        patience_counter = 0\n\n        torch.save(\n            model.state_dict(),\n            \"/kaggle/working/best_aptos_model.pth\"\n        )\n\n        print(\"  ⭐ Best model saved!\")\n\n    else:\n\n        patience_counter += 1\n\n    # Print results\n    print(\n        f\"Epoch {epoch + 1}/{num_epochs} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Train Acc: {train_acc:.4f} | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc:.4f}\"\n    )\n\n    # -------------------------\n    # EARLY STOPPING\n    # -------------------------\n    if patience_counter >= patience:\n\n        print(\"Early stopping triggered.\")\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T04:45:20.821309Z","iopub.execute_input":"2026-08-26T04:45:20.821732Z","iopub.status.idle":"2026-08-26T05:12:22.926828Z","shell.execute_reply.started":"2026-08-26T04:45:20.821700Z","shell.execute_reply":"2026-08-26T05:12:22.926051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load the best model\nmodel.load_state_dict(\n    torch.load(\n        \"/kaggle/working/best_aptos_model.pth\",\n        map_location=device\n    )\n)\n\nmodel = model.to(device)\nmodel.eval()\n\nprint(\"Best model loaded successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:13:37.294494Z","iopub.execute_input":"2026-08-26T05:13:37.295365Z","iopub.status.idle":"2026-08-26T05:13:37.557105Z","shell.execute_reply.started":"2026-08-26T05:13:37.295324Z","shell.execute_reply":"2026-08-26T05:13:37.556496Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\n\nall_predictions = []\nall_labels = []\n\nmodel.eval()\n\nwith torch.no_grad():\n\n    for images, labels in val_loader:\n\n        images = images.to(device, non_blocking=True)\n\n        outputs = model(images)\n\n        predictions = torch.argmax(outputs, dim=1)\n\n        all_predictions.extend(\n            predictions.cpu().numpy()\n        )\n\n        all_labels.extend(\n            labels.numpy()\n        )\n\nall_predictions = np.array(all_predictions)\nall_labels = np.array(all_labels)\n\nprint(\"Number of predictions:\", len(all_predictions))\nprint(\"Number of actual labels:\", len(all_labels))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:14:17.813771Z","iopub.execute_input":"2026-08-26T05:14:17.814176Z","iopub.status.idle":"2026-08-26T05:15:05.020085Z","shell.execute_reply.started":"2026-08-26T05:14:17.814148Z","shell.execute_reply":"2026-08-26T05:15:05.019204Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\n\ncm = confusion_matrix(\n    all_labels,\n    all_predictions,\n    labels=[0, 1, 2, 3, 4]\n)\n\nprint(cm)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:15:55.674543Z","iopub.execute_input":"2026-08-26T05:15:55.675003Z","iopub.status.idle":"2026-08-26T05:15:55.696283Z","shell.execute_reply.started":"2026-08-26T05:15:55.674966Z","shell.execute_reply":"2026-08-26T05:15:55.695714Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import classification_report\n\nreport = classification_report(\n    all_labels,\n    all_predictions,\n    labels=[0, 1, 2, 3, 4],\n    target_names=[\n        \"No DR\",\n        \"Mild\",\n        \"Moderate\",\n        \"Severe\",\n        \"Proliferative\"\n    ],\n    digits=4\n)\n\nprint(report)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:16:57.134081Z","iopub.execute_input":"2026-08-26T05:16:57.134526Z","iopub.status.idle":"2026-08-26T05:16:57.147942Z","shell.execute_reply.started":"2026-08-26T05:16:57.134495Z","shell.execute_reply":"2026-08-26T05:16:57.147123Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert 5-class labels into 2-class labels\n\nactual_referable = (all_labels >= 2).astype(int)\npredicted_referable = (all_predictions >= 2).astype(int)\n\nprint(\"Actual referable cases:\", actual_referable.sum())\nprint(\"Predicted referable cases:\", predicted_referable.sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:17:25.809248Z","iopub.execute_input":"2026-08-26T05:17:25.810083Z","iopub.status.idle":"2026-08-26T05:17:25.815029Z","shell.execute_reply.started":"2026-08-26T05:17:25.810047Z","shell.execute_reply":"2026-08-26T05:17:25.814114Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\n\nreferable_cm = confusion_matrix(\n    actual_referable,\n    predicted_referable\n)\n\nprint(\"Referable DR confusion matrix:\")\nprint(referable_cm)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:17:36.014567Z","iopub.execute_input":"2026-08-26T05:17:36.014842Z","iopub.status.idle":"2026-08-26T05:17:36.021773Z","shell.execute_reply.started":"2026-08-26T05:17:36.014818Z","shell.execute_reply":"2026-08-26T05:17:36.021074Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tn, fp, fn, tp = referable_cm.ravel()\n\nsensitivity = tp / (tp + fn)\nspecificity = tn / (tn + fp)\n\nprint(f\"Sensitivity: {sensitivity:.4f}\")\nprint(f\"Specificity: {specificity:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:17:56.694224Z","iopub.execute_input":"2026-08-26T05:17:56.694644Z","iopub.status.idle":"2026-08-26T05:17:56.699518Z","shell.execute_reply.started":"2026-08-26T05:17:56.694614Z","shell.execute_reply":"2026-08-26T05:17:56.698774Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"baseline_results = {\n    \"accuracy\": 0.7681,\n    \"macro_f1\": 0.6432,\n    \"referable_sensitivity\": 0.9161,\n    \"referable_specificity\": 0.9517\n}\n\nprint(baseline_results)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:19:20.633877Z","iopub.execute_input":"2026-08-26T05:19:20.634371Z","iopub.status.idle":"2026-08-26T05:19:20.639019Z","shell.execute_reply.started":"2026-08-26T05:19:20.634340Z","shell.execute_reply":"2026-08-26T05:19:20.638221Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\n# First: separate 15% for the final test set\ntrain_val_df, test_df = train_test_split(\n    train_csv,\n    test_size=0.15,\n    random_state=42,\n    stratify=train_csv[\"diagnosis\"]\n)\n\n# Second: split the remaining 85%\n# 15% of the total becomes validation\ntrain_df, val_df = train_test_split(\n    train_val_df,\n    test_size=0.17647,\n    random_state=42,\n    stratify=train_val_df[\"diagnosis\"]\n)\n\nprint(\"Training images:\", len(train_df))\nprint(\"Validation images:\", len(val_df))\nprint(\"Test images:\", len(test_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:19:44.059277Z","iopub.execute_input":"2026-08-26T05:19:44.059993Z","iopub.status.idle":"2026-08-26T05:19:44.076492Z","shell.execute_reply.started":"2026-08-26T05:19:44.059960Z","shell.execute_reply":"2026-08-26T05:19:44.075523Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Training:\")\nprint(train_df[\"diagnosis\"].value_counts().sort_index())\n\nprint(\"\\nValidation:\")\nprint(val_df[\"diagnosis\"].value_counts().sort_index())\n\nprint(\"\\nTest:\")\nprint(test_df[\"diagnosis\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:20:03.903563Z","iopub.execute_input":"2026-08-26T05:20:03.903974Z","iopub.status.idle":"2026-08-26T05:20:03.913196Z","shell.execute_reply.started":"2026-08-26T05:20:03.903945Z","shell.execute_reply":"2026-08-26T05:20:03.912040Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_counts = train_df[\"diagnosis\"].value_counts().sort_index().values\n\nclass_weights_np = len(train_df) / (\n    5 * class_counts\n)\n\nprint(\"Class counts:\")\nprint(class_counts)\n\nprint(\"\\nClass weights:\")\nprint(class_weights_np)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:21:09.880405Z","iopub.execute_input":"2026-08-26T05:21:09.881257Z","iopub.status.idle":"2026-08-26T05:21:09.887969Z","shell.execute_reply.started":"2026-08-26T05:21:09.881225Z","shell.execute_reply":"2026-08-26T05:21:09.886768Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_weights = torch.tensor(\n    class_weights_np,\n    dtype=torch.float32\n).to(device)\n\nprint(\"Class weights:\", class_weights)\nprint(\"Device:\", class_weights.device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:21:23.914084Z","iopub.execute_input":"2026-08-26T05:21:23.914876Z","iopub.status.idle":"2026-08-26T05:21:24.273792Z","shell.execute_reply.started":"2026-08-26T05:21:23.914839Z","shell.execute_reply":"2026-08-26T05:21:24.272940Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = APTOSDataset(\n    dataframe=train_df,\n    image_folder=image_folder,\n    transform=train_transform\n)\n\nval_dataset = APTOSDataset(\n    dataframe=val_df,\n    image_folder=image_folder,\n    transform=val_transform\n)\n\ntest_dataset = APTOSDataset(\n    dataframe=test_df,\n    image_folder=image_folder,\n    transform=val_transform\n)\n\nprint(\"Training dataset:\", len(train_dataset))\nprint(\"Validation dataset:\", len(val_dataset))\nprint(\"Test dataset:\", len(test_dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:21:40.590059Z","iopub.execute_input":"2026-08-26T05:21:40.590578Z","iopub.status.idle":"2026-08-26T05:21:40.596293Z","shell.execute_reply.started":"2026-08-26T05:21:40.590548Z","shell.execute_reply":"2026-08-26T05:21:40.595494Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nprint(\"Training batches:\", len(train_loader))\nprint(\"Validation batches:\", len(val_loader))\nprint(\"Test batches:\", len(test_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:21:51.853911Z","iopub.execute_input":"2026-08-26T05:21:51.854614Z","iopub.status.idle":"2026-08-26T05:21:51.860136Z","shell.execute_reply.started":"2026-08-26T05:21:51.854577Z","shell.execute_reply":"2026-08-26T05:21:51.859482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torchvision.models import efficientnet_v2_s, EfficientNet_V2_S_Weights\nimport torch.nn as nn\n\nweights = EfficientNet_V2_S_Weights.DEFAULT\n\nmodel = efficientnet_v2_s(weights=weights)\n\n# Replace the original 1000-class classifier\nmodel.classifier[1] = nn.Linear(\n    model.classifier[1].in_features,\n    5\n)\n\nprint(model.classifier)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:23:08.194762Z","iopub.execute_input":"2026-08-26T05:23:08.195028Z","iopub.status.idle":"2026-08-26T05:23:08.652471Z","shell.execute_reply.started":"2026-08-26T05:23:08.195004Z","shell.execute_reply":"2026-08-26T05:23:08.651733Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for param in model.features.parameters():\n    param.requires_grad = False\n\nprint(\"Feature extractor frozen!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:23:21.034235Z","iopub.execute_input":"2026-08-26T05:23:21.034643Z","iopub.status.idle":"2026-08-26T05:23:21.040729Z","shell.execute_reply.started":"2026-08-26T05:23:21.034613Z","shell.execute_reply":"2026-08-26T05:23:21.039877Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = model.to(device)\n\nprint(\"Model device:\", next(model.parameters()).device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:23:33.488945Z","iopub.execute_input":"2026-08-26T05:23:33.489761Z","iopub.status.idle":"2026-08-26T05:23:33.541368Z","shell.execute_reply.started":"2026-08-26T05:23:33.489728Z","shell.execute_reply":"2026-08-26T05:23:33.540783Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss(\n    weight=class_weights\n)\n\nprint(\"Weighted loss ready!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:23:44.234148Z","iopub.execute_input":"2026-08-26T05:23:44.235771Z","iopub.status.idle":"2026-08-26T05:23:44.242647Z","shell.execute_reply.started":"2026-08-26T05:23:44.235718Z","shell.execute_reply":"2026-08-26T05:23:44.241321Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer = torch.optim.AdamW(\n    filter(\n        lambda p: p.requires_grad,\n        model.parameters()\n    ),\n    lr=1e-3,\n    weight_decay=1e-4\n)\n\nprint(\"Optimizer ready!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:23:58.563944Z","iopub.execute_input":"2026-08-26T05:23:58.564755Z","iopub.status.idle":"2026-08-26T05:23:58.571253Z","shell.execute_reply.started":"2026-08-26T05:23:58.564722Z","shell.execute_reply":"2026-08-26T05:23:58.570336Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=5\n)\n\nprint(\"Scheduler ready!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:24:16.813817Z","iopub.execute_input":"2026-08-26T05:24:16.814276Z","iopub.status.idle":"2026-08-26T05:24:16.825583Z","shell.execute_reply.started":"2026-08-26T05:24:16.814243Z","shell.execute_reply":"2026-08-26T05:24:16.824795Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 5\n\nfor epoch in range(num_epochs):\n\n    train_loss, train_acc = train_one_epoch(\n        model,\n        train_loader,\n        criterion,\n        optimizer,\n        device\n    )\n\n    val_loss, val_acc = validate_one_epoch(\n        model,\n        val_loader,\n        criterion,\n        device\n    )\n\n    scheduler.step()\n\n    print(\n        f\"Epoch {epoch + 1}/{num_epochs} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Train Acc: {train_acc:.4f} | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc:.4f}\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:26:01.114110Z","iopub.execute_input":"2026-08-26T05:26:01.114990Z","iopub.status.idle":"2026-08-26T05:43:29.967818Z","shell.execute_reply.started":"2026-08-26T05:26:01.114932Z","shell.execute_reply":"2026-08-26T05:43:29.967004Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for i, layer in enumerate(model.features):\n    print(i, type(layer).__name__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:44:32.984311Z","iopub.execute_input":"2026-08-26T05:44:32.984913Z","iopub.status.idle":"2026-08-26T05:44:32.989799Z","shell.execute_reply.started":"2026-08-26T05:44:32.984877Z","shell.execute_reply":"2026-08-26T05:44:32.988901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# First freeze everything\nfor param in model.features.parameters():\n    param.requires_grad = False\n\n# Unfreeze the later feature blocks\nfor block in [5, 6, 7]:\n    for param in model.features[block].parameters():\n        param.requires_grad = True\n\n# Keep the classifier trainable\nfor param in model.classifier.parameters():\n    param.requires_grad = True\n\n# Check which parts are trainable\nfor i, layer in enumerate(model.features):\n    trainable = any(\n        param.requires_grad\n        for param in layer.parameters()\n    )\n\n    print(\n        f\"Block {i}:\",\n        \"TRAINABLE 🔥\" if trainable else \"FROZEN ❄️\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:45:04.049005Z","iopub.execute_input":"2026-08-26T05:45:04.049292Z","iopub.status.idle":"2026-08-26T05:45:04.059042Z","shell.execute_reply.started":"2026-08-26T05:45:04.049268Z","shell.execute_reply":"2026-08-26T05:45:04.058389Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"optimizer = torch.optim.AdamW(\n    [\n        {\n            \"params\": model.features[5:].parameters(),\n            \"lr\": 1e-5\n        },\n        {\n            \"params\": model.classifier.parameters(),\n            \"lr\": 1e-4\n        }\n    ],\n    weight_decay=1e-4\n)\n\nprint(\"Fine-tuning optimizer ready!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:45:54.243659Z","iopub.execute_input":"2026-08-26T05:45:54.243907Z","iopub.status.idle":"2026-08-26T05:45:54.250560Z","shell.execute_reply.started":"2026-08-26T05:45:54.243884Z","shell.execute_reply":"2026-08-26T05:45:54.249858Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=10\n)\n\nprint(\"Fine-tuning scheduler ready!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:46:07.204028Z","iopub.execute_input":"2026-08-26T05:46:07.204628Z","iopub.status.idle":"2026-08-26T05:46:07.210093Z","shell.execute_reply.started":"2026-08-26T05:46:07.204573Z","shell.execute_reply":"2026-08-26T05:46:07.209292Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 10\n\nbest_val_loss = float(\"inf\")\npatience = 3\npatience_counter = 0\n\nfor epoch in range(num_epochs):\n\n    # -------------------------\n    # TRAIN\n    # -------------------------\n    train_loss, train_acc = train_one_epoch(\n        model,\n        train_loader,\n        criterion,\n        optimizer,\n        device\n    )\n\n    # -------------------------\n    # VALIDATION\n    # -------------------------\n    val_loss, val_acc = validate_one_epoch(\n        model,\n        val_loader,\n        criterion,\n        device\n    )\n\n    # Update learning rate\n    scheduler.step()\n\n    # -------------------------\n    # SAVE BEST MODEL\n    # -------------------------\n    if val_loss < best_val_loss:\n\n        best_val_loss = val_loss\n        patience_counter = 0\n\n        torch.save(\n            model.state_dict(),\n            \"/kaggle/working/best_finetuned_aptos.pth\"\n        )\n\n        print(\"  ⭐ Best fine-tuned model saved!\")\n\n    else:\n\n        patience_counter += 1\n\n    print(\n        f\"Epoch {epoch + 1}/{num_epochs} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Train Acc: {train_acc:.4f} | \"\n        f\"Val Loss: {val_loss:.4f} | \"\n        f\"Val Acc: {val_acc:.4f}\"\n    )\n\n    # -------------------------\n    # EARLY STOPPING\n    # -------------------------\n    if patience_counter >= patience:\n\n        print(\"Early stopping triggered.\")\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T05:47:39.504631Z","iopub.execute_input":"2026-08-26T05:47:39.505079Z","iopub.status.idle":"2026-08-26T06:20:27.168583Z","shell.execute_reply.started":"2026-08-26T05:47:39.505048Z","shell.execute_reply":"2026-08-26T06:20:27.167654Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(\n    torch.load(\n        \"/kaggle/working/best_finetuned_aptos.pth\",\n        map_location=device\n    )\n)\n\nmodel = model.to(device)\nmodel.eval()\n\nprint(\"Best fine-tuned model loaded!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T06:21:47.226934Z","iopub.execute_input":"2026-08-26T06:21:47.227251Z","iopub.status.idle":"2026-08-26T06:21:47.480743Z","shell.execute_reply.started":"2026-08-26T06:21:47.227218Z","shell.execute_reply":"2026-08-26T06:21:47.479765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_predictions = []\nall_labels = []\n\nmodel.eval()\n\nwith torch.no_grad():\n\n    for images, labels in val_loader:\n\n        images = images.to(device, non_blocking=True)\n\n        outputs = model(images)\n\n        predictions = torch.argmax(outputs, dim=1)\n\n        all_predictions.extend(\n            predictions.cpu().numpy()\n        )\n\n        all_labels.extend(\n            labels.numpy()\n        )\n\nall_predictions = np.array(all_predictions)\nall_labels = np.array(all_labels)\n\nprint(\"Predictions:\", len(all_predictions))\nprint(\"Labels:\", len(all_labels))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T06:21:58.322209Z","iopub.execute_input":"2026-08-26T06:21:58.322649Z","iopub.status.idle":"2026-08-26T06:22:32.966729Z","shell.execute_reply.started":"2026-08-26T06:21:58.322616Z","shell.execute_reply":"2026-08-26T06:22:32.965923Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import classification_report\n\nprint(\n    classification_report(\n        all_labels,\n        all_predictions,\n        labels=[0, 1, 2, 3, 4],\n        target_names=[\n            \"No DR\",\n            \"Mild\",\n            \"Moderate\",\n            \"Severe\",\n            \"Proliferative\"\n        ],\n        digits=4\n    )\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T06:23:21.830824Z","iopub.execute_input":"2026-08-26T06:23:21.831122Z","iopub.status.idle":"2026-08-26T06:23:21.845940Z","shell.execute_reply.started":"2026-08-26T06:23:21.831091Z","shell.execute_reply":"2026-08-26T06:23:21.845166Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"actual_referable = (all_labels >= 2).astype(int)\npredicted_referable = (all_predictions >= 2).astype(int)\n\nreferable_cm = confusion_matrix(\n    actual_referable,\n    predicted_referable\n)\n\nprint(\"Referable DR confusion matrix:\")\nprint(referable_cm)\n\ntn, fp, fn, tp = referable_cm.ravel()\n\nsensitivity = tp / (tp + fn)\nspecificity = tn / (tn + fp)\n\nprint(f\"Sensitivity: {sensitivity:.4f}\")\nprint(f\"Specificity: {specificity:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T06:23:39.736585Z","iopub.execute_input":"2026-08-26T06:23:39.736848Z","iopub.status.idle":"2026-08-26T06:23:39.745097Z","shell.execute_reply.started":"2026-08-26T06:23:39.736824Z","shell.execute_reply":"2026-08-26T06:23:39.744499Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_probs = []\nall_labels = []\n\nmodel.eval()\n\nwith torch.no_grad():\n\n    for images, labels in val_loader:\n\n        images = images.to(device, non_blocking=True)\n\n        outputs = model(images)\n\n        probabilities = torch.softmax(\n            outputs,\n            dim=1\n        )\n\n        all_probs.extend(\n            probabilities.cpu().numpy()\n        )\n\n        all_labels.extend(\n            labels.numpy()\n        )\n\nall_probs = np.array(all_probs)\nall_labels = np.array(all_labels)\n\nprint(\"Probability shape:\", all_probs.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T06:25:44.816966Z","iopub.execute_input":"2026-08-26T06:25:44.817774Z","iopub.status.idle":"2026-08-26T06:26:19.652500Z","shell.execute_reply.started":"2026-08-26T06:25:44.817739Z","shell.execute_reply":"2026-08-26T06:26:19.651519Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"referable_probability = all_probs[:, 2:].sum(axis=1)\n\nprint(\"First 10 referable probabilities:\")\nprint(referable_probability[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T06:27:28.467503Z","iopub.execute_input":"2026-08-26T06:27:28.468353Z","iopub.status.idle":"2026-08-26T06:27:28.473937Z","shell.execute_reply.started":"2026-08-26T06:27:28.468314Z","shell.execute_reply":"2026-08-26T06:27:28.473112Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"threshold = 0.30\n\npredicted_referable = (\n    referable_probability >= threshold\n).astype(int)\n\nactual_referable = (\n    all_labels >= 2\n).astype(int)\n\nreferable_cm = confusion_matrix(\n    actual_referable,\n    predicted_referable\n)\n\ntn, fp, fn, tp = referable_cm.ravel()\n\nsensitivity = tp / (tp + fn)\nspecificity = tn / (tn + fp)\n\nprint(\"Threshold:\", threshold)\nprint(\"Confusion matrix:\")\nprint(referable_cm)\n\nprint(f\"Sensitivity: {sensitivity:.4f}\")\nprint(f\"Specificity: {specificity:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T06:27:46.227330Z","iopub.execute_input":"2026-08-26T06:27:46.228265Z","iopub.status.idle":"2026-08-26T06:27:46.236251Z","shell.execute_reply.started":"2026-08-26T06:27:46.228218Z","shell.execute_reply":"2026-08-26T06:27:46.235683Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"REFERRABLE_THRESHOLD = 0.30\n\nprint(\n    \"Locked referable DR threshold:\",\n    REFERRABLE_THRESHOLD\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-26T06:29:41.987591Z","iopub.execute_input":"2026-08-26T06:29:41.988003Z","iopub.status.idle":"2026-08-26T06:29:41.992983Z","shell.execute_reply.started":"2026-08-26T06:29:41.987972Z","shell.execute_reply":"2026-08-26T06:29:41.992098Z"}},"outputs":[],"execution_count":null}]}