类激活图Cam和GradCam原理解读,代码实例讲解

网上关于类激活图Cam以及梯度类激活图的讲解很多,但都不是非常全面,这里我就全面的介绍一下两者的原理,并讲解代码实现过程,最后通过一个实例进行演示。
CAM: CAM
Grad-CAM:Grad-CAM
类激活图cam(class activation map)通过可视化的热力图将模型认为最显著的结果显示出来,因此可用于解释模型预测的结果。卷积神经网络的最后一层卷积层包含了最丰富的空间和语义信息,于是Cam充分利用了最后一层卷积的特征,并将后面的全连接层和softmax层替换成了GAP层(全局平均池化),用特征图所有像素的均值代替整个特征图的值。每个特征图 A k A^k Ak都有一个对应的权重 w k c w_k^c wkc,与GAP后的特征图求加权和就能得到相应类别C的类激活图,同时也能得到对应的预测得分 Y c Y^c Yc(softmax之前)。

C a m c = ∑ k w k c × A k Cam^c=\sum\limits_k w_k^c\times A^k Camc=kwkc×Ak

可以看到,其实Cam实现并不难,但有个很大的缺陷,由于添加了GAP改变了网络模型,与原始训练好的模型不同。所以还需要对改变的模型进行训练得到相应的权重,大大限制了应用场景。
于是就有了Grad-CAM,它使用梯度的全局平均来计算权重,不需要修改模型,自然也不需要重新训练。经过严格的数学推导,权重为

我们只关心那些有正向作用的像素点,所以加了ReLU。

从公式中可以看出,我们只要对类别C的预测得分进行反向传播得到梯度,并求全局平均就能计算出相应的权重,因此不需要修改模型。

下面我们结合代码进行讲解,GitHub上的GradCam非常多,我挑了其中一个,讲解其中关键的代码。

由于需要计算前向传播的特征和反向传播的梯度,所以介绍前需要先了解hook函数,可以不改变主体情况下,提取网络中间的输出。这里我就不详细介绍了,详情可以看这个博客
hook函数和CAM类激活图

此代码添加前向传播的特征和反向传播的梯度

def _register_hook(self):
    for (name, module) in self.net.named_modules():
        if name == self.layer_name:
            self.handlers.append(module.register_forward_hook(self._get_features_hook))
            self.handlers.append(module.register_backward_hook(self._get_grads_hook))

在计算完GradCam后,需要释放hook,否则计算会越来越慢。

def remove_handlers(self):
    for handle in self.handlers:
        handle.remove()

这里就是计算类激活图的关键代码了,结合上述公式可以很快的理解。

def __call__(self, inputs, index):
    """
    :param inputs: [1,3,H,W]
    :param index: class id
    :return:
    """
    self.net.zero_grad()
    #模型预测得分
    output = self.net(inputs)  # [1,num_classes]
    #取最大得分对应的索引作为类别
    if index is None:
        index = np.argmax(output.cpu().data.numpy())
    target = output[0][index]
    #预测得分反向传播
    target.backward()
    #梯度全局平均求权重
    gradient = self.gradient[0].cpu().data.numpy()  # [C,H,W]
    weight = np.mean(gradient, axis=(1, 2))  # [C]

    feature = self.feature[0].cpu().data.numpy()  # [C,H,W]
    #特征与对应权重加权和并ReLU
    cam = feature * weight[:, np.newaxis, np.newaxis]  # [C,H,W]
    cam = np.sum(cam, axis=0)  # [H,W]
    cam = np.maximum(cam, 0)  # ReLU

    # 数值归一化
    cam -= np.min(cam)
    cam /= np.max(cam)
    # resize to 224*224
    cam = cv2.resize(cam, (224, 224))
    return cam
  • 9
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值