{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":4104,"databundleVersionId":46661,"sourceType":"competition"},{"sourceId":14774,"databundleVersionId":875431,"sourceType":"competition"}],"dockerImageVersionId":30840,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport cv2\nimport os\nimport matplotlib.pyplot as plt\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import EarlyStopping\nfrom tqdm import tqdm\nimport seaborn as sns\nfrom sklearn.metrics import confusion_matrix","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:46:03.790243Z","iopub.execute_input":"2025-12-09T14:46:03.790511Z","iopub.status.idle":"2025-12-09T14:46:16.052399Z","shell.execute_reply.started":"2025-12-09T14:46:03.790492Z","shell.execute_reply":"2025-12-09T14:46:16.051726Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Num GPUs Available:\", len(tf.config.experimental.list_physical_devices('GPU')))\ntf.config.experimental.set_memory_growth(tf.config.experimental.list_physical_devices('GPU')[0], True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:46:16.053545Z","iopub.execute_input":"2025-12-09T14:46:16.054171Z","iopub.status.idle":"2025-12-09T14:46:16.638844Z","shell.execute_reply.started":"2025-12-09T14:46:16.054135Z","shell.execute_reply":"2025-12-09T14:46:16.637832Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## LOADING DATA","metadata":{}},{"cell_type":"code","source":"img_size = 224\nbatch_size = 32\ncsv_path = \"/kaggle/input/aptos2019-blindness-detection/train.csv\"\nimg_dir = \"/kaggle/input/aptos2019-blindness-detection/train_images\"\nsave_dir = \"/kaggle/working/preprocessed_images\" #to save preprocessed images","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:46:16.641589Z","iopub.execute_input":"2025-12-09T14:46:16.641823Z","iopub.status.idle":"2025-12-09T14:46:16.660504Z","shell.execute_reply.started":"2025-12-09T14:46:16.641801Z","shell.execute_reply":"2025-12-09T14:46:16.659593Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.makedirs(save_dir, exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:46:16.661755Z","iopub.execute_input":"2025-12-09T14:46:16.662011Z","iopub.status.idle":"2025-12-09T14:46:16.678353Z","shell.execute_reply.started":"2025-12-09T14:46:16.661991Z","shell.execute_reply":"2025-12-09T14:46:16.677659Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(csv_path)\ndf[\"id_code\"] = df[\"id_code\"].apply(lambda x: os.path.join(img_dir, x + \".png\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:46:16.679052Z","iopub.execute_input":"2025-12-09T14:46:16.679281Z","iopub.status.idle":"2025-12-09T14:46:16.715284Z","shell.execute_reply.started":"2025-12-09T14:46:16.679263Z","shell.execute_reply":"2025-12-09T14:46:16.7147Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## CLACHE PREPROCESSING","metadata":{}},{"cell_type":"code","source":"def apply_clahe_and_save(image_path, save_dir):\n    # Read and resize the image\n    image = cv2.imread(image_path, cv2.IMREAD_COLOR)\n    if image is None:\n        raise ValueError(f\"Unable to read image at path: {image_path}\")\n    image = cv2.resize(image, (img_size, img_size))\n    \n    # Convert to LAB color space\n    lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)\n    l, a, b = cv2.split(lab)\n    \n    # Apply CLAHE to the L channel\n    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))\n    cl = clahe.apply(l)\n    \n    # Merge the LAB channels and convert back to RGB\n    merged_lab = cv2.merge((cl, a, b))\n    final_image = cv2.cvtColor(merged_lab, cv2.COLOR_LAB2RGB)\n    \n    # Save the preprocessed image\n    save_path = os.path.join(save_dir, os.path.basename(image_path))\n    cv2.imwrite(save_path, cv2.cvtColor(final_image, cv2.COLOR_RGB2BGR))\n    \n    return final_image / 255.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:46:16.715999Z","iopub.execute_input":"2025-12-09T14:46:16.716247Z","iopub.status.idle":"2025-12-09T14:46:16.721085Z","shell.execute_reply.started":"2025-12-09T14:46:16.716229Z","shell.execute_reply":"2025-12-09T14:46:16.720292Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Preprocessing images and saving to disk...\")\nfor image_path in tqdm(df[\"id_code\"], desc=\"Processing Images\"):  # Add tqdm here\n    apply_clahe_and_save(image_path, save_dir)\nprint(\"Preprocessing complete!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:46:16.721687Z","iopub.execute_input":"2025-12-09T14:46:16.721857Z","iopub.status.idle":"2025-12-09T14:52:53.638936Z","shell.execute_reply.started":"2025-12-09T14:46:16.721842Z","shell.execute_reply":"2025-12-09T14:52:53.638152Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## LOADING PREPROCESSED DATA INTO TF DATASET","metadata":{}},{"cell_type":"code","source":"def load_preprocessed_image(image_path, label):\n    image = tf.io.read_file(image_path)\n    \n    image = tf.image.decode_png(image, channels=3)\n    image = tf.image.resize(image, [img_size, img_size])\n    image = tf.cast(image, tf.float32) / 255.0\n    \n    return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:52:53.641013Z","iopub.execute_input":"2025-12-09T14:52:53.641243Z","iopub.status.idle":"2025-12-09T14:52:53.645042Z","shell.execute_reply.started":"2025-12-09T14:52:53.641223Z","shell.execute_reply":"2025-12-09T14:52:53.644345Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_paths = [os.path.join(save_dir, os.path.basename(path)) for path in df[\"id_code\"]]\nlabels = df[\"diagnosis\"].values\ndataset = tf.data.Dataset.from_tensor_slices((image_paths, labels))\ndataset = dataset.shuffle(len(df)).map(load_preprocessed_image, num_parallel_calls=tf.data.AUTOTUNE)\ndataset = dataset.batch(batch_size).prefetch(tf.data.AUTOTUNE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:52:53.646418Z","iopub.execute_input":"2025-12-09T14:52:53.64662Z","iopub.status.idle":"2025-12-09T14:52:53.886012Z","shell.execute_reply.started":"2025-12-09T14:52:53.646604Z","shell.execute_reply":"2025-12-09T14:52:53.8852Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## BEFORE AND AFTER CLACHE PREPROCESS","metadata":{}},{"cell_type":"code","source":"def visualize_clahe_effect(df_sample):\n    fig, axes = plt.subplots(len(df_sample), 2, figsize=(10, 5 * len(df_sample)))\n    \n    for i, row in enumerate(df_sample.itertuples()):\n        img_path = row.id_code\n        original = cv2.imread(img_path, cv2.IMREAD_COLOR)\n        original = cv2.resize(original, (img_size, img_size))\n        original = cv2.cvtColor(original, cv2.COLOR_BGR2RGB)\n        \n        processed_path = os.path.join(save_dir, os.path.basename(img_path))\n        processed = cv2.imread(processed_path, cv2.IMREAD_COLOR)\n        processed = cv2.cvtColor(processed, cv2.COLOR_BGR2RGB)\n        \n        axes[i, 0].imshow(original)\n        axes[i, 0].set_title(f\"Original - {row.diagnosis}\")\n        axes[i, 0].axis(\"off\")\n\n        axes[i, 1].imshow(processed)\n        axes[i, 1].set_title(f\"CLAHE Processed - {row.diagnosis}\")\n        axes[i, 1].axis(\"off\")\n    \n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:52:53.886803Z","iopub.execute_input":"2025-12-09T14:52:53.887077Z","iopub.status.idle":"2025-12-09T14:52:53.89238Z","shell.execute_reply.started":"2025-12-09T14:52:53.887047Z","shell.execute_reply":"2025-12-09T14:52:53.891598Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_sample = df.sample(5)\nvisualize_clahe_effect(df_sample)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:52:53.89319Z","iopub.execute_input":"2025-12-09T14:52:53.893435Z","iopub.status.idle":"2025-12-09T14:52:55.591931Z","shell.execute_reply.started":"2025-12-09T14:52:53.893417Z","shell.execute_reply":"2025-12-09T14:52:55.590614Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## SPLITTING AND PREFETCHING DATASET","metadata":{}},{"cell_type":"code","source":"def dataset_split(ds, train=0.7, val=0.15, test=0.15):\n    ds_size = len(ds)\n    train_size = int(ds_size * train)\n    val_size = int(ds_size * val)\n    \n    train_ds = ds.take(train_size)\n    val_ds = ds.skip(train_size).take(val_size)\n    test_ds = ds.skip(train_size + val_size)\n    \n    return train_ds, val_ds, test_ds","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:52:55.592983Z","iopub.execute_input":"2025-12-09T14:52:55.593383Z","iopub.status.idle":"2025-12-09T14:52:55.598854Z","shell.execute_reply.started":"2025-12-09T14:52:55.593345Z","shell.execute_reply":"2025-12-09T14:52:55.59796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_ds, val_ds, test_ds = dataset_split(dataset)\ntrain_ds = train_ds.cache().shuffle(1000).prefetch(tf.data.AUTOTUNE)\nval_ds = val_ds.cache().prefetch(tf.data.AUTOTUNE)\ntest_ds = test_ds.cache().prefetch(tf.data.AUTOTUNE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:52:55.599849Z","iopub.execute_input":"2025-12-09T14:52:55.600188Z","iopub.status.idle":"2025-12-09T14:52:55.630441Z","shell.execute_reply.started":"2025-12-09T14:52:55.600164Z","shell.execute_reply":"2025-12-09T14:52:55.629841Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## MULTIBRANCH CNN WITH TRANSFER LEARNING","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import DenseNet201,ResNet50\nfrom sklearn.utils.class_weight import compute_class_weight","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:52:55.631216Z","iopub.execute_input":"2025-12-09T14:52:55.631507Z","iopub.status.idle":"2025-12-09T14:52:55.636518Z","shell.execute_reply.started":"2025-12-09T14:52:55.631477Z","shell.execute_reply":"2025-12-09T14:52:55.635898Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base1 = DenseNet201(weights=\"imagenet\", include_top=False, input_shape=(img_size, img_size, 3))\nbase2 = ResNet50(weights=\"imagenet\", include_top=False, input_shape=(img_size, img_size, 3))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:52:55.637369Z","iopub.execute_input":"2025-12-09T14:52:55.637654Z","iopub.status.idle":"2025-12-09T14:53:02.943493Z","shell.execute_reply.started":"2025-12-09T14:52:55.637624Z","shell.execute_reply":"2025-12-09T14:53:02.942779Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base1.trainable = True\nbase2.trainable = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:53:02.944401Z","iopub.execute_input":"2025-12-09T14:53:02.94469Z","iopub.status.idle":"2025-12-09T14:53:02.947958Z","shell.execute_reply.started":"2025-12-09T14:53:02.944663Z","shell.execute_reply":"2025-12-09T14:53:02.947248Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for layer in base1.layers[:-10]:  # Unfreeze the last 10 layers of EfficientNetB0\n    layer.trainable = False\nfor layer in base2.layers[:-10]:  # Unfreeze the last 10 layers of ResNet50\n    layer.trainable = False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:53:02.948786Z","iopub.execute_input":"2025-12-09T14:53:02.949053Z","iopub.status.idle":"2025-12-09T14:53:02.97386Z","shell.execute_reply.started":"2025-12-09T14:53:02.94903Z","shell.execute_reply":"2025-12-09T14:53:02.973164Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"inputs = tf.keras.Input(shape=(img_size, img_size, 3))\nx1 = layers.GlobalAveragePooling2D()(base1(inputs))\nx2 = layers.GlobalAveragePooling2D()(base2(inputs))\nmerged = layers.Concatenate()([x1, x2])\nx = layers.Dense(256, activation=\"relu\")(merged)\nx = layers.Dropout(0.5)(x)\noutputs = layers.Dense(5, activation=\"softmax\")(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:53:02.974572Z","iopub.execute_input":"2025-12-09T14:53:02.974794Z","iopub.status.idle":"2025-12-09T14:53:03.015114Z","shell.execute_reply.started":"2025-12-09T14:53:02.974775Z","shell.execute_reply":"2025-12-09T14:53:03.01453Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"multibranch_model_1 = Model(inputs, outputs)\nmultibranch_model_1.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:53:03.015863Z","iopub.execute_input":"2025-12-09T14:53:03.016164Z","iopub.status.idle":"2025-12-09T14:53:03.059522Z","shell.execute_reply.started":"2025-12-09T14:53:03.016138Z","shell.execute_reply":"2025-12-09T14:53:03.058949Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"multibranch_model_1.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n    loss=tf.keras.losses.SparseCategoricalCrossentropy(),\n    metrics=[\"accuracy\"]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:53:03.060167Z","iopub.execute_input":"2025-12-09T14:53:03.06034Z","iopub.status.idle":"2025-12-09T14:53:03.073162Z","shell.execute_reply.started":"2025-12-09T14:53:03.060325Z","shell.execute_reply":"2025-12-09T14:53:03.072468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_weights = compute_class_weight(\"balanced\", classes=np.unique(labels), y=labels)\nclass_weights = dict(enumerate(class_weights))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:53:03.073927Z","iopub.execute_input":"2025-12-09T14:53:03.074184Z","iopub.status.idle":"2025-12-09T14:53:03.079569Z","shell.execute_reply.started":"2025-12-09T14:53:03.074154Z","shell.execute_reply":"2025-12-09T14:53:03.078941Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def lr_scheduler(epoch, lr):\n    if epoch > 0 and epoch % 10 == 0:  # Reduce LR every 10 epochs\n        return lr * 0.1\n    return lr\n\nlr_callback = tf.keras.callbacks.LearningRateScheduler(lr_scheduler)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:53:03.080421Z","iopub.execute_input":"2025-12-09T14:53:03.080673Z","iopub.status.idle":"2025-12-09T14:53:03.091676Z","shell.execute_reply.started":"2025-12-09T14:53:03.080645Z","shell.execute_reply":"2025-12-09T14:53:03.090907Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"early_stopping = tf.keras.callbacks.EarlyStopping(\n    monitor=\"val_loss\",\n    patience=5,  # Stop after 5 epochs without improvement\n    restore_best_weights=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:53:03.095809Z","iopub.execute_input":"2025-12-09T14:53:03.096092Z","iopub.status.idle":"2025-12-09T14:53:03.103808Z","shell.execute_reply.started":"2025-12-09T14:53:03.096072Z","shell.execute_reply":"2025-12-09T14:53:03.103127Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"multibranch_history = multibranch_model_1.fit(\n    train_ds,\n    epochs=50,\n    batch_size=batch_size,\n    verbose=1,\n    class_weight=class_weights,\n    validation_data=val_ds,\n    callbacks=[lr_callback,early_stopping]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T14:53:03.105273Z","iopub.execute_input":"2025-12-09T14:53:03.105469Z","iopub.status.idle":"2025-12-09T15:02:47.121196Z","shell.execute_reply.started":"2025-12-09T14:53:03.105451Z","shell.execute_reply":"2025-12-09T15:02:47.120444Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"multibranch_model_1.save(\"multibranch_model_1.keras\")\nmultibranch_model_1.save(\"multibranch_model_1.h5\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T15:02:47.122232Z","iopub.execute_input":"2025-12-09T15:02:47.122515Z","iopub.status.idle":"2025-12-09T15:02:50.907356Z","shell.execute_reply.started":"2025-12-09T15:02:47.122493Z","shell.execute_reply":"2025-12-09T15:02:50.906382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loss, test_acc = multibranch_model_1.evaluate(test_ds)\nprint(f\"Test Accuracy: {test_acc * 100:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T15:02:50.908859Z","iopub.execute_input":"2025-12-09T15:02:50.909215Z","iopub.status.idle":"2025-12-09T15:03:11.683419Z","shell.execute_reply.started":"2025-12-09T15:02:50.909185Z","shell.execute_reply":"2025-12-09T15:03:11.682719Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport tensorflow as tf\nfrom sklearn.metrics import classification_report, f1_score, recall_score, accuracy_score, confusion_matrix\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport os\n\n# --- Step 1: Ensure Data is Loaded and Split ---\n# Re-running the split logic to ensure test_ds is available.\n# This assumes 'dataset' (the TF dataset of images and labels) is already created.\n# If 'dataset' is not defined, you must run the data loading cells first.\n\ndef dataset_split(ds, train=0.7, val=0.15, test=0.15):\n    ds_size = len(ds)\n    train_size = int(ds_size * train)\n    val_size = int(ds_size * val)\n    \n    train_ds = ds.take(train_size)\n    val_ds = ds.skip(train_size).take(val_size)\n    test_ds = ds.skip(train_size + val_size)\n    \n    return train_ds, val_ds, test_ds\n\n# Split the dataset\n# Note: Ensure 'dataset' is available. If not, re-run the cell where 'dataset' is created.\ntry:\n    train_ds, val_ds, test_ds = dataset_split(dataset)\n    \n    # Apply optimizations (caching and prefetching)\n    # Batching is typically done when creating 'dataset', but good to ensure efficient loading.\n    test_ds = test_ds.cache().prefetch(tf.data.AUTOTUNE)\n\n    # --- Step 2: Generate Predictions ---\n    print(\"Generating predictions for Multi-branch Model...\")\n\n    y_true = []\n    y_pred_probs = []\n\n    # Iterate through the test dataset\n    for images, labels in test_ds:\n        # Append actual labels\n        y_true.extend(labels.numpy())\n        \n        # Generate predictions\n        # multibranch_model_1 is the specific name used for your multi-branch model\n        preds = multibranch_model_1.predict(images, verbose=0)\n        y_pred_probs.extend(preds)\n\n    # Convert to numpy arrays\n    y_true = np.array(y_true)\n    y_pred_probs = np.array(y_pred_probs)\n\n    # Convert probabilities to class labels (index of max probability)\n    y_pred = np.argmax(y_pred_probs, axis=1)\n\n\n    # --- Step 3: Calculate and Print Metrics ---\n\n    # Class names for Diabetic Retinopathy\n    class_names = ['No DR', 'Mild', 'Moderate', 'Severe', 'Proliferative']\n\n    # 1. Classification Report (includes Precision, Recall, F1-Score per class)\n    print(\"\\n\" + \"=\"*60)\n    print(\"CLASSIFICATION REPORT (Multi-branch CNN)\")\n    print(\"=\"*60)\n    report = classification_report(y_true, y_pred, target_names=class_names)\n    print(report)\n\n    # 2. Weighted Metrics (Single values)\n    weighted_f1 = f1_score(y_true, y_pred, average='weighted')\n    weighted_recall = recall_score(y_true, y_pred, average='weighted')\n    overall_acc = accuracy_score(y_true, y_pred)\n\n    print(\"-\" * 30)\n    print(f\"Weighted F1-Score : {weighted_f1:.4f}\")\n    print(f\"Weighted Recall   : {weighted_recall:.4f}\")\n    print(f\"Overall Accuracy  : {overall_acc:.4f}\")\n    print(\"-\" * 30)\n\n    # 3. Confusion Matrix Plot\n    cm = confusion_matrix(y_true, y_pred)\n    plt.figure(figsize=(10, 8))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False, \n                xticklabels=class_names, yticklabels=class_names)\n    plt.xlabel('Predicted Label', fontsize=12)\n    plt.ylabel('True Label', fontsize=12)\n    plt.title('Confusion Matrix - Multi-branch CNN', fontsize=15)\n    plt.show()\n\nexcept NameError:\n    print(\"Error: 'dataset' is not defined. Please run the data loading cells (Cells 3-9 in the notebook) before running this evaluation code.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T15:03:11.684227Z","iopub.execute_input":"2025-12-09T15:03:11.684568Z","iopub.status.idle":"2025-12-09T15:03:48.937516Z","shell.execute_reply.started":"2025-12-09T15:03:11.68454Z","shell.execute_reply":"2025-12-09T15:03:48.936634Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2D CNN","metadata":{}},{"cell_type":"code","source":"def build_2d_cnn(input_shape=(224, 224, 3), num_classes=5):\n    inputs = tf.keras.Input(shape=input_shape)\n    \n    # Convolutional layers\n    x = layers.Conv2D(32, (3, 3), activation=\"relu\", padding=\"same\")(inputs)\n    x = layers.MaxPooling2D((2, 2))(x)\n    x = layers.Conv2D(64, (3, 3), activation=\"relu\", padding=\"same\")(x)\n    x = layers.MaxPooling2D((2, 2))(x)\n    x = layers.Conv2D(128, (3, 3), activation=\"relu\", padding=\"same\")(x)\n    x = layers.MaxPooling2D((2, 2))(x)\n    \n    # Fully connected layers\n    x = layers.Flatten()(x)\n    x = layers.Dense(256, activation=\"relu\")(x)\n    x = layers.Dropout(0.5)(x)\n    \n    # Output layer\n    outputs = layers.Dense(num_classes, activation=\"softmax\")(x)\n    \n    # Build the model\n    cnn_model = Model(inputs, outputs)\n    return cnn_model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T15:03:48.938444Z","iopub.execute_input":"2025-12-09T15:03:48.938763Z","iopub.status.idle":"2025-12-09T15:03:48.94467Z","shell.execute_reply.started":"2025-12-09T15:03:48.938732Z","shell.execute_reply":"2025-12-09T15:03:48.943899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cnn_model = build_2d_cnn(input_shape=(img_size, img_size, 3), num_classes=5)\ncnn_model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T15:03:48.945542Z","iopub.execute_input":"2025-12-09T15:03:48.945836Z","iopub.status.idle":"2025-12-09T15:03:49.02207Z","shell.execute_reply.started":"2025-12-09T15:03:48.945805Z","shell.execute_reply":"2025-12-09T15:03:49.021421Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cnn_model.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n    loss=tf.keras.losses.SparseCategoricalCrossentropy(),\n    metrics=[\"accuracy\"]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T15:03:49.022687Z","iopub.execute_input":"2025-12-09T15:03:49.022866Z","iopub.status.idle":"2025-12-09T15:03:49.029341Z","shell.execute_reply.started":"2025-12-09T15:03:49.02285Z","shell.execute_reply":"2025-12-09T15:03:49.028705Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cnn_history = cnn_model.fit(\n    train_ds,\n    epochs=40,\n    batch_size=batch_size,\n    verbose=1,\n    class_weight=class_weights,\n    validation_data=val_ds,\n    callbacks=[lr_callback]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T15:03:49.030165Z","iopub.execute_input":"2025-12-09T15:03:49.03044Z","iopub.status.idle":"2025-12-09T15:06:51.968326Z","shell.execute_reply.started":"2025-12-09T15:03:49.030415Z","shell.execute_reply":"2025-12-09T15:06:51.967598Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cnn_model.save(\"cnn_model_1.keras\")\ncnn_model.save(\"cnn_model_1.h5\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T15:06:51.969291Z","iopub.execute_input":"2025-12-09T15:06:51.969582Z","iopub.status.idle":"2025-12-09T15:06:53.807814Z","shell.execute_reply.started":"2025-12-09T15:06:51.96956Z","shell.execute_reply":"2025-12-09T15:06:53.806926Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loss, test_acc = cnn_model.evaluate(test_ds)\nprint(f\"Test Accuracy: {test_acc * 100:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T15:06:53.808768Z","iopub.execute_input":"2025-12-09T15:06:53.809078Z","iopub.status.idle":"2025-12-09T15:06:54.848323Z","shell.execute_reply.started":"2025-12-09T15:06:53.809042Z","shell.execute_reply":"2025-12-09T15:06:54.847621Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## VISUALIZATION OF MODEL TRAININGS","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# ✅ Convert only if still a History object\nif hasattr(multibranch_history, 'history'):\n    multibranch_history = multibranch_history.history\n\nif hasattr(cnn_history, 'history'):\n    cnn_history = cnn_history.history\n\n# ✅ Plot Accuracy and Loss Comparison\nfig, axes = plt.subplots(1, 2, figsize=(16, 6))\n\n# --- Accuracy ---\naxes[0].plot(multibranch_history['accuracy'], label='Multi-Branch CNN Training Accuracy')\naxes[0].plot(multibranch_history['val_accuracy'], label='Multi-Branch CNN Validation Accuracy')\naxes[0].plot(cnn_history['accuracy'], label='2D CNN Training Accuracy')\naxes[0].plot(cnn_history['val_accuracy'], label='2D CNN Validation Accuracy')\naxes[0].set_title('Accuracy Comparison')\naxes[0].set_xlabel('Epoch')\naxes[0].set_ylabel('Accuracy')\naxes[0].legend()\naxes[0].grid(True)\n\n# --- Loss ---\naxes[1].plot(multibranch_history['loss'], label='Multi-Branch CNN Training Loss')\naxes[1].plot(multibranch_history['val_loss'], label='Multi-Branch CNN Validation Loss')\naxes[1].plot(cnn_history['loss'], label='2D CNN Training Loss')\naxes[1].plot(cnn_history['val_loss'], label='2D CNN Validation Loss')\naxes[1].set_title('Loss Comparison')\naxes[1].set_xlabel('Epoch')\naxes[1].set_ylabel('Loss')\naxes[1].legend()\naxes[1].grid(True)\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T15:06:54.849127Z","iopub.execute_input":"2025-12-09T15:06:54.849381Z","iopub.status.idle":"2025-12-09T15:06:55.30489Z","shell.execute_reply.started":"2025-12-09T15:06:54.849361Z","shell.execute_reply":"2025-12-09T15:06:55.304086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"multibranch_predictions = multibranch_model_1.predict(test_ds)\ncnn_predictions = cnn_model.predict(test_ds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-09T15:06:55.305658Z","iopub.execute_input":"2025-12-09T15:06:55.305895Z","execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"multibranch_class_labels = np.argmax(multibranch_predictions, axis=1) \ncnn_class_labels = np.argmax(cnn_predictions, axis=1)\n\ntrue_labels = np.concatenate([y for x, y in test_ds], axis=0)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"multibranch_cm = confusion_matrix(true_labels, multibranch_class_labels)\ncnn_cm = confusion_matrix(true_labels, cnn_class_labels)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(16, 6))\n\nplt.subplot(1, 2, 1)\nsns.heatmap(multibranch_cm, annot=True, fmt='d', cmap='Blues', cbar=False)\nplt.title('Multi-Branch CNN Confusion Matrix')\nplt.xlabel('Predicted Labels')\nplt.ylabel('True Labels')\n\nplt.subplot(1, 2, 2)\nsns.heatmap(cnn_cm, annot=True, fmt='d', cmap='Greens', cbar=False)\nplt.title('2D CNN Confusion Matrix')\nplt.xlabel('Predicted Labels')\nplt.ylabel('True Labels')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(12, 6))\n\nplt.subplot(1, 3, 1)\nplt.hist(multibranch_class_labels, bins=5, range=(0, 5), alpha=0.7, color='blue')\nplt.title('Multi-Branch CNN Predictions')\nplt.xlabel('Class')\nplt.ylabel('Frequency')\n\nplt.subplot(1, 3, 2)\nplt.hist(cnn_class_labels, bins=5, range=(0, 5), alpha=0.7, color='green')\nplt.title('2D CNN Predictions')\nplt.xlabel('Class')\nplt.ylabel('Frequency')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## PREDICTING WITH USER INPUTS","metadata":{}},{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport tensorflow as tf\nimport matplotlib.pyplot as plt","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess_image(image_path, img_size=224):\n    \"\"\"\n    Preprocesses the image using CLAHE and resizes it.\n    \"\"\"\n    image = cv2.imread(image_path, cv2.IMREAD_COLOR)\n    if image is None:\n        raise ValueError(f\"Unable to read image at path: {image_path}\")\n\n    image = cv2.resize(image, (img_size, img_size))\n    lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)\n    l, a, b = cv2.split(lab)\n    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))\n    l_clahe = clahe.apply(l)\n\n    merged_lab = cv2.merge((l_clahe, a, b))\n    final_image = cv2.cvtColor(merged_lab, cv2.COLOR_LAB2RGB)\n\n    final_image = final_image / 255.0  # Normalize to [0, 1]\n    return final_image","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def saliency_map(model, img_array):\n    \"\"\"\n    Generates a Saliency Map for a given model and image.\n    \"\"\"\n    img_tensor = tf.convert_to_tensor(img_array)\n    with tf.GradientTape() as tape:\n        tape.watch(img_tensor)\n        predictions = model(img_tensor)\n        top_pred = tf.argmax(predictions[0])\n        loss = predictions[:, top_pred]\n\n    grads = tape.gradient(loss, img_tensor)[0]\n    saliency = tf.reduce_max(tf.abs(grads), axis=-1).numpy()\n    return saliency","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_with_explanations(image_path, multibranch_model, cnn_model, img_size=224):\n    \"\"\"\n    Predicts the class probabilities for an image using multi-branch CNN, 2D CNN, and ensemble.\n    Also generates Saliency Map explanations for each model.\n\n    Args:\n        image_path (str): Path to the input image.\n        multibranch_model (tf.keras.Model): Trained multi-branch CNN model.\n        cnn_model (tf.keras.Model): Trained 2D CNN model.\n        img_size (int): Size of the input image (default: 224).\n        weight_multibranch (float): Weight for multi-branch CNN in the ensemble (default: 0.7).\n        weight_cnn (float): Weight for 2D CNN in the ensemble (default: 0.3).\n\n    Returns:\n        dict: A dictionary containing the predictions, confidence percentages, and Saliency Maps.\n    \"\"\"\n    # Preprocess the image\n    preprocessed_image = preprocess_image(image_path, img_size)\n    img_array = np.expand_dims(preprocessed_image, axis=0)  # Add batch dimension\n\n    # Get predictions from multi-branch CNN\n    multibranch_probs = multibranch_model.predict(img_array, verbose=0)[0]\n    multibranch_class = np.argmax(multibranch_probs)\n    multibranch_confidence = float(multibranch_probs[multibranch_class])\n\n    # Get predictions from 2D CNN\n    cnn_probs = cnn_model.predict(img_array, verbose=0)[0]\n    cnn_class = np.argmax(cnn_probs)\n    cnn_confidence = float(cnn_probs[cnn_class])\n\n    # Generate Saliency Map for multi-branch CNN\n    multibranch_saliency = saliency_map(multibranch_model, img_array)\n\n    # Generate Saliency Map for 2D CNN\n    cnn_saliency = saliency_map(cnn_model, img_array)\n\n    # Return results as a dictionary\n    results = {\n        \"multi_branch_cnn\": {\n            \"class\": int(multibranch_class),\n            \"confidence\": multibranch_confidence,\n            \"probabilities\": [float(prob) for prob in multibranch_probs],\n            \"saliency_map\": multibranch_saliency,\n        },\n        \"2d_cnn\": {\n            \"class\": int(cnn_class),\n            \"confidence\": cnn_confidence,\n            \"probabilities\": [float(prob) for prob in cnn_probs],\n            \"saliency_map\": cnn_saliency,\n        },\n    }\n    return results","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def visualize_results(results, original_image):\n    \"\"\"\n    Visualizes the predictions and Saliency Map explanations.\n\n    Args:\n        results (dict): Dictionary containing predictions and Saliency Maps.\n        original_image (np.array): Original input image.\n    \"\"\"\n    plt.figure(figsize=(18, 12))\n\n    # Display original image\n    plt.subplot(2, 2, 1)\n    plt.imshow(original_image)\n    plt.title(\"Original Image\")\n    plt.axis(\"off\")\n\n    # Display Saliency Map for multi-branch CNN\n    plt.subplot(2, 2, 2)\n    plt.imshow(results[\"multi_branch_cnn\"][\"saliency_map\"], cmap=\"hot\")\n    plt.title(\"Saliency Map (Multi-Branch CNN)\")\n    plt.axis(\"off\")\n\n    # Display Saliency Map for 2D CNN\n    plt.subplot(2, 2, 4)\n    plt.imshow(results[\"2d_cnn\"][\"saliency_map\"], cmap=\"hot\")\n    plt.title(\"Saliency Map (2D CNN)\")\n    plt.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Example usage\nimage_path = \"/kaggle/input/aptos2019-blindness-detection/test_images/006efc72b638.png\"\nresults = predict_with_explanations(image_path, multibranch_model_1, cnn_model)\n\n# Print predictions\nprint(\"Multi-Branch CNN Predictions:\")\nprint(f\"Class: {results['multi_branch_cnn']['class']}\")\nprint(f\"Confidence: {results['multi_branch_cnn']['confidence'] * 100:.2f}%\")\nprint(f\"Probabilities: {results['multi_branch_cnn']['probabilities']}\")\n\nprint(\"\\n2D CNN Predictions:\")\nprint(f\"Class: {results['2d_cnn']['class']}\")\nprint(f\"Confidence: {results['2d_cnn']['confidence'] * 100:.2f}%\")\nprint(f\"Probabilities: {results['2d_cnn']['probabilities']}\")\n\n# Visualize results\noriginal_image = cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB)\nvisualize_results(results, original_image)","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import classification_report, f1_score, recall_score\nimport numpy as np\n\n# 1. Generate Predictions (Ensure y_true and y_pred are available)\n# Assuming test_ds is your test dataset and cnn_model is your trained model\n\ny_true = []\ny_pred_probs = []\n\n# Iterate through the test dataset to get true labels and predictions\nfor images, labels in test_ds:\n    y_true.extend(labels.numpy())\n    preds = cnn_model.predict(images, verbose=0)\n    y_pred_probs.extend(preds)\n\n# Convert to numpy arrays\ny_true = np.array(y_true)\ny_pred_probs = np.array(y_pred_probs)\n\n# Convert probabilities to class labels\ny_pred = np.argmax(y_pred_probs, axis=1)\n\n# 2. Calculate F1-Score and Recall using individual functions\n# 'weighted' averages metrics by support (the number of true instances for each label)\nf1 = f1_score(y_true, y_pred, average='weighted')\nrecall = recall_score(y_true, y_pred, average='weighted')\n\nprint(f\"Weighted F1-Score: {f1:.4f}\")\nprint(f\"Weighted Recall: {recall:.4f}\")\n\n# 3. Comprehensive Report (Precision, Recall, F1-Score per class)\n# This gives you the metrics for each individual class as well\nclass_names = ['No DR', 'Mild', 'Moderate', 'Severe', 'Proliferative']\nprint(\"\\nClassification Report:\\n\")\nprint(classification_report(y_true, y_pred, target_names=class_names))","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport tensorflow as tf\nfrom sklearn.metrics import classification_report, f1_score, recall_score, accuracy_score, confusion_matrix\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport os\n\n# --- Step 1: Ensure Data is Loaded and Split ---\n# Re-running the split logic to ensure test_ds is available.\n# This assumes 'dataset' (the TF dataset of images and labels) is already created.\n# If 'dataset' is not defined, you must run the data loading cells first.\n\ndef dataset_split(ds, train=0.7, val=0.15, test=0.15):\n    ds_size = len(ds)\n    train_size = int(ds_size * train)\n    val_size = int(ds_size * val)\n    \n    train_ds = ds.take(train_size)\n    val_ds = ds.skip(train_size).take(val_size)\n    test_ds = ds.skip(train_size + val_size)\n    \n    return train_ds, val_ds, test_ds\n\n# Split the dataset\n# Note: Ensure 'dataset' is available. If not, re-run the cell where 'dataset' is created.\ntry:\n    train_ds, val_ds, test_ds = dataset_split(dataset)\n    \n    # Apply optimizations (caching and prefetching)\n    # Batching is typically done when creating 'dataset', but good to ensure efficient loading.\n    test_ds = test_ds.cache().prefetch(tf.data.AUTOTUNE)\n\n    # --- Step 2: Generate Predictions ---\n    print(\"Generating predictions for Multi-branch Model...\")\n\n    y_true = []\n    y_pred_probs = []\n\n    # Iterate through the test dataset\n    for images, labels in test_ds:\n        # Append actual labels\n        y_true.extend(labels.numpy())\n        \n        # Generate predictions\n        # multibranch_model_1 is the specific name used for your multi-branch model\n        preds = multibranch_model_1.predict(images, verbose=0)\n        y_pred_probs.extend(preds)\n\n    # Convert to numpy arrays\n    y_true = np.array(y_true)\n    y_pred_probs = np.array(y_pred_probs)\n\n    # Convert probabilities to class labels (index of max probability)\n    y_pred = np.argmax(y_pred_probs, axis=1)\n\n\n    # --- Step 3: Calculate and Print Metrics ---\n\n    # Class names for Diabetic Retinopathy\n    class_names = ['No DR', 'Mild', 'Moderate', 'Severe', 'Proliferative']\n\n    # 1. Classification Report (includes Precision, Recall, F1-Score per class)\n    print(\"\\n\" + \"=\"*60)\n    print(\"CLASSIFICATION REPORT (Multi-branch CNN)\")\n    print(\"=\"*60)\n    report = classification_report(y_true, y_pred, target_names=class_names)\n    print(report)\n\n    # 2. Weighted Metrics (Single values)\n    weighted_f1 = f1_score(y_true, y_pred, average='weighted')\n    weighted_recall = recall_score(y_true, y_pred, average='weighted')\n    overall_acc = accuracy_score(y_true, y_pred)\n\n    print(\"-\" * 30)\n    print(f\"Weighted F1-Score : {weighted_f1:.4f}\")\n    print(f\"Weighted Recall   : {weighted_recall:.4f}\")\n    print(f\"Overall Accuracy  : {overall_acc:.4f}\")\n    print(\"-\" * 30)\n\n    # 3. Confusion Matrix Plot\n    cm = confusion_matrix(y_true, y_pred)\n    plt.figure(figsize=(10, 8))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False, \n                xticklabels=class_names, yticklabels=class_names)\n    plt.xlabel('Predicted Label', fontsize=12)\n    plt.ylabel('True Label', fontsize=12)\n    plt.title('Confusion Matrix - Multi-branch CNN', fontsize=15)\n    plt.show()\n\nexcept NameError:\n    print(\"Error: 'dataset' is not defined. Please run the data loading cells (Cells 3-9 in the notebook) before running this evaluation code.\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-12-09T15:07:11.154Z"}},"outputs":[],"execution_count":null}]}