笔者在学习Grad-Cam算法对应的论文时,注意到该论文利用导向反向传播Guided Backpropagation来可视化细粒度信息,用Grad-Cam来定位判别性区域,大致如下图所示。因此,便对导向反向传播Guided Backpropagation算法进行了学习与复现。如果你想了解Grad-Cam算法的复现,可参考这篇博客:https://editor.csdn.net/md/?articleId=129650976
关于导向反向传播Guided Backpropagation的原理可参考下图。
注意,只需修改下段代码中输入图像的路径及输出图像的路径,然后运行即可。
import os
import cv2
import torch
from torch import nn
from PIL import Image
from torchvision import models
from torchvision import transformsclass Guided_backprop():def __init__(self, model):self.model = modelself.image_reconstruction = Noneself.activation_maps = []self.model.eval()self.register_hooks()def register_hooks(self):def first_layer_hook_fn(module, grad_in, grad_out):# 在全局变量中保存输入图片的梯度,该梯度由第一层卷积层# 反向传播得到,因此该函数需绑定第一个 Conv2d Layerself.image_reconstruction = grad_in[0]def forward_hook_fn(module, input, output):# 在全局变量中保存 ReLU 层的前向传播输出# 用于将来做 guided backpropagationself.activation_maps.append(output)def backward_hook_fn(module, grad_in, grad_out):# ReLU 层反向传播时,用其正向传播的输出作为 guide# 反向传播和正向传播相反,先从后面传起grad = self.activation_maps.pop()# ReLU 正向传播的输出要么大于0,要么等于0,# 大于 0 的部分,梯度为1,# 等于0的部分,梯度还是 0grad[grad > 0] = 1# grad_in[0] 表示 feature 的梯度,只保留大于 0 的部分positive_grad_in = torch.clamp(grad_in[0], min=0.0)# 创建新的输入端梯度new_grad_in = positive_grad_in * grad# ReLU 不含 parameter,输入端梯度是一个只有一个元素的 tuplereturn (new_grad_in,)# 获取 module,这里只针对 alexnet,如果是别的,则需修改modules = list(self.model.features.named_children())# 遍历所有 module,对 ReLU 注册 forward hook 和 backward hookfor name, module in modules:if isinstance(module, nn.ReLU):module.register_forward_hook(forward_hook_fn)module.register_backward_hook(backward_hook_fn)# 对第1层卷积层注册 hookfirst_layer = modules[0][1]first_layer.register_backward_hook(first_layer_hook_fn)def visualize(self, input_image, target_class):# 获取输出,之前注册的 forward hook 开始起作用model_output = self.model(input_image)self.model.zero_grad()pred_class = model_output.argmax().item()# 生成目标类 one-hot 向量,作为反向传播的起点grad_target_map = torch.zeros(model_output.shape,dtype=torch.float)if target_class is not None:grad_target_map[0][target_class] = 1else:grad_target_map[0][pred_class] = 1# 反向传播,之前注册的 backward hook 开始起作用model_output.backward(grad_target_map)# 得到 target class 对输入图片的梯度,转换成图片格式result = self.image_reconstruction.data[0].permute(1, 2, 0)return result.numpy()def normalize(I):# 归一化梯度map,先归一化到 mean=0 std=1norm = (I - I.mean()) / I.std()# 把 std 重置为 0.1,让梯度map中的数值尽可能接近 0norm = norm * 0.1# 均值加 0.5,保证大部分的梯度值为正norm = norm + 0.5# 把 0,1 以外的梯度值分别设置为 0 和 1norm = norm.clip(0, 1)return normif __name__ == '__main__':I = Image.open('./test_0002_aligned.jpg').convert('RGB')transform = transforms.Compose([transforms.Resize((224,224)),transforms.CenterCrop((224,224)),transforms.ToTensor(),transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])tensor = transform(I).unsqueeze(0).requires_grad_()model = models.alexnet(pretrained=True)guided_bp = Guided_backprop(model)result = guided_bp.visualize(tensor, None)result = normalize(result)result= result[:, :, ::-1]*255cv2.imwrite('./test_aligned_deconv.jpg',result)
下左图为上述代码的输入图像,即原图;下右图为为上述代码的输出图像,即原图经导向反向传播后的图。
1.http://ddrv.cn/a/145213
2.https://blog.csdn.net/qq_41647438/article/details/109504316
3.https://zhuanlan.zhihu.com/p/479485138?utm_id=0
4.https://blog.csdn.net/cdknight_happy/article/details/108792065