參考文章:
<<Grad-CAM:Visual Explanations from Deep Networks via Gradient-based Localization>>
參考代碼:
https://github.com/jacobgil/pytorch-grad-cam
類別無關(guān)的可視化
梯度回傳
類別無關(guān)可視化
CAM
對于一張圖片輸入,神經(jīng)網(wǎng)絡(luò)將其預(yù)測為類別A是有其緣由的脚作,通過卷積所提取的特征圖纬黎,我們可以看到圖像中那些區(qū)域?qū)τ陬A(yù)測為類別A的貢獻(xiàn)大疆液,反應(yīng)在特征圖中,即該區(qū)域的響應(yīng)值要大苛秕。
CAM的做法是對于一個訓(xùn)練好的分類網(wǎng)絡(luò)唯笙,用全局池化層替換全連接層螟蒸,重新訓(xùn)練,得到最后一層特征圖對于不同類別的貢獻(xiàn)程度崩掘,即權(quán)重七嫌。
利用權(quán)重將特征圖合在一起,得到對于某一類別的響應(yīng)圖苞慢。
CAM
Grad CAM
對于權(quán)重的計(jì)算诵原,使用梯度的全局平均來計(jì)算。不需要用全局池化替換全連接層挽放,重新訓(xùn)練绍赛。
GradCAM權(quán)重計(jì)算
其中
除了分類纯出,Image Captioning蚯妇,Visual Question Answering也可以用Grad CAM來解釋敷燎。
Grad-CAM
可視化
Guided Grad-CAM
Guided Backpropagation 和 Grad-CAM點(diǎn)乘。
附
完整代碼見https://github.com/jacobgil/pytorch-grad-cam
導(dǎo)向梯度回傳的代碼
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:選取特定類別陨收,如果沒有選取概率最大的類別澄成,得到預(yù)測為該類別的熱力圖
'''
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()
#對特定類別的概率進(jìn)行梯度回傳
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, :]
#權(quán)重
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