Skip to content

Instantly share code, notes, and snippets.

@MFreidank
Created October 23, 2018 16:42
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MFreidank/c61f5c762ed9311c5083a04c826396c9 to your computer and use it in GitHub Desktop.
Save MFreidank/c61f5c762ed9311c5083a04c826396c9 to your computer and use it in GitHub Desktop.
Torch Guided Backprop -- ResNet Compatible version
"""
Created on Thu Oct 26 11:23:47 2017
Original Author:
@author: Utku Ozbulak - github.com/utkuozbulak
Changes for ResNet Compatibility:
Moritz Freidank - github.com/MFreidank
"""
import torch
from torch.nn import ReLU
from misc_functions import (get_params,
convert_to_grayscale,
save_gradient_images,
get_positive_negative_saliency)
class GuidedBackprop():
"""
Produces gradients generated with guided back propagation from the given image
"""
def __init__(self, model):
self.model = model
self.gradients = None
# Put model in evaluation mode
self.model.eval()
self.update_relus()
self.hook_layers()
def hook_layers(self):
def hook_function(module, grad_in, grad_out):
self.gradients = grad_in[0]
# Register hook to the first layer
first_layer = list(self.model.children())[0]
first_layer.register_backward_hook(hook_function)
def update_relus(self):
"""
Updates relu activation functions so that it only returns positive gradients
"""
def relu_hook_function(module, grad_in, grad_out):
"""
If there is a negative gradient, changes it to zero
"""
if isinstance(module, ReLU):
return (torch.clamp(grad_in[0], min=0.0),)
# Loop through layers, hook up ReLUs with relu_hook_function
for module in self.model.modules():
if isinstance(module, ReLU):
module.register_backward_hook(relu_hook_function)
def generate_gradients(self, input_image, target_class):
# Forward pass
model_output = self.model(input_image)
# Zero gradients
self.model.zero_grad()
# Target for backprop
one_hot_output = torch.FloatTensor(1, model_output.size()[-1]).zero_()
one_hot_output[0][target_class] = 1
# Backward pass
model_output.backward(gradient=one_hot_output)
# Convert Pytorch variable to numpy array
# [0] to get rid of the first channel (1,3,224,224)
gradients_as_arr = self.gradients.data.numpy()[0]
return gradients_as_arr
if __name__ == '__main__':
target_example = 0 # Snake
(original_image, prep_img, target_class, file_name_to_export, pretrained_model) =\
get_params(target_example)
# Guided backprop
GBP = GuidedBackprop(pretrained_model)
# Get gradients
guided_grads = GBP.generate_gradients(prep_img, target_class)
# Save colored gradients
save_gradient_images(guided_grads, file_name_to_export + '_Guided_BP_color')
# Convert to grayscale
grayscale_guided_grads = convert_to_grayscale(guided_grads)
# Save grayscale gradients
save_gradient_images(grayscale_guided_grads, file_name_to_export + '_Guided_BP_gray')
# Positive and negative saliency maps
pos_sal, neg_sal = get_positive_negative_saliency(guided_grads)
save_gradient_images(pos_sal, file_name_to_export + '_pos_sal')
save_gradient_images(neg_sal, file_name_to_export + '_neg_sal')
print('Guided backprop completed')
@MFreidank
Copy link
Author

This contains two minor changes in lines 36 and 50 to make guided_backprop.py from utkuozbulak/pytorch-cnn-visualizations#32 support resnet18 (and above) models. The snake given as example in the original repository looks like this when visualized:

snake_guided_bp_color
snake_guided_bp_gray
snake_neg_sal
snake_pos_sal

@adamxyang
Copy link

Hi, thanks for the changes. I tried with ResNet34 but I got this error:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-19-abab40b06bf1> in <module>
----> 1 guided_grads = GBP.generate_gradients(x, y)
      2 guided_grads.shape

/mnt/sdh/adam/test/guided_backprop.py in generate_gradients(self, input_image, target_class)
    102         # Convert Pytorch variable to numpy array
    103         # [0] to get rid of the first channel (1,3,224,224)
--> 104         gradients_as_arr = self.gradients.data.numpy()[0]
    105         return gradients_as_arr

AttributeError: 'NoneType' object has no attribute 'data'

Seems gradients wasn't updated when calling functions in the class. Have you encountered this issue with ResNet?

@surekhag28
Copy link

Yes even I am getting the same error

@ludwigfriborg
Copy link

You can solve the NoneType issue by running input_image.requires_grad_(True) before passing the input image through the model in generate_gradients

@hamedbehzadi
Copy link

Hi
I would like to run this code. However, I got error for missing some files.
Could you please share the files
get_params,
convert_to_grayscale,
save_gradient_images,
get_positive_negative_saliency.

Thank you

@MFreidank
Copy link
Author

@hamedbehzadi see repository linked to above. The gist is not stand-alone but a patch for that repository.
The file you are missing is: https://github.com/utkuozbulak/pytorch-cnn-visualizations/blob/master/src/misc_functions.py

@hamedbehzadi
Copy link

Thank you @MFreidank.
The output of generate_gradients has more than 3 channels (like [64,13,13]). So, in function save_gradient_images, I face below error
TypeError: Cannot handle this data type: (1, 1, 13), |u1
Whould you please help me to solve this error?

@jackmvision
Copy link

@hamedbehzadi yes, i have the same error. the output has the same size as the output size of the first layer. it seems the gradients are not calculated w.r.t. the input image.
@MFreidank any hints for this problem? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment