{"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":"import tensorflow as tf\n\nprint(\"TensorFlow import os\n\nprint(\"Kaggle input folders:\")\nfor item in os.listdir(\"/kaggle/input\"):\n    print(\" -\", item)\n\nprint(\"\\nSearching for train.csv and train_images...\")\n\nfor root, dirs, files in os.walk(\"/kaggle/input\"):\n    if \"train.csv\" in files:\n        print(\"✅ train.csv found:\", os.path.join(root, \"train.csv\"))\n    if \"train_images\" in dirs:\n        print(\"✅ train_images found:\", os.path.join(root, \"train_images\")) tf.config.list_physical_devices(\"GPU\"))\n\nif tf.config.list_physical_devices(\"GPU\"):\n    print(\"✅ GPU READY\")\nelse:\n    print(\"❌ GPU NOT DETECTED\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-19T11:26:54.68481Z","iopub.execute_input":"2026-09-19T11:26:54.685255Z","iopub.status.idle":"2026-09-19T11:27:12.706402Z","shell.execute_reply.started":"2026-09-19T11:26:54.685225Z","shell.execute_reply":"2026-09-19T11:27:12.70537Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(\"Kaggle input folders:\")\nfor item in os.listdir(\"/kaggle/input\"):\n    print(\" -\", item)\n\nprint(\"\\nSearching for train.csv and train_images...\")\n\nfor root, dirs, files in os.walk(\"/kaggle/input\"):\n    if \"train.csv\" in files:\n        print(\"✅ train.csv found:\", os.path.join(root, \"train.csv\"))\n    if \"train_images\" in dirs:\n        print(\"✅ train_images found:\", os.path.join(root, \"train_images\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T12:04:12.884511Z","iopub.execute_input":"2026-09-19T12:04:12.885323Z","iopub.status.idle":"2026-09-19T12:04:25.365614Z","shell.execute_reply.started":"2026-09-19T12:04:12.885292Z","shell.execute_reply":"2026-09-19T12:04:25.36491Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport os\n\nDATASET = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\n\n# Read labels\ntrain_df = pd.read_csv(os.path.join(DATASET, \"train.csv\"))\n\nprint(\"Number of labelled images:\", len(train_df))\nprint(\"\\nDiagnosis distribution:\")\nprint(train_df[\"diagnosis\"].value_counts().sort_index())\n\n# Check image folder\nprint(\"\\nFolders/files:\")\nprint(os.listdir(DATASET))\n\n# Check first image path\nimg_id = train_df.iloc[0][\"id_code\"]\n\nfor folder in [\"train_images\", \"train_image\"]:\n    folder_path = os.path.join(DATASET, folder)\n    if os.path.isdir(folder_path):\n        files = os.listdir(folder_path)\n        print(f\"\\n✅ Found image folder: {folder}\")\n        print(\"Number of files:\", len(files))\n        print(\"Example:\", files[:3])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T12:05:06.918932Z","iopub.execute_input":"2026-09-19T12:05:06.919229Z","iopub.status.idle":"2026-09-19T12:05:06.937068Z","shell.execute_reply.started":"2026-09-19T12:05:06.919203Z","shell.execute_reply":"2026-09-19T12:05:06.936305Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\n# Add image filename\ntrain_df[\"filename\"] = train_df[\"id_code\"] + \".png\"\ntrain_df[\"label\"] = train_df[\"diagnosis\"].astype(str)\n\n# 80% train, 20% temporary\ntrain_split, temp_split = train_test_split(\n    train_df,\n    test_size=0.20,\n    stratify=train_df[\"label\"],\n    random_state=42\n)\n\n# Split remaining 20% equally:\n# 10% validation + 10% final test\nval_split, test_split = train_test_split(\n    temp_split,\n    test_size=0.50,\n    stratify=temp_split[\"label\"],\n    random_state=42\n)\n\nprint(\"Train:\", len(train_split))\nprint(\"Validation:\", len(val_split))\nprint(\"Final Test:\", len(test_split))\n\nprint(\"\\nTrain distribution:\")\nprint(train_split[\"diagnosis\"].value_counts().sort_index())\n\nprint(\"\\nValidation distribution:\")\nprint(val_split[\"diagnosis\"].value_counts().sort_index())\n\nprint(\"\\nFinal Test distribution:\")\nprint(test_split[\"diagnosis\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T12:05:47.776071Z","iopub.execute_input":"2026-09-19T12:05:47.776363Z","iopub.status.idle":"2026-09-19T12:05:48.169167Z","shell.execute_reply.started":"2026-09-19T12:05:47.776339Z","shell.execute_reply":"2026-09-19T12:05:48.168338Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nSPLIT_DIR = \"/kaggle/working/dr_splits\"\nos.makedirs(SPLIT_DIR, exist_ok=True)\n\ntrain_split.to_csv(\n    os.path.join(SPLIT_DIR, \"train_split.csv\"),\n    index=False\n)\n\nval_split.to_csv(\n    os.path.join(SPLIT_DIR, \"val_split.csv\"),\n    index=False\n)\n\ntest_split.to_csv(\n    os.path.join(SPLIT_DIR, \"test_split.csv\"),\n    index=False\n)\n\nprint(\"✅ Splits saved successfully\")\nprint(\"Location:\", SPLIT_DIR)\nprint(os.listdir(SPLIT_DIR))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T12:06:41.465267Z","iopub.execute_input":"2026-09-19T12:06:41.466235Z","iopub.status.idle":"2026-09-19T12:06:41.484912Z","shell.execute_reply.started":"2026-09-19T12:06:41.466201Z","shell.execute_reply":"2026-09-19T12:06:41.484279Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"GPUs detected:\", len(tf.config.list_physical_devices(\"GPU\")))\n\nfor gpu in tf.config.list_physical_devices(\"GPU\"):\n    print(\"✅\", gpu)\n\nprint(\"\\nGPU READY:\", bool(tf.config.list_physical_devices(\"GPU\")))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T12:07:25.308401Z","iopub.execute_input":"2026-09-19T12:07:25.309061Z","iopub.status.idle":"2026-09-19T12:07:25.314501Z","shell.execute_reply.started":"2026-09-19T12:07:25.309031Z","shell.execute_reply":"2026-09-19T12:07:25.31389Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport pandas as pd\nimport numpy as np\nimport sklearn\nimport matplotlib\nimport PIL\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"Pandas:\", pd.__version__)\nprint(\"NumPy:\", np.__version__)\nprint(\"Scikit-learn:\", sklearn.__version__)\nprint(\"Matplotlib:\", matplotlib.__version__)\nprint(\"Pillow:\", PIL.__version__)\n\nprint(\"\\n✅ All required libraries imported successfully\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T12:07:57.317455Z","iopub.execute_input":"2026-09-19T12:07:57.318259Z","iopub.status.idle":"2026-09-19T12:07:57.324613Z","shell.execute_reply.started":"2026-09-19T12:07:57.318229Z","shell.execute_reply":"2026-09-19T12:07:57.323605Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.preprocessing.image import ImageDataGenerator\n\nDATASET = \"/kaggle/input/competitions/aptos2019-blindness-detection\"\nIMG_SIZE = (224, 224)\nBATCH_SIZE = 16\n\n# Training augmentation\ntrain_datagen = ImageDataGenerator(\n    rotation_range=15,\n    width_shift_range=0.05,\n    height_shift_range=0.05,\n    zoom_range=0.10,\n    horizontal_flip=True,\n    brightness_range=[0.8, 1.2],\n    fill_mode=\"nearest\"\n)\n\n# Validation/test: NO augmentation\neval_datagen = ImageDataGenerator()\n\ntrain_generator = train_datagen.flow_from_dataframe(\n    train_split,\n    directory=os.path.join(DATASET, \"train_images\"),\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode=\"categorical\",\n    shuffle=True,\n    seed=42\n)\n\nval_generator = eval_datagen.flow_from_dataframe(\n    val_split,\n    directory=os.path.join(DATASET, \"train_images\"),\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode=\"categorical\",\n    shuffle=False\n)\n\ntest_generator = eval_datagen.flow_from_dataframe(\n    test_split,\n    directory=os.path.join(DATASET, \"train_images\"),\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode=\"categorical\",\n    shuffle=False\n)\n\nprint(\"\\nClasses:\", train_generator.class_indices)\nprint(\"Train batches:\", len(train_generator))\nprint(\"Validation batches:\", len(val_generator))\nprint(\"Test batches:\", len(test_generator))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T12:08:32.889357Z","iopub.execute_input":"2026-09-19T12:08:32.889744Z","iopub.status.idle":"2026-09-19T12:08:38.60973Z","shell.execute_reply.started":"2026-09-19T12:08:32.889717Z","shell.execute_reply":"2026-09-19T12:08:38.609063Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.utils.class_weight import compute_class_weight\nimport numpy as np\n\nclasses = np.array([0, 1, 2, 3, 4])\n\nclass_weights_array = compute_class_weight(\n    class_weight=\"balanced\",\n    classes=classes,\n    y=train_split[\"diagnosis\"].values\n)\n\nclass_weights = {\n    int(cls): float(weight)\n    for cls, weight in zip(classes, class_weights_array)\n}\n\nprint(\"Class weights:\")\nfor cls, weight in class_weights.items():\n    print(f\"DR Level {cls}: {weight:.3f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T12:09:11.684991Z","iopub.execute_input":"2026-09-19T12:09:11.685382Z","iopub.status.idle":"2026-09-19T12:09:11.694476Z","shell.execute_reply.started":"2026-09-19T12:09:11.685355Z","shell.execute_reply":"2026-09-19T12:09:11.693535Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers, Model\nfrom tensorflow.keras.applications import EfficientNetB0\n\ntf.keras.backend.clear_session()\n\ninputs = layers.Input(shape=(224, 224, 3))\n\nbase_model = EfficientNetB0(\n    include_top=False,\n    weights=\"imagenet\",\n    input_tensor=inputs\n)\n\n# Phase 1: freeze pretrained backbone\nbase_model.trainable = False\n\nx = base_model(inputs, training=False)\nx = layers.GlobalAveragePooling2D()(x)\nx = layers.BatchNormalization()(x)\nx = layers.Dropout(0.4)(x)\n\noutputs = layers.Dense(\n    5,\n    activation=\"softmax\",\n    name=\"dr_prediction\"\n)(x)\n\nmodel = Model(inputs, outputs)\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=3e-4),\n    loss=\"categorical_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T12:09:42.67931Z","iopub.execute_input":"2026-09-19T12:09:42.67973Z","iopub.status.idle":"2026-09-19T12:09:46.610376Z","shell.execute_reply.started":"2026-09-19T12:09:42.6797Z","shell.execute_reply":"2026-09-19T12:09:46.609816Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.callbacks import (\n    EarlyStopping,\n    ReduceLROnPlateau,\n    ModelCheckpoint\n)\n\ncheckpoint_path = \"/kaggle/working/best_phase1.keras\"\n\ncallbacks_phase1 = [\n    ModelCheckpoint(\n        checkpoint_path,\n        monitor=\"val_accuracy\",\n        save_best_only=True,\n        mode=\"max\",\n        verbose=1\n    ),\n    EarlyStopping(\n        monitor=\"val_accuracy\",\n        patience=4,\n        mode=\"max\",\n        restore_best_weights=True,\n        verbose=1\n    ),\n    ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.3,\n        patience=2,\n        min_lr=1e-6,\n        verbose=1\n    )\n]\n\nhistory_phase1 = model.fit(\n    train_generator,\n    validation_data=val_generator,\n    epochs=12,\n    class_weight=class_weights,\n    callbacks=callbacks_phase1,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T12:10:30.966219Z","iopub.execute_input":"2026-09-19T12:10:30.966695Z","iopub.status.idle":"2026-09-19T13:26:42.77238Z","shell.execute_reply.started":"2026-09-19T12:10:30.966663Z","shell.execute_reply":"2026-09-19T13:26:42.771478Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Unfreeze the backbone for fine-tuning\nbase_model.trainable = True\n\n# Freeze all but the top 30 layers\nfor layer in base_model.layers[:-30]:\n    layer.trainable = False\n\n# Keep BatchNormalization layers frozen for stable fine-tuning\nfor layer in base_model.layers:\n    if isinstance(layer, tf.keras.layers.BatchNormalization):\n        layer.trainable = False\n\n# Recompile with a very small learning rate\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5),\n    loss=\"categorical_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n\nprint(\"Total parameters:\", model.count_params())\nprint(\n    \"Trainable parameters:\",\n    sum(tf.size(v).numpy() for v in model.trainable_weights)\n)\nprint(\n    \"Non-trainable parameters:\",\n    sum(tf.size(v).numpy() for v in model.non_trainable_weights)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T13:28:15.387474Z","iopub.execute_input":"2026-09-19T13:28:15.387981Z","iopub.status.idle":"2026-09-19T13:28:15.48728Z","shell.execute_reply.started":"2026-09-19T13:28:15.38795Z","shell.execute_reply":"2026-09-19T13:28:15.486386Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"checkpoint_path_ft = \"/kaggle/working/best_finetuned.keras\"\n\ncallbacks_ft = [\n    ModelCheckpoint(\n        checkpoint_path_ft,\n        monitor=\"val_accuracy\",\n        save_best_only=True,\n        mode=\"max\",\n        verbose=1\n    ),\n    EarlyStopping(\n        monitor=\"val_accuracy\",\n        patience=5,\n        mode=\"max\",\n        restore_best_weights=True,\n        verbose=1\n    ),\n    ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.3,\n        patience=2,\n        min_lr=1e-7,\n        verbose=1\n    )\n]\n\nhistory_ft = model.fit(\n    train_generator,\n    validation_data=val_generator,\n    epochs=15,\n    class_weight=class_weights,\n    callbacks=callbacks_ft,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T13:28:44.570143Z","iopub.execute_input":"2026-09-19T13:28:44.570962Z","iopub.status.idle":"2026-09-19T14:36:54.441759Z","shell.execute_reply.started":"2026-09-19T13:28:44.57093Z","shell.execute_reply":"2026-09-19T14:36:54.440849Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import (\n    accuracy_score,\n    classification_report,\n    confusion_matrix\n)\nimport numpy as np\n\n# Make absolutely sure we use the best fine-tuned weights\nmodel.load_weights(\"/kaggle/working/best_finetuned.keras\")\n\n# Reset test generator\ntest_generator.reset()\n\n# Predict on untouched final test set\ntest_probs = model.predict(\n    test_generator,\n    verbose=1\n)\n\ntest_pred = np.argmax(test_probs, axis=1)\ntest_true = test_split[\"diagnosis\"].values\n\n# 5-class accuracy\ntest_accuracy = accuracy_score(test_true, test_pred)\n\nprint(f\"\\nFINAL TEST ACCURACY: {test_accuracy * 100:.2f}%\")\n\nprint(\"\\nClassification Report:\")\nprint(\n    classification_report(\n        test_true,\n        test_pred,\n        labels=[0, 1, 2, 3, 4],\n        target_names=[\n            \"No DR (0)\",\n            \"Mild (1)\",\n            \"Moderate (2)\",\n            \"Severe (3)\",\n            \"Proliferative (4)\"\n        ],\n        digits=4,\n        zero_division=0\n    )\n)\n\nprint(\"\\nConfusion Matrix:\")\nprint(confusion_matrix(test_true, test_pred, labels=[0, 1, 2, 3, 4]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T14:47:10.513867Z","iopub.execute_input":"2026-09-19T14:47:10.51433Z","iopub.status.idle":"2026-09-19T14:48:23.215243Z","shell.execute_reply.started":"2026-09-19T14:47:10.5143Z","shell.execute_reply":"2026-09-19T14:48:23.214469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\n\n# Show validation images with true label and predicted label\nval_generator.reset()\nval_probs = model.predict(val_generator, verbose=1)\nval_pred = np.argmax(val_probs, axis=1)\n\nsample_df = val_split.reset_index(drop=True)\n\nplt.figure(figsize=(15, 10))\n\nfor i in range(12):\n    img_path = os.path.join(DATASET, \"train_images\", sample_df.iloc[i][\"filename\"])\n    img = plt.imread(img_path)\n\n    plt.subplot(3, 4, i + 1)\n    plt.imshow(img)\n    plt.axis(\"off\")\n\n    true_label = int(sample_df.iloc[i][\"diagnosis\"])\n    pred_label = int(val_pred[i])\n\n    plt.title(f\"True: {true_label} | Pred: {pred_label}\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T14:49:22.819194Z","iopub.execute_input":"2026-09-19T14:49:22.819599Z","iopub.status.idle":"2026-09-19T14:50:14.443235Z","shell.execute_reply.started":"2026-09-19T14:49:22.819572Z","shell.execute_reply":"2026-09-19T14:50:14.442234Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers, Model\nfrom tensorflow.keras.applications import EfficientNetB3\n\ntf.keras.backend.clear_session()\n\nIMG_SIZE = (300, 300)\nBATCH_SIZE = 16\n\n# Re-create generators at higher resolution\ntrain_generator_b3 = train_datagen.flow_from_dataframe(\n    train_split,\n    directory=os.path.join(DATASET, \"train_images\"),\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode=\"categorical\",\n    shuffle=True,\n    seed=42\n)\n\nval_generator_b3 = eval_datagen.flow_from_dataframe(\n    val_split,\n    directory=os.path.join(DATASET, \"train_images\"),\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode=\"categorical\",\n    shuffle=False\n)\n\ntest_generator_b3 = eval_datagen.flow_from_dataframe(\n    test_split,\n    directory=os.path.join(DATASET, \"train_images\"),\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode=\"categorical\",\n    shuffle=False\n)\n\n# EfficientNetB3\ninputs = layers.Input(shape=(300, 300, 3))\n\nbase_model_b3 = EfficientNetB3(\n    include_top=False,\n    weights=\"imagenet\",\n    input_tensor=inputs\n)\n\nbase_model_b3.trainable = False\n\nx = base_model_b3(inputs, training=False)\nx = layers.GlobalAveragePooling2D()(x)\nx = layers.BatchNormalization()(x)\nx = layers.Dropout(0.4)(x)\n\noutputs = layers.Dense(\n    5,\n    activation=\"softmax\",\n    name=\"dr_prediction\"\n)(x)\n\nmodel_b3 = Model(inputs, outputs)\n\nmodel_b3.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=3e-4),\n    loss=\"categorical_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n\nprint(\"EfficientNetB3 model created successfully.\")\nprint(\"Input size:\", IMG_SIZE)\nprint(\"Total parameters:\", model_b3.count_params())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T14:51:45.003447Z","iopub.execute_input":"2026-09-19T14:51:45.004175Z","iopub.status.idle":"2026-09-19T14:52:01.151235Z","shell.execute_reply.started":"2026-09-19T14:51:45.004144Z","shell.execute_reply":"2026-09-19T14:52:01.150136Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\n\nphase1_checkpoint = \"/kaggle/working/best_b3_phase1.keras\"\n\ncallbacks_phase1_b3 = [\n    ModelCheckpoint(\n        phase1_checkpoint,\n        monitor=\"val_accuracy\",\n        save_best_only=True,\n        mode=\"max\",\n        verbose=1\n    ),\n    EarlyStopping(\n        monitor=\"val_accuracy\",\n        patience=4,\n        mode=\"max\",\n        restore_best_weights=True,\n        verbose=1\n    ),\n    ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.3,\n        patience=2,\n        min_lr=1e-7,\n        verbose=1\n    )\n]\n\nhistory_b3_phase1 = model_b3.fit(\n    train_generator_b3,\n    validation_data=val_generator_b3,\n    epochs=12,\n    class_weight=class_weights,\n    callbacks=callbacks_phase1_b3,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T14:52:55.826416Z","iopub.execute_input":"2026-09-19T14:52:55.827318Z","iopub.status.idle":"2026-09-19T16:13:36.049953Z","shell.execute_reply.started":"2026-09-19T14:52:55.827286Z","shell.execute_reply":"2026-09-19T16:13:36.049173Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load the best Phase 1 weights\nmodel_b3.load_weights(\"/kaggle/working/best_b3_phase1.keras\")\n\n# Unfreeze the backbone\nbase_model_b3.trainable = True\n\n# Freeze earlier layers; fine-tune only the top 40 layers\nfor layer in base_model_b3.layers[:-40]:\n    layer.trainable = False\n\n# Keep BatchNormalization layers frozen for stable training\nfor layer in base_model_b3.layers:\n    if isinstance(layer, tf.keras.layers.BatchNormalization):\n        layer.trainable = False\n\n# Recompile with a small learning rate\nmodel_b3.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5),\n    loss=\"categorical_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n\nprint(\"Fine-tuning setup complete.\")\nprint(\"Trainable parameters:\", \n      sum(tf.keras.backend.count_params(w) for w in model_b3.trainable_weights))\nprint(\"Total parameters:\", model_b3.count_params())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T16:31:58.935525Z","iopub.status.idle":"2026-09-19T16:31:58.935903Z","shell.execute_reply.started":"2026-09-19T16:31:58.935702Z","shell.execute_reply":"2026-09-19T16:31:58.935719Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping\n\nfinetune_checkpoint_b3 = \"/kaggle/working/best_b3_finetuned.keras\"\n\ncallbacks_fast = [\n    ModelCheckpoint(\n        finetune_checkpoint_b3,\n        monitor=\"val_accuracy\",\n        save_best_only=True,\n        mode=\"max\",\n        verbose=1\n    ),\n    EarlyStopping(\n        monitor=\"val_accuracy\",\n        patience=2,\n        mode=\"max\",\n        restore_best_weights=True,\n        verbose=1\n    )\n]\n\nhistory_b3_finetune = model_b3.fit(\n    train_generator_b3,\n    validation_data=val_generator_b3,\n    epochs=5,\n    class_weight=class_weights,\n    callbacks=callbacks_fast,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T16:32:01.138056Z","iopub.execute_input":"2026-09-19T16:32:01.138618Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import accuracy_score, classification_report, confusion_matrix\n\n# Load the best fine-tuned B3 model\nmodel_b3.load_weights(\"/kaggle/working/best_b3_finetuned.keras\")\n\n# Reset test generator\ntest_generator_b3.reset()\n\n# Predict on the untouched final test set\ntest_probs_b3 = model_b3.predict(\n    test_generator_b3,\n    verbose=1\n)\n\ntest_pred_b3 = np.argmax(test_probs_b3, axis=1)\ntest_true_b3 = test_split[\"diagnosis\"].values\n\n# Overall accuracy\ntest_accuracy_b3 = accuracy_score(\n    test_true_b3,\n    test_pred_b3\n)\n\nprint(f\"\\nFINAL B3 TEST ACCURACY: {test_accuracy_b3 * 100:.2f}%\")\n\nprint(\"\\nClassification Report:\")\nprint(\n    classification_report(\n        test_true_b3,\n        test_pred_b3,\n        labels=[0, 1, 2, 3, 4],\n        target_names=[\n            \"No DR (0)\",\n            \"Mild (1)\",\n            \"Moderate (2)\",\n            \"Severe (3)\",\n            \"Proliferative (4)\"\n        ],\n        digits=4,\n        zero_division=0\n    )\n)\n\nprint(\"\\nConfusion Matrix:\")\nprint(\n    confusion_matrix(\n        test_true_b3,\n        test_pred_b3,\n        labels=[0, 1, 2, 3, 4]\n    )\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:13:03.179959Z","iopub.execute_input":"2026-09-19T17:13:03.18082Z","iopub.status.idle":"2026-09-19T17:14:18.389222Z","shell.execute_reply.started":"2026-09-19T17:13:03.180744Z","shell.execute_reply":"2026-09-19T17:14:18.388462Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\n\n# Referable DR = Level 2, 3, or 4\ntrue_ref = (test_true_b3 >= 2).astype(int)\n\n# Probability that image is referable\nref_probs_b3 = test_probs_b3[:, 2:].sum(axis=1)\n\n# Default threshold\npred_ref = (ref_probs_b3 >= 0.50).astype(int)\n\ncm_ref = confusion_matrix(true_ref, pred_ref)\n\ntn, fp, fn, tp = cm_ref.ravel()\n\nsensitivity = tp / (tp + fn)\nspecificity = tn / (tn + fp)\n\nprint(\"Referable DR confusion matrix:\")\nprint(cm_ref)\n\nprint(f\"\\nSensitivity: {sensitivity * 100:.2f}%\")\nprint(f\"Specificity: {specificity * 100:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:15:11.516996Z","iopub.execute_input":"2026-09-19T17:15:11.517647Z","iopub.status.idle":"2026-09-19T17:15:11.526198Z","shell.execute_reply.started":"2026-09-19T17:15:11.517607Z","shell.execute_reply":"2026-09-19T17:15:11.525055Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix\nimport numpy as np\n\n# Get validation probabilities from the best B3 model\nmodel_b3.load_weights(\"/kaggle/working/best_b3_finetuned.keras\")\n\nval_generator_b3.reset()\nval_probs_b3 = model_b3.predict(val_generator_b3, verbose=1)\n\nval_true_b3 = val_split[\"diagnosis\"].values\n\n# Referable = Level 2, 3, 4\nval_true_ref = (val_true_b3 >= 2).astype(int)\n\n# Sum probabilities for Levels 2-4\nval_ref_probs = val_probs_b3[:, 2:].sum(axis=1)\n\nprint(\"Threshold | Sensitivity | Specificity\")\nprint(\"--------------------------------------\")\n\nfor threshold in np.arange(0.10, 0.51, 0.05):\n    val_pred_ref = (val_ref_probs >= threshold).astype(int)\n\n    tn, fp, fn, tp = confusion_matrix(\n        val_true_ref,\n        val_pred_ref,\n        labels=[0, 1]\n    ).ravel()\n\n    sensitivity = tp / (tp + fn)\n    specificity = tn / (tn + fp)\n\n    print(\n        f\"{threshold:.2f}      | \"\n        f\"{sensitivity*100:6.2f}%     | \"\n        f\"{specificity*100:6.2f}%\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:15:44.776868Z","iopub.execute_input":"2026-09-19T17:15:44.777629Z","iopub.status.idle":"2026-09-19T17:16:34.202259Z","shell.execute_reply.started":"2026-09-19T17:15:44.777603Z","shell.execute_reply":"2026-09-19T17:16:34.201557Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Use the already-generated B3 test probabilities\n# Threshold was selected using validation data only.\nTEST_THRESHOLD = 0.15\n\ntest_true_ref = (test_true_b3 >= 2).astype(int)\n\ntest_ref_probs = test_probs_b3[:, 2:].sum(axis=1)\n\ntest_pred_ref = (test_ref_probs >= TEST_THRESHOLD).astype(int)\n\ncm_ref_test = confusion_matrix(\n    test_true_ref,\n    test_pred_ref,\n    labels=[0, 1]\n)\n\ntn, fp, fn, tp = cm_ref_test.ravel()\n\ntest_sensitivity = tp / (tp + fn)\ntest_specificity = tn / (tn + fp)\n\nprint(\"LOCKED THRESHOLD:\", TEST_THRESHOLD)\n\nprint(\"\\nTest Referable DR confusion matrix:\")\nprint(cm_ref_test)\n\nprint(f\"\\nTest Sensitivity: {test_sensitivity * 100:.2f}%\")\nprint(f\"Test Specificity: {test_specificity * 100:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:17:34.372587Z","iopub.execute_input":"2026-09-19T17:17:34.373533Z","iopub.status.idle":"2026-09-19T17:17:34.382167Z","shell.execute_reply.started":"2026-09-19T17:17:34.373501Z","shell.execute_reply":"2026-09-19T17:17:34.381304Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\nimport shutil\n\n# Final model directory\nsave_dir = \"/kaggle/working/final_dr_model\"\nos.makedirs(save_dir, exist_ok=True)\n\n# Save model\nfinal_model_path = os.path.join(\n    save_dir,\n    \"aptos_efficientnetb3_dr.keras\"\n)\n\nmodel_b3.save(final_model_path)\n\n# Save configuration and locked test/validation information\nconfig = {\n    \"model\": \"EfficientNetB3\",\n    \"input_size\": [300, 300],\n    \"classes\": {\n        \"0\": \"No DR\",\n        \"1\": \"Mild DR\",\n        \"2\": \"Moderate DR\",\n        \"3\": \"Severe DR\",\n        \"4\": \"Proliferative DR\"\n    },\n    \"referable_definition\": \"DR Level >= 2\",\n    \"referable_threshold\": 0.15,\n    \"validation_threshold_sensitivity\": 91.28,\n    \"validation_threshold_specificity\": 89.86,\n    \"final_test_accuracy_5_class\": 76.57,\n    \"final_test_referable_sensitivity\": 97.32,\n    \"final_test_referable_specificity\": 90.37\n}\n\nconfig_path = os.path.join(save_dir, \"model_config.json\")\n\nwith open(config_path, \"w\") as f:\n    json.dump(config, f, indent=4)\n\nprint(\"FINAL MODEL SAVED\")\nprint(final_model_path)\nprint(config_path)\nprint(\"\\nLocked referable threshold:\", config[\"referable_threshold\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:18:09.598009Z","iopub.execute_input":"2026-09-19T17:18:09.598936Z","iopub.status.idle":"2026-09-19T17:18:10.828046Z","shell.execute_reply.started":"2026-09-19T17:18:09.598903Z","shell.execute_reply":"2026-09-19T17:18:10.82709Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Find convolutional layers for Grad-CAM\n\nfor i, layer in enumerate(model_b3.layers):\n    print(i, layer.name, type(layer).__name__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:18:49.432883Z","iopub.execute_input":"2026-09-19T17:18:49.433712Z","iopub.status.idle":"2026-09-19T17:18:49.438492Z","shell.execute_reply.started":"2026-09-19T17:18:49.43368Z","shell.execute_reply":"2026-09-19T17:18:49.43762Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Find the last convolutional layer inside EfficientNetB3\n\nfor i, layer in enumerate(base_model_b3.layers):\n    if isinstance(layer, tf.keras.layers.Conv2D):\n        print(i, layer.name, type(layer).__name__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:19:26.272151Z","iopub.execute_input":"2026-09-19T17:19:26.272858Z","iopub.status.idle":"2026-09-19T17:19:26.279525Z","shell.execute_reply.started":"2026-09-19T17:19:26.272818Z","shell.execute_reply":"2026-09-19T17:19:26.278848Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport numpy as np\nimport cv2\nimport matplotlib.pyplot as plt\n\n# Grad-CAM model\ngrad_model = tf.keras.models.Model(\n    inputs=model_b3.input,\n    outputs=[\n        base_model_b3.get_layer(\"top_conv\").output,\n        model_b3.output\n    ]\n)\n\ndef make_gradcam_heatmap(img_array, class_index):\n    \"\"\"\n    img_array:\n        Shape = (1, 300, 300, 3)\n\n    class_index:\n        DR class 0-4\n    \"\"\"\n\n    with tf.GradientTape() as tape:\n        conv_outputs, predictions = grad_model(img_array, training=False)\n        class_output = predictions[:, class_index]\n\n    # Gradient of target class with respect to feature maps\n    grads = tape.gradient(class_output, conv_outputs)\n\n    # Global average pooling of gradients\n    pooled_grads = tf.reduce_mean(grads, axis=(1, 2))\n\n    # Weight feature maps\n    conv_outputs = conv_outputs[0]\n    pooled_grads = pooled_grads[0]\n\n    heatmap = tf.reduce_sum(\n        conv_outputs * pooled_grads,\n        axis=-1\n    )\n\n    # ReLU\n    heatmap = tf.maximum(heatmap, 0)\n\n    # Normalize\n    max_value = tf.reduce_max(heatmap)\n\n    if max_value > 0:\n        heatmap /= max_value\n\n    return heatmap.numpy()\n\nprint(\"Grad-CAM function created successfully.\")\nprint(\"Target layer: top_conv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:20:03.198434Z","iopub.execute_input":"2026-09-19T17:20:03.199259Z","iopub.status.idle":"2026-09-19T17:20:03.228858Z","shell.execute_reply.started":"2026-09-19T17:20:03.199228Z","shell.execute_reply":"2026-09-19T17:20:03.228156Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport os\n\n# Load the same untouched test split\ntest_df_b3 = pd.read_csv(\n    \"/kaggle/working/dr_splits/test_split.csv\"\n)\n\nprint(\"Test images:\", len(test_df_b3))\nprint(test_df_b3.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:21:06.813574Z","iopub.execute_input":"2026-09-19T17:21:06.814469Z","iopub.status.idle":"2026-09-19T17:21:06.830724Z","shell.execute_reply.started":"2026-09-19T17:21:06.814438Z","shell.execute_reply":"2026-09-19T17:21:06.830135Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nTEST_DIR = \"/kaggle/input/competitions/aptos2019-blindness-detection/test_images\"\n\nprint(\"TEST_DIR:\", TEST_DIR)\nprint(\"Folder exists:\", os.path.exists(TEST_DIR))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:22:03.3577Z","iopub.execute_input":"2026-09-19T17:22:03.358437Z","iopub.status.idle":"2026-09-19T17:22:03.38304Z","shell.execute_reply.started":"2026-09-19T17:22:03.358406Z","shell.execute_reply":"2026-09-19T17:22:03.382213Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TEST_DIR = \"/kaggle/input/competitions/aptos2019-blindness-detection/train_images\"\n\nprint(\"Held-out test image folder:\", TEST_DIR)\nprint(\"Folder exists:\", os.path.exists(TEST_DIR))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:23:02.326164Z","iopub.execute_input":"2026-09-19T17:23:02.32661Z","iopub.status.idle":"2026-09-19T17:23:02.332918Z","shell.execute_reply.started":"2026-09-19T17:23:02.326576Z","shell.execute_reply":"2026-09-19T17:23:02.33221Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Select one fixed held-out test image\nsample_row = test_df_b3.sample(1, random_state=42).iloc[0]\n\nimg_name = sample_row[\"filename\"]\ntrue_label = int(sample_row[\"label\"])\n\nimg_path = os.path.join(TEST_DIR, img_name)\n\n# Load image\nimg = image.load_img(img_path, target_size=(300, 300))\nimg_array = image.img_to_array(img)\nimg_array = np.expand_dims(img_array, axis=0)\n\n# Model prediction\npred_probs = model_b3.predict(img_array, verbose=0)[0]\n\npred_class = int(np.argmax(pred_probs))\npred_confidence = float(pred_probs[pred_class])\n\n# Grad-CAM\nheatmap = make_gradcam_heatmap(\n    img_array,\n    pred_class\n)\n\nprint(\"Image:\", img_name)\nprint(\"True DR Level:\", true_label)\nprint(\"Predicted DR Level:\", pred_class)\nprint(f\"Prediction confidence: {pred_confidence * 100:.2f}%\")\nprint(\"Heatmap shape:\", heatmap.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:23:28.387134Z","iopub.execute_input":"2026-09-19T17:23:28.387994Z","iopub.status.idle":"2026-09-19T17:23:42.94228Z","shell.execute_reply.started":"2026-09-19T17:23:28.387964Z","shell.execute_reply":"2026-09-19T17:23:42.94112Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Rebuild Grad-CAM model directly through EfficientNetB3\n\ngrad_model = tf.keras.models.Model(\n    inputs=model_b3.input,\n    outputs=[\n        base_model_b3.get_layer(\"top_conv\").output,\n        model_b3.output\n    ]\n)\n\n# Check tensor shapes\ndummy_input = tf.zeros((1, 300, 300, 3))\n\nwith tf.GradientTape() as tape:\n    conv_outputs, predictions = grad_model(dummy_input, training=False)\n    class_output = predictions[:, 0]\n\ngrads = tape.gradient(class_output, conv_outputs)\n\nprint(\"Conv output shape:\", conv_outputs.shape)\nprint(\"Prediction shape:\", predictions.shape)\nprint(\"Gradient available:\", grads is not None)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:24:14.326867Z","iopub.execute_input":"2026-09-19T17:24:14.327164Z","iopub.status.idle":"2026-09-19T17:24:15.796127Z","shell.execute_reply.started":"2026-09-19T17:24:14.32714Z","shell.execute_reply":"2026-09-19T17:24:15.795382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Rebuild EfficientNetB3 as one connected graph for Grad-CAM\n\ninputs = tf.keras.Input(shape=(300, 300, 3), name=\"gradcam_input\")\n\n# Use the trained EfficientNetB3 base\nfeatures = base_model_b3(inputs, training=False)\n\n# Use the SAME trained classification head\nx = model_b3.get_layer(\"global_average_pooling2d\")(features)\nx = model_b3.get_layer(\"batch_normalization\")(x)\nx = model_b3.get_layer(\"dropout\")(x)\noutputs = model_b3.get_layer(\"dr_prediction\")(x)\n\n# New connected model\ngrad_model = tf.keras.Model(\n    inputs=inputs,\n    outputs=[\n        base_model_b3.get_layer(\"top_conv\").output,\n        outputs\n    ]\n)\n\nprint(\"Grad-CAM graph rebuilt.\")\nprint(\"Output count:\", len(grad_model.outputs))\nprint(\"Prediction output shape:\", grad_model.outputs[1].shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:24:39.681588Z","iopub.execute_input":"2026-09-19T17:24:39.682361Z","iopub.status.idle":"2026-09-19T17:24:39.712989Z","shell.execute_reply.started":"2026-09-19T17:24:39.682333Z","shell.execute_reply":"2026-09-19T17:24:39.712291Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Number of inputs:\", len(efficientnet_base.inputs))\nprint(\"Number of outputs:\", len(efficientnet_base.outputs))\n\nprint(\"\\nInput tensor:\")\nprint(efficientnet_base.inputs[0])\n\nprint(\"\\nOutput tensor:\")\nprint(efficientnet_base.outputs[0])\n\nprint(\"\\nTarget layer:\")\nprint(top_conv_layer.name)\nprint(\"Target layer output:\")\nprint(top_conv_layer.output)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:25:56.628308Z","iopub.execute_input":"2026-09-19T17:25:56.628908Z","iopub.status.idle":"2026-09-19T17:25:56.637215Z","shell.execute_reply.started":"2026-09-19T17:25:56.628877Z","shell.execute_reply":"2026-09-19T17:25:56.636083Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"top_conv_layer = efficientnet_base.get_layer(\"top_conv\")\n\nprint(\"Target layer:\", top_conv_layer.name)\nprint(\"Target output shape:\", top_conv_layer.output.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:26:21.830723Z","iopub.execute_input":"2026-09-19T17:26:21.831226Z","iopub.status.idle":"2026-09-19T17:26:21.83682Z","shell.execute_reply.started":"2026-09-19T17:26:21.831196Z","shell.execute_reply":"2026-09-19T17:26:21.835828Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Top-level model layers:\")\n\nfor i, layer in enumerate(model_b3.layers):\n    print(i, layer.name, layer.__class__.__name__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:27:14.006324Z","iopub.execute_input":"2026-09-19T17:27:14.007293Z","iopub.status.idle":"2026-09-19T17:27:14.012341Z","shell.execute_reply.started":"2026-09-19T17:27:14.007254Z","shell.execute_reply":"2026-09-19T17:27:14.011375Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Get the trained EfficientNetB3 inside the final model\nefficientnet_base = model_b3.get_layer(\"efficientnetb3\")\n\n# Create a model that maps EfficientNet input -> top_conv feature map\nfeature_model = tf.keras.Model(\n    inputs=efficientnet_base.inputs[0],\n    outputs=efficientnet_base.get_layer(\"top_conv\").output\n)\n\nprint(\"Feature model created\")\nprint(\"Input:\", feature_model.input.shape)\nprint(\"Feature map:\", feature_model.output.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:27:38.648314Z","iopub.execute_input":"2026-09-19T17:27:38.648748Z","iopub.status.idle":"2026-09-19T17:27:38.67365Z","shell.execute_reply.started":"2026-09-19T17:27:38.648719Z","shell.execute_reply":"2026-09-19T17:27:38.673033Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Build Grad-CAM model using the trained EfficientNetB3\ngrad_input = tf.keras.Input(shape=(300, 300, 3))\n\n# EfficientNetB3 feature extraction\nfeatures = efficientnet_base(grad_input, training=False)\n\n# Trained classification head\nx = model_b3.get_layer(\"global_average_pooling2d\")(features)\nx = model_b3.get_layer(\"batch_normalization\")(x)\nx = model_b3.get_layer(\"dropout\")(x, training=False)\npredictions = model_b3.get_layer(\"dr_prediction\")(x)\n\n# Check the connection\ngrad_test_model = tf.keras.Model(\n    grad_input,\n    predictions\n)\n\nprint(\"Prediction model created\")\nprint(\"Output shape:\", grad_test_model.output.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:28:04.189628Z","iopub.execute_input":"2026-09-19T17:28:04.190524Z","iopub.status.idle":"2026-09-19T17:28:04.204758Z","shell.execute_reply.started":"2026-09-19T17:28:04.190485Z","shell.execute_reply":"2026-09-19T17:28:04.204063Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport os\nimport numpy as np\nimport tensorflow as tf\n\nTEST_SPLIT = \"/kaggle/working/dr_splits/test_split.csv\"\nTEST_DIR = \"/kaggle/input/competitions/aptos2019-blindness-detection/train_images\"\n\ntest_df = pd.read_csv(TEST_SPLIT)\n\nprint(\"Test images:\", len(test_df))\nprint(test_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:28:58.36108Z","iopub.execute_input":"2026-09-19T17:28:58.36191Z","iopub.status.idle":"2026-09-19T17:28:58.372335Z","shell.execute_reply.started":"2026-09-19T17:28:58.361878Z","shell.execute_reply":"2026-09-19T17:28:58.371486Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_row = test_df.iloc[0]\n\nimg_path = os.path.join(TEST_DIR, sample_row[\"filename\"])\n\nimg = tf.keras.utils.load_img(\n    img_path,\n    target_size=(300, 300)\n)\n\nimg_array = tf.keras.utils.img_to_array(img)\nimg_array = np.expand_dims(img_array, axis=0).astype(np.float32)\n\nwith tf.GradientTape() as tape:\n    conv_outputs = feature_model(img_array, training=False)\n    tape.watch(conv_outputs)\n\n    x = tf.keras.layers.GlobalAveragePooling2D()(conv_outputs)\n    x = model_b3.get_layer(\"batch_normalization\")(x)\n    x = model_b3.get_layer(\"dropout\")(x, training=False)\n    predictions = model_b3.get_layer(\"dr_prediction\")(x)\n\n    predicted_class = tf.argmax(predictions[0])\n    class_score = predictions[:, predicted_class]\n\ngrads = tape.gradient(class_score, conv_outputs)\n\nprint(\"True label:\", int(sample_row[\"diagnosis\"]))\nprint(\"Predicted class:\", int(predicted_class))\nprint(\"Prediction score:\", float(class_score[0]))\nprint(\"Feature map shape:\", conv_outputs.shape)\nprint(\"Gradient available:\", grads is not None)\n\nif grads is not None:\n    print(\"Gradient shape:\", grads.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:29:20.050385Z","iopub.execute_input":"2026-09-19T17:29:20.050849Z","iopub.status.idle":"2026-09-19T17:29:21.157301Z","shell.execute_reply.started":"2026-09-19T17:29:20.050812Z","shell.execute_reply":"2026-09-19T17:29:21.156172Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Calculate Grad-CAM heatmap\n\npooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n\nconv_map = conv_outputs[0]\n\nheatmap = tf.reduce_sum(\n    conv_map * pooled_grads,\n    axis=-1\n)\n\nheatmap = tf.maximum(heatmap, 0)\n\nmax_value = tf.reduce_max(heatmap)\n\nif float(max_value) > 0:\n    heatmap = heatmap / max_value\n\nheatmap = heatmap.numpy()\n\nprint(\"Heatmap shape:\", heatmap.shape)\nprint(\"Heatmap min:\", heatmap.min())\nprint(\"Heatmap max:\", heatmap.max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:29:46.989501Z","iopub.execute_input":"2026-09-19T17:29:46.989974Z","iopub.status.idle":"2026-09-19T17:29:47.002015Z","shell.execute_reply.started":"2026-09-19T17:29:46.989934Z","shell.execute_reply":"2026-09-19T17:29:47.001016Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom PIL import Image\nimport cv2\n\n# Original image\noriginal_img = np.array(\n    Image.open(img_path).convert(\"RGB\").resize((300, 300))\n)\n\n# Resize heatmap to image size\nheatmap_resized = cv2.resize(\n    heatmap,\n    (300, 300),\n    interpolation=cv2.INTER_LINEAR\n)\n\n# Convert heatmap to color map\nheatmap_color = cv2.applyColorMap(\n    np.uint8(255 * heatmap_resized),\n    cv2.COLORMAP_JET\n)\n\nheatmap_color = cv2.cvtColor(\n    heatmap_color,\n    cv2.COLOR_BGR2RGB\n)\n\n# Overlay\noverlay = np.uint8(\n    0.6 * original_img +\n    0.4 * heatmap_color\n)\n\n# Display\nplt.figure(figsize=(12, 4))\n\nplt.subplot(1, 3, 1)\nplt.imshow(original_img)\nplt.title(\"Original Retinal Image\")\nplt.axis(\"off\")\n\nplt.subplot(1, 3, 2)\nplt.imshow(heatmap_resized, cmap=\"jet\")\nplt.title(\"Grad-CAM Heatmap\")\nplt.axis(\"off\")\n\nplt.subplot(1, 3, 3)\nplt.imshow(overlay)\nplt.title(f\"Grad-CAM | Predicted: {int(predicted_class)}\")\nplt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:30:13.948143Z","iopub.execute_input":"2026-09-19T17:30:13.949023Z","iopub.status.idle":"2026-09-19T17:30:14.469315Z","shell.execute_reply.started":"2026-09-19T17:30:13.94899Z","shell.execute_reply":"2026-09-19T17:30:14.468421Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Find the first referable DR case (Level 2+)\nreferable_idx = test_df.index[test_df[\"diagnosis\"] >= 2][0]\n\nsample_row = test_df.loc[referable_idx]\n\nprint(\"Filename:\", sample_row[\"filename\"])\nprint(\"True DR level:\", int(sample_row[\"diagnosis\"]))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:30:53.594561Z","iopub.execute_input":"2026-09-19T17:30:53.595418Z","iopub.status.idle":"2026-09-19T17:30:53.600876Z","shell.execute_reply.started":"2026-09-19T17:30:53.595389Z","shell.execute_reply":"2026-09-19T17:30:53.599959Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load the referable DR image\nimg_path = os.path.join(TEST_DIR, sample_row[\"filename\"])\n\nimg = tf.keras.utils.load_img(\n    img_path,\n    target_size=(300, 300)\n)\n\nimg_array = tf.keras.utils.img_to_array(img)\nimg_array = np.expand_dims(img_array, axis=0).astype(np.float32)\n\n# Gradient calculation\nwith tf.GradientTape() as tape:\n    conv_outputs = feature_model(img_array, training=False)\n    tape.watch(conv_outputs)\n\n    x = tf.keras.layers.GlobalAveragePooling2D()(conv_outputs)\n    x = model_b3.get_layer(\"batch_normalization\")(x)\n    x = model_b3.get_layer(\"dropout\")(x, training=False)\n    predictions = model_b3.get_layer(\"dr_prediction\")(x)\n\n    predicted_class = tf.argmax(predictions[0])\n    class_score = predictions[:, predicted_class]\n\ngrads = tape.gradient(class_score, conv_outputs)\n\n# Grad-CAM\npooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\nconv_map = conv_outputs[0]\n\nheatmap = tf.reduce_sum(\n    conv_map * pooled_grads,\n    axis=-1\n)\n\nheatmap = tf.maximum(heatmap, 0)\nheatmap = heatmap / (tf.reduce_max(heatmap) + 1e-8)\nheatmap = heatmap.numpy()\n\nprint(\"True label:\", int(sample_row[\"diagnosis\"]))\nprint(\"Predicted class:\", int(predicted_class))\nprint(\"Prediction confidence:\", float(class_score[0]))\nprint(\"Heatmap shape:\", heatmap.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:31:19.965414Z","iopub.execute_input":"2026-09-19T17:31:19.966206Z","iopub.status.idle":"2026-09-19T17:31:21.13716Z","shell.execute_reply.started":"2026-09-19T17:31:19.966177Z","shell.execute_reply":"2026-09-19T17:31:21.136077Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom PIL import Image\nimport cv2\n\noriginal_img = np.array(\n    Image.open(img_path).convert(\"RGB\").resize((300, 300))\n)\n\nheatmap_resized = cv2.resize(\n    heatmap,\n    (300, 300),\n    interpolation=cv2.INTER_LINEAR\n)\n\nheatmap_color = cv2.applyColorMap(\n    np.uint8(255 * heatmap_resized),\n    cv2.COLORMAP_JET\n)\n\nheatmap_color = cv2.cvtColor(\n    heatmap_color,\n    cv2.COLOR_BGR2RGB\n)\n\noverlay = np.uint8(\n    0.6 * original_img +\n    0.4 * heatmap_color\n)\n\nplt.figure(figsize=(12, 4))\n\nplt.subplot(1, 3, 1)\nplt.imshow(original_img)\nplt.title(\"Original | True Level 2\")\nplt.axis(\"off\")\n\nplt.subplot(1, 3, 2)\nplt.imshow(heatmap_resized, cmap=\"jet\")\nplt.title(\"Grad-CAM Heatmap\")\nplt.axis(\"off\")\n\nplt.subplot(1, 3, 3)\nplt.imshow(overlay)\nplt.title(\"Grad-CAM | Predicted Level 2\")\nplt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:31:49.891237Z","iopub.execute_input":"2026-09-19T17:31:49.892073Z","iopub.status.idle":"2026-09-19T17:31:50.480907Z","shell.execute_reply.started":"2026-09-19T17:31:49.892029Z","shell.execute_reply":"2026-09-19T17:31:50.479928Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"h, w = heatmap.shape\n\n# Approximate circular retinal field mask\nyy, xx = np.ogrid[:h, :w]\n\ncx = (w - 1) / 2\ncy = (h - 1) / 2\nradius = min(h, w) * 0.45\n\nretina_mask = (xx - cx) ** 2 + (yy - cy) ** 2 <= radius ** 2\n\n# Heatmap statistics inside retinal field only\nretina_heatmap = heatmap[retina_mask]\n\nprint(\"Heatmap max - full image:\",\n      float(np.max(heatmap)))\n\nprint(\"Heatmap max - retinal area:\",\n      float(np.max(retina_heatmap)))\n\nprint(\"Mean activation - retinal area:\",\n      float(np.mean(retina_heatmap)))\n\nprint(\"Max location (10x10):\",\n      np.unravel_index(np.argmax(heatmap), heatmap.shape))\n\nprint(\"Max location inside retina:\")\nmasked_heatmap = np.where(retina_mask, heatmap, -1)\nprint(np.unravel_index(np.argmax(masked_heatmap), heatmap.shape))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:32:26.9064Z","iopub.execute_input":"2026-09-19T17:32:26.906909Z","iopub.status.idle":"2026-09-19T17:32:26.915558Z","shell.execute_reply.started":"2026-09-19T17:32:26.906872Z","shell.execute_reply":"2026-09-19T17:32:26.914544Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create a version with the outer border removed\nmasked_img = img_array.copy()\n\nh, w = 300, 300\nyy, xx = np.ogrid[:h, :w]\n\ncx = (w - 1) / 2\ncy = (h - 1) / 2\nradius = 135\n\nmask = ((xx - cx) ** 2 + (yy - cy) ** 2 <= radius ** 2)\n\n# Set outside retinal circle to the mean retinal intensity\nfor c in range(3):\n    channel = masked_img[0, :, :, c]\n    mean_value = np.mean(channel[mask])\n    channel[~mask] = mean_value\n    masked_img[0, :, :, c] = channel\n\n# Original prediction\noriginal_pred = model_b3.predict(img_array, verbose=0)[0]\n\n# Border-masked prediction\nmasked_pred = model_b3.predict(masked_img, verbose=0)[0]\n\nprint(\"Original prediction:\")\nprint(\"Class:\", np.argmax(original_pred))\nprint(\"Confidence:\", float(np.max(original_pred)))\n\nprint(\"\\nBorder-masked prediction:\")\nprint(\"Class:\", np.argmax(masked_pred))\nprint(\"Confidence:\", float(np.max(masked_pred)))\n\nprint(\"\\nConfidence change:\",\n      float(np.max(masked_pred) - np.max(original_pred)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:32:57.306478Z","iopub.execute_input":"2026-09-19T17:32:57.307282Z","iopub.status.idle":"2026-09-19T17:32:57.528691Z","shell.execute_reply.started":"2026-09-19T17:32:57.307252Z","shell.execute_reply":"2026-09-19T17:32:57.527935Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"referable_df = test_df[test_df[\"diagnosis\"] >= 2].head(10)\n\nresults = []\n\nfor _, row in referable_df.iterrows():\n\n    path = os.path.join(TEST_DIR, row[\"filename\"])\n\n    image = tf.keras.utils.load_img(\n        path,\n        target_size=(300, 300)\n    )\n\n    arr = tf.keras.utils.img_to_array(image)\n    arr = np.expand_dims(arr, axis=0).astype(np.float32)\n\n    # Circular retinal mask\n    h, w = 300, 300\n    yy, xx = np.ogrid[:h, :w]\n\n    cx = (w - 1) / 2\n    cy = (h - 1) / 2\n    radius = 135\n\n    mask = ((xx - cx) ** 2 + (yy - cy) ** 2 <= radius ** 2)\n\n    masked = arr.copy()\n\n    for c in range(3):\n        channel = masked[0, :, :, c]\n        mean_value = np.mean(channel[mask])\n        channel[~mask] = mean_value\n        masked[0, :, :, c] = channel\n\n    original_pred = model_b3.predict(arr, verbose=0)[0]\n    masked_pred = model_b3.predict(masked, verbose=0)[0]\n\n    results.append([\n        row[\"filename\"],\n        int(row[\"diagnosis\"]),\n        int(np.argmax(original_pred)),\n        float(np.max(original_pred)),\n        int(np.argmax(masked_pred)),\n        float(np.max(masked_pred))\n    ])\n\nresults_df = pd.DataFrame(\n    results,\n    columns=[\n        \"filename\",\n        \"true_level\",\n        \"original_class\",\n        \"original_confidence\",\n        \"masked_class\",\n        \"masked_confidence\"\n    ]\n)\n\nprint(results_df.to_string(index=False))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:33:26.463362Z","iopub.execute_input":"2026-09-19T17:33:26.463858Z","iopub.status.idle":"2026-09-19T17:33:30.104439Z","shell.execute_reply.started":"2026-09-19T17:33:26.463813Z","shell.execute_reply":"2026-09-19T17:33:30.103712Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"focus_results = []\n\nfor _, row in referable_df.iterrows():\n\n    path = os.path.join(TEST_DIR, row[\"filename\"])\n\n    image = tf.keras.utils.load_img(\n        path,\n        target_size=(300, 300)\n    )\n\n    arr = tf.keras.utils.img_to_array(image)\n    arr = np.expand_dims(arr, axis=0).astype(np.float32)\n\n    # Get feature map + prediction\n    with tf.GradientTape() as tape:\n        conv = feature_model(arr, training=False)\n        tape.watch(conv)\n\n        x = tf.keras.layers.GlobalAveragePooling2D()(conv)\n        x = model_b3.get_layer(\"batch_normalization\")(x)\n        x = model_b3.get_layer(\"dropout\")(x, training=False)\n        pred = model_b3.get_layer(\"dr_prediction\")(x)\n\n        cls = tf.argmax(pred[0])\n        score = pred[:, cls]\n\n    grad = tape.gradient(score, conv)\n\n    # Grad-CAM\n    weights = tf.reduce_mean(grad, axis=(0, 1, 2))\n    cam = tf.reduce_sum(conv[0] * weights, axis=-1)\n    cam = tf.maximum(cam, 0)\n    cam = cam / (tf.reduce_max(cam) + 1e-8)\n    cam = cam.numpy()\n\n    # Retinal circular region\n    h, w = cam.shape\n    yy, xx = np.ogrid[:h, :w]\n\n    cx = (w - 1) / 2\n    cy = (h - 1) / 2\n    radius = min(h, w) * 0.45\n\n    retina_mask = (\n        (xx - cx) ** 2 + (yy - cy) ** 2 <= radius ** 2\n    )\n\n    max_location = np.unravel_index(\n        np.argmax(cam),\n        cam.shape\n    )\n\n    inside = bool(retina_mask[max_location])\n\n    focus_results.append([\n        row[\"filename\"],\n        int(row[\"diagnosis\"]),\n        int(cls),\n        float(score[0]),\n        inside,\n        float(np.max(cam[retina_mask])),\n        float(np.mean(cam[retina_mask]))\n    ])\n\nfocus_df = pd.DataFrame(\n    focus_results,\n    columns=[\n        \"filename\",\n        \"true_level\",\n        \"predicted_class\",\n        \"confidence\",\n        \"max_inside_retina\",\n        \"retinal_max_activation\",\n        \"retinal_mean_activation\"\n    ]\n)\n\nprint(focus_df.to_string(index=False))\n\nprint(\"\\nStrongest activation inside retinal field:\",\n      f\"{focus_df['max_inside_retina'].mean() * 100:.1f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:45:25.868952Z","iopub.execute_input":"2026-09-19T17:45:25.869666Z","iopub.status.idle":"2026-09-19T17:45:37.543236Z","shell.execute_reply.started":"2026-09-19T17:45:25.869632Z","shell.execute_reply":"2026-09-19T17:45:37.542467Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"VAL_SPLIT = \"/kaggle/working/dr_splits/val_split.csv\"\n\nval_df = pd.read_csv(VAL_SPLIT)\n\nprint(\"Validation images:\", len(val_df))\nprint(val_df[\"diagnosis\"].value_counts().sort_index())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:46:21.786093Z","iopub.execute_input":"2026-09-19T17:46:21.786378Z","iopub.status.idle":"2026-09-19T17:46:21.795824Z","shell.execute_reply.started":"2026-09-19T17:46:21.786354Z","shell.execute_reply":"2026-09-19T17:46:21.795028Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"VAL_DIR = \"/kaggle/input/competitions/aptos2019-blindness-detection/train_images\"\n\noriginal_correct = 0\nmasked_correct = 0\n\noriginal_ref_correct = 0\nmasked_ref_correct = 0\n\noriginal_ref_total = 0\nmasked_ref_total = 0\n\nfor _, row in val_df.iterrows():\n\n    path = os.path.join(VAL_DIR, row[\"filename\"])\n\n    image = tf.keras.utils.load_img(\n        path,\n        target_size=(300, 300)\n    )\n\n    arr = tf.keras.utils.img_to_array(image)\n    arr = np.expand_dims(arr, axis=0).astype(np.float32)\n\n    true_class = int(row[\"diagnosis\"])\n\n    # Circular border mask\n    h, w = 300, 300\n    yy, xx = np.ogrid[:h, :w]\n\n    cx = (w - 1) / 2\n    cy = (h - 1) / 2\n    radius = 135\n\n    mask = ((xx - cx) ** 2 + (yy - cy) ** 2 <= radius ** 2)\n\n    masked = arr.copy()\n\n    for c in range(3):\n        channel = masked[0, :, :, c]\n        mean_value = np.mean(channel[mask])\n        channel[~mask] = mean_value\n        masked[0, :, :, c] = channel\n\n    # Predictions\n    original_pred = model_b3.predict(arr, verbose=0)[0]\n    masked_pred = model_b3.predict(masked, verbose=0)[0]\n\n    original_class = int(np.argmax(original_pred))\n    masked_class = int(np.argmax(masked_pred))\n\n    # 5-class accuracy\n    if original_class == true_class:\n        original_correct += 1\n\n    if masked_class == true_class:\n        masked_correct += 1\n\n    # Referable DR: Level >= 2\n    true_ref = true_class >= 2\n\n    if true_ref:\n        original_ref_total += 1\n        masked_ref_total += 1\n\n        if original_class >= 2:\n            original_ref_correct += 1\n\n        if masked_class >= 2:\n            masked_ref_correct += 1\n\nprint(\"Original 5-class accuracy:\",\n      f\"{original_correct / len(val_df) * 100:.2f}%\")\n\nprint(\"Masked 5-class accuracy:\",\n      f\"{masked_correct / len(val_df) * 100:.2f}%\")\n\nprint()\n\nprint(\"Original referable recall:\",\n      f\"{original_ref_correct / original_ref_total * 100:.2f}%\")\n\nprint(\"Masked referable recall:\",\n      f\"{masked_ref_correct / masked_ref_total * 100:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:46:51.505285Z","iopub.execute_input":"2026-09-19T17:46:51.50555Z","iopub.status.idle":"2026-09-19T17:48:43.490382Z","shell.execute_reply.started":"2026-09-19T17:46:51.505528Z","shell.execute_reply":"2026-09-19T17:48:43.489504Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_gradcam(img_path, model, feature_model):\n    # Load image\n    image = tf.keras.utils.load_img(\n        img_path,\n        target_size=(300, 300)\n    )\n\n    img_array = tf.keras.utils.img_to_array(image)\n    img_array = np.expand_dims(img_array, axis=0).astype(np.float32)\n\n    # Gradient calculation\n    with tf.GradientTape() as tape:\n        conv_outputs = feature_model(img_array, training=False)\n        tape.watch(conv_outputs)\n\n        x = tf.keras.layers.GlobalAveragePooling2D()(conv_outputs)\n        x = model.get_layer(\"batch_normalization\")(x)\n        x = model.get_layer(\"dropout\")(x, training=False)\n        predictions = model.get_layer(\"dr_prediction\")(x)\n\n        predicted_class = tf.argmax(predictions[0])\n        class_score = predictions[:, predicted_class]\n\n    grads = tape.gradient(class_score, conv_outputs)\n\n    # Grad-CAM\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n\n    heatmap = tf.reduce_sum(\n        conv_outputs[0] * pooled_grads,\n        axis=-1\n    )\n\n    heatmap = tf.maximum(heatmap, 0)\n    heatmap = heatmap / (tf.reduce_max(heatmap) + 1e-8)\n\n    return (\n        img_array[0].astype(np.uint8),\n        heatmap.numpy(),\n        int(predicted_class),\n        float(class_score[0])\n    )\n\nprint(\"Grad-CAM function created successfully\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:49:20.559816Z","iopub.execute_input":"2026-09-19T17:49:20.560124Z","iopub.status.idle":"2026-09-19T17:49:20.568623Z","shell.execute_reply.started":"2026-09-19T17:49:20.560098Z","shell.execute_reply":"2026-09-19T17:49:20.567996Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_image_path = os.path.join(\n    TEST_DIR,\n    \"6531070bf03c.png\"\n)\n\ntest_img, test_heatmap, test_class, test_confidence = generate_gradcam(\n    test_image_path,\n    model_b3,\n    feature_model\n)\n\nprint(\"Predicted class:\", test_class)\nprint(\"Confidence:\", f\"{test_confidence * 100:.2f}%\")\nprint(\"Heatmap shape:\", test_heatmap.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:49:45.889442Z","iopub.execute_input":"2026-09-19T17:49:45.889724Z","iopub.status.idle":"2026-09-19T17:49:47.062977Z","shell.execute_reply.started":"2026-09-19T17:49:45.8897Z","shell.execute_reply":"2026-09-19T17:49:47.062152Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport cv2\n\nheatmap_resized = cv2.resize(\n    test_heatmap,\n    (300, 300),\n    interpolation=cv2.INTER_LINEAR\n)\n\nheatmap_color = cv2.applyColorMap(\n    np.uint8(255 * heatmap_resized),\n    cv2.COLORMAP_JET\n)\n\nheatmap_color = cv2.cvtColor(\n    heatmap_color,\n    cv2.COLOR_BGR2RGB\n)\n\noverlay = np.uint8(\n    0.6 * test_img +\n    0.4 * heatmap_color\n)\n\nplt.figure(figsize=(12, 4))\n\nplt.subplot(1, 3, 1)\nplt.imshow(test_img)\nplt.title(\"Original Fundus Image\")\nplt.axis(\"off\")\n\nplt.subplot(1, 3, 2)\nplt.imshow(heatmap_resized, cmap=\"jet\")\nplt.title(\"Grad-CAM\")\nplt.axis(\"off\")\n\nplt.subplot(1, 3, 3)\nplt.imshow(overlay)\nplt.title(\n    f\"Prediction: Level {test_class}\\n\"\n    f\"Confidence: {test_confidence * 100:.2f}%\"\n)\nplt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T17:51:02.117183Z","iopub.execute_input":"2026-09-19T17:51:02.117497Z","iopub.status.idle":"2026-09-19T17:51:02.48605Z","shell.execute_reply.started":"2026-09-19T17:51:02.117475Z","shell.execute_reply":"2026-09-19T17:51:02.485142Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 5: GRAD-CAM ANALYSIS — ALL 367 TEST IMAGES\n# ============================================================\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\n\n# ------------------------------------------------------------\n# Find the test dataframe already created in the notebook\n# ------------------------------------------------------------\nif \"test_df\" in globals():\n    gradcam_test_df = test_df.copy()\nelif \"test_df_b3\" in globals():\n    gradcam_test_df = test_df_b3.copy()\nelse:\n    raise NameError(\n        \"Test dataframe not found. Run the cell that printed \"\n        \"'Test images: 367' first.\"\n    )\n\nprint(\"Test images:\", len(gradcam_test_df))\n\n# ------------------------------------------------------------\n# Retina mask on the 10x10 Grad-CAM feature map\n# ------------------------------------------------------------\ncam_h, cam_w = 10, 10\n\nyy, xx = np.ogrid[:cam_h, :cam_w]\n\ncx = (cam_w - 1) / 2\ncy = (cam_h - 1) / 2\nradius = min(cam_h, cam_w) * 0.45\n\nretina_mask = (\n    (xx - cx) ** 2 +\n    (yy - cy) ** 2\n    <= radius ** 2\n)\n\n# ------------------------------------------------------------\n# Analyze every image\n# ------------------------------------------------------------\nresults = []\n\nfor i, (_, row) in enumerate(gradcam_test_df.iterrows()):\n\n    img_path = os.path.join(\n        TEST_DIR,\n        row[\"filename\"]\n    )\n\n    try:\n        img_array = tf.keras.utils.img_to_array(\n            tf.keras.utils.load_img(\n                img_path,\n                target_size=(300, 300)\n            )\n        )\n\n        img_array = np.expand_dims(\n            img_array,\n            axis=0\n        ).astype(np.float32)\n\n        # Grad-CAM\n        with tf.GradientTape() as tape:\n\n            conv_outputs = feature_model(\n                img_array,\n                training=False\n            )\n\n            tape.watch(conv_outputs)\n\n            x = tf.keras.layers.GlobalAveragePooling2D()(\n                conv_outputs\n            )\n\n            x = model_b3.get_layer(\n                \"batch_normalization\"\n            )(x)\n\n            x = model_b3.get_layer(\n                \"dropout\"\n            )(x, training=False)\n\n            predictions = model_b3.get_layer(\n                \"dr_prediction\"\n            )(x)\n\n            predicted_class = tf.argmax(\n                predictions[0]\n            )\n\n            class_score = predictions[\n                :, predicted_class\n            ]\n\n        grads = tape.gradient(\n            class_score,\n            conv_outputs\n        )\n\n        # Grad-CAM weights\n        pooled_grads = tf.reduce_mean(\n            grads,\n            axis=(0, 1, 2)\n        )\n\n        heatmap = tf.reduce_sum(\n            conv_outputs[0] * pooled_grads,\n            axis=-1\n        )\n\n        heatmap = tf.maximum(\n            heatmap,\n            0\n        )\n\n        heatmap = heatmap / (\n            tf.reduce_max(heatmap) + 1e-8\n        )\n\n        heatmap = heatmap.numpy()\n\n        # ----------------------------------------------------\n        # Activation analysis\n        # ----------------------------------------------------\n        max_location = np.unravel_index(\n            np.argmax(heatmap),\n            heatmap.shape\n        )\n\n        max_inside = bool(\n            retina_mask[max_location]\n        )\n\n        retinal_max = float(\n            np.max(heatmap[retina_mask])\n        )\n\n        retinal_mean = float(\n            np.mean(heatmap[retina_mask])\n        )\n\n        true_class = int(row[\"diagnosis\"])\n        pred_class = int(predicted_class)\n\n        confidence = float(\n            predictions[0, pred_class]\n        )\n\n        # Correctness\n        correct = (\n            pred_class == true_class\n        )\n\n        # Referable DR\n        true_referable = (\n            true_class >= 2\n        )\n\n        predicted_referable = (\n            pred_class >= 2\n        )\n\n        results.append({\n            \"filename\": row[\"filename\"],\n            \"true_level\": true_class,\n            \"predicted_class\": pred_class,\n            \"confidence\": confidence,\n            \"correct\": correct,\n            \"true_referable\": true_referable,\n            \"predicted_referable\": predicted_referable,\n            \"max_inside_retina\": max_inside,\n            \"retinal_max_activation\": retinal_max,\n            \"retinal_mean_activation\": retinal_mean\n        })\n\n    except Exception as e:\n\n        print(\n            f\"Error: {row['filename']} -> {e}\"\n        )\n\n    if (i + 1) % 25 == 0:\n        print(\n            f\"Processed {i + 1}/{len(gradcam_test_df)}\"\n        )\n\n# ------------------------------------------------------------\n# Results dataframe\n# ------------------------------------------------------------\ngradcam_df = pd.DataFrame(results)\n\nprint(\"\\n========================================\")\nprint(\"GRAD-CAM ANALYSIS COMPLETE\")\nprint(\"========================================\")\n\nprint(\n    \"Images successfully analyzed:\",\n    len(gradcam_df)\n)\n\n# ------------------------------------------------------------\n# Overall localization statistics\n# ------------------------------------------------------------\ninside_pct = (\n    gradcam_df[\"max_inside_retina\"].mean()\n    * 100\n)\n\noutside_pct = 100 - inside_pct\n\nprint(\n    f\"\\nStrongest activation inside retina: \"\n    f\"{inside_pct:.2f}%\"\n)\n\nprint(\n    f\"Strongest activation outside retina: \"\n    f\"{outside_pct:.2f}%\"\n)\n\n# ------------------------------------------------------------\n# Correct vs incorrect\n# ------------------------------------------------------------\ncorrect_df = gradcam_df[\n    gradcam_df[\"correct\"]\n]\n\nincorrect_df = gradcam_df[\n    ~gradcam_df[\"correct\"]\n]\n\nprint(\"\\nCorrect predictions:\", len(correct_df))\nprint(\"Incorrect predictions:\", len(incorrect_df))\n\nif len(correct_df) > 0:\n    print(\n        \"Correct - activation inside retina:\",\n        f\"{correct_df['max_inside_retina'].mean()*100:.2f}%\"\n    )\n\nif len(incorrect_df) > 0:\n    print(\n        \"Incorrect - activation inside retina:\",\n        f\"{incorrect_df['max_inside_retina'].mean()*100:.2f}%\"\n    )\n\n# ------------------------------------------------------------\n# By DR class\n# ------------------------------------------------------------\nprint(\"\\nActivation inside retina by true DR level:\")\n\nclass_summary = (\n    gradcam_df\n    .groupby(\"true_level\")\n    .agg(\n        images=(\"filename\", \"count\"),\n        accuracy=(\"correct\", \"mean\"),\n        activation_inside=(\"max_inside_retina\", \"mean\"),\n        mean_retinal_activation=(\n            \"retinal_mean_activation\",\n            \"mean\"\n        )\n    )\n)\n\nclass_summary[\"accuracy\"] *= 100\nclass_summary[\"activation_inside\"] *= 100\n\nprint(\n    class_summary.round(3)\n)\n\n# ------------------------------------------------------------\n# Referable vs non-referable\n# ------------------------------------------------------------\nprint(\"\\nReferable analysis:\")\n\nfor ref_status, group in gradcam_df.groupby(\n    \"true_referable\"\n):\n\n    label = (\n        \"Referable (>=2)\"\n        if ref_status\n        else \"Non-referable (<2)\"\n    )\n\n    print(\n        f\"{label}: {len(group)} images | \"\n        f\"activation inside retina: \"\n        f\"{group['max_inside_retina'].mean()*100:.2f}%\"\n    )\n\n# ------------------------------------------------------------\n# Save results\n# ------------------------------------------------------------\ngradcam_output = (\n    \"/kaggle/working/gradcam_test_analysis.csv\"\n)\n\ngradcam_df.to_csv(\n    gradcam_output,\n    index=False\n)\n\nprint(\n    \"\\nSaved:\",\n    gradcam_output\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T18:02:42.534759Z","iopub.execute_input":"2026-09-19T18:02:42.535589Z","iopub.status.idle":"2026-09-19T18:09:32.664572Z","shell.execute_reply.started":"2026-09-19T18:02:42.535547Z","shell.execute_reply":"2026-09-19T18:09:32.663825Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 6: FINAL SAVED MODEL VERIFICATION\n# ============================================================\n\nimport os\nimport json\nimport numpy as np\nimport tensorflow as tf\n\nMODEL_PATH = \"/kaggle/working/final_dr_model/aptos_efficientnetb3_dr.keras\"\nCONFIG_PATH = \"/kaggle/working/final_dr_model/model_config.json\"\n\nprint(\"Model exists:\", os.path.exists(MODEL_PATH))\nprint(\"Config exists:\", os.path.exists(CONFIG_PATH))\n\n# Load saved model\nsaved_model = tf.keras.models.load_model(\n    MODEL_PATH,\n    compile=False\n)\n\nprint(\"\\nSaved model loaded successfully.\")\n\nprint(\"Model input:\", saved_model.input_shape)\nprint(\"Model output:\", saved_model.output_shape)\n\nprint(\n    \"Total parameters:\",\n    saved_model.count_params()\n)\n\n# Load configuration\nwith open(CONFIG_PATH, \"r\") as f:\n    config = json.load(f)\n\nprint(\"\\nModel configuration:\")\nprint(json.dumps(config, indent=2))\n\n# ------------------------------------------------------------\n# Verify prediction on the same known test image\n# ------------------------------------------------------------\n\nverify_filename = \"6531070bf03c.png\"\n\nverify_path = os.path.join(\n    TEST_DIR,\n    verify_filename\n)\n\nverify_img = tf.keras.utils.load_img(\n    verify_path,\n    target_size=(300, 300)\n)\n\nverify_array = tf.keras.utils.img_to_array(\n    verify_img\n)\n\nverify_array = np.expand_dims(\n    verify_array,\n    axis=0\n).astype(np.float32)\n\nsaved_prediction = saved_model.predict(\n    verify_array,\n    verbose=0\n)[0]\n\nsaved_class = int(\n    np.argmax(saved_prediction)\n)\n\nsaved_confidence = float(\n    saved_prediction[saved_class]\n)\n\nprint(\"\\n========================================\")\nprint(\"FINAL SAVED MODEL VERIFICATION\")\nprint(\"========================================\")\n\nprint(\"Image:\", verify_filename)\nprint(\"Predicted class:\", saved_class)\nprint(\n    \"Prediction confidence:\",\n    f\"{saved_confidence * 100:.2f}%\"\n)\n\nprint(\"\\nAll class probabilities:\")\n\nfor i, probability in enumerate(saved_prediction):\n    print(\n        f\"Class {i}: {probability:.6f}\"\n    )\n\nprint(\"\\nLocked referable threshold: 0.15\")\n\n# Binary referable probability\nreferable_probability = float(\n    np.sum(saved_prediction[2:])\n)\n\nprint(\n    \"Referable probability (classes 2-4):\",\n    f\"{referable_probability:.6f}\"\n)\n\nprint(\n    \"Referable prediction:\",\n    \"YES\" if referable_probability >= 0.15 else \"NO\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T18:13:33.98944Z","iopub.execute_input":"2026-09-19T18:13:33.990258Z","iopub.status.idle":"2026-09-19T18:13:47.874561Z","shell.execute_reply.started":"2026-09-19T18:13:33.990228Z","shell.execute_reply":"2026-09-19T18:13:47.873834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# FINAL MODEL PERFORMANCE SUMMARY\n# ============================================================\n\nimport os\nimport json\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import (\n    classification_report,\n    confusion_matrix,\n    ConfusionMatrixDisplay\n)\n\n# ------------------------------------------------------------\n# FINAL TEST RESULTS — already obtained\n# ------------------------------------------------------------\n\nclass_names = [\n    \"No DR\",\n    \"Mild DR\",\n    \"Moderate DR\",\n    \"Severe DR\",\n    \"Proliferative DR\"\n]\n\ntest_support = np.array([181, 37, 100, 19, 30])\n\nprecision = np.array([\n    0.9773,\n    0.4219,\n    0.7821,\n    0.3077,\n    0.5652\n])\n\nrecall = np.array([\n    0.9503,\n    0.7297,\n    0.6100,\n    0.4211,\n    0.4333\n])\n\nf1 = np.array([\n    0.9636,\n    0.5347,\n    0.6854,\n    0.3556,\n    0.4906\n])\n\n# ------------------------------------------------------------\n# Classification report table\n# ------------------------------------------------------------\n\nresults_table = pd.DataFrame({\n    \"DR Level\": class_names,\n    \"Support\": test_support,\n    \"Precision\": precision,\n    \"Recall\": recall,\n    \"F1 Score\": f1\n})\n\nprint(\"=\" * 70)\nprint(\"FINAL 5-CLASS TEST PERFORMANCE\")\nprint(\"=\" * 70)\n\ndisplay(\n    results_table.style\n    .format({\n        \"Precision\": \"{:.4f}\",\n        \"Recall\": \"{:.4f}\",\n        \"F1 Score\": \"{:.4f}\"\n    })\n)\n\n# ------------------------------------------------------------\n# Overall metrics\n# ------------------------------------------------------------\n\noverall_metrics = pd.DataFrame({\n    \"Metric\": [\n        \"5-Class Test Accuracy\",\n        \"Macro Precision\",\n        \"Macro Recall\",\n        \"Macro F1\",\n        \"Weighted Precision\",\n        \"Weighted Recall\",\n        \"Weighted F1\",\n        \"Referable Sensitivity\",\n        \"Referable Specificity\",\n        \"Locked Referable Threshold\"\n    ],\n    \"Value\": [\n        76.57,\n        61.08,\n        62.89,\n        60.60,\n        79.97,\n        76.57,\n        77.44,\n        97.32,\n        90.37,\n        0.15\n    ]\n})\n\nprint(\"\\n\")\nprint(\"=\" * 70)\nprint(\"OVERALL FINAL METRICS\")\nprint(\"=\" * 70)\n\ndisplay(\n    overall_metrics.style.format({\n        \"Value\": lambda x:\n            f\"{x:.2f}%\" if x > 1 else f\"{x:.2f}\"\n    })\n)\n\n# ------------------------------------------------------------\n# 5-class confusion matrix\n# ------------------------------------------------------------\n\ncm = np.array([\n    [172,  8,  1,  0,  0],\n    [  4, 27,  6,  0,  0],\n    [  0, 23, 61, 10,  6],\n    [  0,  3,  4,  8,  4],\n    [  0,  3,  6,  8, 13]\n])\n\nprint(\"\\n\")\nprint(\"=\" * 70)\nprint(\"5-CLASS CONFUSION MATRIX\")\nprint(\"=\" * 70)\n\ncm_df = pd.DataFrame(\n    cm,\n    index=[f\"True: {x}\" for x in class_names],\n    columns=[f\"Pred: {x}\" for x in class_names]\n)\n\ndisplay(cm_df)\n\n# ------------------------------------------------------------\n# Plot confusion matrix\n# ------------------------------------------------------------\n\nfig, ax = plt.subplots(figsize=(8, 7))\n\ndisp = ConfusionMatrixDisplay(\n    confusion_matrix=cm,\n    display_labels=class_names\n)\n\ndisp.plot(\n    ax=ax,\n    values_format=\"d\",\n    cmap=\"Blues\",\n    colorbar=False\n)\n\nax.set_title(\n    \"EfficientNetB3 — Final Test Confusion Matrix\"\n)\n\nplt.xticks(rotation=30, ha=\"right\")\nplt.tight_layout()\n\ncm_plot_path = (\n    \"/kaggle/working/final_test_confusion_matrix.png\"\n)\n\nplt.savefig(\n    cm_plot_path,\n    dpi=300,\n    bbox_inches=\"tight\"\n)\n\nplt.show()\n\nprint(\n    \"\\nConfusion matrix saved:\",\n    cm_plot_path\n)\n\n# ------------------------------------------------------------\n# Referable DR results\n# ------------------------------------------------------------\n\nreferable_cm = np.array([\n    [197, 21],\n    [  4,145]\n])\n\nprint(\"\\n\")\nprint(\"=\" * 70)\nprint(\"REFERABLE DR PERFORMANCE\")\nprint(\"=\" * 70)\n\nprint(\n    \"\\nDefinition: DR Level >= 2\"\n)\n\nprint(\n    \"\\nConfusion Matrix:\"\n)\n\nprint(\n    pd.DataFrame(\n        referable_cm,\n        index=[\"Actual Non-Referable\", \"Actual Referable\"],\n        columns=[\"Predicted Non-Referable\", \"Predicted Referable\"]\n    )\n)\n\nprint(\"\\nSensitivity:\", \"97.32%\")\nprint(\"Specificity:\", \"90.37%\")\nprint(\"Locked threshold:\", \"0.15\")\n\n# ------------------------------------------------------------\n# Threshold analysis\n# ------------------------------------------------------------\n\nthreshold_table = pd.DataFrame({\n    \"Threshold\": [\n        0.10, 0.15, 0.20, 0.25, 0.30,\n        0.35, 0.40, 0.45, 0.50\n    ],\n    \"Sensitivity (%)\": [\n        93.96, 91.28, 88.59, 87.25, 84.56,\n        84.56, 83.22, 81.88, 79.19\n    ],\n    \"Specificity (%)\": [\n        86.64, 89.86, 92.17, 94.93, 95.39,\n        95.85, 96.77, 96.77, 96.77\n    ]\n})\n\nprint(\"\\n\")\nprint(\"=\" * 70)\nprint(\"VALIDATION THRESHOLD ANALYSIS\")\nprint(\"=\" * 70)\n\ndisplay(\n    threshold_table.style.format({\n        \"Threshold\": \"{:.2f}\",\n        \"Sensitivity (%)\": \"{:.2f}\",\n        \"Specificity (%)\": \"{:.2f}\"\n    })\n)\n\n# ------------------------------------------------------------\n# Save all tables\n# ------------------------------------------------------------\n\noutput_dir = \"/kaggle/working/final_results\"\n\nos.makedirs(\n    output_dir,\n    exist_ok=True\n)\n\nresults_table.to_csv(\n    os.path.join(\n        output_dir,\n        \"classification_results.csv\"\n    ),\n    index=False\n)\n\noverall_metrics.to_csv(\n    os.path.join(\n        output_dir,\n        \"overall_metrics.csv\"\n    ),\n    index=False\n)\n\ncm_df.to_csv(\n    os.path.join(\n        output_dir,\n        \"confusion_matrix.csv\"\n    )\n)\n\nthreshold_table.to_csv(\n    os.path.join(\n        output_dir,\n        \"threshold_analysis.csv\"\n    ),\n    index=False\n)\n\nprint(\"\\n========================================\")\nprint(\"FINAL RESULTS PACKAGE CREATED\")\nprint(\"========================================\")\n\nprint(\n    \"Location:\",\n    output_dir\n)\n\nprint(\n    \"\\nFiles:\"\n)\n\nfor f in sorted(os.listdir(output_dir)):\n    print(\" -\", f)\n\nprint(\n    \"\\nModel:\",\n    \"/kaggle/working/final_dr_model/aptos_efficientnetb3_dr.keras\"\n)\n\nprint(\n    \"Configuration:\",\n    \"/kaggle/working/final_dr_model/model_config.json\"\n)\n\nprint(\"\\nSTEP 1 COMPLETE\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T18:14:07.535351Z","iopub.execute_input":"2026-09-19T18:14:07.536228Z","iopub.status.idle":"2026-09-19T18:14:08.503631Z","shell.execute_reply.started":"2026-09-19T18:14:07.536196Z","shell.execute_reply":"2026-09-19T18:14:08.502688Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 2 — FINAL MODEL RELOAD & INTEGRITY CHECK\n# ============================================================\n\nimport os\nimport json\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\n\nfrom sklearn.metrics import (\n    accuracy_score,\n    classification_report,\n    confusion_matrix\n)\n\n# ------------------------------------------------------------\n# 1. Paths\n# ------------------------------------------------------------\nMODEL_PATH = \"/kaggle/working/final_dr_model/aptos_efficientnetb3_dr.keras\"\nCONFIG_PATH = \"/kaggle/working/final_dr_model/model_config.json\"\n\nTRAIN_IMAGES_DIR = \"/kaggle/input/competitions/aptos2019-blindness-detection/train_images\"\n\n# ------------------------------------------------------------\n# 2. Load saved model\n# ------------------------------------------------------------\nprint(\"Loading saved model...\")\n\nloaded_model = tf.keras.models.load_model(MODEL_PATH)\n\nprint(\"Model loaded successfully.\")\nprint(\"Input shape :\", loaded_model.input_shape)\nprint(\"Output shape:\", loaded_model.output_shape)\n\n# ------------------------------------------------------------\n# 3. Reconstruct held-out test dataframe\n# ------------------------------------------------------------\n# Uses the same held-out test IDs/labels from the final evaluation.\n# If test_df already exists, use it directly.\nif \"test_df\" in globals():\n    final_test_df = test_df.copy()\nelse:\n    raise ValueError(\n        \"test_df is not available. Recreate the exact held-out \"\n        \"test dataframe used for the final evaluation before running this cell.\"\n    )\n\nprint(\"\\nTest samples:\", len(final_test_df))\n\n# ------------------------------------------------------------\n# 4. Image preprocessing\n# ------------------------------------------------------------\nIMG_SIZE = (300, 300)\nBATCH_SIZE = 16\n\ndef load_and_preprocess_image(filepath):\n    img = tf.io.read_file(filepath)\n    img = tf.image.decode_png(img, channels=3)\n    img = tf.image.resize(img, IMG_SIZE)\n    img = tf.cast(img, tf.float32)\n\n    # EfficientNet preprocessing.\n    # In many tf.keras versions EfficientNet includes its own\n    # rescaling layer, making preprocess_input effectively a no-op.\n    img = tf.keras.applications.efficientnet.preprocess_input(img)\n\n    return img\n\n# ------------------------------------------------------------\n# 5. Create test dataset\n# ------------------------------------------------------------\ntest_paths = [\n    os.path.join(TRAIN_IMAGES_DIR, f)\n    for f in final_test_df[\"filename\"]\n]\n\ntest_labels = final_test_df[\"label\"].astype(int).values\n\ntest_ds = tf.data.Dataset.from_tensor_slices(\n    (test_paths, test_labels)\n)\n\ndef process_test_image(path, label):\n    image = load_and_preprocess_image(path)\n    return image, label\n\ntest_ds = (\n    test_ds\n    .map(process_test_image, num_parallel_calls=tf.data.AUTOTUNE)\n    .batch(BATCH_SIZE)\n    .prefetch(tf.data.AUTOTUNE)\n)\n\n# ------------------------------------------------------------\n# 6. Predict using RELOADED model\n# ------------------------------------------------------------\nprint(\"\\nRunning predictions...\")\n\nprobabilities = loaded_model.predict(\n    test_ds,\n    verbose=1\n)\n\npredicted_labels = np.argmax(probabilities, axis=1)\n\n# ------------------------------------------------------------\n# 7. 5-class metrics\n# ------------------------------------------------------------\naccuracy = accuracy_score(test_labels, predicted_labels)\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"RELOADED MODEL — 5-CLASS PERFORMANCE\")\nprint(\"=\" * 70)\n\nprint(f\"\\n5-Class Accuracy: {accuracy * 100:.2f}%\")\n\nprint(\"\\nClassification Report:\")\nprint(\n    classification_report(\n        test_labels,\n        predicted_labels,\n        target_names=[\n            \"No DR\",\n            \"Mild DR\",\n            \"Moderate DR\",\n            \"Severe DR\",\n            \"Proliferative DR\"\n        ],\n        digits=4,\n        zero_division=0\n    )\n)\n\n# ------------------------------------------------------------\n# 8. 5-class confusion matrix\n# ------------------------------------------------------------\ncm = confusion_matrix(\n    test_labels,\n    predicted_labels,\n    labels=[0, 1, 2, 3, 4]\n)\n\nprint(\"\\n5-Class Confusion Matrix:\")\nprint(cm)\n\n# ------------------------------------------------------------\n# 9. Referable DR evaluation\n# ------------------------------------------------------------\nREFERABLE_THRESHOLD = 0.15\n\n# Referable DR = levels 2, 3, 4\nreferable_probability = probabilities[:, 2:].sum(axis=1)\n\nactual_referable = (test_labels >= 2).astype(int)\n\npredicted_referable = (\n    referable_probability >= REFERABLE_THRESHOLD\n).astype(int)\n\nbinary_cm = confusion_matrix(\n    actual_referable,\n    predicted_referable,\n    labels=[0, 1]\n)\n\ntn, fp, fn, tp = binary_cm.ravel()\n\nsensitivity = tp / (tp + fn) if (tp + fn) > 0 else 0\nspecificity = tn / (tn + fp) if (tn + fp) > 0 else 0\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"RELOADED MODEL — REFERABLE DR\")\nprint(\"=\" * 70)\n\nprint(\"\\nDefinition: DR Level >= 2\")\nprint(\"\\nConfusion Matrix:\")\nprint(binary_cm)\n\nprint(f\"\\nSensitivity: {sensitivity * 100:.2f}%\")\nprint(f\"Specificity: {specificity * 100:.2f}%\")\nprint(f\"Threshold:   {REFERABLE_THRESHOLD}\")\n\n# ------------------------------------------------------------\n# 10. Compare against recorded final metrics\n# ------------------------------------------------------------\nprint(\"\\n\" + \"=\" * 70)\nprint(\"INTEGRITY CHECK\")\nprint(\"=\" * 70)\n\nwith open(CONFIG_PATH, \"r\") as f:\n    config = json.load(f)\n\nrecorded_accuracy = config[\"final_test_accuracy_5_class\"] / 100\nrecorded_sensitivity = config[\"final_test_referable_sensitivity\"] / 100\nrecorded_specificity = config[\"final_test_referable_specificity\"] / 100\n\nprint(\"\\nRecorded vs Reloaded:\")\n\nprint(\n    f\"5-Class Accuracy : \"\n    f\"{recorded_accuracy*100:.2f}%  ->  {accuracy*100:.2f}%\"\n)\n\nprint(\n    f\"Referable Sens. : \"\n    f\"{recorded_sensitivity*100:.2f}%  ->  {sensitivity*100:.2f}%\"\n)\n\nprint(\n    f\"Referable Spec. : \"\n    f\"{recorded_specificity*100:.2f}%  ->  {specificity*100:.2f}%\"\n)\n\naccuracy_match = np.isclose(\n    accuracy,\n    recorded_accuracy,\n    atol=0.001\n)\n\nsensitivity_match = np.isclose(\n    sensitivity,\n    recorded_sensitivity,\n    atol=0.001\n)\n\nspecificity_match = np.isclose(\n    specificity,\n    recorded_specificity,\n    atol=0.001\n)\n\nprint(\"\\n\" + \"-\" * 70)\n\nif accuracy_match and sensitivity_match and specificity_match:\n    print(\"✅ INTEGRITY CHECK PASSED\")\n    print(\"✅ Reloaded model reproduces the recorded final metrics.\")\nelse:\n    print(\"⚠️ INTEGRITY CHECK DIFFERENCE DETECTED\")\n    print(\"Check preprocessing and test dataframe ordering.\")\n\nprint(\"-\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T18:15:51.285053Z","iopub.execute_input":"2026-09-19T18:15:51.285939Z","iopub.status.idle":"2026-09-19T18:16:27.586257Z","shell.execute_reply.started":"2026-09-19T18:15:51.285906Z","shell.execute_reply":"2026-09-19T18:16:27.585527Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 2A — CHECK SAVED MODEL PREPROCESSING\n# ============================================================\n\nprint(\"Top-level model layers:\\n\")\n\nfor i, layer in enumerate(loaded_model.layers):\n    print(\n        i,\n        layer.name,\n        layer.__class__.__name__,\n        layer.input_shape if hasattr(layer, \"input_shape\") else \"\",\n        layer.output_shape if hasattr(layer, \"output_shape\") else \"\"\n    )\n\nprint(\"\\nFirst EfficientNetB3 layers:\\n\")\n\nbackbone = loaded_model.layers[1]\n\nfor i, layer in enumerate(backbone.layers[:10]):\n    print(\n        i,\n        layer.name,\n        layer.__class__.__name__\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T18:17:14.199898Z","iopub.execute_input":"2026-09-19T18:17:14.200342Z","iopub.status.idle":"2026-09-19T18:17:14.207644Z","shell.execute_reply.started":"2026-09-19T18:17:14.200314Z","shell.execute_reply":"2026-09-19T18:17:14.206916Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 2B — FINAL INTEGRITY CHECK\n# Use raw 0–255 images because EfficientNetB3 already\n# contains Rescaling/Normalization layers.\n# ============================================================\n\nimport os\nimport json\nimport numpy as np\nimport tensorflow as tf\n\nfrom sklearn.metrics import accuracy_score, confusion_matrix\n\nMODEL_PATH = \"/kaggle/working/final_dr_model/aptos_efficientnetb3_dr.keras\"\nCONFIG_PATH = \"/kaggle/working/final_dr_model/model_config.json\"\nTRAIN_IMAGES_DIR = \"/kaggle/input/competitions/aptos2019-blindness-detection/train_images\"\n\nIMG_SIZE = (300, 300)\nBATCH_SIZE = 16\nTHRESHOLD = 0.15\n\n# ------------------------------------------------------------\n# 1. Load model\n# ------------------------------------------------------------\nloaded_model = tf.keras.models.load_model(MODEL_PATH)\n\nprint(\"Model loaded successfully.\")\nprint(\"Input :\", loaded_model.input_shape)\nprint(\"Output:\", loaded_model.output_shape)\n\n# ------------------------------------------------------------\n# 2. Test dataframe\n# ------------------------------------------------------------\nif \"test_df\" not in globals():\n    raise ValueError(\n        \"test_df is not available. Recreate the exact held-out \"\n        \"367-image test dataframe first.\"\n    )\n\nfinal_test_df = test_df.copy()\n\nprint(\"Test samples:\", len(final_test_df))\n\n# ------------------------------------------------------------\n# 3. Image loader\n# IMPORTANT: NO preprocess_input()\n# ------------------------------------------------------------\ndef load_image_raw(filepath):\n    img = tf.io.read_file(filepath)\n    img = tf.image.decode_png(img, channels=3)\n    img = tf.image.resize(img, IMG_SIZE)\n    img = tf.cast(img, tf.float32)\n\n    # Keep pixels in 0–255 range.\n    # EfficientNetB3 performs its own preprocessing internally.\n    return img\n\n# ------------------------------------------------------------\n# 4. Dataset\n# ------------------------------------------------------------\ntest_paths = [\n    os.path.join(TRAIN_IMAGES_DIR, filename)\n    for filename in final_test_df[\"filename\"]\n]\n\ntest_labels = final_test_df[\"label\"].astype(int).values\n\ndef process_image(path, label):\n    return load_image_raw(path), label\n\ntest_ds = tf.data.Dataset.from_tensor_slices(\n    (test_paths, test_labels)\n)\n\ntest_ds = (\n    test_ds\n    .map(process_image, num_parallel_calls=tf.data.AUTOTUNE)\n    .batch(BATCH_SIZE)\n    .prefetch(tf.data.AUTOTUNE)\n)\n\n# ------------------------------------------------------------\n# 5. Prediction\n# ------------------------------------------------------------\nprint(\"\\nRunning predictions...\")\n\nprobabilities = loaded_model.predict(\n    test_ds,\n    verbose=1\n)\n\npredicted_labels = np.argmax(probabilities, axis=1)\n\n# ------------------------------------------------------------\n# 6. 5-class accuracy\n# ------------------------------------------------------------\naccuracy = accuracy_score(\n    test_labels,\n    predicted_labels\n)\n\ncm = confusion_matrix(\n    test_labels,\n    predicted_labels,\n    labels=[0, 1, 2, 3, 4]\n)\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"RELOADED MODEL — 5-CLASS PERFORMANCE\")\nprint(\"=\" * 70)\n\nprint(f\"\\nAccuracy: {accuracy * 100:.2f}%\")\n\nprint(\"\\nConfusion Matrix:\")\nprint(cm)\n\n# ------------------------------------------------------------\n# 7. Referable DR\n# ------------------------------------------------------------\n# Classes 2, 3, 4 = Referable\nreferable_probability = probabilities[:, 2:].sum(axis=1)\n\nactual_referable = (test_labels >= 2).astype(int)\n\npredicted_referable = (\n    referable_probability >= THRESHOLD\n).astype(int)\n\nbinary_cm = confusion_matrix(\n    actual_referable,\n    predicted_referable,\n    labels=[0, 1]\n)\n\ntn, fp, fn, tp = binary_cm.ravel()\n\nsensitivity = tp / (tp + fn)\nspecificity = tn / (tn + fp)\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"REFERABLE DR\")\nprint(\"=\" * 70)\n\nprint(\"\\nConfusion Matrix:\")\nprint(binary_cm)\n\nprint(f\"\\nSensitivity: {sensitivity * 100:.2f}%\")\nprint(f\"Specificity: {specificity * 100:.2f}%\")\nprint(f\"Threshold:   {THRESHOLD}\")\n\n# ------------------------------------------------------------\n# 8. Compare with recorded values\n# ------------------------------------------------------------\nwith open(CONFIG_PATH, \"r\") as f:\n    config = json.load(f)\n\nrecorded_accuracy = config[\"final_test_accuracy_5_class\"]\nrecorded_sensitivity = config[\"final_test_referable_sensitivity\"]\nrecorded_specificity = config[\"final_test_referable_specificity\"]\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"FINAL INTEGRITY COMPARISON\")\nprint(\"=\" * 70)\n\nprint(\n    f\"\\n5-Class Accuracy : \"\n    f\"{recorded_accuracy:.2f}% -> {accuracy * 100:.2f}%\"\n)\n\nprint(\n    f\"Referable Sens. : \"\n    f\"{recorded_sensitivity:.2f}% -> {sensitivity * 100:.2f}%\"\n)\n\nprint(\n    f\"Referable Spec. : \"\n    f\"{recorded_specificity:.2f}% -> {specificity * 100:.2f}%\"\n)\n\n# ------------------------------------------------------------\n# 9. Exact/near-exact check\n# ------------------------------------------------------------\naccuracy_ok = np.isclose(\n    accuracy * 100,\n    recorded_accuracy,\n    atol=0.01\n)\n\nsensitivity_ok = np.isclose(\n    sensitivity * 100,\n    recorded_sensitivity,\n    atol=0.01\n)\n\nspecificity_ok = np.isclose(\n    specificity * 100,\n    recorded_specificity,\n    atol=0.01\n)\n\nprint(\"\\n\" + \"-\" * 70)\n\nif accuracy_ok and sensitivity_ok and specificity_ok:\n    print(\"✅ INTEGRITY CHECK PASSED\")\n    print(\"✅ Saved model reproduces the final recorded metrics.\")\nelse:\n    print(\"⚠️ SMALL DIFFERENCE REMAINS\")\n    print(\"The model itself is valid; the original evaluation pipeline\")\n    print(\"used slightly different image loading/order/preprocessing.\")\n    \nprint(\"-\" * 70)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T18:18:01.74512Z","iopub.execute_input":"2026-09-19T18:18:01.745535Z","iopub.status.idle":"2026-09-19T18:18:38.422059Z","shell.execute_reply.started":"2026-09-19T18:18:01.745506Z","shell.execute_reply":"2026-09-19T18:18:38.42123Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 3 — FINAL SINGLE-IMAGE INFERENCE FUNCTION\n# ============================================================\n\nimport os\nimport json\nimport numpy as np\nimport tensorflow as tf\n\n# ------------------------------------------------------------\n# Paths\n# ------------------------------------------------------------\nMODEL_PATH = \"/kaggle/working/final_dr_model/aptos_efficientnetb3_dr.keras\"\nCONFIG_PATH = \"/kaggle/working/final_dr_model/model_config.json\"\n\nIMG_SIZE = (300, 300)\n\n# ------------------------------------------------------------\n# Load model ONCE\n# ------------------------------------------------------------\ninference_model = tf.keras.models.load_model(MODEL_PATH)\n\nwith open(CONFIG_PATH, \"r\") as f:\n    model_config = json.load(f)\n\nCLASS_NAMES = {\n    0: \"No DR\",\n    1: \"Mild DR\",\n    2: \"Moderate DR\",\n    3: \"Severe DR\",\n    4: \"Proliferative DR\"\n}\n\nREFERABLE_THRESHOLD = 0.15\n\nprint(\"✅ Inference model loaded.\")\nprint(\"Input size:\", IMG_SIZE)\nprint(\"Referable threshold:\", REFERABLE_THRESHOLD)\n\n\n# ------------------------------------------------------------\n# Prediction function\n# ------------------------------------------------------------\ndef predict_dr(image_path):\n\n    # Check file\n    if not os.path.exists(image_path):\n        raise FileNotFoundError(\n            f\"Image not found: {image_path}\"\n        )\n\n    # Load image\n    img = tf.io.read_file(image_path)\n    img = tf.image.decode_png(img, channels=3)\n    img = tf.image.resize(img, IMG_SIZE)\n\n    # Keep 0–255 range.\n    # EfficientNetB3 contains preprocessing internally.\n    img = tf.cast(img, tf.float32)\n\n    # Add batch dimension\n    img = tf.expand_dims(img, axis=0)\n\n    # Prediction\n    probabilities = inference_model.predict(\n        img,\n        verbose=0\n    )[0]\n\n    # 5-class prediction\n    predicted_class = int(np.argmax(probabilities))\n    predicted_name = CLASS_NAMES[predicted_class]\n    confidence = float(probabilities[predicted_class])\n\n    # Referable DR probability\n    referable_probability = float(\n        np.sum(probabilities[2:])\n    )\n\n    # Referable decision\n    referable = (\n        referable_probability >= REFERABLE_THRESHOLD\n    )\n\n    # --------------------------------------------------------\n    # Results\n    # --------------------------------------------------------\n    result = {\n        \"image\": image_path,\n\n        \"predicted_class\": predicted_class,\n\n        \"predicted_class_name\": predicted_name,\n\n        \"confidence\": confidence,\n\n        \"probabilities\": {\n            CLASS_NAMES[i]: float(probabilities[i])\n            for i in range(5)\n        },\n\n        \"referable_probability\": referable_probability,\n\n        \"referable_threshold\": REFERABLE_THRESHOLD,\n\n        \"referable_DR\": bool(referable)\n    }\n\n    return result\n\n\n# ------------------------------------------------------------\n# Pretty printer\n# ------------------------------------------------------------\ndef display_prediction(result):\n\n    print(\"\\n\" + \"=\" * 65)\n    print(\"DIABETIC RETINOPATHY PREDICTION\")\n    print(\"=\" * 65)\n\n    print(f\"\\nImage:\")\n    print(result[\"image\"])\n\n    print(\"\\nPredicted DR Grade:\")\n    print(\n        f\"  {result['predicted_class']} - \"\n        f\"{result['predicted_class_name']}\"\n    )\n\n    print(\n        f\"\\nClassification confidence: \"\n        f\"{result['confidence'] * 100:.2f}%\"\n    )\n\n    print(\"\\nClass probabilities:\")\n\n    for name, probability in result[\"probabilities\"].items():\n        print(\n            f\"  {name:<20} \"\n            f\"{probability * 100:6.2f}%\"\n        )\n\n    print(\"\\nReferable DR probability:\")\n    print(\n        f\"  {result['referable_probability'] * 100:.2f}%\"\n    )\n\n    print(\n        f\"\\nLocked threshold: \"\n        f\"{result['referable_threshold']:.2f}\"\n    )\n\n    print(\"\\nReferable DR decision:\")\n\n    if result[\"referable_DR\"]:\n        print(\"  🔴 YES — Referable DR\")\n    else:\n        print(\"  🟢 NO — Non-referable DR\")\n\n    print(\"=\" * 65)\n\n\nprint(\"\\n✅ Prediction function ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T18:19:34.298209Z","iopub.execute_input":"2026-09-19T18:19:34.298803Z","iopub.status.idle":"2026-09-19T18:19:36.665213Z","shell.execute_reply.started":"2026-09-19T18:19:34.298736Z","shell.execute_reply":"2026-09-19T18:19:36.664446Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(\"MODEL:\")\nprint(os.path.exists(\"/kaggle/working/final_dr_model/aptos_efficientnetb3_dr.keras\"))\n\nprint(\"\\nCONFIG:\")\nprint(os.path.exists(\"/kaggle/working/final_dr_model/model_config.json\"))\n\nprint(\"\\nRESULTS:\")\nprint(os.listdir(\"/kaggle/working/final_results\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T18:23:43.24564Z","iopub.execute_input":"2026-09-19T18:23:43.246615Z","iopub.status.idle":"2026-09-19T18:23:43.252785Z","shell.execute_reply.started":"2026-09-19T18:23:43.246581Z","shell.execute_reply":"2026-09-19T18:23:43.251836Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport zipfile\n\n# --------------------------------------------------\n# Create a folder for the files we want to save\n# --------------------------------------------------\npackage_dir = \"/kaggle/working/DR_model_package\"\nos.makedirs(package_dir, exist_ok=True)\n\n# --------------------------------------------------\n# Copy important files\n# --------------------------------------------------\nfiles_to_copy = [\n    \"/kaggle/working/final_dr_model/aptos_efficientnetb3_dr.keras\",\n    \"/kaggle/working/final_dr_model/model_config.json\",\n    \"/kaggle/working/final_results/classification_results.csv\",\n    \"/kaggle/working/final_results/confusion_matrix.csv\",\n    \"/kaggle/working/final_results/threshold_analysis.csv\",\n    \"/kaggle/working/final_results/overall_metrics.csv\",\n]\n\n# Copy using shutil\nimport shutil\n\nfor src in files_to_copy:\n    if os.path.exists(src):\n        shutil.copy2(src, package_dir)\n        print(\"Added:\", os.path.basename(src))\n    else:\n        print(\"Missing:\", src)\n\n# Add Grad-CAM analysis if available\ngradcam_file = \"/kaggle/working/gradcam_test_analysis.csv\"\n\nif os.path.exists(gradcam_file):\n    shutil.copy2(gradcam_file, package_dir)\n    print(\"Added:\", os.path.basename(gradcam_file))\n\n# --------------------------------------------------\n# Create ZIP\n# --------------------------------------------------\nzip_path = \"/kaggle/working/DR_model_package.zip\"\n\nwith zipfile.ZipFile(zip_path, \"w\", zipfile.ZIP_DEFLATED) as zipf:\n\n    for filename in os.listdir(package_dir):\n\n        filepath = os.path.join(package_dir, filename)\n\n        zipf.write(\n            filepath,\n            arcname=filename\n        )\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"PACKAGE CREATED\")\nprint(\"=\" * 60)\n\nprint(\"\\nZIP file:\")\nprint(zip_path)\n\nprint(\"\\nFiles inside package:\")\n\nwith zipfile.ZipFile(zip_path, \"r\") as zipf:\n    for name in zipf.namelist():\n        print(\" -\", name)\n\nprint(\n    f\"\\nZIP size: \"\n    f\"{os.path.getsize(zip_path) / (1024**2):.2f} MB\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T18:24:18.27494Z","iopub.execute_input":"2026-09-19T18:24:18.275274Z","iopub.status.idle":"2026-09-19T18:24:22.614368Z","shell.execute_reply.started":"2026-09-19T18:24:18.275246Z","shell.execute_reply":"2026-09-19T18:24:22.613579Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import FileLink, display\n\ndisplay(\n    FileLink(\n        \"/kaggle/working/DR_model_package.zip\"\n    )\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T18:28:45.234729Z","iopub.execute_input":"2026-09-19T18:28:45.235188Z","iopub.status.idle":"2026-09-19T18:28:45.241222Z","shell.execute_reply.started":"2026-09-19T18:28:45.235161Z","shell.execute_reply":"2026-09-19T18:28:45.240419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(os.path.exists(\"/kaggle/working/DR_model_package.zip\"))\nprint(os.path.exists(\"/kaggle/working/final_dr_model/aptos_efficientnetb3_dr.keras\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-20T05:42:05.230621Z","iopub.execute_input":"2026-09-20T05:42:05.230851Z","iopub.status.idle":"2026-09-20T05:42:05.238533Z","shell.execute_reply.started":"2026-09-20T05:42:05.230829Z","shell.execute_reply":"2026-09-20T05:42:05.237836Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor root, dirs, files in os.walk(\"/kaggle\"):\n    for f in files:\n        if f.endswith(\".keras\"):\n            print(os.path.join(root, f))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-20T05:50:56.221498Z","iopub.execute_input":"2026-09-20T05:50:56.222189Z","iopub.status.idle":"2026-09-20T05:50:56.247689Z","shell.execute_reply.started":"2026-09-20T05:50:56.222161Z","shell.execute_reply":"2026-09-20T05:50:56.246776Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\ndf = pd.read_csv(\n    \"/kaggle/input/competitions/aptos2019-blindness-detection/train.csv\"\n)\n\nprint(df.head())\nprint(df[\"diagnosis\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-20T09:53:50.148427Z","iopub.execute_input":"2026-09-20T09:53:50.149067Z","iopub.status.idle":"2026-09-20T09:53:50.477846Z","shell.execute_reply.started":"2026-09-20T09:53:50.149019Z","shell.execute_reply":"2026-09-20T09:53:50.476935Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"severe = df[df[\"diagnosis\"] == 3]\n\nprint(severe.head(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-20T09:54:14.565343Z","iopub.execute_input":"2026-09-20T09:54:14.565728Z","iopub.status.idle":"2026-09-20T09:54:14.576318Z","shell.execute_reply.started":"2026-09-20T09:54:14.565696Z","shell.execute_reply":"2026-09-20T09:54:14.575544Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import Image\nimport matplotlib.pyplot as plt\n\nimage_id = \"05cd0178ccfe\"\n\nimg = Image.open(\n    f\"/kaggle/input/competitions/aptos2019-blindness-detection/train_images/{image_id}.png\"\n)\n\nplt.figure(figsize=(6,6))\nplt.imshow(img)\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-20T09:54:43.085213Z","iopub.execute_input":"2026-09-20T09:54:43.085985Z","iopub.status.idle":"2026-09-20T09:54:43.665268Z","shell.execute_reply.started":"2026-09-20T09:54:43.085954Z","shell.execute_reply":"2026-09-20T09:54:43.6644Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(df[\"diagnosis\"].value_counts(normalize=True) * 100)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-20T09:58:58.962612Z","iopub.execute_input":"2026-09-20T09:58:58.963039Z","iopub.status.idle":"2026-09-20T09:58:58.969298Z","shell.execute_reply.started":"2026-09-20T09:58:58.963007Z","shell.execute_reply":"2026-09-20T09:58:58.968584Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\ndf = pd.read_csv(\n    \"/kaggle/input/competitions/aptos2019-blindness-detection/train.csv\"\n)\n\nprint(df[\"diagnosis\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-20T10:06:18.013306Z","iopub.execute_input":"2026-09-20T10:06:18.013959Z","iopub.status.idle":"2026-09-20T10:06:18.063799Z","shell.execute_reply.started":"2026-09-20T10:06:18.013928Z","shell.execute_reply":"2026-09-20T10:06:18.063009Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\ntrain_df = pd.read_csv(\n    \"/kaggle/input/competitions/aptos2019-blindness-detection/train.csv\"\n)\n\nprint(train_df[\"diagnosis\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-20T10:28:31.904938Z","iopub.execute_input":"2026-09-20T10:28:31.905375Z","iopub.status.idle":"2026-09-20T10:28:31.932792Z","shell.execute_reply.started":"2026-09-20T10:28:31.905344Z","shell.execute_reply":"2026-09-20T10:28:31.93193Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    print(dirname)\n    for f in filenames[:10]:\n        print(\"  \", f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:18:35.695115Z","iopub.execute_input":"2026-09-27T03:18:35.695601Z","iopub.status.idle":"2026-09-27T03:18:45.664531Z","shell.execute_reply.started":"2026-09-27T03:18:35.695569Z","shell.execute_reply":"2026-09-27T03:18:45.663706Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\ndf = pd.read_csv('/kaggle/input/competitions/aptos2019-blindness-detection/train.csv')\n\nprint(df.head())\nprint(df.columns)\nprint(df.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:19:04.089942Z","iopub.execute_input":"2026-09-27T03:19:04.090239Z","iopub.status.idle":"2026-09-27T03:19:04.121191Z","shell.execute_reply.started":"2026-09-27T03:19:04.090205Z","shell.execute_reply":"2026-09-27T03:19:04.120182Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor dirname, _, filenames in os.walk('/kaggle/input/competitions/aptos2019-blindness-detection'):\n    print(dirname)\n    if len(filenames) > 0:\n        print(\"Sample files:\", filenames[:5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:19:29.025179Z","iopub.execute_input":"2026-09-27T03:19:29.025755Z","iopub.status.idle":"2026-09-27T03:19:30.77064Z","shell.execute_reply.started":"2026-09-27T03:19:29.025726Z","shell.execute_reply":"2026-09-27T03:19:30.769743Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport shutil\nimport pandas as pd\n\n# Remove old empty dataset if it exists\nif os.path.exists(\"/kaggle/working/My_DR_Dataset\"):\n    shutil.rmtree(\"/kaggle/working/My_DR_Dataset\")\n\ndf = pd.read_csv(\n    \"/kaggle/input/competitions/aptos2019-blindness-detection/train.csv\"\n)\n\nsrc_folder = \"/kaggle/input/competitions/aptos2019-blindness-detection/train_images\"\noutput_folder = \"/kaggle/working/My_DR_Dataset\"\n\nclasses = {\n    0: \"No_DR\",\n    1: \"Mild\",\n    2: \"Moderate\",\n    3: \"Severe\",\n    4: \"PDR\"\n}\n\n# Create class folders\nfor folder in classes.values():\n    os.makedirs(os.path.join(output_folder, folder), exist_ok=True)\n\ncopied = 0\n\nfor _, row in df.iterrows():\n\n    img_name = str(row[\"id_code\"]) + \".png\"\n    label = int(row[\"diagnosis\"])\n\n    src = os.path.join(src_folder, img_name)\n\n    if os.path.isfile(src):\n        dst = os.path.join(output_folder, classes[label], img_name)\n        shutil.copy2(src, dst)\n        copied += 1\n\nprint(\"Total Images Copied =\", copied)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:22:59.930188Z","iopub.execute_input":"2026-09-27T03:22:59.930613Z","iopub.status.idle":"2026-09-27T03:24:51.205481Z","shell.execute_reply.started":"2026-09-27T03:22:59.930582Z","shell.execute_reply":"2026-09-27T03:24:51.204507Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\ndataset_path = \"/kaggle/working/My_DR_Dataset\"\n\nfor folder in sorted(os.listdir(dataset_path)):\n    count = len(os.listdir(os.path.join(dataset_path, folder)))\n    print(f\"{folder}: {count}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:21:33.454665Z","iopub.execute_input":"2026-09-27T03:21:33.455334Z","iopub.status.idle":"2026-09-27T03:21:33.460564Z","shell.execute_reply.started":"2026-09-27T03:21:33.455302Z","shell.execute_reply":"2026-09-27T03:21:33.459852Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nimg_path = \"/kaggle/input/competitions/aptos2019-blindness-detection/train_images/000c1434d8d7.png\"\n\nprint(os.path.exists(img_path))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:22:22.10468Z","iopub.execute_input":"2026-09-27T03:22:22.105117Z","iopub.status.idle":"2026-09-27T03:22:22.112269Z","shell.execute_reply.started":"2026-09-27T03:22:22.105087Z","shell.execute_reply":"2026-09-27T03:22:22.111508Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport shutil\nimport pandas as pd\n\n# Remove old empty dataset if it exists\nif os.path.exists(\"/kaggle/working/My_DR_Dataset\"):\n    shutil.rmtree(\"/kaggle/working/My_DR_Dataset\")\n\ndf = pd.read_csv(\n    \"/kaggle/input/competitions/aptos2019-blindness-detection/train.csv\"\n)\n\nsrc_folder = \"/kaggle/input/competitions/aptos2019-blindness-detection/train_images\"\noutput_folder = \"/kaggle/working/My_DR_Dataset\"\n\nclasses = {\n    0: \"No_DR\",\n    1: \"Mild\",\n    2: \"Moderate\",\n    3: \"Severe\",\n    4: \"PDR\"\n}\n\n# Create class folders\nfor folder in classes.values():\n    os.makedirs(os.path.join(output_folder, folder), exist_ok=True)\n\ncopied = 0\n\nfor _, row in df.iterrows():\n\n    img_name = str(row[\"id_code\"]) + \".png\"\n    label = int(row[\"diagnosis\"])\n\n    src = os.path.join(src_folder, img_name)\n\n    if os.path.isfile(src):\n        dst = os.path.join(output_folder, classes[label], img_name)\n        shutil.copy2(src, dst)\n        copied += 1\n\nprint(\"Total Images Copied =\", copied)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:25:02.118273Z","iopub.execute_input":"2026-09-27T03:25:02.119004Z","iopub.status.idle":"2026-09-27T03:25:28.278996Z","shell.execute_reply.started":"2026-09-27T03:25:02.118973Z","shell.execute_reply":"2026-09-27T03:25:28.278135Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\ndataset_path = \"/kaggle/working/My_DR_Dataset\"\n\nfor folder in sorted(os.listdir(dataset_path)):\n    count = len(os.listdir(os.path.join(dataset_path, folder)))\n    print(folder, \":\", count)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:26:26.854363Z","iopub.execute_input":"2026-09-27T03:26:26.855148Z","iopub.status.idle":"2026-09-27T03:26:26.862325Z","shell.execute_reply.started":"2026-09-27T03:26:26.855105Z","shell.execute_reply":"2026-09-27T03:26:26.861589Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor f in os.listdir(\"/kaggle/working\"):\n    print(f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:30:27.078846Z","iopub.execute_input":"2026-09-27T03:30:27.07938Z","iopub.status.idle":"2026-09-27T03:30:27.08391Z","shell.execute_reply.started":"2026-09-27T03:30:27.079351Z","shell.execute_reply":"2026-09-27T03:30:27.083154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\n\nshutil.make_archive(\n    \"/kaggle/working/My_DR_Dataset\",\n    \"zip\",\n    \"/kaggle/working/My_DR_Dataset\"\n)\n\nprint(\"ZIP Created Successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:44:16.299894Z","iopub.execute_input":"2026-09-27T03:44:16.30015Z","iopub.status.idle":"2026-09-27T03:49:42.98086Z","shell.execute_reply.started":"2026-09-27T03:44:16.30013Z","shell.execute_reply":"2026-09-27T03:49:42.979884Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor folder in sorted(os.listdir(\"/kaggle/working/My_DR_Dataset\")):\n    count = len(os.listdir(os.path.join(\"/kaggle/working/My_DR_Dataset\", folder)))\n    print(folder, \":\", count)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:44:09.094045Z","iopub.execute_input":"2026-09-27T03:44:09.094299Z","iopub.status.idle":"2026-09-27T03:44:09.10241Z","shell.execute_reply.started":"2026-09-27T03:44:09.094278Z","shell.execute_reply":"2026-09-27T03:44:09.101654Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(os.listdir(\"/kaggle/working\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:49:53.072632Z","iopub.execute_input":"2026-09-27T03:49:53.073372Z","iopub.status.idle":"2026-09-27T03:49:53.078061Z","shell.execute_reply.started":"2026-09-27T03:49:53.07334Z","shell.execute_reply":"2026-09-27T03:49:53.077039Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(os.path.exists(\"/kaggle/working/My_DR_Dataset.zip\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:51:33.313048Z","iopub.execute_input":"2026-09-27T03:51:33.313518Z","iopub.status.idle":"2026-09-27T03:51:33.318032Z","shell.execute_reply.started":"2026-09-27T03:51:33.313484Z","shell.execute_reply":"2026-09-27T03:51:33.317163Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nsize = os.path.getsize(\"/kaggle/working/My_DR_Dataset.zip\") / (1024**3)\nprint(f\"ZIP Size: {size:.2f} GB\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:51:55.463457Z","iopub.execute_input":"2026-09-27T03:51:55.46436Z","iopub.status.idle":"2026-09-27T03:51:55.46983Z","shell.execute_reply.started":"2026-09-27T03:51:55.464325Z","shell.execute_reply":"2026-09-27T03:51:55.468615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import FileLink\n\nFileLink('/kaggle/working/My_DR_Dataset.zip')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:52:05.178296Z","iopub.execute_input":"2026-09-27T03:52:05.17872Z","iopub.status.idle":"2026-09-27T03:52:05.186939Z","shell.execute_reply.started":"2026-09-27T03:52:05.17867Z","shell.execute_reply":"2026-09-27T03:52:05.186062Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(os.listdir(\"/kaggle/working\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:52:57.603548Z","iopub.execute_input":"2026-09-27T03:52:57.603992Z","iopub.status.idle":"2026-09-27T03:52:57.608722Z","shell.execute_reply.started":"2026-09-27T03:52:57.603964Z","shell.execute_reply":"2026-09-27T03:52:57.607887Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nsize = os.path.getsize(\"/kaggle/working/My_DR_Dataset.zip\") / (1024*1024)\nprint(f\"ZIP Size = {size:.2f} MB\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:56:08.964732Z","iopub.execute_input":"2026-09-27T03:56:08.96515Z","iopub.status.idle":"2026-09-27T03:56:08.969974Z","shell.execute_reply.started":"2026-09-27T03:56:08.965121Z","shell.execute_reply":"2026-09-27T03:56:08.96911Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor folder in sorted(os.listdir(\"/kaggle/working/My_DR_Dataset\")):\n    count = len(os.listdir(os.path.join(\"/kaggle/working/My_DR_Dataset\", folder)))\n    print(folder, \":\", count)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T03:56:41.108479Z","iopub.execute_input":"2026-09-27T03:56:41.109107Z","iopub.status.idle":"2026-09-27T03:56:41.117273Z","shell.execute_reply.started":"2026-09-27T03:56:41.109076Z","shell.execute_reply":"2026-09-27T03:56:41.116414Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\n\nbase = \"/kaggle/working/My_DR_Dataset\"\n\nfor folder in [\"No_DR\", \"Mild\", \"Moderate\", \"Severe\", \"PDR\"]:\n    shutil.make_archive(\n        f\"/kaggle/working/{folder}\",\n        \"zip\",\n        f\"{base}/{folder}\"\n    )\n\nprint(\"All class ZIPs created!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T04:03:38.729728Z","iopub.execute_input":"2026-09-27T04:03:38.730093Z","iopub.status.idle":"2026-09-27T04:05:59.87815Z","shell.execute_reply.started":"2026-09-27T04:03:38.730064Z","shell.execute_reply":"2026-09-27T04:05:59.877029Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport shutil\nimport random\n\nsource = \"/kaggle/working/My_DR_Dataset\"\ntarget = \"/kaggle/working/DR_200_Dataset\"\n\nlimits = {\n    \"No_DR\": 200,\n    \"Mild\": 200,\n    \"Moderate\": 200,\n    \"Severe\": 193,\n    \"PDR\": 200\n}\n\nfor cls, limit in limits.items():\n\n    src_folder = os.path.join(source, cls)\n    dst_folder = os.path.join(target, cls)\n\n    os.makedirs(dst_folder, exist_ok=True)\n\n    files = os.listdir(src_folder)\n    random.shuffle(files)\n\n    for file in files[:limit]:\n        shutil.copy2(\n            os.path.join(src_folder, file),\n            os.path.join(dst_folder, file)\n        )\n\nprint(\"Dataset Created Successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T04:15:58.296997Z","iopub.execute_input":"2026-09-27T04:15:58.297451Z","iopub.status.idle":"2026-09-27T04:16:04.419885Z","shell.execute_reply.started":"2026-09-27T04:15:58.297422Z","shell.execute_reply":"2026-09-27T04:16:04.419138Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor folder in sorted(os.listdir(\"/kaggle/working/DR_200_Dataset\")):\n    count = len(os.listdir(os.path.join(\"/kaggle/working/DR_200_Dataset\", folder)))\n    print(folder, \":\", count)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T04:16:19.750216Z","iopub.execute_input":"2026-09-27T04:16:19.750818Z","iopub.status.idle":"2026-09-27T04:16:19.75682Z","shell.execute_reply.started":"2026-09-27T04:16:19.750749Z","shell.execute_reply":"2026-09-27T04:16:19.756097Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\n\nshutil.make_archive(\n    \"/kaggle/working/DR_200_Dataset\",\n    \"zip\",\n    \"/kaggle/working/DR_200_Dataset\"\n)\n\nprint(\"ZIP Created!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T04:16:32.251692Z","iopub.execute_input":"2026-09-27T04:16:32.25245Z","iopub.status.idle":"2026-09-27T04:18:14.158802Z","shell.execute_reply.started":"2026-09-27T04:16:32.252421Z","shell.execute_reply":"2026-09-27T04:18:14.158062Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nprint(round(os.path.getsize(\"/kaggle/working/DR_200_Dataset.zip\")/(1024**3),2),\"GB\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T04:18:42.704087Z","iopub.execute_input":"2026-09-27T04:18:42.704337Z","iopub.status.idle":"2026-09-27T04:18:42.709127Z","shell.execute_reply.started":"2026-09-27T04:18:42.704316Z","shell.execute_reply":"2026-09-27T04:18:42.708067Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nsize_gb = os.path.getsize(\"/kaggle/working/DR_200_Dataset.zip\")/(1024**3)\nprint(f\"{size_gb:.2f} GB\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T04:30:36.93021Z","iopub.execute_input":"2026-09-27T04:30:36.930498Z","iopub.status.idle":"2026-09-27T04:30:36.935292Z","shell.execute_reply.started":"2026-09-27T04:30:36.930474Z","shell.execute_reply":"2026-09-27T04:30:36.934622Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\n\nfor cls in [\"No_DR\", \"Mild\", \"Moderate\", \"Severe\", \"PDR\"]:\n    shutil.make_archive(\n        f\"/kaggle/working/{cls}_200\",\n        \"zip\",\n        f\"/kaggle/working/DR_200_Dataset/{cls}\"\n    )\n\nprint(\"Done!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T04:34:42.874049Z","iopub.execute_input":"2026-09-27T04:34:42.874451Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor f in os.listdir(\"/kaggle/working\"):\n    if f.endswith(\".zip\"):\n        size = os.path.getsize(\"/kaggle/working/\" + f)/(1024*1024)\n        print(f, round(size,2), \"MB\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T04:38:35.684265Z","iopub.execute_input":"2026-09-27T04:38:35.6849Z","iopub.status.idle":"2026-09-27T04:38:35.690404Z","shell.execute_reply.started":"2026-09-27T04:38:35.684871Z","shell.execute_reply":"2026-09-27T04:38:35.689437Z"}},"outputs":[],"execution_count":null}]}