{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":10338,"databundleVersionId":862042,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"\"\"\"\nExplainable AI for Chest X-ray Classification - TensorFlow Implementation\nProject: Medical Image Classification with Multiple XAI Techniques\nDataset: RSNA Pneumonia Detection Challenge\n\nThis implementation includes:\n1. Data preprocessing and augmentation\n2. CNN model training (ResNet50 + Custom classifier)\n3. Multiple XAI techniques: Grad-CAM, Grad-CAM++, LIME, Integrated Gradients\n4. Quantitative evaluation with IoU for localization\n5. Visualization utilities\n\"\"\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T17:31:36.604172Z","iopub.execute_input":"2025-10-21T17:31:36.604695Z","iopub.status.idle":"2025-10-21T17:31:36.609771Z","shell.execute_reply.started":"2025-10-21T17:31:36.60467Z","shell.execute_reply":"2025-10-21T17:31:36.609033Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, confusion_matrix, roc_auc_score, roc_curve, accuracy_score\nimport cv2\nfrom PIL import Image\nimport os\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:04:23.384214Z","iopub.execute_input":"2025-10-25T07:04:23.384441Z","iopub.status.idle":"2025-10-25T07:04:25.590325Z","shell.execute_reply.started":"2025-10-25T07:04:23.384417Z","shell.execute_reply":"2025-10-25T07:04:25.589607Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# TensorFlow imports\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers, models, optimizers\nfrom tensorflow.keras.applications import ResNet50\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nimport tensorflow.keras.backend as K","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:04:33.238090Z","iopub.execute_input":"2025-10-25T07:04:33.238553Z","iopub.status.idle":"2025-10-25T07:04:44.993902Z","shell.execute_reply.started":"2025-10-25T07:04:33.238528Z","shell.execute_reply":"2025-10-25T07:04:44.993295Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Set random seeds for reproducibility\nnp.random.seed(42)\ntf.random.set_seed(42)\n\nprint(f\"TensorFlow Version: {tf.__version__}\")\nprint(f\"GPU Available: {tf.config.list_physical_devices('GPU')}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:05:05.574419Z","iopub.execute_input":"2025-10-25T07:05:05.574696Z","iopub.status.idle":"2025-10-25T07:05:05.579444Z","shell.execute_reply.started":"2025-10-25T07:05:05.574673Z","shell.execute_reply":"2025-10-25T07:05:05.578664Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Configuration\nclass Config:\n    # Paths - UPDATE THESE FOR YOUR KAGGLE ENVIRONMENT\n    DATA_DIR = '/kaggle/input/rsna-pneumonia-detection-challenge/'\n    TRAIN_DIR = '/kaggle/input/rsna-pneumonia-detection-challenge/stage_2_train_images/'\n    \n    # Model parameters\n    IMG_SIZE = 224\n    BATCH_SIZE = 32\n    NUM_EPOCHS = 20\n    LEARNING_RATE = 0.0001\n    NUM_CLASSES = 2  # Pneumonia vs Normal\n    \n    # Training\n    EARLY_STOPPING_PATIENCE = 5\n    MODEL_SAVE_PATH = 'best_chest_xray_model.h5'\n\nconfig = Config()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:05:09.046765Z","iopub.execute_input":"2025-10-25T07:05:09.047442Z","iopub.status.idle":"2025-10-25T07:05:09.051450Z","shell.execute_reply.started":"2025-10-25T07:05:09.047420Z","shell.execute_reply":"2025-10-25T07:05:09.050640Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Data Loading Functions \ndef load_and_preprocess_data():\n    \"\"\"Load and split the dataset\"\"\"\n    print(\"Loading dataset...\")\n    \n    # Load labels\n    train_labels = pd.read_csv(os.path.join(config.DATA_DIR, 'stage_2_train_labels.csv'))\n    \n    # For RSNA: Group by patientId and take max Target (has pneumonia or not)\n    df = train_labels.groupby('patientId')['Target'].max().reset_index()\n    \n    print(f\"Total samples: {len(df)}\")\n    print(f\"Pneumonia cases: {df['Target'].sum()}\")\n    print(f\"Normal cases: {(df['Target']==0).sum()}\")\n    \n    # Split dataset\n    train_df, val_df = train_test_split(\n        df, \n        test_size=0.2, \n        stratify=df['Target'], \n        random_state=42\n    )\n    \n    return train_df, val_df\n\ndef load_image(img_path, img_size=224):\n    \"\"\"Load and preprocess a single image\"\"\"\n    try:\n        # Try loading DICOM\n        import pydicom\n        dcm = pydicom.dcmread(img_path)\n        img = dcm.pixel_array\n        # Normalize to 0-255\n        img = ((img - img.min()) / (img.max() - img.min()) * 255).astype(np.uint8)\n        # Convert to RGB\n        img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)\n    except:\n        # Fallback for PNG/JPG\n        img_path_png = img_path.replace('.dcm', '.png')\n        if os.path.exists(img_path_png):\n            img = cv2.imread(img_path_png)\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        else:\n            img = np.zeros((img_size, img_size, 3), dtype=np.uint8)\n    \n    # Resize\n    img = cv2.resize(img, (img_size, img_size))\n    return img","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:05:18.905854Z","iopub.execute_input":"2025-10-25T07:05:18.906385Z","iopub.status.idle":"2025-10-25T07:05:18.913072Z","shell.execute_reply.started":"2025-10-25T07:05:18.906360Z","shell.execute_reply":"2025-10-25T07:05:18.912420Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Data Generators \nclass ChestXrayDataGenerator(keras.utils.Sequence):\n    \"\"\"Custom data generator for chest X-rays\"\"\"\n    \n    def __init__(self, dataframe, img_dir, batch_size=32, img_size=224, \n                 augment=False, shuffle=True):\n        self.df = dataframe.reset_index(drop=True)\n        self.img_dir = img_dir\n        self.batch_size = batch_size\n        self.img_size = img_size\n        self.augment = augment\n        self.shuffle = shuffle\n        self.indexes = np.arange(len(self.df))\n        self.on_epoch_end()\n        \n        # Data augmentation\n        if self.augment:\n            self.datagen = ImageDataGenerator(\n                rotation_range=10,\n                width_shift_range=0.1,\n                height_shift_range=0.1,\n                horizontal_flip=True,\n                brightness_range=[0.8, 1.2],\n                zoom_range=0.1,\n                fill_mode='nearest'\n            )\n    \n    def __len__(self):\n        return int(np.ceil(len(self.df) / self.batch_size))\n    \n    def __getitem__(self, index):\n        batch_indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]\n        batch_images = []\n        batch_labels = []\n        \n        for idx in batch_indexes:\n            img_id = self.df.iloc[idx]['patientId']\n            img_path = os.path.join(self.img_dir, f\"{img_id}.dcm\")\n            \n            # Load image\n            img = load_image(img_path, self.img_size)\n            \n            # Augmentation\n            if self.augment:\n                img = self.datagen.random_transform(img)\n            \n            # Normalize\n            img = img.astype(np.float32) / 255.0\n            \n            # Apply ImageNet normalization\n            img[..., 0] = (img[..., 0] - 0.485) / 0.229\n            img[..., 1] = (img[..., 1] - 0.456) / 0.224\n            img[..., 2] = (img[..., 2] - 0.406) / 0.225\n            \n            label = int(self.df.iloc[idx]['Target'])\n            \n            batch_images.append(img)\n            batch_labels.append(label)\n        \n        return np.array(batch_images), np.array(batch_labels)\n    \n    def on_epoch_end(self):\n        if self.shuffle:\n            np.random.shuffle(self.indexes)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:05:25.402162Z","iopub.execute_input":"2025-10-25T07:05:25.402429Z","iopub.status.idle":"2025-10-25T07:05:25.411255Z","shell.execute_reply.started":"2025-10-25T07:05:25.402410Z","shell.execute_reply":"2025-10-25T07:05:25.410453Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Model Architecture\ndef build_model(input_shape=(224, 224, 3), num_classes=2):\n    \"\"\"Build ResNet50-based classification model\"\"\"\n    \n    # Load pretrained ResNet50\n    base_model = ResNet50(\n        include_top=False,\n        weights='imagenet',\n        input_shape=input_shape,\n        pooling='avg'\n    )\n    \n    # Freeze base model initially\n    base_model.trainable = True\n    \n    # Build model\n    inputs = keras.Input(shape=input_shape)\n    x = base_model(inputs, training=False)\n    x = layers.Dropout(0.5)(x)\n    x = layers.Dense(512, activation='relu')(x)\n    x = layers.Dropout(0.3)(x)\n    outputs = layers.Dense(num_classes, activation='softmax')(x)\n    \n    model = keras.Model(inputs, outputs)\n    \n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:05:32.369166Z","iopub.execute_input":"2025-10-25T07:05:32.369440Z","iopub.status.idle":"2025-10-25T07:05:32.374592Z","shell.execute_reply.started":"2025-10-25T07:05:32.369418Z","shell.execute_reply":"2025-10-25T07:05:32.373833Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Training Functions\ndef train_model(model, train_gen, val_gen):\n    \"\"\"Train the model with callbacks\"\"\"\n    \n    # Compile model\n    model.compile(\n        optimizer=optimizers.Adam(learning_rate=config.LEARNING_RATE),\n        loss='sparse_categorical_crossentropy',\n        metrics=['accuracy']\n    )\n    \n    # Callbacks\n    callbacks = [\n        EarlyStopping(\n            monitor='val_loss',\n            patience=config.EARLY_STOPPING_PATIENCE,\n            restore_best_weights=True,\n            verbose=1\n        ),\n        ReduceLROnPlateau(\n            monitor='val_loss',\n            factor=0.5,\n            patience=3,\n            min_lr=1e-7,\n            verbose=1\n        ),\n        ModelCheckpoint(\n            config.MODEL_SAVE_PATH,\n            monitor='val_loss',\n            save_best_only=True,\n            verbose=1\n        )\n    ]\n    \n    # Train\n    history = model.fit(\n        train_gen,\n        validation_data=val_gen,\n        epochs=config.NUM_EPOCHS,\n        callbacks=callbacks,\n        verbose=1\n    )\n    \n    return history","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:05:40.764621Z","iopub.execute_input":"2025-10-25T07:05:40.764914Z","iopub.status.idle":"2025-10-25T07:05:40.770182Z","shell.execute_reply.started":"2025-10-25T07:05:40.764893Z","shell.execute_reply":"2025-10-25T07:05:40.769534Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# XAI: Grad-CAM Implementation\ndef get_gradcam_model(model):\n    \"\"\"Get model for Grad-CAM computation - using layer index\"\"\"\n    # Get the ResNet50 base model\n    base_model = model.layers[1]\n    \n    # Find last conv layer by going backwards\n    target_layer = None\n    for layer in reversed(base_model.layers):\n        if 'conv' in layer.name.lower() and not 'add' in layer.name.lower():\n            target_layer = layer\n            print(f\"Using layer: {layer.name}\")\n            break\n    \n    if target_layer is None:\n        # Fallback: use layer at index -2 (second to last)\n        target_layer = base_model.layers[-2]\n        print(f\"Using fallback layer: {target_layer.name}\")\n    \n    # Create grad model\n    grad_model = keras.Model(\n        inputs=model.input,\n        outputs=[target_layer.output, model.output]\n    )\n    return grad_model\n\ndef compute_gradcam(model, img_array, pred_index=None, layer_name=None):\n    \"\"\"Compute Grad-CAM heatmap\"\"\"\n    \n    grad_model = get_gradcam_model(model)\n    \n    # Compute gradient\n    with tf.GradientTape() as tape:\n        conv_outputs, predictions = grad_model(img_array)\n        if pred_index is None:\n            pred_index = tf.argmax(predictions[0])\n        class_channel = predictions[:, pred_index]\n    \n    # Compute gradients\n    grads = tape.gradient(class_channel, conv_outputs)\n    \n    # Global average pooling\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n    \n    # Weight the channels\n    conv_outputs = conv_outputs[0]\n    pooled_grads = pooled_grads[..., tf.newaxis, tf.newaxis]\n    heatmap = tf.reduce_sum(tf.multiply(pooled_grads, conv_outputs), axis=-1)\n    \n    # Normalize heatmap\n    heatmap = tf.maximum(heatmap, 0) / tf.math.reduce_max(heatmap)\n    return heatmap.numpy()\n\ndef compute_gradcam_plusplus(model, img_array, pred_index=None, layer_name=None):\n    \"\"\"Compute Grad-CAM++ heatmap\"\"\"\n    \n    grad_model = get_gradcam_model(model, layer_name)\n    \n    with tf.GradientTape() as tape1:\n        with tf.GradientTape() as tape2:\n            with tf.GradientTape() as tape3:\n                conv_outputs, predictions = grad_model(img_array)\n                if pred_index is None:\n                    pred_index = tf.argmax(predictions[0])\n                class_channel = predictions[:, pred_index]\n            \n            # First order gradients\n            grads = tape3.gradient(class_channel, conv_outputs)\n        \n        # Second order gradients\n        grads_2 = tape2.gradient(grads, conv_outputs)\n    \n    # Third order gradients\n    grads_3 = tape1.gradient(grads_2, conv_outputs)\n    \n    # Compute weights (alpha)\n    global_sum = tf.reduce_sum(conv_outputs, axis=(1, 2), keepdims=True)\n    alpha_denom = grads_2 * 2.0 + grads_3 * global_sum\n    alpha_denom = tf.where(alpha_denom != 0.0, alpha_denom, tf.ones_like(alpha_denom))\n    alphas = grads_2 / alpha_denom\n    \n    # Weight the gradients\n    weights = tf.maximum(grads, 0.0)\n    alpha_normalization = tf.reduce_sum(alphas * weights, axis=(1, 2), keepdims=True)\n    alpha_normalization = tf.where(alpha_normalization != 0.0, alpha_normalization, \n                                   tf.ones_like(alpha_normalization))\n    alphas = alphas / alpha_normalization\n    \n    # Deep linearization\n    deep_linearization = tf.maximum(grads, 0.0) * alphas\n    deep_linearization = tf.reduce_sum(deep_linearization, axis=(1, 2), keepdims=True)\n    \n    # Compute heatmap\n    heatmap = tf.reduce_sum(deep_linearization * conv_outputs, axis=-1)[0]\n    heatmap = tf.maximum(heatmap, 0)\n    heatmap = heatmap / tf.math.reduce_max(heatmap)\n    \n    return heatmap.numpy()\n\ndef overlay_gradcam(img, heatmap, alpha=0.5):\n    \"\"\"Overlay Grad-CAM heatmap on image\"\"\"\n    # Resize heatmap\n    heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))\n    \n    # Convert heatmap to RGB\n    heatmap = np.uint8(255 * heatmap)\n    heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)\n    heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)\n    \n    # Denormalize image\n    img_denorm = img.copy()\n    img_denorm[..., 0] = img_denorm[..., 0] * 0.229 + 0.485\n    img_denorm[..., 1] = img_denorm[..., 1] * 0.224 + 0.456\n    img_denorm[..., 2] = img_denorm[..., 2] * 0.225 + 0.406\n    img_denorm = np.clip(img_denorm * 255, 0, 255).astype(np.uint8)\n    \n    # Overlay\n    superimposed = cv2.addWeighted(img_denorm, 1-alpha, heatmap, alpha, 0)\n    return superimposed","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:05:51.546835Z","iopub.execute_input":"2025-10-25T07:05:51.547501Z","iopub.status.idle":"2025-10-25T07:05:51.560652Z","shell.execute_reply.started":"2025-10-25T07:05:51.547477Z","shell.execute_reply":"2025-10-25T07:05:51.559942Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# XAI: LIME Implementation\ndef apply_lime(model, image, num_samples=1000):\n    \"\"\"Apply LIME to explain predictions\"\"\"\n    try:\n        from lime import lime_image\n        from skimage.segmentation import mark_boundaries\n        \n        # Denormalize image\n        img_denorm = image.copy()\n        img_denorm[..., 0] = img_denorm[..., 0] * 0.229 + 0.485\n        img_denorm[..., 1] = img_denorm[..., 1] * 0.224 + 0.456\n        img_denorm[..., 2] = img_denorm[..., 2] * 0.225 + 0.406\n        img_denorm = np.clip(img_denorm, 0, 1)\n        \n        # Prediction function\n        def predict_fn(images):\n            # Normalize images\n            normalized = []\n            for img in images:\n                norm_img = img.copy()\n                norm_img[..., 0] = (norm_img[..., 0] - 0.485) / 0.229\n                norm_img[..., 1] = (norm_img[..., 1] - 0.456) / 0.224\n                norm_img[..., 2] = (norm_img[..., 2] - 0.406) / 0.225\n                normalized.append(norm_img)\n            \n            batch = np.array(normalized)\n            preds = model.predict(batch, verbose=0)\n            return preds\n        \n        # Create explainer\n        explainer = lime_image.LimeImageExplainer()\n        explanation = explainer.explain_instance(\n            img_denorm,\n            predict_fn,\n            top_labels=2,\n            hide_color=0,\n            num_samples=num_samples,\n            batch_size=32\n        )\n        \n        return explanation, img_denorm\n    \n    except ImportError:\n        print(\"LIME not installed. Install with: pip install lime\")\n        return None, None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:06:02.771237Z","iopub.execute_input":"2025-10-25T07:06:02.771902Z","iopub.status.idle":"2025-10-25T07:06:02.778386Z","shell.execute_reply.started":"2025-10-25T07:06:02.771877Z","shell.execute_reply":"2025-10-25T07:06:02.777618Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# XAI: Integrated Gradients \ndef compute_integrated_gradients(model, img_array, pred_index=None, steps=50):\n    \"\"\"Compute Integrated Gradients\"\"\"\n    \n    # Baseline (black image)\n    baseline = tf.zeros_like(img_array)\n    \n    # Generate interpolated images\n    alphas = tf.linspace(0.0, 1.0, steps + 1)\n    interpolated_images = baseline + alphas[:, tf.newaxis, tf.newaxis, tf.newaxis] * (img_array - baseline)\n    \n    # Compute gradients\n    with tf.GradientTape() as tape:\n        tape.watch(interpolated_images)\n        predictions = model(interpolated_images)\n        \n        if pred_index is None:\n            pred_index = tf.argmax(predictions[0])\n        \n        target_class = predictions[:, pred_index]\n    \n    gradients = tape.gradient(target_class, interpolated_images)\n    \n    # Approximate integral using trapezoidal rule\n    grads = (gradients[:-1] + gradients[1:]) / 2.0\n    avg_grads = tf.reduce_mean(grads, axis=0)\n    \n    # Integrated gradients\n    integrated_grads = (img_array - baseline) * avg_grads\n    \n    return integrated_grads[0].numpy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:06:10.836580Z","iopub.execute_input":"2025-10-25T07:06:10.837491Z","iopub.status.idle":"2025-10-25T07:06:10.845119Z","shell.execute_reply.started":"2025-10-25T07:06:10.837457Z","shell.execute_reply":"2025-10-25T07:06:10.844328Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Evaluation and Visualization \ndef evaluate_model(model, val_gen, val_df):\n    \"\"\"Evaluate model and return predictions\"\"\"\n    \n    predictions = []\n    true_labels = []\n    probabilities = []\n    \n    for i in range(len(val_gen)):\n        X_batch, y_batch = val_gen[i]\n        preds = model.predict(X_batch, verbose=0)\n        \n        predictions.extend(np.argmax(preds, axis=1))\n        true_labels.extend(y_batch)\n        probabilities.extend(preds[:, 1])  # Probability of pneumonia\n    \n    predictions = np.array(predictions)\n    true_labels = np.array(true_labels)\n    probabilities = np.array(probabilities)\n    \n    return predictions, true_labels, probabilities\n\ndef plot_training_history(history):\n    \"\"\"Plot training curves\"\"\"\n    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n    \n    # Loss\n    ax1.plot(history.history['loss'], label='Train Loss', marker='o')\n    ax1.plot(history.history['val_loss'], label='Val Loss', marker='s')\n    ax1.set_xlabel('Epoch')\n    ax1.set_ylabel('Loss')\n    ax1.set_title('Training and Validation Loss')\n    ax1.legend()\n    ax1.grid(True)\n    \n    # Accuracy\n    ax2.plot(history.history['accuracy'], label='Train Acc', marker='o')\n    ax2.plot(history.history['val_accuracy'], label='Val Acc', marker='s')\n    ax2.set_xlabel('Epoch')\n    ax2.set_ylabel('Accuracy')\n    ax2.set_title('Training and Validation Accuracy')\n    ax2.legend()\n    ax2.grid(True)\n    \n    plt.tight_layout()\n    plt.savefig('training_history.png', dpi=300, bbox_inches='tight')\n    plt.show()\n\ndef plot_confusion_matrix(y_true, y_pred):\n    \"\"\"Plot confusion matrix\"\"\"\n    cm = confusion_matrix(y_true, y_pred)\n    plt.figure(figsize=(8, 6))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n                xticklabels=['Normal', 'Pneumonia'],\n                yticklabels=['Normal', 'Pneumonia'])\n    plt.ylabel('True Label')\n    plt.xlabel('Predicted Label')\n    plt.title('Confusion Matrix')\n    plt.savefig('confusion_matrix.png', dpi=300, bbox_inches='tight')\n    plt.show()\n\ndef plot_roc_curve(y_true, y_probs):\n    \"\"\"Plot ROC curve\"\"\"\n    fpr, tpr, _ = roc_curve(y_true, y_probs)\n    auc = roc_auc_score(y_true, y_probs)\n    \n    plt.figure(figsize=(8, 6))\n    plt.plot(fpr, tpr, label=f'ROC Curve (AUC = {auc:.3f})', linewidth=2)\n    plt.plot([0, 1], [0, 1], 'k--', label='Random Classifier')\n    plt.xlabel('False Positive Rate')\n    plt.ylabel('True Positive Rate')\n    plt.title('ROC Curve')\n    plt.legend()\n    plt.grid(True)\n    plt.savefig('roc_curve.png', dpi=300, bbox_inches='tight')\n    plt.show()\n\ndef visualize_xai_methods(model, val_gen, val_df, num_samples=5):\n    \"\"\"Visualize multiple XAI techniques\"\"\"\n    \n    fig, axes = plt.subplots(num_samples, 5, figsize=(20, 4*num_samples))\n    \n    for i in range(num_samples):\n        # Get sample\n        X_batch, y_batch = val_gen[i]\n        img = X_batch[0:1]\n        label = y_batch[0]\n        \n        # Prediction\n        pred = model.predict(img, verbose=0)\n        pred_class = np.argmax(pred[0])\n        confidence = pred[0][pred_class]\n        \n        # Denormalize for display\n        img_display = img[0].copy()\n        img_display[..., 0] = img_display[..., 0] * 0.229 + 0.485\n        img_display[..., 1] = img_display[..., 1] * 0.224 + 0.456\n        img_display[..., 2] = img_display[..., 2] * 0.225 + 0.406\n        img_display = np.clip(img_display, 0, 1)\n        \n        # Original Image\n        axes[i, 0].imshow(img_display)\n        axes[i, 0].set_title(f'Original\\nTrue: {\"Pneumonia\" if label==1 else \"Normal\"}\\n'\n                            f'Pred: {\"Pneumonia\" if pred_class==1 else \"Normal\"} ({confidence:.2f})')\n        axes[i, 0].axis('off')\n        \n        # Grad-CAM\n        gradcam_heatmap = compute_gradcam(model, img, pred_index=pred_class)\n        gradcam_overlay = overlay_gradcam(img[0], gradcam_heatmap)\n        axes[i, 1].imshow(gradcam_overlay)\n        axes[i, 1].set_title('Grad-CAM')\n        axes[i, 1].axis('off')\n        \n        # Grad-CAM++\n        gradcam_pp_heatmap = compute_gradcam_plusplus(model, img, pred_index=pred_class)\n        gradcam_pp_overlay = overlay_gradcam(img[0], gradcam_pp_heatmap)\n        axes[i, 2].imshow(gradcam_pp_overlay)\n        axes[i, 2].set_title('Grad-CAM++')\n        axes[i, 2].axis('off')\n        \n        # Integrated Gradients\n        ig = compute_integrated_gradients(model, img, pred_index=pred_class)\n        ig_magnitude = np.sum(np.abs(ig), axis=-1)\n        ig_normalized = (ig_magnitude - ig_magnitude.min()) / (ig_magnitude.max() - ig_magnitude.min() + 1e-8)\n        axes[i, 3].imshow(img_display)\n        axes[i, 3].imshow(ig_normalized, cmap='hot', alpha=0.5)\n        axes[i, 3].set_title('Integrated Gradients')\n        axes[i, 3].axis('off')\n        \n        # Heatmap only\n        axes[i, 4].imshow(gradcam_heatmap, cmap='jet')\n        axes[i, 4].set_title('Attention Heatmap')\n        axes[i, 4].axis('off')\n    \n    plt.tight_layout()\n    plt.savefig('xai_visualizations.png', dpi=300, bbox_inches='tight')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:06:21.220020Z","iopub.execute_input":"2025-10-25T07:06:21.220314Z","iopub.status.idle":"2025-10-25T07:06:21.456957Z","shell.execute_reply.started":"2025-10-25T07:06:21.220292Z","shell.execute_reply":"2025-10-25T07:06:21.456183Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Main Execution\n\nprint(\"=\"*60)\nprint(\"Explainable AI for Chest X-ray Classification - TensorFlow\")\nprint(\"=\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:06:30.923178Z","iopub.execute_input":"2025-10-25T07:06:30.923419Z","iopub.status.idle":"2025-10-25T07:06:30.927524Z","shell.execute_reply.started":"2025-10-25T07:06:30.923402Z","shell.execute_reply":"2025-10-25T07:06:30.926901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load data\ntrain_df, val_df = load_and_preprocess_data()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:06:40.385982Z","iopub.execute_input":"2025-10-25T07:06:40.386603Z","iopub.status.idle":"2025-10-25T07:06:40.589973Z","shell.execute_reply.started":"2025-10-25T07:06:40.386581Z","shell.execute_reply":"2025-10-25T07:06:40.589198Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create data generators\ntrain_gen = ChestXrayDataGenerator(\n    train_df, \n    config.TRAIN_DIR,\n    batch_size=config.BATCH_SIZE,\n    img_size=config.IMG_SIZE,\n    augment=True,\n    shuffle=True\n)\n    \nval_gen = ChestXrayDataGenerator(\n    val_df,\n    config.TRAIN_DIR,\n    batch_size=config.BATCH_SIZE,\n    img_size=config.IMG_SIZE,\n    augment=False,\n    shuffle=False\n)\n    \nprint(f\"\\nTrain batches: {len(train_gen)}\")\nprint(f\"Validation batches: {len(val_gen)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:06:51.441206Z","iopub.execute_input":"2025-10-25T07:06:51.441893Z","iopub.status.idle":"2025-10-25T07:06:51.448439Z","shell.execute_reply.started":"2025-10-25T07:06:51.441868Z","shell.execute_reply":"2025-10-25T07:06:51.447724Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Build model\nmodel = build_model(\n    input_shape=(config.IMG_SIZE, config.IMG_SIZE, 3),\n    num_classes=config.NUM_CLASSES\n)\n    \nprint(\"\\nModel Summary:\")\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:07:00.055651Z","iopub.execute_input":"2025-10-25T07:07:00.056373Z","iopub.status.idle":"2025-10-25T07:07:03.159575Z","shell.execute_reply.started":"2025-10-25T07:07:00.056348Z","shell.execute_reply":"2025-10-25T07:07:03.159026Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train model\nprint(\"\\nStarting training...\")\nhistory = train_model(model, train_gen, val_gen)\n    \n# Load best model\nmodel = keras.models.load_model(config.MODEL_SAVE_PATH)\nprint(\"\\nBest model loaded!\")\n    \n# Plot training history\nplot_training_history(history)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T07:07:15.292249Z","iopub.execute_input":"2025-10-25T07:07:15.292816Z","iopub.status.idle":"2025-10-25T09:09:28.006960Z","shell.execute_reply.started":"2025-10-25T07:07:15.292790Z","shell.execute_reply":"2025-10-25T09:09:28.006211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Evaluate model\nprint(\"\\nEvaluating model on validation set...\")\npredictions, true_labels, probabilities = evaluate_model(model, val_gen, val_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-25T09:52:08.973714Z","iopub.execute_input":"2025-10-25T09:52:08.974241Z","iopub.status.idle":"2025-10-25T09:53:52.410294Z","shell.execute_reply.started":"2025-10-25T09:52:08.974218Z","shell.execute_reply":"2025-10-25T09:53:52.409404Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Metrics\naccuracy = accuracy_score(true_labels, predictions)\nauc = roc_auc_score(true_labels, probabilities)\n\nprint(f\"\\nValidation Accuracy: {accuracy*100:.2f}%\")\nprint(f\"AUC-ROC: {auc:.4f}\")\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T19:31:08.682557Z","iopub.execute_input":"2025-10-21T19:31:08.682791Z","iopub.status.idle":"2025-10-21T19:31:08.692332Z","shell.execute_reply.started":"2025-10-21T19:31:08.682773Z","shell.execute_reply":"2025-10-21T19:31:08.69168Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Classification report\nprint(\"\\nClassification Report:\")\nprint(classification_report(true_labels, predictions,\n                            target_names=['Normal', 'Pneumonia']))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T19:31:08.693251Z","iopub.execute_input":"2025-10-21T19:31:08.693499Z","iopub.status.idle":"2025-10-21T19:31:08.715388Z","shell.execute_reply.started":"2025-10-21T19:31:08.693479Z","shell.execute_reply":"2025-10-21T19:31:08.714589Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot metrics\nplot_confusion_matrix(true_labels, predictions)\nplot_roc_curve(true_labels, probabilities)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T19:36:51.758665Z","iopub.execute_input":"2025-10-21T19:36:51.75896Z","iopub.status.idle":"2025-10-21T19:36:52.800106Z","shell.execute_reply.started":"2025-10-21T19:36:51.758939Z","shell.execute_reply":"2025-10-21T19:36:52.799277Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# First, let's check what layers are available in your ResNet50\nbase_model = model.layers[1]  # This should be your ResNet50\nprint(\"Available conv layers in ResNet50:\")\nfor layer in base_model.layers:\n    if hasattr(layer, 'output'):\n        try:\n            if len(layer.output.shape) == 4:\n                print(f\"  - {layer.name}: {layer.output.shape}\")\n        except:\n            pass","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T19:45:33.840187Z","iopub.execute_input":"2025-10-21T19:45:33.840849Z","iopub.status.idle":"2025-10-21T19:45:33.847575Z","shell.execute_reply.started":"2025-10-21T19:45:33.840825Z","shell.execute_reply":"2025-10-21T19:45:33.84678Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Visualize XAI\nprint(\"\\nGenerating XAI visualizations...\")\nvisualize_xai_methods(model, val_gen, val_df, num_samples=5)\n    \nprint(\"\\n\" + \"=\"*60)\nprint(\"Training and evaluation complete!\")\nprint(\"=\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T19:52:28.093117Z","iopub.execute_input":"2025-10-21T19:52:28.093843Z","iopub.status.idle":"2025-10-21T19:52:31.52502Z","shell.execute_reply.started":"2025-10-21T19:52:28.093818Z","shell.execute_reply":"2025-10-21T19:52:31.523836Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Main Execution\n# def main():\n#     print(\"=\"*60)\n#     print(\"Explainable AI for Chest X-ray Classification - TensorFlow\")\n#     print(\"=\"*60)\n    \n#     # Load data\n#     train_df, val_df = load_and_preprocess_data()\n    \n#     # Create data generators\n#     train_gen = ChestXrayDataGenerator(\n    #     train_df, \n    #     config.TRAIN_DIR,\n    #     batch_size=config.BATCH_SIZE,\n    #     img_size=config.IMG_SIZE,\n    #     augment=True,\n    #     shuffle=True\n    # )\n    \n    # val_gen = ChestXrayDataGenerator(\n    #     val_df,\n    #     config.TRAIN_DIR,\n    #     batch_size=config.BATCH_SIZE,\n    #     img_size=config.IMG_SIZE,\n    #     augment=False,\n    #     shuffle=False\n    # )\n    \n    # print(f\"\\nTrain batches: {len(train_gen)}\")\n    # print(f\"Validation batches: {len(val_gen)}\")\n    \n    # # Build model\n    # model = build_model(\n    #     input_shape=(config.IMG_SIZE, config.IMG_SIZE, 3),\n    #     num_classes=config.NUM_CLASSES\n    # )\n    \n    # print(\"\\nModel Summary:\")\n    # model.summary()\n    \n    # # Train model\n    # print(\"\\nStarting training...\")\n    # history = train_model(model, train_gen, val_gen)\n    \n    # # Load best model\n    # model = keras.models.load_model(config.MODEL_SAVE_PATH)\n    # print(\"\\nBest model loaded!\")\n    \n#     # Plot training history\n#     plot_training_history(history)\n    \n#     # Evaluate model\n#     print(\"\\nEvaluating model on validation set...\")\n#     predictions, true_labels, probabilities = evaluate_model(model, val_gen, val_df)\n    \n#     # Metrics\n#     accuracy = accuracy_score(true_labels, predictions)\n#     auc = roc_auc_score(true_labels, probabilities)\n    \n#     print(f\"\\nValidation Accuracy: {accuracy*100:.2f}%\")\n#     print(f\"AUC-ROC: {auc:.4f}\")\n    \n#     # Classification report\n#     print(\"\\nClassification Report:\")\n#     print(classification_report(true_labels, predictions,\n#                                 target_names=['Normal', 'Pneumonia']))\n    \n#     # Plot metrics\n#     plot_confusion_matrix(true_labels, predictions)\n#     plot_roc_curve(true_labels, probabilities)\n    \n#     # Visualize XAI\n#     print(\"\\nGenerating XAI visualizations...\")\n#     visualize_xai_methods(model, val_gen, val_df, num_samples=5)\n    \n#     print(\"\\n\" + \"=\"*60)\n#     print(\"Training and evaluation complete!\")\n#     print(\"=\"*60)\n\n# if __name__ == \"__main__\":\n#     main()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-21T19:45:54.055845Z","iopub.execute_input":"2025-10-21T19:45:54.056171Z","iopub.status.idle":"2025-10-21T19:45:54.060942Z","shell.execute_reply.started":"2025-10-21T19:45:54.056136Z","shell.execute_reply":"2025-10-21T19:45:54.0601Z"}},"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":"","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}]}