{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":14774,"databundleVersionId":875431,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Import necessary libraries\nimport numpy as np\nimport pandas as pd\nimport os\nimport cv2 # OpenCV for image processing\nimport matplotlib.pyplot as plt # For plotting\nimport seaborn as sns # For enhanced visualizations\nimport time # To measure training times\n\nimport tensorflow as tf\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.models import Model, Sequential\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout, Conv2D, MaxPooling2D, Flatten, Activation\nfrom tensorflow.keras.applications import VGG16, ResNet50\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, confusion_matrix, accuracy_score\n\n\nBASE_DRIVE_DIR = '/kaggle/input/aptos2019-blindness-detection/'\n\nTRAIN_CSV_PATH = os.path.join(BASE_DRIVE_DIR, 'train.csv')\nTRAIN_IMG_PATH = os.path.join(BASE_DRIVE_DIR, 'train_images/')\n\nprint(f\"TensorFlow Version: {tf.__version__}\")\n\n# --- 2. Verify GPU Availability ---\ngpus = tf.config.list_physical_devices('GPU')\nif gpus:\n    try:\n        tf.config.set_visible_devices(gpus[0], 'GPU')\n        logical_gpus = tf.config.list_logical_devices('GPU')\n        print(len(gpus), \" GPU found,\", len(logical_gpus), \"logic GPU will be used.\")\n        for gpu in gpus:\n            tf.config.experimental.set_memory_growth(gpu, True)\n        print(\"GPU memory set for the dynamic increase.\")\n    except RuntimeError as e:\n        print(f\"GPU settings error: {e}\")\nelse:\n    print(\"GPU couldn't found. CPU will be used.\")\n\n# Project parameters\nIMG_SIZE = 224\nBATCH_SIZE = 32  # Suitable for GPU.\nprint(f\"Batch Size to be used: {BATCH_SIZE}\")\n\nEPOCHS = 50 # Adjust based on training time and convergence\nNUM_CLASSES = 5 # Diabetic retinopathy stages (0, 1, 2, 3, 4)\nRANDOM_STATE = 42\n\n# Check if data paths exist\nif not os.path.exists(TRAIN_CSV_PATH):\n    print(f\"ERROR: Training CSV not found at {TRAIN_CSV_PATH}\")\n    print(\"Please verify the BASE_DRIVE_DIR path.\")\nif not os.path.exists(TRAIN_IMG_PATH):\n    print(f\"ERROR: Training images folder not found at {TRAIN_IMG_PATH}\")\n    print(\"Please verify the BASE_DRIVE_DIR path.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-27T14:49:38.837977Z","iopub.execute_input":"2025-05-27T14:49:38.838144Z","iopub.status.idle":"2025-05-27T14:49:38.847298Z","shell.execute_reply.started":"2025-05-27T14:49:38.838130Z","shell.execute_reply":"2025-05-27T14:49:38.846601Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Data Loading and Preprocessing ---\nprint(\"\\n--- 1. Data Loading and Preprocessing ---\") \ntrain_df = pd.read_csv(TRAIN_CSV_PATH)\ntrain_df['image_path'] = train_df['id_code'].apply(lambda x: os.path.join(TRAIN_IMG_PATH, x + '.png'))\n# Convert diagnosis to string for ImageDataGenerator's class_mode='categorical'\ntrain_df['diagnosis'] = train_df['diagnosis'].astype(str)\n\nprint(f\"Training dataset size: {train_df.shape}\")\nprint(train_df.head())\n\n# Check class distribution\nprint(\"\\nClass Distribution in Training Data:\")\nprint(train_df['diagnosis'].value_counts().sort_index())\nsns.countplot(x='diagnosis', data=train_df)\nplt.title('Class Distribution in Training Data')\nplt.show()\n\n# Split data into training and validation sets\ntrain_data_df, val_data_df = train_test_split(train_df,\n                                        test_size=0.2, # 20% for validation\n                                        random_state=RANDOM_STATE,\n                                        stratify=train_df['diagnosis']) # Preserve class distribution\n\nprint(f\"\\nSplit Training Data Size: {train_data_df.shape}\")\nprint(f\"Validation Data Size: {val_data_df.shape}\")\n\n# Image data augmentation and normalization\ntrain_datagen = ImageDataGenerator(\n    rescale=1./255, # Normalize pixel values to [0,1]\n    rotation_range=20,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    shear_range=0.1,\n    zoom_range=0.1,\n    horizontal_flip=True,\n    fill_mode='nearest'\n)\n\nval_datagen = ImageDataGenerator(rescale=1./255) # Only rescale for validation\n\n# Create data generators\ntrain_generator = train_datagen.flow_from_dataframe(\n    dataframe=train_data_df,\n    x_col='image_path',\n    y_col='diagnosis',\n    target_size=(IMG_SIZE, IMG_SIZE),\n    batch_size=BATCH_SIZE,\n    class_mode='categorical', # For multi-class classification\n    shuffle=True\n)\n\nvalidation_generator = val_datagen.flow_from_dataframe(\n    dataframe=val_data_df,\n    x_col='image_path',\n    y_col='diagnosis',\n    target_size=(IMG_SIZE, IMG_SIZE),\n    batch_size=BATCH_SIZE,\n    class_mode='categorical',\n    shuffle=False # No need to shuffle validation data\n)\n\n# Calculate class weights for imbalanced datasets (optional, but can be helpful)\nfrom sklearn.utils import class_weight\ntry:\n    class_labels_int = np.unique(train_df['diagnosis'].astype(int))\n    class_counts = np.bincount(train_df['diagnosis'].astype(int)) # Ensures all classes from 0 to N-1 are counted\n    \n    # Compute class weights using sklearn.utils.class_weight\n    # Filter out classes with zero samples if any, though stratify should prevent this for train_df\n    active_classes = [i for i, count in enumerate(class_counts) if count > 0]\n    active_class_labels = train_df[train_df['diagnosis'].astype(int).isin(active_classes)]['diagnosis'].astype(int)\n    \n    if len(active_class_labels) > 0:\n        weights = class_weight.compute_class_weight(\n            class_weight='balanced',\n            classes=np.unique(active_class_labels),\n            y=active_class_labels\n        )\n        class_weights_dict = dict(zip(np.unique(active_class_labels), weights))\n        print(\"\\nCalculated Class Weights:\", class_weights_dict)\n    else:\n        print(\"\\nCould not calculate class weights: No active classes found.\")\n        class_weights_dict = None\n\nexcept Exception as e:\n    print(f\"\\nError calculating class weights: {e}. Class weights will not be used.\")\n    class_weights_dict = None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T14:52:02.938947Z","iopub.execute_input":"2025-05-27T14:52:02.939258Z","iopub.status.idle":"2025-05-27T14:52:05.110770Z","shell.execute_reply.started":"2025-05-27T14:52:02.939229Z","shell.execute_reply":"2025-05-27T14:52:05.110009Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- CustomAlexNet Definition ---\ndef CustomAlexNet(input_shape=(IMG_SIZE, IMG_SIZE, 3), num_classes=NUM_CLASSES):\n    model = Sequential([\n        Conv2D(96, (11, 11), strides=(4,4), padding='same', input_shape=input_shape), Activation('relu'),\n        MaxPooling2D(pool_size=(3, 3), strides=(2, 2)),\n        Conv2D(256, (5, 5), padding='same'), Activation('relu'),\n        MaxPooling2D(pool_size=(3, 3), strides=(2, 2)),\n        Conv2D(384, (3, 3), padding='same'), Activation('relu'),\n        Conv2D(384, (3, 3), padding='same'), Activation('relu'),\n        Conv2D(256, (3, 3), padding='same'), Activation('relu'),\n        MaxPooling2D(pool_size=(3, 3), strides=(2, 2)),\n        Flatten(),\n        Dense(4096), Activation('relu'), Dropout(0.5),\n        Dense(4096), Activation('relu'), Dropout(0.5),\n        Dense(num_classes, activation='softmax') # Softmax for multi-class\n    ], name=\"AlexNet_Custom\")\n    return model\n\n# --- 2. Model Definition Function ---\ndef build_model(model_name, input_shape=(IMG_SIZE, IMG_SIZE, 3), num_classes=NUM_CLASSES):\n    \"\"\"\n    Builds the specified model: AlexNet (custom), VGG16, or ResNet50.\n    \"\"\"\n    if model_name.lower() == 'alexnet':\n        model = CustomAlexNet(input_shape=input_shape, num_classes=num_classes)\n    elif model_name.lower() == 'vgg16':\n        base_model = VGG16(weights='imagenet', include_top=False, input_shape=input_shape)\n        x = base_model.output\n        x = GlobalAveragePooling2D()(x)\n        x = Dense(1024, activation='relu')(x)\n        x = Dropout(0.5)(x) # Regularization\n        predictions = Dense(num_classes, activation='softmax')(x)\n        model = Model(inputs=base_model.input, outputs=predictions, name=model_name)\n    elif model_name.lower() == 'resnet50':\n        base_model = ResNet50(weights='imagenet', include_top=False, input_shape=input_shape)\n        x = base_model.output\n        x = GlobalAveragePooling2D()(x)\n        x = Dense(1024, activation='relu')(x)\n        x = Dropout(0.5)(x) # Regularization\n        predictions = Dense(num_classes, activation='softmax')(x)\n        model = Model(inputs=base_model.input, outputs=predictions, name=model_name)\n    else:\n        raise ValueError(f\"Unsupported model name: {model_name}\")\n    print(f\"{model_name} model defined.\")\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T14:53:03.084769Z","iopub.execute_input":"2025-05-27T14:53:03.085504Z","iopub.status.idle":"2025-05-27T14:53:03.094085Z","shell.execute_reply.started":"2025-05-27T14:53:03.085476Z","shell.execute_reply":"2025-05-27T14:53:03.093395Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 3. Training and Comparing Models ---\nprint(\"\\n--- 3. Training and Comparing Models ---\")\n\nmodel_names_list = ['AlexNet', 'VGG16', 'ResNet50'] # Models to compare\ntrained_models_dict = {}\nhistories_dict = {}\ntraining_times_dict = {}\nevaluation_results_dict = {}\n\nfor current_model_name in model_names_list:\n    print(f\"\\n===== Processing Model: {current_model_name} (Expecting GPU usage) =====\")\n    start_time_model_processing = time.time()\n\n    # Build and compile the model\n    print(f\"Building and compiling {current_model_name}...\")\n    keras_model = build_model(current_model_name,\n                              input_shape=(IMG_SIZE, IMG_SIZE, 3),\n                              num_classes=NUM_CLASSES)\n    \n    optimizer = Adam(learning_rate=1e-4) # Start with a common learning rate\n    \n    keras_model.compile(optimizer=optimizer,\n                        loss='categorical_crossentropy', # For multi-class classification\n                        metrics=['accuracy'])\n    print(f\"{current_model_name} model compiled.\")\n    keras_model.summary() #print model summary\n\n    # Callbacks\n    early_stopping_cb = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True, verbose=1)\n    # Save model checkpoints to Drive (ensure the path is writable)\n    kaggle_output_dir = \"/kaggle/working/\"\n    if not os.path.exists(kaggle_output_dir):\n        os.makedirs(kaggle_output_dir)\n    checkpoint_save_path = os.path.join(kaggle_output_dir, f'{current_model_name}_best_weights_gpu.keras')\n    print(f\"Model checkpoints will be saved to: {checkpoint_save_path}\")\n    model_checkpoint_cb = ModelCheckpoint(filepath=checkpoint_save_path,\n                                          monitor='val_accuracy',\n                                          save_best_only=True,\n                                          save_weights_only=False, # Save the full model\n                                          mode='max',\n                                          verbose=1)\n    reduce_lr_cb = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=5, min_lr=1e-6, verbose=1)\n    callbacks_to_use = [early_stopping_cb, model_checkpoint_cb, reduce_lr_cb]\n\n    print(f\"\\n--- Training {current_model_name} Model ---\")\n    \n    steps_per_epoch_val = max(1, train_generator.samples // BATCH_SIZE)\n    validation_steps_val = max(1, validation_generator.samples // BATCH_SIZE)\n\n    history_obj = keras_model.fit(\n        train_generator,\n        steps_per_epoch=steps_per_epoch_val,\n        epochs=EPOCHS,\n        validation_data=validation_generator,\n        validation_steps=validation_steps_val,\n        callbacks=callbacks_to_use,\n        class_weight=class_weights_dict # Use calculated class weights if available\n    )\n\n    end_time_model_processing = time.time()\n    model_training_time = end_time_model_processing - start_time_model_processing\n    print(f\"Total processing time for {current_model_name}: {model_training_time:.2f} seconds\")\n\n    trained_models_dict[current_model_name] = keras_model\n    histories_dict[current_model_name] = history_obj\n    training_times_dict[current_model_name] = model_training_time\n\n    # --- 4. Plotting Model Performance ---\n    if history_obj and history_obj.history:\n        plt.figure(figsize=(14, 5))\n        plt.subplot(1, 2, 1)\n        if 'accuracy' in history_obj.history and 'val_accuracy' in history_obj.history:\n            plt.plot(history_obj.history['accuracy'], label='Training Accuracy')\n            plt.plot(history_obj.history['val_accuracy'], label='Validation Accuracy')\n            plt.title(f'{current_model_name} Accuracy')\n            plt.xlabel('Epoch'); plt.ylabel('Accuracy'); plt.legend()\n        else:\n            plt.text(0.5, 0.5, 'Accuracy data not available', ha='center', va='center')\n\n        plt.subplot(1, 2, 2)\n        if 'loss' in history_obj.history and 'val_loss' in history_obj.history:\n            plt.plot(history_obj.history['loss'], label='Training Loss')\n            plt.plot(history_obj.history['val_loss'], label='Validation Loss')\n            plt.title(f'{current_model_name} Loss')\n            plt.xlabel('Epoch'); plt.ylabel('Loss'); plt.legend()\n        else:\n            plt.text(0.5, 0.5, 'Loss data not available', ha='center', va='center')\n        plt.suptitle(f'Training History for {current_model_name}', fontsize=16)\n        plt.tight_layout(rect=[0, 0, 1, 0.96]) # Adjust layout to make space for suptitle\n        plt.show()\n    else:\n        print(f\"No training history found for {current_model_name} to plot.\")\n\n    # --- 5. Enhanced Model Evaluation (on Validation Set) ---\n    print(f\"\\n--- Evaluating {current_model_name} on Validation Set ---\")\n    # Ensure the generator is reset before prediction\n    validation_generator.reset()\n    # If ModelCheckpoint restored best weights, current_tf_model has them.\n    # Otherwise, load the best model:\n    print(f\"Loading best weights for {current_model_name} from {checkpoint_save_path}\")\n    best_model = tf.keras.models.load_model(checkpoint_save_path) # This might be needed if restore_best_weights=False or if evaluating later\n\n    y_pred_probabilities = keras_model.predict(validation_generator,\n                                             steps=max(1, (validation_generator.samples // BATCH_SIZE) + 1),\n                                             verbose=1)\n    y_pred_classes_indices = np.argmax(y_pred_probabilities, axis=1)\n    y_true_indices = validation_generator.classes # True class indices\n    \n    # Ensure lengths match for metrics calculation, taking the shortest length\n    num_samples_to_evaluate = min(len(y_pred_classes_indices), len(y_true_indices), validation_generator.samples)\n    y_pred_classes_indices = y_pred_classes_indices[:num_samples_to_evaluate]\n    y_true_indices = y_true_indices[:num_samples_to_evaluate]\n\n    class_labels_str = [str(k) for k, v in sorted(validation_generator.class_indices.items(), key=lambda item: item[1])]\n\n    # Accuracy\n    overall_accuracy = accuracy_score(y_true_indices, y_pred_classes_indices)\n    print(f\"\\nOverall Accuracy for {current_model_name}: {overall_accuracy:.4f}\")\n\n    # Classification Report (Precision, Recall/Sensitivity, F1-score)\n    print(f\"\\nClassification Report for {current_model_name}:\")\n    cls_report = classification_report(y_true_indices, y_pred_classes_indices, target_names=class_labels_str, zero_division=0)\n    print(cls_report)\n    \n    # Confusion Matrix and Specificity Calculation\n    print(f\"Confusion Matrix for {current_model_name}:\")\n    cm = confusion_matrix(y_true_indices, y_pred_classes_indices)\n    plt.figure(figsize=(8, 6))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=class_labels_str, yticklabels=class_labels_str)\n    plt.title(f'{current_model_name} Confusion Matrix'); plt.xlabel('Predicted Label'); plt.ylabel('True Label'); plt.show()\n\n    specificities = []\n    for i in range(NUM_CLASSES):\n        tn = 0\n        fp = 0\n        # Calculate TN and FP for class i\n        tp_i = cm[i, i]\n        fp_i = np.sum(cm[:, i]) - tp_i\n        fn_i = np.sum(cm[i, :]) - tp_i\n        tn_i = np.sum(cm) - (tp_i + fp_i + fn_i)\n\n        specificity_i = tn_i / (tn_i + fp_i) if (tn_i + fp_i) > 0 else 0.0\n        specificities.append(specificity_i)\n        print(f\"Specificity for class {class_labels_str[i]} ({current_model_name}): {specificity_i:.4f}\")\n    \n    avg_specificity = np.mean(specificities)\n    print(f\"Average Specificity for {current_model_name}: {avg_specificity:.4f}\")\n\n    evaluation_results_dict[current_model_name] = {\n        'accuracy': overall_accuracy,\n        'classification_report_str': cls_report, # Storing as string\n        'confusion_matrix_obj': cm,\n        'specificities_list': specificities,\n        'average_specificity': avg_specificity\n    }\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T14:59:42.093456Z","iopub.execute_input":"2025-05-27T14:59:42.094031Z","iopub.status.idle":"2025-05-27T21:03:53.381709Z","shell.execute_reply.started":"2025-05-27T14:59:42.094010Z","shell.execute_reply":"2025-05-27T21:03:53.380930Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 6. Final Results Comparison ---\nprint(\"\\n--- Final Results Comparison ---\")\nprint(\"\\nTraining Times:\")\nfor model_n, time_val in training_times_dict.items():\n    print(f\"{model_n}: {time_val:.2f} seconds\")\n\nprint(\"\\n--- Detailed Metrics per Model (Recalculating Recall from Confusion Matrix) ---\")\n\nclass_labels_for_table = [f\"Class {i}\" for i in range(NUM_CLASSES)] \nif model_names_list and evaluation_results_dict.get(model_names_list[0]):\n    first_model_eval_data = evaluation_results_dict[model_names_list[0]]\n    if 'confusion_matrix_obj' in first_model_eval_data:\n        try:\n            class_labels_for_table = [str(k) for k, v in sorted(validation_generator.class_indices.items(), key=lambda item: item[1])]\n        except NameError: # validation_generator might not be in scope if this cell is run alone\n             print(\"Warning: validation_generator not in scope to get class_labels_str. Using generic labels for table.\")\n\n\nfor model_n, results_data in evaluation_results_dict.items():\n    print(f\"\\nMetrics for Model: {model_n}\")\n    print(f\"  Overall Accuracy: {results_data.get('accuracy', 'N/A'):.4f}\")\n    print(f\"  Average Specificity: {results_data.get('average_specificity', 'N/A'):.4f}\")\n\n    cm = results_data.get('confusion_matrix_obj')\n    if cm is not None and isinstance(cm, np.ndarray):\n        per_class_recall = []\n        class_supports = []\n        for i in range(NUM_CLASSES):\n            tp_i = cm[i, i]\n            fn_i = np.sum(cm[i, :]) - tp_i # Sum of row i minus TP_i\n            \n            recall_i = tp_i / (tp_i + fn_i) if (tp_i + fn_i) > 0 else 0.0\n            per_class_recall.append(recall_i)\n            class_supports.append(tp_i + fn_i) # Support for class i is TP_i + FN_i\n\n        if per_class_recall:\n            macro_avg_recall = np.mean(per_class_recall)\n            weighted_avg_recall = np.average(per_class_recall, weights=class_supports if sum(class_supports) > 0 else None)\n            \n            print(f\"  Macro Avg Recall (Sensitivity) (recalculated): {macro_avg_recall:.4f}\")\n            print(f\"  Weighted Avg Recall (Sensitivity) (recalculated): {weighted_avg_recall:.4f}\")\n            \n            print(\"  Per-Class Recall (Sensitivity) (recalculated):\")\n            for i in range(NUM_CLASSES):\n                class_label_name = class_labels_for_table[i] if i < len(class_labels_for_table) else f\"Class {i}\"\n                print(f\"    {class_label_name}: {per_class_recall[i]:.4f}\")\n            \n            # Store these recalculated recalls for the summary table\n            results_data['recalculated_macro_avg_recall'] = macro_avg_recall\n            results_data['recalculated_weighted_avg_recall'] = weighted_avg_recall\n            results_data['recalculated_per_class_recall'] = per_class_recall\n        else:\n            print(\"  Could not recalculate recall: Per-class recall list is empty.\")\n            results_data['recalculated_macro_avg_recall'] = 0.0\n            results_data['recalculated_weighted_avg_recall'] = 0.0\n            results_data['recalculated_per_class_recall'] = [0.0] * NUM_CLASSES\n\n    else:\n        print(f\"  Confusion matrix not found for model {model_n}, cannot recalculate recall.\")\n        results_data['recalculated_macro_avg_recall'] = 'N/A'\n        results_data['recalculated_weighted_avg_recall'] = 'N/A'\n        results_data['recalculated_per_class_recall'] = ['N/A'] * NUM_CLASSES\n\n\n# Create a more detailed comparison DataFrame\ncomparison_data = []\nfor model_n in model_names_list:\n    if model_n in evaluation_results_dict:\n        res = evaluation_results_dict[model_n]\n        \n        # Using recalculated recall values\n        macro_recall = res.get('recalculated_macro_avg_recall', 0.0)\n        weighted_recall = res.get('recalculated_weighted_avg_recall', 0.0)\n        per_class_recalls = res.get('recalculated_per_class_recall', [0.0] * NUM_CLASSES)\n\n        # Prepare data for DataFrame, handling 'N/A' for missing recall\n        row_data = {\n            'Model': model_n,\n            'Training Time (s)': training_times_dict.get(model_n, 0),\n            'Accuracy': res.get('accuracy', 0.0),\n            'Avg. Specificity': res.get('average_specificity', 0.0)\n        }\n        \n        if macro_recall != 'N/A':\n            row_data['Macro Recall (Sensitivity)'] = macro_recall\n        else:\n            row_data['Macro Recall (Sensitivity)'] = 0.0 # Or np.nan\n\n        if weighted_recall != 'N/A':\n            row_data['Weighted Recall (Sensitivity)'] = weighted_recall\n        else:\n            row_data['Weighted Recall (Sensitivity)'] = 0.0 # Or np.nan\n            \n        # Add per-class recalls to the table\n        for i in range(NUM_CLASSES):\n            class_label_name = class_labels_for_table[i] if i < len(class_labels_for_table) else f\"Class {i}\"\n            recall_value = per_class_recalls[i] if per_class_recalls[i] != 'N/A' else 0.0 # Or np.nan\n            row_data[f'Recall {class_label_name}'] = recall_value\n            \n        comparison_data.append(row_data)\n\nif comparison_data:\n    comparison_df = pd.DataFrame(comparison_data)\n    print(\"\\nComparison Summary Table (with Recalculated Recall):\")\n    pd.set_option('display.max_columns', None) \n    pd.set_option('display.width', 1000)\n    # Format float columns for better readability\n    float_cols = [col for col in comparison_df.columns if comparison_df[col].dtype == 'float64' or comparison_df[col].dtype == 'float32']\n    for col in float_cols:\n        comparison_df[col] = comparison_df[col].map(lambda x: f\"{x:.4f}\" if isinstance(x, (float, np.float_)) else x)\n\n    print(comparison_df)\nelse:\n    print(\"\\nNo evaluation results to display in summary table.\")\n\nprint(\"\\nProject finished.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T21:16:20.979680Z","iopub.execute_input":"2025-05-27T21:16:20.979942Z","iopub.status.idle":"2025-05-27T21:16:21.007775Z","shell.execute_reply.started":"2025-05-27T21:16:20.979922Z","shell.execute_reply":"2025-05-27T21:16:21.007082Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd \n\n\ntry:\n    class_labels_for_table = [str(k) for k, v in sorted(validation_generator.class_indices.items(), key=lambda item: item[1])]\nexcept NameError:\n    print(\"Warning: validation_generator not in scope for class_labels_for_table. Using generic labels for plotting.\")\n    class_labels_for_table = [str(i) for i in range(NUM_CLASSES)] \n\nmodel_names_plot = list(evaluation_results_dict.keys())\naccuracies_plot = [evaluation_results_dict[m].get('accuracy', 0) for m in model_names_plot]\navg_specificities_plot = [evaluation_results_dict[m].get('average_specificity', 0) for m in model_names_plot]\n\nmacro_recalls_plot = []\n\nper_class_recalls_plot = {f\"Recall {label}\": [] for label in class_labels_for_table}\n\nfor model_n in model_names_plot:\n    res = evaluation_results_dict[model_n]\n    macro_recalls_plot.append(res.get('recalculated_macro_avg_recall', 0))\n    \n    recalls_for_model = res.get('recalculated_per_class_recall', [0.0] * NUM_CLASSES)\n    for i in range(NUM_CLASSES):\n        if i < len(class_labels_for_table):\n            class_label_name = class_labels_for_table[i]\n            key_name = f\"Recall {class_label_name}\"\n            if key_name in per_class_recalls_plot: \n                 per_class_recalls_plot[key_name].append(recalls_for_model[i])\n            else:\n                print(f\"Warning: Key '{key_name}' not found in per_class_recalls_plot. Skipping for model {model_n}, class index {i}.\")\n        else:\n             print(f\"Warning: Index {i} out of bounds for class_labels_for_table. Skipping for model {model_n}.\")\n\n\nmetrics_to_plot = {\n    'Accuracy': accuracies_plot,\n    'Avg. Specificity': avg_specificities_plot,\n    'Macro Recall (Sensitivity)': macro_recalls_plot\n}\n\ndf_general_metrics = pd.DataFrame(metrics_to_plot, index=model_names_plot)\n\ndf_general_metrics.plot(kind='bar', figsize=(15, 7), colormap='viridis')\nplt.title('General Performance Metrics Comparison per Model', fontsize=16)\nplt.ylabel('Score', fontsize=14)\nplt.xlabel('Model', fontsize=14)\nplt.xticks(rotation=0)\nplt.legend(title='Metric', bbox_to_anchor=(1.05, 1), loc='upper left')\nplt.grid(axis='y', linestyle='--')\nplt.tight_layout()\nplt.show()\n\n\n\nvalid_per_class_data = {k: v for k, v in per_class_recalls_plot.items() if len(v) == len(model_names_plot)}\nif not valid_per_class_data:\n    print(\"Warning: Not enough data to plot per-class recall. All lists might be empty or have inconsistent lengths.\")\nelse:\n    df_per_class_recall = pd.DataFrame(valid_per_class_data, index=model_names_plot)\n    # Sütun adlarını düzeltelim (Recall 0 -> 0)\n    df_per_class_recall.columns = [col.replace(\"Recall \", \"\") for col in df_per_class_recall.columns]\n\n    df_per_class_recall.plot(kind='bar', figsize=(18, 8), colormap='plasma')\n    plt.title('Per-Class Recall (Sensitivity) Comparison per Model', fontsize=16)\n    plt.ylabel('Recall (Sensitivity)', fontsize=14)\n    plt.xlabel('Model', fontsize=14)\n    plt.xticks(rotation=0)\n    plt.legend(title='Class', bbox_to_anchor=(1.05, 1), loc='upper left')\n    plt.grid(axis='y', linestyle='--')\n    plt.ylim(0, 1.05)\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T21:48:28.202277Z","iopub.execute_input":"2025-05-27T21:48:28.202942Z","iopub.status.idle":"2025-05-27T21:48:28.665094Z","shell.execute_reply.started":"2025-05-27T21:48:28.202922Z","shell.execute_reply":"2025-05-27T21:48:28.664447Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nif len(model_names_plot) > 0:\n    num_models = len(model_names_plot)\n    fig, axes = plt.subplots(1, num_models, figsize=(6 * num_models, 5))\n    if num_models == 1:\n        axes = [axes]\n        \n    for i, model_n in enumerate(model_names_plot):\n        cm = evaluation_results_dict[model_n].get('confusion_matrix_obj')\n        if cm is not None and isinstance(cm, np.ndarray):\n            sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=axes[i], \n                        xticklabels=class_labels_for_table, yticklabels=class_labels_for_table)\n            axes[i].set_title(f'{model_n}\\nConfusion Matrix', fontsize=14)\n            axes[i].set_xlabel('Predicted Label')\n            axes[i].set_ylabel('True Label')\n        else:\n            axes[i].text(0.5, 0.5, 'CM not available', ha='center', va='center')\n            axes[i].set_title(f'{model_n}\\nConfusion Matrix', fontsize=14)\n\n    plt.tight_layout()\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T21:50:11.698641Z","iopub.execute_input":"2025-05-27T21:50:11.699302Z","iopub.status.idle":"2025-05-27T21:50:12.441399Z","shell.execute_reply.started":"2025-05-27T21:50:11.699279Z","shell.execute_reply":"2025-05-27T21:50:12.440725Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nval_data_ordered = val_data_df.reset_index(drop=True)\n\nresults_df = pd.DataFrame({\n    'image_path': val_data_ordered['image_path'],\n    'true_label': val_data_ordered['diagnosis'],\n    'predicted_label': y_pred_classes_indices\n})\n\nimport cv2\nimport matplotlib.pyplot as plt\n\nunique_classes = results_df['true_label'].unique()\nunique_classes = sorted([int(cls) for cls in unique_classes])\n\nplt.figure(figsize=(15, 3 * len(unique_classes)))\n\nfor i, class_id in enumerate(unique_classes):\n    idx = results_df[results_df['true_label'].astype(int) == class_id].index[0]\n    image_path = results_df.iloc[idx]['image_path']\n    true_label = results_df.iloc[idx]['true_label']\n    pred_label = results_df.iloc[idx]['predicted_label']\n\n    img = cv2.imread(image_path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    \n    plt.subplot(len(unique_classes), 1, i+1)\n    plt.imshow(img)\n    plt.axis('off')\n    plt.title(f\"Image: {os.path.basename(image_path)} | True: {true_label} | Predicted: {pred_label}\", fontsize=14)\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-27T22:00:12.686498Z","iopub.execute_input":"2025-05-27T22:00:12.687052Z","iopub.status.idle":"2025-05-27T22:00:15.846108Z","shell.execute_reply.started":"2025-05-27T22:00:12.687030Z","shell.execute_reply":"2025-05-27T22:00:15.845353Z"}},"outputs":[],"execution_count":null}]}