参考文章:
<<Grad-CAM:Visual Explanations from Deep Networks via Gradient-based Localization>>
参考代码:
https://github.com/jacobgil/pytorch-grad-cam
类别无关的可视化
CAM
对于一张图片输入,神经网络将其预测为类别A是有其缘由的,通过卷积所提取的特征图,我们可以看到图像中那些区域对于预测为类别A的贡献大,反应在特征图中,即该区域的响应值要大。
CAM的做法是对于一个训练好的分类网络,用全局池化层替换全连接层,重新训练,得到最后一层特征图对于不同类别的贡献程度,即权重。
利用权重将特征图合在一起,得到对于某一类别的响应图。
Grad CAM
对于权重的计算,使用梯度的全局平均来计算。不需要用全局池化替换全连接层,重新训练。
其中表示第k个特征图对类别c的权重,表示类别c的概率,表示在第k个特征图,(i,j)位置的像素。
除了分类,Image Captioning,Visual Question Answering也可以用Grad CAM来解释。
Guided Grad-CAM
Guided Backpropagation 和 Grad-CAM点乘。
附
完整代码见https://github.com/jacobgil/pytorch-grad-cam
导向梯度回传的代码
class GuidedBackpropReLU(Function):
@staticmethod
def forward(self, input):
positive_mask = (input > 0).type_as(input)
output = torch.addcmul(torch.zeros(input.size()).type_as(input), input, positive_mask)
self.save_for_backward(input, output)
return output
@staticmethod
def backward(self, grad_output):
input, output = self.saved_tensors
grad_input = None
positive_mask_1 = (input > 0).type_as(grad_output)
positive_mask_2 = (grad_output > 0).type_as(grad_output)
#梯度回传时,要求梯度值为正,relu输入值为正
grad_input = torch.addcmul(torch.zeros(input.size()).type_as(input),
torch.addcmul(torch.zeros(input.size()).type_as(input),
grad_output, positive_mask_1), positive_mask_2)
return grad_input
class GradCam:
def __init__(self, model, feature_module, target_layer_names, use_cuda):
'''
model:resnet50
feature_module:module.layer4,即第4层
target_layer_names:2,即第4层中index=2的block
'''
self.model = model
self.feature_module = feature_module
self.model.eval()
self.cuda = use_cuda
if self.cuda:
self.model = model.cuda()
self.extractor = ModelOutputs(self.model, self.feature_module, target_layer_names)
def forward(self, input):
return self.model(input)
def __call__(self, input, index=None):
'''
input:输入图像
index:选取特定类别,如果没有选取概率最大的类别,得到预测为该类别的热力图
'''
if self.cuda:
features, output = self.extractor(input.cuda())
else:
features, output = self.extractor(input)
if index == None:
index = np.argmax(output.cpu().data.numpy())
one_hot = np.zeros((1, output.size()[-1]), dtype=np.float32)
one_hot[0][index] = 1
one_hot = torch.from_numpy(one_hot).requires_grad_(True)
if self.cuda:
one_hot = torch.sum(one_hot.cuda() * output)
else:
one_hot = torch.sum(one_hot * output)
self.feature_module.zero_grad()
self.model.zero_grad()
#对特定类别的概率进行梯度回传
one_hot.backward(retain_graph=True)
grads_val = self.extractor.get_gradients()[-1].cpu().data.numpy()
#特征图
target = features[-1]
target = target.cpu().data.numpy()[0, :]
#权重
weights = np.mean(grads_val, axis=(2, 3))[0, :]
cam = np.zeros(target.shape[1:], dtype=np.float32)
for i, w in enumerate(weights):
cam += w * target[i, :, :]
#归一化0到1
cam = np.maximum(cam, 0)
cam = cv2.resize(cam, input.shape[2:])
cam = cam - np.min(cam)
cam = cam / np.max(cam)
return cam