| import math |
| from typing import NoReturn |
|
|
| import numpy as np |
| import matplotlib.pyplot as plt |
| import torch |
| from torchsummary import summary |
| from torchvision import transforms |
| from pytorch_grad_cam import GradCAM |
| from pytorch_grad_cam.utils.image import show_cam_on_image |
|
|
| from dataclasses import dataclass |
| from typing import NoReturn |
| import pandas as pd |
| import seaborn as sn |
| import torch |
| import torch.nn as nn |
| from torchvision import transforms |
| from sklearn.metrics import confusion_matrix |
|
|
|
|
| |
| def display_mnist_data_samples(dataset: 'DataLoader object', number_of_samples: int) -> NoReturn: |
| """ |
| Function to display samples for dataloader |
| :param dataset: Train or Test dataset transformed to Tensor |
| :param number_of_samples: Number of samples to be displayed |
| """ |
| |
| batch_data = [] |
| batch_label = [] |
| for count, item in enumerate(dataset): |
| if not count <= number_of_samples: |
| break |
| batch_data.append(item[0]) |
| batch_label.append(item[1]) |
|
|
| |
| fig = plt.figure() |
| x_count = 5 |
| y_count = 1 if number_of_samples <= 5 else math.floor(number_of_samples / x_count) |
|
|
| |
| for i in range(number_of_samples): |
| plt.subplot(y_count, x_count, i + 1) |
| plt.tight_layout() |
| plt.imshow(batch_data[i].squeeze(), cmap='gray') |
| plt.title(batch_label[i]) |
| plt.xticks([]) |
| plt.yticks([]) |
|
|
|
|
| def display_cifar_data_samples(data_set, number_of_samples: int, classes: list): |
| """ |
| Function to display samples for data_set |
| :param data_set: Train or Test data_set transformed to Tensor |
| :param number_of_samples: Number of samples to be displayed |
| :param classes: Name of classes to be displayed |
| """ |
| |
| batch_data = [] |
| batch_label = [] |
| for count, item in enumerate(data_set): |
| if not count <= number_of_samples: |
| break |
| batch_data.append(item[0]) |
| batch_label.append(item[1]) |
| batch_data = torch.stack(batch_data, dim=0).numpy() |
|
|
| |
| fig = plt.figure() |
| x_count = 5 |
| y_count = 1 if number_of_samples <= 5 else math.floor(number_of_samples / x_count) |
|
|
| for i in range(number_of_samples): |
| plt.subplot(y_count, x_count, i + 1) |
| plt.tight_layout() |
| plt.imshow(np.transpose(batch_data[i].squeeze(), (1, 2, 0))) |
| plt.title(classes[batch_label[i]]) |
| plt.xticks([]) |
| plt.yticks([]) |
|
|
|
|
| |
| def display_cifar_misclassified_data(data: list, |
| classes: list[str], |
| inv_normalize: transforms.Normalize, |
| number_of_samples: int = 10): |
| """ |
| Function to plot images with labels |
| :param data: List[Tuple(image, label)] |
| :param classes: Name of classes in the dataset |
| :param inv_normalize: Mean and Standard deviation values of the dataset |
| :param number_of_samples: Number of images to print |
| """ |
| fig = plt.figure(figsize=(10, 10)) |
|
|
| x_count = 5 |
| y_count = 1 if number_of_samples <= 5 else math.floor(number_of_samples / x_count) |
|
|
| for i in range(number_of_samples): |
| plt.subplot(y_count, x_count, i + 1) |
| img = data[i][0].squeeze().to('cpu') |
| img = inv_normalize(img) |
| plt.imshow(np.transpose(img, (1, 2, 0))) |
| plt.title(r"Correct: " + classes[data[i][1].item()] + '\n' + 'Output: ' + classes[data[i][2].item()]) |
| plt.xticks([]) |
| plt.yticks([]) |
|
|
|
|
| def display_mnist_misclassified_data(data: list, |
| number_of_samples: int = 10): |
| """ |
| Function to plot images with labels |
| :param data: List[Tuple(image, label)] |
| :param number_of_samples: Number of images to print |
| """ |
| fig = plt.figure(figsize=(8, 5)) |
|
|
| x_count = 5 |
| y_count = 1 if number_of_samples <= 5 else math.floor(number_of_samples / x_count) |
|
|
| for i in range(number_of_samples): |
| plt.subplot(y_count, x_count, i + 1) |
| img = data[i][0].squeeze(0).to('cpu') |
| plt.imshow(np.transpose(img, (1, 2, 0)), cmap='gray') |
| plt.title(r"Correct: " + str(data[i][1].item()) + '\n' + 'Output: ' + str(data[i][2].item())) |
| plt.xticks([]) |
| plt.yticks([]) |
|
|
|
|
| |
| def visualize_cifar_augmentation(data_set, data_transforms): |
| """ |
| Function to visualize the augmented data |
| :param data_set: Dataset without transformations |
| :param data_transforms: Dictionary of transforms |
| """ |
| sample, label = data_set[6] |
| total_augmentations = len(data_transforms) |
|
|
| fig = plt.figure(figsize=(10, 5)) |
| for count, (key, trans) in enumerate(data_transforms.items()): |
| if count == total_augmentations - 1: |
| break |
| plt.subplot(math.ceil(total_augmentations / 5), 5, count + 1) |
| augmented = trans(image=sample)['image'] |
| plt.imshow(augmented) |
| plt.title(key) |
| plt.xticks([]) |
| plt.yticks([]) |
|
|
|
|
| def visualize_mnist_augmentation(data_set, data_transforms): |
| """ |
| Function to visualize the augmented data |
| :param data_set: Dataset to visualize the augmentations |
| :param data_transforms: Dictionary of transforms |
| """ |
| sample, label = data_set[6] |
| total_augmentations = len(data_transforms) |
|
|
| fig = plt.figure(figsize=(10, 5)) |
| for count, (key, trans) in enumerate(data_transforms.items()): |
| if count == total_augmentations - 1: |
| break |
| plt.subplot(math.ceil(total_augmentations / 5), 5, count + 1) |
| img = trans(sample).to('cpu') |
| plt.imshow(np.transpose(img, (1, 2, 0)), cmap='gray') |
| plt.title(key) |
| plt.xticks([]) |
| plt.yticks([]) |
|
|
|
|
| |
| def display_loss_and_accuracies(train_losses: list, |
| train_acc: list, |
| test_losses: list, |
| test_acc: list, |
| plot_size: tuple = (10, 10)) -> NoReturn: |
| """ |
| Function to display training and test information(losses and accuracies) |
| :param train_losses: List containing training loss of each epoch |
| :param train_acc: List containing training accuracy of each epoch |
| :param test_losses: List containing test loss of each epoch |
| :param test_acc: List containing test accuracy of each epoch |
| :param plot_size: Size of the plot |
| """ |
| |
| fig, axs = plt.subplots(2, 2, figsize=plot_size) |
|
|
| |
| axs[0, 0].plot(train_losses) |
| axs[0, 0].set_title("Training Loss") |
| axs[1, 0].plot(train_acc) |
| axs[1, 0].set_title("Training Accuracy") |
|
|
| |
| axs[0, 1].plot(test_losses) |
| axs[0, 1].set_title("Test Loss") |
| axs[1, 1].plot(test_acc) |
| axs[1, 1].set_title("Test Accuracy") |
|
|
|
|
| |
|
|
| @dataclass |
| class ConvLayerInfo: |
| """ |
| Data Class to store Conv layer's information |
| """ |
| layer_number: int |
| weights: torch.nn.parameter.Parameter |
| layer_info: torch.nn.modules.conv.Conv2d |
|
|
|
|
| class FeatureMapVisualizer: |
| """ |
| Class to visualize Feature Map of the Layers |
| """ |
|
|
| def __init__(self, model): |
| """ |
| Contructor |
| :param model: Model Architecture |
| """ |
| self.conv_layers = [] |
| self.outputs = [] |
| self.layerwise_kernels = None |
|
|
| |
| counter = 0 |
| model_children = model.children() |
| for children in model_children: |
| if type(children) == nn.Sequential: |
| for child in children: |
| if type(child) == nn.Conv2d: |
| counter += 1 |
| self.conv_layers.append(ConvLayerInfo(layer_number=counter, |
| weights=child.weight, |
| layer_info=child) |
| ) |
|
|
| def get_model_weights(self): |
| """ |
| Method to get the model weights |
| """ |
| model_weights = [layer.weights for layer in self.conv_layers] |
| return model_weights |
|
|
| def get_conv_layers(self): |
| """ |
| Get the convolution layers |
| """ |
| conv_layers = [layer.layer_info for layer in self.conv_layers] |
| return conv_layers |
|
|
| def get_total_conv_layers(self) -> int: |
| """ |
| Get total number of convolution layers |
| """ |
| out = self.get_conv_layers() |
| return len(out) |
|
|
| def feature_maps_of_all_kernels(self, image: torch.Tensor) -> dict: |
| """ |
| Get feature maps from all the kernels of all the layers |
| :param image: Image to be passed to the network |
| """ |
| image = image.unsqueeze(0) |
| image = image.to('cpu') |
|
|
| outputs = {} |
|
|
| layers = self.get_conv_layers() |
| for index, layer in enumerate(layers): |
| image = layer(image) |
| outputs[str(layer)] = image |
| self.outputs = outputs |
| return outputs |
|
|
| def visualize_feature_map_of_kernel(self, image: torch.Tensor, kernel_number: int) -> None: |
| """ |
| Function to visualize feature map of kernel number from each layer |
| :param image: Image passed to the network |
| :param kernel_number: Number of kernel in each layer (Should be less than or equal to the minimum number of kernel in the network) |
| """ |
| |
| processed = [] |
|
|
| |
| outputs = self.feature_maps_of_all_kernels(image) |
|
|
| |
| for feature_map in outputs.values(): |
| try: |
| feature_map = feature_map[0][kernel_number] |
| except IndexError: |
| print("Filter number should be less than the minimum number of channels in a network") |
| break |
| finally: |
| gray_scale = feature_map / feature_map.shape[0] |
| processed.append(gray_scale.data.numpy()) |
|
|
| |
| x_range = len(outputs) // 5 + 4 |
| fig = plt.figure(figsize=(10, 10)) |
| for i in range(len(processed)): |
| a = fig.add_subplot(x_range, 5, i + 1) |
| imgplot = plt.imshow(processed[i]) |
| a.axis("off") |
| title = f"{list(outputs.keys())[i].split('(')[0]}_l{i + 1}_k{kernel_number}" |
| a.set_title(title, fontsize=10) |
| return fig |
|
|
| def get_max_kernel_number(self): |
| """ |
| Function to get maximum number of kernels in the network (for a layer) |
| """ |
| layers = self.get_conv_layers() |
| channels = [layer.out_channels for layer in layers] |
| self.layerwise_kernels = channels |
| return max(channels) |
|
|
| def visualize_kernels_from_layer(self, layer_number: int): |
| """ |
| Visualize Kernels from a layer |
| :param layer_number: Number of layer from which kernels are to be visualized |
| """ |
| |
| self.get_max_kernel_number() |
|
|
| |
| layer_number = layer_number - 1 |
| _kernels = self.layerwise_kernels[layer_number] |
|
|
| grid = math.ceil(math.sqrt(_kernels)) |
|
|
| fig = plt.figure(figsize=(5, 4)) |
| model_weights = self.get_model_weights() |
| _layer_weights = model_weights[layer_number].cpu() |
| for i, filter in enumerate(_layer_weights): |
| plt.subplot(grid, grid, i + 1) |
| plt.imshow(filter[0, :, :].detach(), cmap='gray') |
| plt.axis('off') |
| |
| return fig |
|
|
|
|
| |
| def visualize_confusion_matrix(classes: list[str], device: str, model: 'DL Model', |
| test_loader: torch.utils.data.DataLoader): |
| """ |
| Function to generate and visualize confusion matrix |
| :param classes: List of class names |
| :param device: cuda/cpu |
| :param model: Model Architecture |
| :param test_loader: DataLoader for test set |
| """ |
| nb_classes = len(classes) |
| device = 'cuda' |
| cm = torch.zeros(nb_classes, nb_classes) |
|
|
| model.eval() |
| with torch.no_grad(): |
| for inputs, labels in test_loader: |
| inputs = inputs.to(device) |
| labels = labels.to(device) |
| model = model.to(device) |
|
|
| preds = model(inputs) |
| preds = preds.argmax(dim=1) |
|
|
| for t, p in zip(labels.view(-1), preds.view(-1)): |
| cm[t, p] = cm[t, p] + 1 |
|
|
| |
| labels = labels.to('cpu') |
| preds = preds.to('cpu') |
| cf_matrix = confusion_matrix(labels, preds) |
| df_cm = pd.DataFrame(cf_matrix / np.sum(cf_matrix, axis=1)[:, None], |
| index=[i for i in classes], |
| columns=[i for i in classes]) |
| plt.figure(figsize=(12, 7)) |
| sn.heatmap(df_cm, annot=True) |
|
|
| def get_summary(model: 'object of model architecture', input_size: tuple) -> NoReturn: |
| """ |
| Function to get the summary of the model architecture |
| :param model: Object of model architecture class |
| :param input_size: Input data shape (Channels, Height, Width) |
| """ |
| use_cuda = torch.cuda.is_available() |
| device = torch.device("cuda" if use_cuda else "cpu") |
| network = model.to(device) |
| summary(network, input_size=input_size) |
|
|
|
|
| def get_misclassified_data(model, device, test_loader): |
| """ |
| Function to run the model on test set and return misclassified images |
| :param model: Network Architecture |
| :param device: CPU/GPU |
| :param test_loader: DataLoader for test set |
| """ |
| |
| model.eval() |
|
|
| |
| misclassified_data = [] |
|
|
| |
| with torch.no_grad(): |
| |
| for data, target in test_loader: |
|
|
| |
| data, target = data.to(device), target.to(device) |
|
|
| |
| for image, label in zip(data, target): |
|
|
| |
| image = image.unsqueeze(0) |
|
|
| |
| output = model(image) |
|
|
| |
| pred = output.argmax(dim=1, keepdim=True) |
|
|
| |
| if pred != label: |
| misclassified_data.append((image, label, pred)) |
| return misclassified_data |
|
|
|
|
| |
| def get_mnist_statistics(data_set, data_set_type='Train'): |
| """ |
| Function to return the statistics of the training data |
| :param data_set: Training dataset |
| :param data_set_type: Type of dataset [Train/Test/Val] |
| """ |
| |
| train_data = data_set.train_data |
| train_data = data_set.transform(train_data.numpy()) |
|
|
| print(f'[{data_set_type}]') |
| print(' - Numpy Shape:', data_set.train_data.cpu().numpy().shape) |
| print(' - Tensor Shape:', data_set.train_data.size()) |
| print(' - min:', torch.min(train_data)) |
| print(' - max:', torch.max(train_data)) |
| print(' - mean:', torch.mean(train_data)) |
| print(' - std:', torch.std(train_data)) |
| print(' - var:', torch.var(train_data)) |
|
|
| dataiter = next(iter(data_set)) |
| images, labels = dataiter[0], dataiter[1] |
|
|
| print(images.shape) |
| print(labels) |
|
|
| |
| plt.imshow(images[0].numpy().squeeze(), cmap='gray') |
|
|
|
|
| def get_cifar_property(images, operation): |
| """ |
| Get the property on each channel of the CIFAR |
| :param images: Get the property value on the images |
| :param operation: Mean, std, Variance, etc |
| """ |
| param_r = eval('images[:, 0, :, :].' + operation + '()') |
| param_g = eval('images[:, 1, :, :].' + operation + '()') |
| param_b = eval('images[:, 2, :, :].' + operation + '()') |
| return param_r, param_g, param_b |
|
|
|
|
| def get_cifar_statistics(data_set, data_set_type='Train'): |
| """ |
| Function to get the statistical information of the CIFAR dataset |
| :param data_set: Training set of CIFAR |
| :param data_set_type: Training or Test data |
| """ |
| |
| images = [item[0] for item in data_set] |
| images = torch.stack(images, dim=0).numpy() |
|
|
| |
| mean_r, mean_g, mean_b = get_cifar_property(images, 'mean') |
|
|
| |
| std_r, std_g, std_b = get_cifar_property(images, 'std') |
|
|
| |
| min_r, min_g, min_b = get_cifar_property(images, 'min') |
|
|
| |
| max_r, max_g, max_b = get_cifar_property(images, 'max') |
|
|
| |
| var_r, var_g, var_b = get_cifar_property(images, 'var') |
|
|
| print(f'[{data_set_type}]') |
| print(f' - Total {data_set_type} Images: {len(data_set)}') |
| print(f' - Tensor Shape: {images[0].shape}') |
| print(f' - min: {min_r, min_g, min_b}') |
| print(f' - max: {max_r, max_g, max_b}') |
| print(f' - mean: {mean_r, mean_g, mean_b}') |
| print(f' - std: {std_r, std_g, std_b}') |
| print(f' - var: {var_r, var_g, var_b}') |
|
|
| |
| plt.imshow(np.transpose(images[1].squeeze(), (1, 2, 0))) |
|
|
|
|
| |
| def display_gradcam_output(data: list, |
| classes: list[str], |
| inv_normalize: transforms.Normalize, |
| model: 'DL Model', |
| target_layers: list['model_layer'], |
| targets=None, |
| number_of_samples: int = 10, |
| transparency: float = 0.60): |
| """ |
| Function to visualize GradCam output on the data |
| :param data: List[Tuple(image, label)] |
| :param classes: Name of classes in the dataset |
| :param inv_normalize: Mean and Standard deviation values of the dataset |
| :param model: Model architecture |
| :param target_layers: Layers on which GradCam should be executed |
| :param targets: Classes to be focused on for GradCam |
| :param number_of_samples: Number of images to print |
| :param transparency: Weight of Normal image when mixed with activations |
| """ |
| |
| fig = plt.figure(figsize=(10, 10)) |
| x_count = 5 |
| y_count = 1 if number_of_samples <= 5 else math.floor(number_of_samples / x_count) |
|
|
| |
| cam = GradCAM(model=model, target_layers=target_layers, use_cuda=True) |
|
|
| |
| for i in range(number_of_samples): |
| plt.subplot(y_count, x_count, i + 1) |
| input_tensor = data[i][0] |
|
|
| |
| grayscale_cam = cam(input_tensor=input_tensor, targets=targets) |
| grayscale_cam = grayscale_cam[0, :] |
|
|
| |
| img = input_tensor.squeeze(0).to('cpu') |
| img = inv_normalize(img) |
| rgb_img = np.transpose(img, (1, 2, 0)) |
| rgb_img = rgb_img.numpy() |
|
|
| |
| visualization = show_cam_on_image(rgb_img, grayscale_cam, use_rgb=True, image_weight=transparency) |
|
|
| |
| plt.imshow(visualization) |
| plt.title(r"Correct: " + classes[data[i][1].item()] + '\n' + 'Output: ' + classes[data[i][2].item()]) |
| plt.xticks([]) |
| plt.yticks([]) |
|
|