Tri-Netra-AI / src /explain.py
anannyavyas1's picture
Upload folder using huggingface_hub
1cf3825 verified
Raw
History Blame Contribute Delete
5.47 kB
import argparse
import os
import sys
from pathlib import Path
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
root = Path(__file__).resolve().parents[1]
sys.path.append(str(root))
from src.data import get_datasets, prepare_dataset
from src.models import get_model
from src.utils import make_gradcam_heatmap, overlay_heatmap
def parse_args():
parser = argparse.ArgumentParser(description='Generate explainability outputs for brain tumor models')
parser.add_argument('--model', choices=['cnn', 'transfer', 'vit'], default='cnn')
parser.add_argument('--dataset', default='dataset')
parser.add_argument('--weights', required=True)
parser.add_argument('--batch_size', type=int, default=8)
parser.add_argument('--output', default='artifacts')
parser.add_argument('--examples', type=int, default=4)
return parser.parse_args()
def _get_default_conv_layer(model_type):
if model_type == 'cnn':
return 'conv_block_3'
if model_type == 'transfer':
return 'conv5_block3_out'
return None
def _get_sample_images(dataset, max_examples=4):
images = []
labels = []
for batch, (x, y) in enumerate(dataset):
for i in range(x.shape[0]):
if len(images) >= max_examples:
return np.array(images), np.array(labels)
images.append(x[i].numpy())
labels.append(int(y[i].numpy()))
if len(images) >= max_examples:
break
return np.array(images), np.array(labels)
def _plot_image(image, title, save_path):
plt.figure(figsize=(5, 5))
plt.imshow(image.astype('uint8'))
plt.title(title)
plt.axis('off')
plt.tight_layout()
plt.savefig(save_path)
plt.close()
def vit_patch_saliency(model, image, image_size=(224, 224)):
"""Saliency for the hybrid ResNet50+ViT classifier defined in src/models.py.
The hybrid model projects the ResNet50 feature map (7x7) into patch tokens via
a 1x1 Conv2D named 'hybrid_patch_projection', then reshapes to a sequence and
passes it through transformer blocks. We compute gradient-based saliency on
the patch token sequence (after position embedding) and reshape it back to
a 7x7 grid before resizing to the input resolution.
"""
try:
token_layer = model.get_layer('hybrid_patch_tokens')
except ValueError:
token_layer = model.get_layer('hybrid_patch_projection')
token_model = tf.keras.Model(inputs=model.inputs, outputs=token_layer.output)
image_batch = tf.expand_dims(image, axis=0)
with tf.GradientTape() as tape:
tokens = token_model(image_batch)
tape.watch(tokens)
predictions = model(image_batch)
loss = predictions[:, 0]
grads = tape.gradient(loss, tokens)
importance = tf.reduce_mean(tf.abs(grads * tokens), axis=-1)
importance = tf.squeeze(importance).numpy()
num_tokens = importance.shape[0] if importance.ndim == 1 else importance.size
side = int(round(num_tokens ** 0.5))
if side * side != num_tokens:
side = max(1, int(np.floor(num_tokens ** 0.5)))
importance = importance[: side * side]
importance = importance.reshape(side, side)
importance = importance / (importance.max() + 1e-8)
importance = tf.expand_dims(importance, axis=-1)
importance = tf.image.resize(importance, image_size, method='bilinear').numpy()
return np.squeeze(importance)
def explain_examples(model, model_type, images, labels, output_dir):
os.makedirs(output_dir, exist_ok=True)
conv_layer = _get_default_conv_layer(model_type)
for idx, (image, label) in enumerate(zip(images, labels)):
title = f'Label={label}'
sample_image = image.astype('uint8') if image.dtype != 'uint8' else image
_plot_image(sample_image, f'Input {idx} ({title})', os.path.join(output_dir, f'input_{idx}.png'))
if model_type in ['cnn', 'transfer']:
heatmap = make_gradcam_heatmap(tf.expand_dims(image, axis=0), model, conv_layer)
overlay = overlay_heatmap(sample_image, heatmap)
_plot_image(overlay, f'Grad-CAM {idx} ({title})', os.path.join(output_dir, f'gradcam_{idx}.png'))
if model_type == 'vit':
heatmap = vit_patch_saliency(model, image)
overlay = overlay_heatmap(sample_image, heatmap)
_plot_image(overlay, f'ViT Patch Saliency {idx} ({title})', os.path.join(output_dir, f'vit_saliency_{idx}.png'))
prediction = model.predict(tf.expand_dims(image, axis=0), verbose=0)[0][0]
print(f'Example {idx}: true={label}, score={prediction:.4f}')
def main():
args = parse_args()
train_ds, val_ds, test_ds = get_datasets(args.dataset, batch_size=args.batch_size)
eval_ds = test_ds if test_ds is not None else val_ds
if eval_ds is None:
raise ValueError('No validation or test dataset available for explanation.')
eval_ds = prepare_dataset(eval_ds)
model = get_model(args.model)
model.load_weights(args.weights)
images, labels = _get_sample_images(eval_ds, max_examples=args.examples)
explain_dir = os.path.join(args.output, args.model, 'explain')
explain_examples(model, args.model, images, labels, explain_dir)
print(f'Explainability outputs saved to {explain_dir}')
if __name__ == '__main__':
main()