这篇博客主要用于自己学习过程的记录,有很多不完善或者错误的地方,还请路过的大佬多指教
在CNN中,网络进行前向传播(一般取最后一个卷积层)得到特征图A与该类别的输出预测值,为了能够理解网络这个“小黑盒”,对输出预测值反向传播(即输出预测值对特征图A求偏导数),得到特征图A的梯度信息(得到的梯度信息就表示特征图A中每个元素对于输出预测的贡献大小),将梯度信息值在w,h上求均值就能得到对该类别特征图A每个通道的重要程度,对特征图A每个通道的数据加权求和,在经ReLU函数就可以实现Grad-CAM。
选择最后一层卷积层是因为最后一层卷积层包含丰富的语义信息和详细的空间信息,经过全连接层之后空间信息就会丢失。
Grad-CAM参考 https://blog.csdn.net/qq_37541097/article/details/123089851
代码地址: https://github.com/Wenxc/YOLOv5-GradCAM
有太多的内容不理解,希望大佬路过指点一下,代码是对于YOLOv5进行的Grad-CAM,但是需要将其改为YOLOv3上的Grad-CAM的应用。
在这里主要对gradcam.py, yolo_detector.py, main.py进行学习,所有的内容我都换成了我自己的地址,包括网络模型,网络层名称:model_45_cv1_act(这一层是我随便设置的)。
def get_res_img(bbox, mask, res_img):
mask = mask.squeeze(0).mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).detach().cpu().numpy().astype(np.uint8)
heatmap = cv2.applyColorMap(mask, cv2.COLORMAP_JET).astype(np.float32)
bbox = [int(b) for b in bbox]
tmp = np.ones_like(res_img,dtype=np.float32) * 0
tmp[bbox[1]:bbox[3],bbox[0]:bbox[2]] = 1
res_img = cv2.add(res_img, heatmap)
res_img = (res_img / res_img.max())
return res_img, heatmap
#这部分主要是找到目标层用来计算Grad-CAM
def find_yolo_layer(model, layer_name):
"""Find yolov5 layer to calculate GradCAM and GradCAM++
Args:
model: yolov5 model.
layer_name (str): the name of layer with its hierarchical information.
Return:
target_layer: found layer
"""
hierarchy = layer_name.split('_')#这里是我们选择的目标层,在main.py中需要使用下划线进行连接
#为了方便查看结果,我将其打印出来hierarchy[0]:model,hierarchy[1:]: ['45', 'cv1', 'act']
target_layer = model.model._modules[hierarchy[0]]
# print('target',target_layer),如果不知道网络的名字,可以在这里打印一下网络结构
for h in hierarchy[1:]:
target_layer = target_layer._modules[h]#分别找到‘45’,‘cv1’, ‘act’所对应层的结构
return target_layer#最后返回的就是具体的model_45_cv1_act,即ReLU6(inplace=True)这一层
gradcam.py中的YOLOV5GradCAM这部分代码比较长,为了方便展示,我拆开进行分析。
class YOLOV5GradCAM:
def __init__(self, model, layer_name, img_size=(608, 608)):
self.model = model
# self.gradients = []
# self.activations = []
target_layer = find_yolo_layer(self.model, layer_name)
print('Target_layer', target_layer)
# we don't use backward hook to record gradients.
target_layer.register_forward_hook(self.save_activation) #获取指定层前向传播的输入输出,这里指特征图
target_layer.register_forward_hook(self.save_gradient)#在这里用到下边保存的梯度值(与特征图是同一层)
# print('目标',target_layer.register_forward_hook)
device = 'cuda' if next(self.model.model.parameters()).is_cuda else 'cpu'
self.model(torch.zeros(1, 3, *img_size, device=device))
#这里我打印了一下,输出如下,我不太明白是什么意思
#([[[]], [[]], [[]], [[]]], [tensor([], size=(0, 80))])
print('[INFO] saliency_map_size :', self.activations[0].shape[2:])
#此处的代码用于获取网络的输入和输出
def save_activation(self, module, input, output):
activation = output.clone()
# print('tidu',activation.shape)
self.activations = activation.cpu().detach()
# self.activations['value'] = activation.cpu().detach()
print('forward:', type(output))
return None
#获取反向传播的梯度值
def save_gradient(self, module, input, output):
if not hasattr(output, "requires_grad") or not output.requires_grad:
# You can only register hooks on tensor requires grad.
#这里的作用大概是告诉网络要进行梯度回传了
return
# Gradients are computed in reverse order
def _store_grad(grad):
self.gradients = grad.cpu().detach()
# print('TIDU',grad)在这里我打印了一下梯度值,有四张输出(有四个目标),但是其中三张图的梯度值都是0,只有一张正常,不知道原因是什么,梯度值就像下边这样。
output.register_hook(_store_grad)
下边是gradcam.py剩余的部分代码(将每部分都打印了一下):
def forward(self, input_img, class_idx=True):
"""
Args:
input_img: input image with shape of (1, 3, H, W)
Return:
mask: saliency map of the same spatial dimension with input
logit: model output
preds: The object predictions
"""
saliency_maps = []
b, c, h, w = input_img.size()#打印出的b,c,h,w=1,3,480,608(部下都为啥输入变成了480,我的图片明明是800*600的大小,上边也是608*608,不知道为啥)
tic = time.time()
preds, logits = self.model(input_img)
# print(preds,logits)
print("[INFO] model-forward took: ", round(time.time() - tic, 4), 'seconds')
for logit, cls, cls_name in zip(logits[0], preds[1][0], preds[2][0]):
# print(logit, cls, cls_name)
if class_idx:
score = logit[cls]
# print('fenshu',score)
else:
score = logit.max()
self.model.zero_grad()
#print(self.model.zero_grad())#打印完之后全为None,不知道哪里出了问题
tic = time.time()
score.backward(retain_graph=True)
print(f"[INFO] {cls_name}, model-backward took: ", round(time.time() - tic, 4), 'seconds')
gradients = self.gradients
# print(gradients)
activations = self.activations
# print(activations)
b, k, u, v = gradients.size()
# print(b, k, u, v)#1 128 60 76
alpha = gradients.view(b, k, -1).mean(2)
# print(alpha)
weights = alpha.view(b, k, 1, 1)
saliency_map = (weights * activations).sum(1, keepdim=True)
saliency_map = F.relu(saliency_map)
saliency_map = F.interpolate(saliency_map, size=(h, w), mode='bilinear', align_corners=False)
saliency_map_min, saliency_map_max = saliency_map.min(), saliency_map.max()
saliency_map = (saliency_map - saliency_map_min).div(saliency_map_max - saliency_map_min).data
saliency_maps.append(saliency_map)
# print(saliency_maps, logits, preds)
return saliency_maps, logits, preds
def __call__(self, input_img):
return self.forward(input_img)