{"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-10-08T11:59:48.693261Z","iopub.execute_input":"2026-10-08T11:59:48.693761Z","iopub.status.idle":"2026-10-08T11:59:57.276712Z","shell.execute_reply.started":"2026-10-08T11:59:48.693713Z","shell.execute_reply":"2026-10-08T11:59:57.275879Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport random\nimport json\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom PIL import Image\n\nimport cv2\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\n\nimport torchvision\nfrom torchvision import models\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nfrom sklearn.metrics import (\n    accuracy_score,\n    balanced_accuracy_score,\n    precision_recall_fscore_support,\n    confusion_matrix,\n    classification_report,\n    cohen_kappa_score\n)\n\nfrom sklearn.utils.class_weight import compute_class_weight\n\nfrom tqdm.auto import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T11:59:57.27799Z","iopub.execute_input":"2026-10-08T11:59:57.278462Z","iopub.status.idle":"2026-10-08T12:00:05.805982Z","shell.execute_reply.started":"2026-10-08T11:59:57.278438Z","shell.execute_reply":"2026-10-08T12:00:05.805201Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CONFIGURATION\n# ============================================================\n\nSEED = 42\n\nIMG_SIZE = 384\nNUM_CLASSES = 5\n\nBATCH_SIZE = 16\nNUM_WORKERS = 2\n\nEPOCHS = 25\nEARLY_STOPPING_PATIENCE = 5\n\nLEARNING_RATE = 1e-4\nWEIGHT_DECAY = 1e-4\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nprint(\"Device:\", DEVICE)\nprint(\"PyTorch:\", torch.__version__)\nprint(\"Torchvision:\", torchvision.__version__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:05.806934Z","iopub.execute_input":"2026-10-08T12:00:05.807457Z","iopub.status.idle":"2026-10-08T12:00:05.81335Z","shell.execute_reply.started":"2026-10-08T12:00:05.807425Z","shell.execute_reply":"2026-10-08T12:00:05.812697Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# REPRODUCIBILITY\n# ============================================================\n\ndef seed_everything(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n\n    # Reproducibility\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\n\nseed_everything(SEED)\n\nprint(\"Random seed set to:\", SEED)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:05.815301Z","iopub.execute_input":"2026-10-08T12:00:05.815574Z","iopub.status.idle":"2026-10-08T12:00:05.859379Z","shell.execute_reply.started":"2026-10-08T12:00:05.815552Z","shell.execute_reply":"2026-10-08T12:00:05.858478Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# FIND APTOS PREPROCESSED DATASET\n# ============================================================\n\nimport os\n\n# Search common Kaggle locations\nsearch_roots = [\n    \"/kaggle/working\",\n    \"/kaggle/input\"\n]\n\nmatches = []\n\nfor root in search_roots:\n    if not os.path.exists(root):\n        continue\n\n    for current_root, dirs, files in os.walk(root):\n        for d in dirs:\n            if \"APTOS_Preprocessed\" in d:\n                matches.append(\n                    os.path.join(current_root, d)\n                )\n\nprint(\"Possible APTOS_Preprocessed folders found:\")\n\nfor path in matches:\n    print(path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:05.860476Z","iopub.execute_input":"2026-10-08T12:00:05.861214Z","iopub.status.idle":"2026-10-08T12:00:09.502494Z","shell.execute_reply.started":"2026-10-08T12:00:05.861189Z","shell.execute_reply":"2026-10-08T12:00:09.501841Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# FIND ORIGINAL APTOS DATASET\n# ============================================================\n\nimport os\n\nsearch_roots = [\n    \"/kaggle/input\",\n    \"/kaggle/working\"\n]\n\nprint(\"Searching for APTOS files...\\n\")\n\nfound = []\n\nfor root in search_roots:\n\n    if not os.path.exists(root):\n        continue\n\n    for current_root, dirs, files in os.walk(root):\n\n        # Look for common APTOS files\n        for file in files:\n\n            if file.lower() in [\n                \"train.csv\",\n                \"train_images.csv\",\n                \"test.csv\"\n            ]:\n\n                path = os.path.join(\n                    current_root,\n                    file\n                )\n\n                found.append(path)\n\nfor path in found:\n    print(path)\n\nprint(\"\\nTotal files found:\", len(found))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.503379Z","iopub.execute_input":"2026-10-08T12:00:09.50371Z","iopub.status.idle":"2026-10-08T12:00:09.527981Z","shell.execute_reply.started":"2026-10-08T12:00:09.503684Z","shell.execute_reply":"2026-10-08T12:00:09.527416Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# SHOW KAGGLE INPUT DATASETS\n# ============================================================\n\nprint(\"Kaggle input directories:\\n\")\n\nfor item in os.listdir(\"/kaggle/input\"):\n    print(\"📁\", item)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.528811Z","iopub.execute_input":"2026-10-08T12:00:09.5292Z","iopub.status.idle":"2026-10-08T12:00:09.533857Z","shell.execute_reply.started":"2026-10-08T12:00:09.529176Z","shell.execute_reply":"2026-10-08T12:00:09.533076Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CHECK ORIGINAL APTOS DATASET\n# ============================================================\n\nimport os\n\nAPTOS_ROOT = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\n\nprint(\"APTOS root:\")\nprint(APTOS_ROOT)\n\nprint(\"\\nContents:\\n\")\n\nfor item in os.listdir(APTOS_ROOT):\n    path = os.path.join(APTOS_ROOT, item)\n\n    if os.path.isdir(path):\n        print(\"📁\", item)\n    else:\n        print(\"📄\", item)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.534803Z","iopub.execute_input":"2026-10-08T12:00:09.535128Z","iopub.status.idle":"2026-10-08T12:00:09.555479Z","shell.execute_reply.started":"2026-10-08T12:00:09.535108Z","shell.execute_reply":"2026-10-08T12:00:09.554863Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# IMPORTS\n# ============================================================\n\nimport os\nimport random\nimport cv2\nimport numpy as np\nimport pandas as pd\n\nfrom tqdm.auto import tqdm\n\nfrom sklearn.model_selection import train_test_split","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.557094Z","iopub.execute_input":"2026-10-08T12:00:09.557485Z","iopub.status.idle":"2026-10-08T12:00:09.597478Z","shell.execute_reply.started":"2026-10-08T12:00:09.557464Z","shell.execute_reply":"2026-10-08T12:00:09.596969Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# PATHS\n# ============================================================\n\nAPTOS_ROOT = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\n\nORIGINAL_TRAIN_DIR = os.path.join(\n    APTOS_ROOT,\n    \"train_images\"\n)\n\nORIGINAL_TEST_DIR = os.path.join(\n    APTOS_ROOT,\n    \"test_images\"\n)\n\nORIGINAL_TRAIN_CSV = os.path.join(\n    APTOS_ROOT,\n    \"train.csv\"\n)\n\n# ------------------------------------------------------------\n# OUTPUT\n# ------------------------------------------------------------\n\nOUTPUT_DIR = \"/kaggle/working/APTOS_Preprocessed\"\n\nSIZE_DIR = os.path.join(\n    OUTPUT_DIR,\n    \"size_384x384\"\n)\n\nTRAIN_OUTPUT_DIR = os.path.join(\n    SIZE_DIR,\n    \"train\"\n)\n\nVAL_OUTPUT_DIR = os.path.join(\n    SIZE_DIR,\n    \"val\"\n)\n\nTEST_OUTPUT_DIR = os.path.join(\n    SIZE_DIR,\n    \"test\"\n)\n\n# Create directories\nos.makedirs(TRAIN_OUTPUT_DIR, exist_ok=True)\nos.makedirs(VAL_OUTPUT_DIR, exist_ok=True)\nos.makedirs(TEST_OUTPUT_DIR, exist_ok=True)\n\nprint(\"Original train directory:\")\nprint(ORIGINAL_TRAIN_DIR)\n\nprint(\"\\nOutput directory:\")\nprint(OUTPUT_DIR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.599852Z","iopub.execute_input":"2026-10-08T12:00:09.600264Z","iopub.status.idle":"2026-10-08T12:00:09.607216Z","shell.execute_reply.started":"2026-10-08T12:00:09.600243Z","shell.execute_reply":"2026-10-08T12:00:09.606548Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# VERIFY DIRECTORIES\n# ============================================================\n\npaths_to_check = [\n    APTOS_ROOT,\n    ORIGINAL_TRAIN_DIR,\n    ORIGINAL_TEST_DIR,\n    ORIGINAL_TRAIN_CSV,\n    OUTPUT_DIR,\n    TRAIN_OUTPUT_DIR,\n    VAL_OUTPUT_DIR,\n    TEST_OUTPUT_DIR\n]\n\nfor path in paths_to_check:\n\n    print(\n        \"✓\" if os.path.exists(path) else \"✗\",\n        path\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.60826Z","iopub.execute_input":"2026-10-08T12:00:09.608463Z","iopub.status.idle":"2026-10-08T12:00:09.623287Z","shell.execute_reply.started":"2026-10-08T12:00:09.608427Z","shell.execute_reply":"2026-10-08T12:00:09.622543Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# LOAD APTOS TRAIN CSV\n# ============================================================\n\ndf = pd.read_csv(\n    ORIGINAL_TRAIN_CSV\n)\n\nprint(\"Dataset shape:\", df.shape)\n\nprint(\"\\nColumns:\")\nprint(df.columns.tolist())\n\nprint(\"\\nFirst 5 rows:\")\ndisplay(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.624038Z","iopub.execute_input":"2026-10-08T12:00:09.624425Z","iopub.status.idle":"2026-10-08T12:00:09.692776Z","shell.execute_reply.started":"2026-10-08T12:00:09.624403Z","shell.execute_reply":"2026-10-08T12:00:09.692026Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CLASS DISTRIBUTION\n# ============================================================\n\nprint(\n    df[\"diagnosis\"]\n    .value_counts()\n    .sort_index()\n)\n\nprint(\"\\nPercentage:\")\nprint(\n    df[\"diagnosis\"]\n    .value_counts(normalize=True)\n    .sort_index() * 100\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.693636Z","iopub.execute_input":"2026-10-08T12:00:09.693924Z","iopub.status.idle":"2026-10-08T12:00:09.729562Z","shell.execute_reply.started":"2026-10-08T12:00:09.693892Z","shell.execute_reply":"2026-10-08T12:00:09.728975Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STRATIFIED 70 / 15 / 15 SPLIT\n# ============================================================\n\nRANDOM_SEED = 42\n\ntrain_df, temp_df = train_test_split(\n    df,\n    test_size=0.30,\n    stratify=df[\"diagnosis\"],\n    random_state=RANDOM_SEED\n)\n\nval_df, test_df = train_test_split(\n    temp_df,\n    test_size=0.50,\n    stratify=temp_df[\"diagnosis\"],\n    random_state=RANDOM_SEED\n)\n\n# Reset indices\ntrain_df = train_df.reset_index(drop=True)\nval_df = val_df.reset_index(drop=True)\ntest_df = test_df.reset_index(drop=True)\n\nprint(\"Train:\", len(train_df))\nprint(\"Validation:\", len(val_df))\nprint(\"Test:\", len(test_df))\n\nprint(\n    \"\\nPercentages:\"\n)\n\nprint(\n    \"Train:\",\n    len(train_df) / len(df) * 100\n)\n\nprint(\n    \"Validation:\",\n    len(val_df) / len(df) * 100\n)\n\nprint(\n    \"Test:\",\n    len(test_df) / len(df) * 100\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.730484Z","iopub.execute_input":"2026-10-08T12:00:09.730875Z","iopub.status.idle":"2026-10-08T12:00:09.752169Z","shell.execute_reply.started":"2026-10-08T12:00:09.730833Z","shell.execute_reply":"2026-10-08T12:00:09.751393Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# VERIFY STRATIFIED SPLIT\n# ============================================================\n\nfor name, split_df in [\n    (\"TRAIN\", train_df),\n    (\"VALIDATION\", val_df),\n    (\"TEST\", test_df)\n]:\n\n    print(\"\\n\" + \"=\" * 50)\n    print(name)\n    print(\"=\" * 50)\n\n    distribution = (\n        split_df[\"diagnosis\"]\n        .value_counts()\n        .sort_index()\n    )\n\n    percentage = (\n        split_df[\"diagnosis\"]\n        .value_counts(normalize=True)\n        .sort_index() * 100\n    )\n\n    for cls in range(5):\n\n        count = distribution.get(cls, 0)\n        pct = percentage.get(cls, 0)\n\n        print(\n            f\"Class {cls}: \"\n            f\"{count:4d} images \"\n            f\"({pct:.2f}%)\"\n        )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.753134Z","iopub.execute_input":"2026-10-08T12:00:09.753433Z","iopub.status.idle":"2026-10-08T12:00:09.766501Z","shell.execute_reply.started":"2026-10-08T12:00:09.753403Z","shell.execute_reply":"2026-10-08T12:00:09.765934Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# SAVE SPLITS\n# ============================================================\n\ntrain_df.to_csv(\n    os.path.join(\n        OUTPUT_DIR,\n        \"train.csv\"\n    ),\n    index=False\n)\n\nval_df.to_csv(\n    os.path.join(\n        OUTPUT_DIR,\n        \"val.csv\"\n    ),\n    index=False\n)\n\ntest_df.to_csv(\n    os.path.join(\n        OUTPUT_DIR,\n        \"test.csv\"\n    ),\n    index=False\n)\n\nprint(\"Split CSV files saved.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.767501Z","iopub.execute_input":"2026-10-08T12:00:09.768138Z","iopub.status.idle":"2026-10-08T12:00:09.79998Z","shell.execute_reply.started":"2026-10-08T12:00:09.768081Z","shell.execute_reply":"2026-10-08T12:00:09.79917Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# BLACK BORDER / GRAY CROP\n# ============================================================\n\ndef crop_image_from_gray(img, tol=7):\n\n    if img.ndim == 2:\n        mask = img > tol\n        return img[np.ix_(mask.any(1), mask.any(0))]\n\n    gray = cv2.cvtColor(\n        img,\n        cv2.COLOR_RGB2GRAY\n    )\n\n    mask = gray > tol\n\n    if not mask.any():\n        return img\n\n    coords = np.argwhere(mask)\n\n    y0, x0 = coords.min(axis=0)\n    y1, x1 = coords.max(axis=0) + 1\n\n    return img[\n        y0:y1,\n        x0:x1\n    ]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.801116Z","iopub.execute_input":"2026-10-08T12:00:09.801385Z","iopub.status.idle":"2026-10-08T12:00:09.806352Z","shell.execute_reply.started":"2026-10-08T12:00:09.801364Z","shell.execute_reply":"2026-10-08T12:00:09.805539Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CLAHE\n# ============================================================\n\ndef apply_clahe(\n    image,\n    clip_limit=2.0,\n    tile_grid_size=(8, 8)\n):\n\n    # Convert RGB → LAB\n    lab = cv2.cvtColor(\n        image,\n        cv2.COLOR_RGB2LAB\n    )\n\n    l_channel, a_channel, b_channel = cv2.split(lab)\n\n    clahe = cv2.createCLAHE(\n        clipLimit=clip_limit,\n        tileGridSize=tile_grid_size\n    )\n\n    l_channel = clahe.apply(\n        l_channel\n    )\n\n    lab = cv2.merge([\n        l_channel,\n        a_channel,\n        b_channel\n    ])\n\n    # LAB → RGB\n    result = cv2.cvtColor(\n        lab,\n        cv2.COLOR_LAB2RGB\n    )\n\n    return result","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.807262Z","iopub.execute_input":"2026-10-08T12:00:09.807532Z","iopub.status.idle":"2026-10-08T12:00:09.821224Z","shell.execute_reply.started":"2026-10-08T12:00:09.807502Z","shell.execute_reply":"2026-10-08T12:00:09.820679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# PREPROCESSING FUNCTION\n# ============================================================\n\nTARGET_SIZE = (384, 384)\n\ndef preprocess_image(\n    image_path,\n    target_size=TARGET_SIZE\n):\n\n    # --------------------------------------------------------\n    # Read image\n    # --------------------------------------------------------\n\n    image = cv2.imread(\n        image_path\n    )\n\n    if image is None:\n        raise ValueError(\n            f\"Could not read image: {image_path}\"\n        )\n\n    # --------------------------------------------------------\n    # BGR → RGB\n    # --------------------------------------------------------\n\n    image = cv2.cvtColor(\n        image,\n        cv2.COLOR_BGR2RGB\n    )\n\n    # --------------------------------------------------------\n    # Remove black borders\n    # --------------------------------------------------------\n\n    image = crop_image_from_gray(\n        image\n    )\n\n    # --------------------------------------------------------\n    # Resize\n    # --------------------------------------------------------\n\n    image = cv2.resize(\n        image,\n        target_size,\n        interpolation=cv2.INTER_AREA\n    )\n\n    # --------------------------------------------------------\n    # CLAHE\n    # --------------------------------------------------------\n\n    image = apply_clahe(\n        image,\n        clip_limit=2.0,\n        tile_grid_size=(8, 8)\n    )\n\n    # --------------------------------------------------------\n    # Convert to float [0,1]\n    # --------------------------------------------------------\n\n    image = image.astype(\n        np.float32\n    ) / 255.0\n\n    return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.822114Z","iopub.execute_input":"2026-10-08T12:00:09.82239Z","iopub.status.idle":"2026-10-08T12:00:09.840562Z","shell.execute_reply.started":"2026-10-08T12:00:09.822367Z","shell.execute_reply":"2026-10-08T12:00:09.839787Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# TEST PREPROCESSING\n# ============================================================\n\nsample_id = train_df.iloc[0][\"id_code\"]\n\nsample_path = os.path.join(\n    ORIGINAL_TRAIN_DIR,\n    sample_id + \".png\"\n)\n\nprint(\"Sample ID:\", sample_id)\nprint(\"Path:\", sample_path)\nprint(\"Exists:\", os.path.exists(sample_path))\n\nsample_image = preprocess_image(\n    sample_path\n)\n\nprint(\"\\nProcessed shape:\")\nprint(sample_image.shape)\n\nprint(\"\\nData type:\")\nprint(sample_image.dtype)\n\nprint(\"\\nMin:\", sample_image.min())\nprint(\"Max:\", sample_image.max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:09.841453Z","iopub.execute_input":"2026-10-08T12:00:09.841913Z","iopub.status.idle":"2026-10-08T12:00:10.453047Z","shell.execute_reply.started":"2026-10-08T12:00:09.841874Z","shell.execute_reply":"2026-10-08T12:00:10.452247Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# VISUALIZE PREPROCESSING\n# ============================================================\nimport matplotlib.pyplot as plt\noriginal = cv2.imread(\n    sample_path\n)\n\noriginal = cv2.cvtColor(\n    original,\n    cv2.COLOR_BGR2RGB\n)\n\nprocessed = preprocess_image(\n    sample_path\n)\n\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.imshow(original)\nplt.title(\"Original APTOS Image\")\nplt.axis(\"off\")\n\nplt.subplot(1, 2, 2)\nplt.imshow(processed)\nplt.title(\"Preprocessed: Crop + Resize + CLAHE\")\nplt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:10.454148Z","iopub.execute_input":"2026-10-08T12:00:10.454477Z","iopub.status.idle":"2026-10-08T12:00:11.595185Z","shell.execute_reply.started":"2026-10-08T12:00:10.454453Z","shell.execute_reply":"2026-10-08T12:00:11.594323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# PROCESS AND SAVE SPLIT\n# ============================================================\n\ndef process_and_save_split(\n    dataframe,\n    input_dir,\n    output_dir\n):\n\n    os.makedirs(\n        output_dir,\n        exist_ok=True\n    )\n\n    failed = []\n\n    for _, row in tqdm(\n        dataframe.iterrows(),\n        total=len(dataframe),\n        desc=f\"Processing {os.path.basename(output_dir)}\"\n    ):\n\n        # APTOS uses \"id_code\"\n        image_id = str(row[\"id_code\"])\n\n        input_path = os.path.join(\n            input_dir,\n            image_id + \".png\"\n        )\n\n        output_path = os.path.join(\n            output_dir,\n            image_id + \".npy\"\n        )\n\n        try:\n\n            image = preprocess_image(\n                input_path\n            )\n\n            np.save(\n                output_path,\n                image\n            )\n\n        except Exception as e:\n\n            failed.append({\n                \"id_code\": image_id,\n                \"error\": str(e)\n            })\n\n    return failed","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:11.596178Z","iopub.execute_input":"2026-10-08T12:00:11.596491Z","iopub.status.idle":"2026-10-08T12:00:11.602683Z","shell.execute_reply.started":"2026-10-08T12:00:11.596466Z","shell.execute_reply":"2026-10-08T12:00:11.601841Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# PROCESS TRAIN SET\n# ============================================================\n\ntrain_failed = process_and_save_split(\n    train_df,\n    ORIGINAL_TRAIN_DIR,\n    TRAIN_OUTPUT_DIR\n)\n\nprint(\n    \"\\nTrain processing complete.\"\n)\n\nprint(\n    \"Failed images:\",\n    len(train_failed)\n)\n\nif train_failed:\n    print(train_failed[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:00:11.60369Z","iopub.execute_input":"2026-10-08T12:00:11.603983Z","iopub.status.idle":"2026-10-08T12:07:44.077735Z","shell.execute_reply.started":"2026-10-08T12:00:11.60396Z","shell.execute_reply":"2026-10-08T12:07:44.076965Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# PROCESS VALIDATION SET\n# ============================================================\n\nval_failed = process_and_save_split(\n    val_df,\n    ORIGINAL_TRAIN_DIR,\n    VAL_OUTPUT_DIR\n)\n\nprint(\n    \"\\nValidation processing complete.\"\n)\n\nprint(\n    \"Failed images:\",\n    len(val_failed)\n)\n\nif val_failed:\n    print(val_failed[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:07:44.078831Z","iopub.execute_input":"2026-10-08T12:07:44.079152Z","iopub.status.idle":"2026-10-08T12:09:21.947483Z","shell.execute_reply.started":"2026-10-08T12:07:44.079128Z","shell.execute_reply":"2026-10-08T12:09:21.946385Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# PROCESS HELD-OUT TEST SET\n# ============================================================\n\ntest_failed = process_and_save_split(\n    test_df,\n    ORIGINAL_TRAIN_DIR,\n    TEST_OUTPUT_DIR\n)\n\nprint(\n    \"\\nTest processing complete.\"\n)\n\nprint(\n    \"Failed images:\",\n    len(test_failed)\n)\n\nif test_failed:\n    print(test_failed[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:09:21.948795Z","iopub.execute_input":"2026-10-08T12:09:21.94926Z","iopub.status.idle":"2026-10-08T12:10:56.617471Z","shell.execute_reply.started":"2026-10-08T12:09:21.949232Z","shell.execute_reply":"2026-10-08T12:10:56.616691Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# VERIFY OUTPUT\n# ============================================================\n\ntrain_npy = [\n    f for f in os.listdir(TRAIN_OUTPUT_DIR)\n    if f.endswith(\".npy\")\n]\n\nval_npy = [\n    f for f in os.listdir(VAL_OUTPUT_DIR)\n    if f.endswith(\".npy\")\n]\n\ntest_npy = [\n    f for f in os.listdir(TEST_OUTPUT_DIR)\n    if f.endswith(\".npy\")\n]\n\nprint(\"Train .npy:\", len(train_npy))\nprint(\"Val .npy  :\", len(val_npy))\nprint(\"Test .npy :\", len(test_npy))\n\nprint(\"\\nExpected:\")\nprint(\"Train:\", len(train_df))\nprint(\"Val  :\", len(val_df))\nprint(\"Test :\", len(test_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:10:56.618671Z","iopub.execute_input":"2026-10-08T12:10:56.618982Z","iopub.status.idle":"2026-10-08T12:10:56.62829Z","shell.execute_reply.started":"2026-10-08T12:10:56.618958Z","shell.execute_reply":"2026-10-08T12:10:56.627428Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CALCULATE TRAINING MEAN / STD\n# ============================================================\n\ndef calculate_mean_std(\n    image_dir,\n    dataframe\n):\n\n    channel_sum = np.zeros(\n        3,\n        dtype=np.float64\n    )\n\n    channel_sum_sq = np.zeros(\n        3,\n        dtype=np.float64\n    )\n\n    total_pixels = 0\n\n    for _, row in tqdm(\n        dataframe.iterrows(),\n        total=len(dataframe),\n        desc=\"Calculating mean/std\"\n    ):\n\n        image_id = str(row[\"id_code\"])\n\n        path = os.path.join(\n            image_dir,\n            image_id + \".npy\"\n        )\n\n        image = np.load(\n            path\n        ).astype(np.float64)\n\n        pixels = image.reshape(-1, 3)\n\n        channel_sum += pixels.sum(axis=0)\n\n        channel_sum_sq += (\n            (pixels ** 2).sum(axis=0)\n        )\n\n        total_pixels += pixels.shape[0]\n\n    mean = channel_sum / total_pixels\n\n    variance = (\n        channel_sum_sq / total_pixels\n    ) - (mean ** 2)\n\n    variance = np.maximum(\n        variance,\n        1e-12\n    )\n\n    std = np.sqrt(variance)\n\n    return (\n        mean.astype(np.float32),\n        std.astype(np.float32)\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:10:56.629237Z","iopub.execute_input":"2026-10-08T12:10:56.629554Z","iopub.status.idle":"2026-10-08T12:10:56.636696Z","shell.execute_reply.started":"2026-10-08T12:10:56.629519Z","shell.execute_reply":"2026-10-08T12:10:56.636114Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# SAVE MEAN / STD\n# ============================================================\n\nRETINA_MEAN, RETINA_STD = calculate_mean_std(\n    TRAIN_OUTPUT_DIR,\n    train_df\n)\n\nprint(\"Training mean:\")\nprint(RETINA_MEAN)\n\nprint(\"\\nTraining std:\")\nprint(RETINA_STD)\n\nnp.save(\n    os.path.join(\n        OUTPUT_DIR,\n        \"retina_mean.npy\"\n    ),\n    RETINA_MEAN\n)\n\nnp.save(\n    os.path.join(\n        OUTPUT_DIR,\n        \"retina_std.npy\"\n    ),\n    RETINA_STD\n)\n\nprint(\"\\nMean/std saved.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:10:56.640421Z","iopub.execute_input":"2026-10-08T12:10:56.640754Z","iopub.status.idle":"2026-10-08T12:11:11.288318Z","shell.execute_reply.started":"2026-10-08T12:10:56.640729Z","shell.execute_reply":"2026-10-08T12:11:11.287665Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# FINAL DATASET STRUCTURE\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"APTOS PREPROCESSED DATASET\")\nprint(\"=\" * 60)\n\nprint(\"\\nOutput directory:\")\nprint(OUTPUT_DIR)\n\nprint(\"\\nFiles:\")\n\nfor item in sorted(\n    os.listdir(OUTPUT_DIR)\n):\n    print(\" \", item)\n\nprint(\"\\n384x384 folders:\")\n\nfor item in sorted(\n    os.listdir(SIZE_DIR)\n):\n    print(\" \", item)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.28938Z","iopub.execute_input":"2026-10-08T12:11:11.289764Z","iopub.status.idle":"2026-10-08T12:11:11.295927Z","shell.execute_reply.started":"2026-10-08T12:11:11.289737Z","shell.execute_reply":"2026-10-08T12:11:11.295083Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# DENSENET DATA PATH\n# ============================================================\n\nDATA_BASE = \"/kaggle/working/APTOS_Preprocessed\"\n\nif not os.path.exists(DATA_BASE):\n    raise FileNotFoundError(\n        f\"Dataset not found at: {DATA_BASE}\"\n    )\n\nprint(\"Dataset found!\")\nprint(DATA_BASE)\n\nprint(\"\\nContents:\")\n\nfor item in sorted(os.listdir(DATA_BASE)):\n    print(\" -\", item)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.297015Z","iopub.execute_input":"2026-10-08T12:11:11.297317Z","iopub.status.idle":"2026-10-08T12:11:11.314836Z","shell.execute_reply.started":"2026-10-08T12:11:11.297283Z","shell.execute_reply":"2026-10-08T12:11:11.314002Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 224x224 DATA PATHS\n# ============================================================\n\nSIZE_FOLDER = os.path.join(DATA_BASE, \"size_384x384\")\n\nTRAIN_DIR = os.path.join(SIZE_FOLDER, \"train\")\nVAL_DIR   = os.path.join(SIZE_FOLDER, \"val\")\nTEST_DIR  = os.path.join(SIZE_FOLDER, \"test\")\n\nTRAIN_CSV = os.path.join(DATA_BASE, \"train.csv\")\nVAL_CSV   = os.path.join(DATA_BASE, \"val.csv\")\nTEST_CSV  = os.path.join(DATA_BASE, \"test.csv\")\n\nMEAN_PATH = os.path.join(DATA_BASE, \"retina_mean.npy\")\nSTD_PATH  = os.path.join(DATA_BASE, \"retina_std.npy\")\n\nfor path in [\n    TRAIN_DIR,\n    VAL_DIR,\n    TEST_DIR,\n    TRAIN_CSV,\n    VAL_CSV,\n    TEST_CSV\n]:\n    print(path, \"->\", os.path.exists(path))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.315895Z","iopub.execute_input":"2026-10-08T12:11:11.316197Z","iopub.status.idle":"2026-10-08T12:11:11.33078Z","shell.execute_reply.started":"2026-10-08T12:11:11.316172Z","shell.execute_reply":"2026-10-08T12:11:11.330123Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# LOAD SPLITS\n# ============================================================\n\ntrain_df = pd.read_csv(TRAIN_CSV)\nval_df   = pd.read_csv(VAL_CSV)\ntest_df  = pd.read_csv(TEST_CSV)\n\nprint(\"Train:\", train_df.shape)\nprint(\"Validation:\", val_df.shape)\nprint(\"Test:\", test_df.shape)\n\nprint(\"\\nTrain columns:\")\nprint(train_df.columns.tolist())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.331518Z","iopub.execute_input":"2026-10-08T12:11:11.331822Z","iopub.status.idle":"2026-10-08T12:11:11.355567Z","shell.execute_reply.started":"2026-10-08T12:11:11.331798Z","shell.execute_reply":"2026-10-08T12:11:11.354788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport random\nimport json\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom PIL import Image\n\nimport cv2\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\n\nimport torchvision\nfrom torchvision import models\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nfrom sklearn.metrics import (\n    accuracy_score,\n    balanced_accuracy_score,\n    precision_recall_fscore_support,\n    confusion_matrix,\n    classification_report,\n    cohen_kappa_score\n)\n\nfrom sklearn.utils.class_weight import compute_class_weight\n\nfrom tqdm.auto import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.35673Z","iopub.execute_input":"2026-10-08T12:11:11.357157Z","iopub.status.idle":"2026-10-08T12:11:11.372565Z","shell.execute_reply.started":"2026-10-08T12:11:11.357118Z","shell.execute_reply":"2026-10-08T12:11:11.372012Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CHECK IMAGE LEAKAGE\n# ============================================================\n\ntrain_ids = set(train_df[\"id_code\"].astype(str))\nval_ids   = set(val_df[\"id_code\"].astype(str))\ntest_ids  = set(test_df[\"id_code\"].astype(str))\n\nprint(\"Train ∩ Validation:\", len(train_ids & val_ids))\nprint(\"Train ∩ Test:\", len(train_ids & test_ids))\nprint(\"Validation ∩ Test:\", len(val_ids & test_ids))\n\nassert len(train_ids & val_ids) == 0\nassert len(train_ids & test_ids) == 0\nassert len(val_ids & test_ids) == 0\n\nprint(\"\\nNo image overlap detected.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.373672Z","iopub.execute_input":"2026-10-08T12:11:11.373958Z","iopub.status.idle":"2026-10-08T12:11:11.399861Z","shell.execute_reply.started":"2026-10-08T12:11:11.373936Z","shell.execute_reply":"2026-10-08T12:11:11.398843Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport albumentations as A\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset\n\n\nclass RetinalDataset(Dataset):\n    def __init__(self, dataframe, image_dir, transform=None, mean=None, std=None):\n        self.df = dataframe.reset_index(\n            drop=False\n        )  # drop=False preserves index as a column if needed\n        self.image_dir = image_dir\n        self.transform = transform\n        self.mean = (\n            np.array(mean, dtype=np.float32)\n            if mean is not None\n            else np.array([0.0, 0.0, 0.0])\n        )\n        self.std = (\n            np.array(std, dtype=np.float32)\n            if std is not None\n            else np.array([1.0, 1.0, 1.0])\n        )\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n\n        # ----------------------------------------------------\n        # Extract Image ID (Handles various column names)\n        # ----------------------------------------------------\n        id_cols = [\n            \"id\",\n            \"image_id\",\n            \"id_code\",\n            \"filename\",\n            \"image\",\n            \"index\",\n        ]\n        image_id = None\n\n        for col in id_cols:\n            if col in row.index:\n                image_id = str(row[col])\n                break\n\n        if image_id is None:\n            raise KeyError(\n                f\"Could not find an image ID column. Available columns: {list(row.index)}\"\n            )\n\n        # ----------------------------------------------------\n        # Extract Diagnosis/Label\n        # ----------------------------------------------------\n        label_cols = [\"diagnosis\", \"label\", \"target\", \"class\"]\n        label = None\n\n        for col in label_cols:\n            if col in row.index:\n                label = int(row[col])\n                break\n\n        if label is None:\n            raise KeyError(\n                f\"Could not find a label column. Available columns: {list(row.index)}\"\n            )\n\n        # ----------------------------------------------------\n        # Locate .npy file\n        # ----------------------------------------------------\n        npy_path = os.path.join(self.image_dir, image_id + \".npy\")\n        if not os.path.exists(npy_path):\n            npy_path_alt = os.path.join(\n                self.image_dir, os.path.splitext(image_id)[0] + \".npy\"\n            )\n            if os.path.exists(npy_path_alt):\n                npy_path = npy_path_alt\n            else:\n                raise FileNotFoundError(f\"Image not found:\\n{npy_path}\")\n\n        # ----------------------------------------------------\n        # Load & Preprocess Image\n        # ----------------------------------------------------\n        image = np.load(npy_path).astype(np.float32)\n\n        if image.ndim != 3:\n            raise ValueError(\n                f\"Unexpected image shape {image.shape} for {image_id}\"\n            )\n\n        if image.max() > 1.0:\n            image = image / 255.0\n        image = np.clip(image, 0.0, 1.0)\n\n        # Apply Albumentations\n        if self.transform is not None:\n            transformed = self.transform(image=image)\n            image = transformed[\"image\"]\n\n        # Convert NumPy (H, W, C) to PyTorch Tensor (C, H, W)\n        if isinstance(image, np.ndarray):\n            image = torch.from_numpy(image.transpose(2, 0, 1)).float()\n\n        # Channel-wise Normalization\n        mean = torch.tensor(self.mean, dtype=torch.float32).view(3, 1, 1)\n        std = torch.tensor(self.std, dtype=torch.float32).view(3, 1, 1)\n        image = (image - mean) / std\n\n        return image, torch.tensor(label, dtype=torch.long)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.400936Z","iopub.execute_input":"2026-10-08T12:11:11.401359Z","iopub.status.idle":"2026-10-08T12:11:11.417079Z","shell.execute_reply.started":"2026-10-08T12:11:11.401337Z","shell.execute_reply":"2026-10-08T12:11:11.416477Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# TRAINING AUGMENTATION\n# ============================================================\n\ntrain_transform = A.Compose([\n\n    # Retinal images can reasonably be flipped horizontally.\n    A.HorizontalFlip(p=0.5),\n\n    # Small rotations only.\n    A.Rotate(\n        limit=10,\n        border_mode=cv2.BORDER_REFLECT_101,\n        p=0.5\n    ),\n\n    # Mild illumination variation.\n    A.RandomBrightnessContrast(\n        brightness_limit=0.05,\n        contrast_limit=0.05,\n        p=0.2\n    ),\n\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.417984Z","iopub.execute_input":"2026-10-08T12:11:11.41879Z","iopub.status.idle":"2026-10-08T12:11:11.445035Z","shell.execute_reply.started":"2026-10-08T12:11:11.418754Z","shell.execute_reply":"2026-10-08T12:11:11.444294Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# VALIDATION / TEST TRANSFORM\n# ============================================================\n\neval_transform = A.Compose([])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.446149Z","iopub.execute_input":"2026-10-08T12:11:11.44649Z","iopub.status.idle":"2026-10-08T12:11:11.466346Z","shell.execute_reply.started":"2026-10-08T12:11:11.446454Z","shell.execute_reply":"2026-10-08T12:11:11.465466Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CREATE DATASETS\n# ============================================================\n\ntrain_dataset = RetinalDataset(\n    dataframe=train_df,\n    image_dir=TRAIN_DIR,\n    transform=train_transform,\n    mean=RETINA_MEAN,\n    std=RETINA_STD\n)\n\nval_dataset = RetinalDataset(\n    dataframe=val_df,\n    image_dir=VAL_DIR,\n    transform=eval_transform,\n    mean=RETINA_MEAN,\n    std=RETINA_STD\n)\n\ntest_dataset = RetinalDataset(\n    dataframe=test_df,\n    image_dir=TEST_DIR,\n    transform=eval_transform,\n    mean=RETINA_MEAN,\n    std=RETINA_STD\n)\n\nprint(\"Train samples:\", len(train_dataset))\nprint(\"Validation samples:\", len(val_dataset))\nprint(\"Test samples:\", len(test_dataset))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.467256Z","iopub.execute_input":"2026-10-08T12:11:11.467531Z","iopub.status.idle":"2026-10-08T12:11:11.493348Z","shell.execute_reply.started":"2026-10-08T12:11:11.467501Z","shell.execute_reply":"2026-10-08T12:11:11.492404Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# DATALOADERS\n# ============================================================\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    shuffle=True,\n    \n    pin_memory=torch.cuda.is_available(),\n    drop_last=False\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=16,\n    shuffle=False,\n    \n    pin_memory=torch.cuda.is_available(),\n    drop_last=False\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=16,\n    shuffle=False,\n    \n    pin_memory=torch.cuda.is_available(),\n    drop_last=False\n)\n\nprint(\"Train batches:\", len(train_loader))\nprint(\"Validation batches:\", len(val_loader))\nprint(\"Test batches:\", len(test_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.494256Z","iopub.execute_input":"2026-10-08T12:11:11.494565Z","iopub.status.idle":"2026-10-08T12:11:11.509238Z","shell.execute_reply.started":"2026-10-08T12:11:11.494529Z","shell.execute_reply":"2026-10-08T12:11:11.508688Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# VISUALIZE AUGMENTATION\n# ============================================================\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# ----------------------------------------------------\n# 1. Define CLASS_NAMES (Standard Diabetic Retinopathy Grades)\n# ----------------------------------------------------\nif \"CLASS_NAMES\" not in globals():\n    CLASS_NAMES = [\n        \"0 - No DR\",\n        \"1 - Mild\",\n        \"2 - Moderate\",\n        \"3 - Severe\",\n        \"4 - Proliferative DR\",\n    ]\ndef denormalize_image(tensor, mean, std):\n\n    mean = torch.tensor(mean).view(3, 1, 1)\n    std = torch.tensor(std).view(3, 1, 1)\n\n    image = tensor.cpu() * std + mean\n\n    return torch.clamp(image, 0, 1)\n\n\nfig, axes = plt.subplots(2, 4, figsize=(14, 7))\n\nfor i in range(8):\n\n    image, label = train_dataset[\n        np.random.randint(len(train_dataset))\n    ]\n\n    image_vis = denormalize_image(\n        image,\n        RETINA_MEAN,\n        RETINA_STD\n    )\n\n    image_vis = image_vis.permute(1, 2, 0).numpy()\n\n    axes.flat[i].imshow(image_vis)\n    axes.flat[i].set_title(\n        f\"{label.item()} - {CLASS_NAMES[label.item()]}\"\n    )\n    axes.flat[i].axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:11.510164Z","iopub.execute_input":"2026-10-08T12:11:11.510407Z","iopub.status.idle":"2026-10-08T12:11:12.811985Z","shell.execute_reply.started":"2026-10-08T12:11:11.510386Z","shell.execute_reply":"2026-10-08T12:11:12.811184Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport torch\nfrom sklearn.utils.class_weight import compute_class_weight\n\n# ----------------------------------------------------\n# 1. Define NUM_CLASSES if not already defined\n# ----------------------------------------------------\nif \"NUM_CLASSES\" not in globals():\n    NUM_CLASSES = 5  # Standard for Diabetic Retinopathy (0, 1, 2, 3, 4)\n\n# ----------------------------------------------------\n# 2. Extract train labels from dataset or DataFrame\n# ----------------------------------------------------\nif \"train_labels\" not in globals():\n    label_cols = [\"diagnosis\", \"label\", \"target\", \"class\"]\n    label_col = next(col for col in label_cols if col in train_df.columns)\n    train_labels = train_df[label_col].values\n\n# ----------------------------------------------------\n# 3. Compute Class Weights\n# ----------------------------------------------------\nclass_weights = compute_class_weight(\n    class_weight=\"balanced\", classes=np.unique(train_labels), y=train_labels\n)\n\n# Convert to PyTorch FloatTensor for CrossEntropyLoss\nclass_weights_tensor = torch.tensor(class_weights, dtype=torch.float32)\n\nprint(\"Classes found:\", np.unique(train_labels))\nprint(\"Computed Class Weights:\", class_weights_tensor)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:12.813129Z","iopub.execute_input":"2026-10-08T12:11:12.813491Z","iopub.status.idle":"2026-10-08T12:11:12.938996Z","shell.execute_reply.started":"2026-10-08T12:11:12.813453Z","shell.execute_reply":"2026-10-08T12:11:12.938157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom torchvision import models\n\n# ----------------------------------------------------\n# 1. Set Device (GPU if available, else CPU)\n# ----------------------------------------------------\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {DEVICE}\")\n\n# ----------------------------------------------------\n# 2. Instantiate Model (DenseNet121)\n# ----------------------------------------------------\n# Pass pretrained weights (or weights=None if training from scratch)\nmodel = models.densenet121(weights=models.DenseNet121_Weights.DEFAULT)\n\n# Replace the classifier head for your target classes (e.g., 5 for Diabetic Retinopathy)\nNUM_CLASSES = 5\nin_features = model.classifier.in_features\nmodel.classifier = nn.Linear(in_features, NUM_CLASSES)\n\n# ----------------------------------------------------\n# 3. Move Model to Device\n# ----------------------------------------------------\nmodel = model.to(DEVICE)\n\n# ----------------------------------------------------\n# 4. Verify Model Structure\n# ----------------------------------------------------\nprint(\"\\nModel successfully initialized and moved to device!\")\nprint(model.classifier)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:12.939999Z","iopub.execute_input":"2026-10-08T12:11:12.940291Z","iopub.status.idle":"2026-10-08T12:11:13.929822Z","shell.execute_reply.started":"2026-10-08T12:11:12.940269Z","shell.execute_reply":"2026-10-08T12:11:13.929128Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\n# ============================================================\n# CONVERT NUMPY CLASS WEIGHTS TO PYTORCH TENSOR\n# ============================================================\n\n# Convert NumPy array to PyTorch FloatTensor and move to target device\nclass_weights_tensor = torch.tensor(class_weights, dtype=torch.float32).to(\n    DEVICE\n)\n\n# Initialize CrossEntropyLoss with weighted classes\ncriterion = nn.CrossEntropyLoss(weight=class_weights_tensor)\n\nprint(\"✓ Successfully initialized CrossEntropyLoss with class weights!\")\nprint(f\"Device: {DEVICE}\")\nprint(f\"Class Weights Tensor: {class_weights_tensor}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:13.930943Z","iopub.execute_input":"2026-10-08T12:11:13.931242Z","iopub.status.idle":"2026-10-08T12:11:14.525022Z","shell.execute_reply.started":"2026-10-08T12:11:13.931217Z","shell.execute_reply":"2026-10-08T12:11:14.524017Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\n\n# ----------------------------------------------------\n# 1. Define Hyperparameters (if not already set)\n# ----------------------------------------------------\nif \"LEARNING_RATE\" not in globals():\n    LEARNING_RATE = 1e-4\n\nif \"WEIGHT_DECAY\" not in globals():\n    WEIGHT_DECAY = 1e-2\n\n# ----------------------------------------------------\n# 2. Define Loss Function with Class Weights\n# ----------------------------------------------------\n# Uses class_weights_tensor if defined in CELL 17, else standard CrossEntropyLoss\nif \"class_weights_tensor\" in globals():\n    criterion = nn.CrossEntropyLoss(\n        weight=class_weights_tensor.to(DEVICE)\n    )\nelse:\n    criterion = nn.CrossEntropyLoss()\n\n# ----------------------------------------------------\n# 3. Initialize Optimizer\n# ----------------------------------------------------\noptimizer = optim.AdamW(\n    model.parameters(), lr=LEARNING_RATE, weight_decay=WEIGHT_DECAY\n)\n\n# Optional: Learning rate scheduler\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer, mode=\"min\", factor=0.5, patience=2\n)\n\nprint(f\"Loss Function: {criterion}\")\nprint(f\"Optimizer: AdamW (lr={LEARNING_RATE}, weight_decay={WEIGHT_DECAY})\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:14.526374Z","iopub.execute_input":"2026-10-08T12:11:14.526818Z","iopub.status.idle":"2026-10-08T12:11:14.535427Z","shell.execute_reply.started":"2026-10-08T12:11:14.52679Z","shell.execute_reply":"2026-10-08T12:11:14.534678Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# OPTIMIZER\n# ============================================================\n\noptimizer = optim.AdamW(\n    model.parameters(),\n    lr=LEARNING_RATE,\n    weight_decay=WEIGHT_DECAY\n)\n\nprint(optimizer)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:14.536421Z","iopub.execute_input":"2026-10-08T12:11:14.536819Z","iopub.status.idle":"2026-10-08T12:11:14.554599Z","shell.execute_reply.started":"2026-10-08T12:11:14.536797Z","shell.execute_reply":"2026-10-08T12:11:14.554062Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# LR SCHEDULER\n# ============================================================\n\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer,\n    mode=\"max\",\n    factor=0.5,\n    patience=2,\n    min_lr=1e-7\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:14.555549Z","iopub.execute_input":"2026-10-08T12:11:14.555855Z","iopub.status.idle":"2026-10-08T12:11:14.569778Z","shell.execute_reply.started":"2026-10-08T12:11:14.555834Z","shell.execute_reply":"2026-10-08T12:11:14.568942Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# METRICS\n# ============================================================\n\ndef calculate_metrics(y_true, y_pred):\n\n    accuracy = accuracy_score(\n        y_true,\n        y_pred\n    )\n\n    balanced_acc = balanced_accuracy_score(\n        y_true,\n        y_pred\n    )\n\n    precision_macro, recall_macro, f1_macro, _ = \\\n        precision_recall_fscore_support(\n            y_true,\n            y_pred,\n            average=\"macro\",\n            zero_division=0\n        )\n\n    precision_weighted, recall_weighted, f1_weighted, _ = \\\n        precision_recall_fscore_support(\n            y_true,\n            y_pred,\n            average=\"weighted\",\n            zero_division=0\n        )\n\n    qwk = cohen_kappa_score(\n        y_true,\n        y_pred,\n        weights=\"quadratic\"\n    )\n\n    return {\n        \"accuracy\": accuracy,\n        \"balanced_accuracy\": balanced_acc,\n        \"macro_precision\": precision_macro,\n        \"macro_recall\": recall_macro,\n        \"macro_f1\": f1_macro,\n        \"weighted_precision\": precision_weighted,\n        \"weighted_recall\": recall_weighted,\n        \"weighted_f1\": f1_weighted,\n        \"qwk\": qwk\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:14.57068Z","iopub.execute_input":"2026-10-08T12:11:14.570956Z","iopub.status.idle":"2026-10-08T12:11:14.585908Z","shell.execute_reply.started":"2026-10-08T12:11:14.570934Z","shell.execute_reply":"2026-10-08T12:11:14.585307Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# TRAINING FUNCTION\n# ============================================================\n\ndef train_one_epoch(\n    model,\n    loader,\n    criterion,\n    optimizer,\n    device\n):\n\n    model.train()\n\n    running_loss = 0.0\n\n    all_labels = []\n    all_preds = []\n\n    progress = tqdm(\n        loader,\n        desc=\"Training\",\n        leave=False\n    )\n\n    for images, labels in progress:\n\n        images = images.to(\n            device,\n            non_blocking=True\n        )\n\n        labels = labels.to(\n            device,\n            non_blocking=True\n        )\n\n        optimizer.zero_grad()\n\n        outputs = model(images)\n\n        loss = criterion(\n            outputs,\n            labels\n        )\n\n        loss.backward()\n\n        optimizer.step()\n\n        running_loss += (\n            loss.item() * images.size(0)\n        )\n\n        predictions = torch.argmax(\n            outputs,\n            dim=1\n        )\n\n        all_labels.extend(\n            labels.detach().cpu().numpy()\n        )\n\n        all_preds.extend(\n            predictions.detach().cpu().numpy()\n        )\n\n        progress.set_postfix(\n            loss=f\"{loss.item():.4f}\"\n        )\n\n    epoch_loss = (\n        running_loss / len(loader.dataset)\n    )\n\n    metrics = calculate_metrics(\n        all_labels,\n        all_preds\n    )\n\n    return epoch_loss, metrics","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:14.586801Z","iopub.execute_input":"2026-10-08T12:11:14.587061Z","iopub.status.idle":"2026-10-08T12:11:14.606559Z","shell.execute_reply.started":"2026-10-08T12:11:14.587039Z","shell.execute_reply":"2026-10-08T12:11:14.606038Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# VALIDATION FUNCTION\n# ============================================================\n\n@torch.no_grad()\ndef evaluate(\n    model,\n    loader,\n    criterion,\n    device\n):\n\n    model.eval()\n\n    running_loss = 0.0\n\n    all_labels = []\n    all_preds = []\n\n    progress = tqdm(\n        loader,\n        desc=\"Validation\",\n        leave=False\n    )\n\n    for images, labels in progress:\n\n        images = images.to(\n            device,\n            non_blocking=True\n        )\n\n        labels = labels.to(\n            device,\n            non_blocking=True\n        )\n\n        outputs = model(images)\n\n        loss = criterion(\n            outputs,\n            labels\n        )\n\n        running_loss += (\n            loss.item() * images.size(0)\n        )\n\n        predictions = torch.argmax(\n            outputs,\n            dim=1\n        )\n\n        all_labels.extend(\n            labels.cpu().numpy()\n        )\n\n        all_preds.extend(\n            predictions.cpu().numpy()\n        )\n\n    epoch_loss = (\n        running_loss / len(loader.dataset)\n    )\n\n    metrics = calculate_metrics(\n        all_labels,\n        all_preds\n    )\n\n    return (\n        epoch_loss,\n        metrics,\n        np.array(all_labels),\n        np.array(all_preds)\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:14.607522Z","iopub.execute_input":"2026-10-08T12:11:14.607849Z","iopub.status.idle":"2026-10-08T12:11:14.630787Z","shell.execute_reply.started":"2026-10-08T12:11:14.607828Z","shell.execute_reply":"2026-10-08T12:11:14.630055Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport torch\n\n# ============================================================\n# SAFETY CHECKS & HYPERPARAMETER DEFINITIONS\n# ============================================================\nif \"EPOCHS\" not in globals():\n    EPOCHS = 20\n\nif \"EARLY_STOPPING_PATIENCE\" not in globals():\n    EARLY_STOPPING_PATIENCE = 3\n\nif \"DEVICE\" not in globals():\n    DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nif \"BEST_MODEL_PATH\" not in globals():\n    BEST_MODEL_PATH = \"/kaggle/working/best_densenet121_384.pth\"\n\n# If no scheduler exists, initialize a dummy/placeholder or ReduceLROnPlateau\nif \"scheduler\" not in globals():\n    scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n        optimizer, mode=\"max\", factor=0.5, patience=2\n    )\n\n# ============================================================\n# TRAIN DENSENET121\n# ============================================================\n\nbest_qwk = -np.inf\nbest_epoch = 0\nepochs_without_improvement = 0\n\nhistory = []\n\nprint(\"=\" * 70)\nprint(f\"STARTING DENSENET121 TRAINING ON {DEVICE}\")\nprint(\"=\" * 70)\n\nfor epoch in range(1, EPOCHS + 1):\n\n    print(f\"\\nEpoch {epoch}/{EPOCHS}\")\n    print(\"-\" * 70)\n\n    # --------------------------------------------------------\n    # TRAIN\n    # --------------------------------------------------------\n    train_loss, train_metrics = train_one_epoch(\n        model, train_loader, criterion, optimizer, DEVICE\n    )\n\n    # --------------------------------------------------------\n    # VALIDATION\n    # --------------------------------------------------------\n    val_loss, val_metrics, _, _ = evaluate(\n        model, val_loader, criterion, DEVICE\n    )\n\n    # --------------------------------------------------------\n    # SCHEDULER STEP\n    # --------------------------------------------------------\n    # Mode 'max' for QWK metric, mode 'min' for loss\n    if isinstance(scheduler, torch.optim.lr_scheduler.ReduceLROnPlateau):\n        scheduler.step(val_metrics[\"qwk\"])\n    else:\n        scheduler.step()\n\n    current_lr = optimizer.param_groups[0][\"lr\"]\n\n    # --------------------------------------------------------\n    # SAVE HISTORY\n    # --------------------------------------------------------\n    epoch_result = {\n        \"epoch\": epoch,\n        \"lr\": current_lr,\n        \"train_loss\": train_loss,\n        \"train_accuracy\": train_metrics.get(\"accuracy\", 0.0),\n        \"train_balanced_accuracy\": train_metrics.get(\"balanced_accuracy\", 0.0),\n        \"train_macro_f1\": train_metrics.get(\"macro_f1\", 0.0),\n        \"train_qwk\": train_metrics.get(\"qwk\", 0.0),\n        \"val_loss\": val_loss,\n        \"val_accuracy\": val_metrics.get(\"accuracy\", 0.0),\n        \"val_balanced_accuracy\": val_metrics.get(\"balanced_accuracy\", 0.0),\n        \"val_macro_f1\": val_metrics.get(\"macro_f1\", 0.0),\n        \"val_qwk\": val_metrics.get(\"qwk\", 0.0),\n    }\n\n    history.append(epoch_result)\n\n    # --------------------------------------------------------\n    # PRINT METRICS\n    # --------------------------------------------------------\n    print(\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Train Acc: {train_metrics.get('accuracy', 0.0):.4f} | \"\n        f\"Train F1: {train_metrics.get('macro_f1', 0.0):.4f} | \"\n        f\"Train QWK: {train_metrics.get('qwk', 0.0):.4f}\"\n    )\n\n    print(\n        f\"Val Loss:   {val_loss:.4f} | \"\n        f\"Val Acc:   {val_metrics.get('accuracy', 0.0):.4f} | \"\n        f\"Val F1:    {val_metrics.get('macro_f1', 0.0):.4f} | \"\n        f\"Val QWK:   {val_metrics.get('qwk', 0.0):.4f}\"\n    )\n\n    print(f\"Learning Rate: {current_lr:.2e}\")\n\n    # --------------------------------------------------------\n    # BEST MODEL BASED ON VALIDATION QWK\n    # --------------------------------------------------------\n    if val_metrics[\"qwk\"] > best_qwk:\n\n        best_qwk = val_metrics[\"qwk\"]\n        best_epoch = epoch\n        epochs_without_improvement = 0\n\n        torch.save(model.state_dict(), BEST_MODEL_PATH)\n\n        print(f\"✓ Best model saved (Validation QWK = {best_qwk:.4f})\")\n\n    else:\n\n        epochs_without_improvement += 1\n\n        print(f\"No improvement for {epochs_without_improvement} epoch(s)\")\n\n    # --------------------------------------------------------\n    # EARLY STOPPING\n    # --------------------------------------------------------\n    if epochs_without_improvement >= EARLY_STOPPING_PATIENCE:\n\n        print(\"\\nEarly stopping triggered.\")\n\n        break\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"TRAINING COMPLETE\")\nprint(\"=\" * 70)\n\nprint(\"Best epoch:\", best_epoch)\nprint(\"Best validation QWK:\", best_qwk)\nprint(\"Best model path:\", BEST_MODEL_PATH)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:11:14.631836Z","iopub.execute_input":"2026-10-08T12:11:14.632171Z","iopub.status.idle":"2026-10-08T12:37:53.820596Z","shell.execute_reply.started":"2026-10-08T12:11:14.63215Z","shell.execute_reply":"2026-10-08T12:37:53.819966Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# TRAINING HISTORY\n# ============================================================\n\nhistory_df = pd.DataFrame(history)\n\nhistory_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:37:53.821862Z","iopub.execute_input":"2026-10-08T12:37:53.822209Z","iopub.status.idle":"2026-10-08T12:37:53.838942Z","shell.execute_reply.started":"2026-10-08T12:37:53.822184Z","shell.execute_reply":"2026-10-08T12:37:53.838217Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# LOSS CURVE\n# ============================================================\n\nplt.figure(figsize=(8, 5))\n\nplt.plot(\n    history_df[\"epoch\"],\n    history_df[\"train_loss\"],\n    label=\"Train Loss\"\n)\n\nplt.plot(\n    history_df[\"epoch\"],\n    history_df[\"val_loss\"],\n    label=\"Validation Loss\"\n)\n\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"DenseNet121 Training and Validation Loss\")\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:37:53.839732Z","iopub.execute_input":"2026-10-08T12:37:53.840467Z","iopub.status.idle":"2026-10-08T12:37:54.015383Z","shell.execute_reply.started":"2026-10-08T12:37:53.840435Z","shell.execute_reply":"2026-10-08T12:37:54.014744Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# ACCURACY CURVE\n# ============================================================\n\nplt.figure(figsize=(8, 5))\n\nplt.plot(\n    history_df[\"epoch\"],\n    history_df[\"train_accuracy\"],\n    label=\"Train Accuracy\"\n)\n\nplt.plot(\n    history_df[\"epoch\"],\n    history_df[\"val_accuracy\"],\n    label=\"Validation Accuracy\"\n)\n\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.title(\"DenseNet121 Accuracy\")\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:37:54.016344Z","iopub.execute_input":"2026-10-08T12:37:54.0167Z","iopub.status.idle":"2026-10-08T12:37:54.183949Z","shell.execute_reply.started":"2026-10-08T12:37:54.016674Z","shell.execute_reply":"2026-10-08T12:37:54.183082Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# QWK CURVE\n# ============================================================\n\nplt.figure(figsize=(8, 5))\n\nplt.plot(\n    history_df[\"epoch\"],\n    history_df[\"train_qwk\"],\n    label=\"Train QWK\"\n)\n\nplt.plot(\n    history_df[\"epoch\"],\n    history_df[\"val_qwk\"],\n    label=\"Validation QWK\"\n)\n\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Quadratic Weighted Kappa\")\nplt.title(\"DenseNet121 QWK\")\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:37:54.185102Z","iopub.execute_input":"2026-10-08T12:37:54.185446Z","iopub.status.idle":"2026-10-08T12:37:54.342529Z","shell.execute_reply.started":"2026-10-08T12:37:54.185408Z","shell.execute_reply":"2026-10-08T12:37:54.341866Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# LOAD BEST MODEL\n# ============================================================\n\nif not os.path.exists(BEST_MODEL_PATH):\n    raise FileNotFoundError(\n        \"Best model file was not created.\"\n    )\n\nmodel.load_state_dict(\n    torch.load(\n        BEST_MODEL_PATH,\n        map_location=DEVICE\n    )\n)\n\nmodel = model.to(DEVICE)\n\nprint(\"Loaded best DenseNet121 model.\")\nprint(\"Best validation QWK:\", best_qwk)\nprint(\"Best epoch:\", best_epoch)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:37:54.343543Z","iopub.execute_input":"2026-10-08T12:37:54.343925Z","iopub.status.idle":"2026-10-08T12:37:54.566145Z","shell.execute_reply.started":"2026-10-08T12:37:54.343893Z","shell.execute_reply":"2026-10-08T12:37:54.565256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# FINAL TEST EVALUATION\n# ============================================================\n\ntest_loss, test_metrics, y_true, y_pred = evaluate(\n    model,\n    test_loader,\n    criterion,\n    DEVICE\n)\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"FINAL TEST RESULTS\")\nprint(\"=\" * 70)\n\nprint(f\"Test Loss            : {test_loss:.4f}\")\nprint(f\"Test Accuracy        : {test_metrics['accuracy']:.4f}\")\nprint(f\"Balanced Accuracy    : {test_metrics['balanced_accuracy']:.4f}\")\nprint(f\"Macro Precision      : {test_metrics['macro_precision']:.4f}\")\nprint(f\"Macro Recall         : {test_metrics['macro_recall']:.4f}\")\nprint(f\"Macro F1             : {test_metrics['macro_f1']:.4f}\")\nprint(f\"Weighted Precision   : {test_metrics['weighted_precision']:.4f}\")\nprint(f\"Weighted Recall      : {test_metrics['weighted_recall']:.4f}\")\nprint(f\"Weighted F1          : {test_metrics['weighted_f1']:.4f}\")\nprint(f\"QWK                  : {test_metrics['qwk']:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:37:54.567214Z","iopub.execute_input":"2026-10-08T12:37:54.567544Z","iopub.status.idle":"2026-10-08T12:38:01.285264Z","shell.execute_reply.started":"2026-10-08T12:37:54.567519Z","shell.execute_reply":"2026-10-08T12:38:01.28457Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CLASSIFICATION REPORT\n# ============================================================\n\ntarget_names = [\n    CLASS_NAMES[i]\n    for i in range(NUM_CLASSES)\n]\n\nprint(\n    classification_report(\n        y_true,\n        y_pred,\n        labels=np.arange(NUM_CLASSES),\n        target_names=target_names,\n        digits=4,\n        zero_division=0\n    )\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:38:01.286088Z","iopub.execute_input":"2026-10-08T12:38:01.286456Z","iopub.status.idle":"2026-10-08T12:38:01.300291Z","shell.execute_reply.started":"2026-10-08T12:38:01.28643Z","shell.execute_reply":"2026-10-08T12:38:01.299682Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CONFUSION MATRIX\n# ============================================================\n\ncm = confusion_matrix(\n    y_true,\n    y_pred,\n    labels=np.arange(NUM_CLASSES)\n)\n\nplt.figure(figsize=(8, 7))\n\nsns.heatmap(\n    cm,\n    annot=True,\n    fmt=\"d\",\n    cmap=\"Blues\",\n    xticklabels=[\n        f\"{i}: {CLASS_NAMES[i]}\"\n        for i in range(NUM_CLASSES)\n    ],\n    yticklabels=[\n        f\"{i}: {CLASS_NAMES[i]}\"\n        for i in range(NUM_CLASSES)\n    ]\n)\n\nplt.xlabel(\"Predicted Class\")\nplt.ylabel(\"Actual Class\")\nplt.title(\"DenseNet121 — Test Confusion Matrix\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:38:01.301355Z","iopub.execute_input":"2026-10-08T12:38:01.301804Z","iopub.status.idle":"2026-10-08T12:38:01.541784Z","shell.execute_reply.started":"2026-10-08T12:38:01.301738Z","shell.execute_reply":"2026-10-08T12:38:01.540923Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# PER-CLASS METRICS\n# ============================================================\n\nprecision, recall, f1, support = \\\n    precision_recall_fscore_support(\n        y_true,\n        y_pred,\n        labels=np.arange(NUM_CLASSES),\n        zero_division=0\n    )\n\nper_class_df = pd.DataFrame({\n    \"Class\": np.arange(NUM_CLASSES),\n    \"Stage\": [\n        CLASS_NAMES[i]\n        for i in range(NUM_CLASSES)\n    ],\n    \"Precision\": precision,\n    \"Recall\": recall,\n    \"F1\": f1,\n    \"Support\": support\n})\n\nper_class_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:38:01.543206Z","iopub.execute_input":"2026-10-08T12:38:01.543562Z","iopub.status.idle":"2026-10-08T12:38:01.559266Z","shell.execute_reply.started":"2026-10-08T12:38:01.543525Z","shell.execute_reply":"2026-10-08T12:38:01.558541Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# SAVE RESULTS\n# ============================================================\n\nRESULTS_DIR = \"/kaggle/working/densenet121_results\"\n\nos.makedirs(\n    RESULTS_DIR,\n    exist_ok=True\n)\n\n# History\nhistory_df.to_csv(\n    os.path.join(\n        RESULTS_DIR,\n        \"training_history.csv\"\n    ),\n    index=False\n)\n\n# Per-class metrics\nper_class_df.to_csv(\n    os.path.join(\n        RESULTS_DIR,\n        \"per_class_metrics.csv\"\n    ),\n    index=False\n)\n\n# Overall metrics\noverall_results = {\n    \"model\": \"DenseNet121\",\n    \"image_size\": \"384x384\",\n    \"best_epoch\": int(best_epoch),\n    \"best_validation_qwk\": float(best_qwk),\n    **{\n        k: float(v)\n        for k, v in test_metrics.items()\n    }\n}\n\nwith open(\n    os.path.join(\n        RESULTS_DIR,\n        \"test_results.json\"\n    ),\n    \"w\"\n) as f:\n\n    json.dump(\n        overall_results,\n        f,\n        indent=4\n    )\n\nprint(\"Results saved to:\")\nprint(RESULTS_DIR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:38:01.560351Z","iopub.execute_input":"2026-10-08T12:38:01.560753Z","iopub.status.idle":"2026-10-08T12:38:01.571521Z","shell.execute_reply.started":"2026-10-08T12:38:01.560719Z","shell.execute_reply":"2026-10-08T12:38:01.570992Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# SAVE CONFUSION MATRIX\n# ============================================================\n\ncm_df = pd.DataFrame(\n    cm,\n    index=[\n        f\"Actual {i}\"\n        for i in range(NUM_CLASSES)\n    ],\n    columns=[\n        f\"Predicted {i}\"\n        for i in range(NUM_CLASSES)\n    ]\n)\n\ncm_df.to_csv(\n    os.path.join(\n        RESULTS_DIR,\n        \"confusion_matrix.csv\"\n    )\n)\n\nprint(\"Confusion matrix saved.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:38:01.572271Z","iopub.execute_input":"2026-10-08T12:38:01.572641Z","iopub.status.idle":"2026-10-08T12:38:01.597492Z","shell.execute_reply.started":"2026-10-08T12:38:01.572578Z","shell.execute_reply":"2026-10-08T12:38:01.596659Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# FINAL SUMMARY\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"DENSENET121 BASELINE SUMMARY\")\nprint(\"=\" * 70)\n\nprint(f\"Image size             : {IMG_SIZE} x {IMG_SIZE}\")\nprint(f\"Model                  : DenseNet121\")\nprint(f\"Pretrained             : ImageNet\")\nprint(f\"Loss                   : Weighted Cross Entropy\")\nprint(f\"Optimizer              : AdamW\")\nprint(f\"Initial LR             : {LEARNING_RATE}\")\nprint(f\"Weight decay           : {WEIGHT_DECAY}\")\nprint(f\"Training epochs        : {len(history_df)}\")\nprint(f\"Best epoch             : {best_epoch}\")\n\nprint(\"\\nTEST:\")\nprint(f\"Accuracy               : {test_metrics['accuracy']:.4f}\")\nprint(f\"Balanced Accuracy      : {test_metrics['balanced_accuracy']:.4f}\")\nprint(f\"Macro F1               : {test_metrics['macro_f1']:.4f}\")\nprint(f\"Weighted F1            : {test_metrics['weighted_f1']:.4f}\")\nprint(f\"QWK                    : {test_metrics['qwk']:.4f}\")\n\nprint(\"=\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-08T12:38:01.598527Z","iopub.execute_input":"2026-10-08T12:38:01.599903Z","iopub.status.idle":"2026-10-08T12:38:01.611816Z","shell.execute_reply.started":"2026-10-08T12:38:01.599856Z","shell.execute_reply":"2026-10-08T12:38:01.611114Z"}},"outputs":[],"execution_count":null}]}