Grad cam 参考我导的讲解
论文小结 总的公式如下:对于某一类C,比如Cat有
其中:
A代表某个特征层,在论文中一般指的是最后一个卷积层输出的特征层
K代表特征层A中第k个通道(channel)
c代表类别c
$A^k$代表特征层A中通道k的数据
$\alpha^c_k$ 代表$A^k$对应的权重
最后输出的是一个特征层宽高的矩阵。
计算权重 后面那一项其实是第c类对权重的梯度,可以通过反向传播得到,本质就是对反向传播的矩阵进行一个求平均操作,输出K维的向量作为权重。
根据上式计算Mat 转换为图片 这时已经得到了0~1的灰度图mask,如果想放大则直接使用插值得到结果图
cv2.resize(mat,targetsize)
转rgb热力图 灰度图只能显示提取到的特征,不能很好显示注意力的位置,可以修改为热力图来显示。
heat_map=cv2.applyColorMap(mask,colormap=colormap) heat_map=heat_map/255. image=image+heat_map image=image/np.max (image)
代码 class GradCam : def __init__ (self,model_name:list ,target_size=None ): self.fh=[] self.bh=[] self.target_size=target_size for i in model_name: i.register_forward_hook(self.forward_hook) i.register_backward_hook(self.backward_hook) def forward_hook (self,module,input ,output ): self.fh.append(output.cpu().detach().numpy()) def backward_hook (self,module, grad_in, grad_out ): self.bh = [grad_out[0 ].cpu().detach().numpy()] + self.bh def get_rgb_image (self,image,mask,colormap: int = cv2.COLORMAP_JET,rgb_or_bgr=False ,use_heatmap=True ): if use_heatmap==True : mask = np.uint8(mask * 255 ) heat_map=cv2.applyColorMap(mask,colormap=colormap) if rgb_or_bgr==True : heat_map=cv2.cvtColor(heat_map,cv2.COLOR_BGR2RGB) heat_map=heat_map/255. image=image+heat_map image=image/np.max (image) else : mask=np.expand_dims(mask,axis=2 ) image=image*mask return image def cal_cam (self,image,colormap: int = cv2.COLORMAP_JET,rgb_or_bgr=False ,use_heatmap=True ): ''' 获得各层的 mask和image :param image: 输入的图片 0~1格式 :param colormap: 热力图colormap格式 :param rgb_or_bgr: 输入图片是否为rgb格式HWC :param use_heatmap: 输出热力图或蒙版图 :return: images,masks ''' self.Image=[] self.masks=[] for a, a_ in zip (self.fh, self.bh): a=np.squeeze(a) a_=np.squeeze(a_) alpha = np.mean(a_, axis=(1 ,2 ),keepdims=True ) mat = np.sum (alpha*a,axis=0 ) mat[mat<0 ]=0 mat=(mat-np.min (mat))/(np.max (mat)+1e-7 ) if self.target_size!=None : mask=cv2.resize(mat,self.target_size) else : mask=mat image=self.get_rgb_image(image,mask,colormap=colormap,rgb_or_bgr=False ,use_heatmap=use_heatmap) self.masks.append(mask) self.Image.append(image) return self.Image,self.masks cam=GradCam(feature_name,target_size) images,_=cam.cal_cam(raw_image/255. ) cv2.imshow(images[0 ]) cv2.waitKey() cv2.destroyAllWindows()
Guide backpropagation 论文小结 总的来说就是根据正反向传播通过relu激活函数的特性进行一个可视化:正向传播中被成功激活的才认为是有效信息,反向传播也同理,所以我们通过正向传播获得激活参数的位置,与反向传播权重进行relu后相乘获得新的反向传播权重权重并回传。
注意这个是要走一遍所有反向传播过程,只是在relu层进行重新回传梯度这个操作,所以最后输出是第一个网络层的梯度(这里需要设置图片允许求梯度,否则第一个会是None),维度与输入图片相同,梯度的输出含义则是图片的边缘信息。
论文里实现部分讲的很难理解,我是根据代码进一步理解的。
我尝试了直接输出第一个网络层(上图)的梯度与GBP(下图)进行对比,只筛选被激活部分还是挺能说明一些东西的。
代码 代码实现时有几点要注意的顺便总结一下:
permute,transpose,view:
view:将图片全部展开为一维,然后按照给定的维度进行拼接,会改变原始的矩阵信息。
transpose和permute,不会改变原始矩阵信息,用法也相同,只是一个是numpy一个是tensor。
比如permute(2,1,0),代表原本第2个维到到第一个位置 ,第1个元素到第二个位置第0维到第三个位置。
直接输出的图片太暗:
通过debug可以发现最后输出并不是0~1,所以我们需要把他进行一个0~1的转换,然后由于我们只关心高频边缘信息,所以把0~1直接的全部置0,只保留原始为1的数。这里转换还要注意一点,为了增强对比度,我们将均值设置在0.5左右,标准差设置在0.1 ,上图为min-max归一化,中图为mean-std归一化,下图在中图基础上还设置了方差和均值。
由于会改变反向传播的梯度,不能在训练的时候用也不能和grad-cam同时用(这里的同时是指只进行一次前后向传播)
输出gbp.image不存在:需要给前向传播的那张图片设置允许求梯度requiregrad ()
class GuideBackPropagation : def __init__ (self,model_name:list ): self.fh=[] self.model=model for name, module in model.features.named_children(): if isinstance (module, nn.ReLU): module.register_forward_hook(self.forward_hook) module.register_backward_hook(self.backward_hook) for i in model_name: i.register_backward_hook(self.first_backward_hook) def normalize (self,I ): norm = (I - I.mean()) / I.std() norm = norm * 0.1 norm = norm + 0.5 norm = norm.clip(0 , 1 ) return norm def forward_hook (self,module,input ,output ): self.fh.append(output.cpu().detach().numpy()) def first_backward_hook (self,module,grad_in,grad_out ): self.image=grad_in[0 ].cpu().detach()[0 ] self.image=self.image.permute(1 ,2 ,0 ).numpy() self.image=self.normalize(self.image) pass def backward_hook (self,module, grad_in, grad_out ): a=self.fh.pop() a[a>0 ]=1 new_grad=torch.clamp(grad_out[0 ],min =0.0 ) return (new_grad*a,) gbp=GuideBackPropagation(feature_name) res=model(image.require_grad_()) res[0 ,0 ].backward() image=gbp.image