diff --git a/.gitignore b/.gitignore index b6e4761..522fa88 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +# IntelliJ project files +.idea + # Distribution / packaging .Python build/ diff --git a/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_1.jpg b/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_1.jpg new file mode 100644 index 0000000..55f8b8b Binary files /dev/null and b/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_1.jpg differ diff --git a/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_1.py b/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_1.py new file mode 100644 index 0000000..bcede65 --- /dev/null +++ b/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_1.py @@ -0,0 +1,77 @@ +### Source from +### https://towardsdatascience.com/saliency-map-using-pytorch-68270fe45e80 + +import torch +import torch.nn as nn +import torchvision +from torchvision import models +import matplotlib.pyplot as plt + +# Initialize the model +#model = torch.load('.pth') +model = torchvision.models.resnet18(pretrained=False) + + +# Set the model to run on the GPU +device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") +model = model.to(device) + +# Set the model on Eval Mode +model.eval() + +from torchvision import transforms +from PIL import Image + +# Open the image file +image = Image.open('Vanilla_backprop_1.jpg') + +# Set up the transformations +transform_ = transforms.Compose([ + transforms.Resize(256), + transforms.CenterCrop(224),transforms.ToTensor(), + transforms.Normalize(mean=[0., 0., 0.], std=[1., 1., 1.]) +]) + +# Transforms the image +image = transform_(image) + +image = image.reshape(1, 3, 224, 224) + +image = image.to(device) + +image.requires_grad_() + +# Retrieve output from the image +output = model(image) +print(output.shape) + +# Catch the output +output_idx = output.argmax() +print(output_idx) + +output_max = output[0, output_idx] +print(output_max) + +# Do backpropagation to get the derivative of the output based on the image +output_max.backward() + +# Retireve the saliency map and also pick the maximum value from channels on each pixel. +# In this case, we look at dim=1. Recall the shape (batch_size, channel, width, height) + +saliency, _ = torch.max(image.grad.abs(), dim=1) +print(saliency.shape) + +saliency = saliency.reshape(224, 224) + +# Reshape the image +image = image.reshape(-1, 224, 224) + +# Visualize the image and the saliency map +fig, ax = plt.subplots(1, 2) +ax[0].imshow(image.cpu().detach().numpy().transpose(1, 2, 0)) +ax[0].axis('off') +ax[1].imshow(saliency.cpu(), cmap='hot') +ax[1].axis('off') +plt.tight_layout() +fig.suptitle('The Image and Its Saliency Map') +plt.show() \ No newline at end of file diff --git a/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_2.jpg b/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_2.jpg new file mode 100644 index 0000000..aee1168 Binary files /dev/null and b/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_2.jpg differ diff --git a/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_2.py b/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_2.py new file mode 100644 index 0000000..44c1af2 --- /dev/null +++ b/Interpretable_AI/Vanilla_gradient/Vanilla_backprop_2.py @@ -0,0 +1,74 @@ +### Source from +### https://medium.datadriveninvestor.com/visualizing-neural-networks-using-saliency-maps-in-pytorch-289d8e244ab4 + +import torch +import torchvision +import torchvision.transforms as T +import matplotlib.pyplot as plt +import requests +from PIL import Image + +#Using VGG# -19 pretrained model for image classification +model = torchvision.models.vgg19(pretrained=True) +for param in model.parameters(): + param.requires_grad = False + +def download(url, fname): + response = requests.get(url) + with open(fname, "wb") as f: + f.write(response.content) + +# Downloading the image +download("https://specials-images.forbesimg.com/imageserve/5db4c7b464b49a0007e9dfac/960x0.jpg?fit=scale", "Vanilla_backprop_2.jpg") + +# Opening the image +img = Image.open('Vanilla_backprop_2.jpg') + +# Preprocess the image +def preprocess(image, size=224): + transform = T.Compose([ + T.Resize((size, size)), + T.ToTensor(), + T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), + T.Lambda(lambda x: x[None]), + ]) + return transform(image) + +# preprocess the image +X = preprocess(img) + +# we would run the model in evaluation mode +model.eval() + +# we need to find the gradient with respect to the input image, so we need to call requires_grad_on it +X.requires_grad_() + +''' +forward pass through the model to get the scores, note that VGG-19 model doesn't perform softmax at the end +and we also don't need softmax, we need scores, so that's perfect for us. +''' + +scores = model(X) + +# Get the index corresponding to the maximum score and the maximum score itself. +score_max_index = scores.argmax() +score_max = scores[0, score_max_index] + +''' +backward function on score_max performs the backward pass in the computation graph and calculates the gradient of +score_max with respect to nodes in the computation graph +''' +score_max.backward() + +''' +Saliency would be the gradient with respect to the input image now. But note that the input image has 3 channels, +R, G and B. To derive a single class saliency value for each pixel (i, j), we take the maximum magnitude +across all colour channels. +''' +saliency, _ = torch.max(X.grad.data.abs(), dim=1) +print(saliency[0].shape) + +# code to plot the saliency map as a heatmap +plt.imshow(saliency[0], cmap=plt.cm.hot) +plt.axis('off') +plt.show() \ No newline at end of file diff --git a/Interpretable_AI/pytorch_test/pytorch_cnn.py b/Interpretable_AI/pytorch_test/pytorch_cnn.py new file mode 100644 index 0000000..eabb9b7 --- /dev/null +++ b/Interpretable_AI/pytorch_test/pytorch_cnn.py @@ -0,0 +1,79 @@ +import torch +from functools import partial +from sklearn.datasets import make_classification +from torch.utils.data import Dataset +from torch.utils.data import DataLoader + +X, y = make_classification(n_samples=100, n_features=5, random_state=42) + +class CustomDataset(Dataset): + def __init__(self, X, y, transform=None): + self.X = X + self.y = y + self.transform = transform + + def __len__(self): + return len(self.X) + + def __getitem__(self, idx): + x = X[idx] + label = y[idx] + + if self.transform: + x = self.transform(x) + label = self.transform(label) + + return x, label + +torch_tensor_float32 = partial(torch.tensor, dtype=torch.float32) +transformed_dataset = CustomDataset(X, y, transform=torch_tensor_float32) + +dataloader = DataLoader(transformed_dataset, batch_size=4, shuffle=True) + +num_features = 5 +num_outputs = 1 +layer_dims = [num_features, 5, 3, num_outputs] + +# Model Definition +model = torch.nn.Sequential( + torch.nn.Linear(5, 5), + torch.nn.ReLU(), + torch.nn.Linear(5, 3), + torch.nn.ReLU(), + torch.nn.Linear(3, 1), + torch.nn.Sigmoid() +) + +class BinaryClassifier(torch.nn.Sequential): + def __init__(self, layer_dims): + super(BinaryClassifier, self).__init__() + + for idx, dim in enumerate(layer_dims): + if idx < len(layer_dims) - 1: + module = torch.nn.Linear(dim, layer_dims[idx + 1]) + self.add_module(f"linear{idx}", module) + if idx < len(layer_dims) - 2: + activation = torch.nn.ReLU() + self.add_module(f"relu{idx}", activation) + elif idx == len(layer_dims) - 2: + activation = torch.nn.Sigmoid() + self.add_module(f"sigmoid{idx}", activation) + +bc_model = BinaryClassifier(layer_dims) + +criterion = torch.nn.BCELoss() +optimizer = torch.optim.Adam(bc_model.parameters()) + +num_epochs = 10 + +for epoch in range(num_epochs): + for idx, (X_batch, labels) in enumerate(dataloader): + optimizer.zero_grad() + outputs = bc_model(X_batch) + outputs = torch.flatten(outputs) + loss = criterion(outputs, labels.float()) + loss.backward() + optimizer.step() + +pred_var = bc_model(transformed_dataset[0][0]) +print(pred_var.detach().numpy()[0]) \ No newline at end of file