总结:Grad-CAM是一种CAM的变式,使用回传的梯度作为权对特征图的每个通道进行加权求和。
附Grad-CAM的论文出处:
https://arxiv.org/abs/1610.02391
附Grad-CAM的实现代码(里面有好多各种各样的CAM):
https://github.com/jacobgil/pytorch-grad-cam
以下为转载部分:
1、原理
首先简单提下CAM,CAM原理如下图所示,其实就是将某层的激活图按权重进行加权和。我们关注两点:
1)激活图,即某层的特征图。2)权重对应每层的重要程度。实际上在我所知的各种变形CAM方法中,都是基于激活图和权重值的加权和原理,只不过不同方法获取权重值的方法不一样,Grad-CAM就是利用梯度来计算权重值。那么在CAM中权重值就是全连接层中对应类的weights。
而Grad-CAM的原理和CAM差不多,只不过获取权重的方法不同。Grad-CAM从名字可以看出,它是通过梯度来获取权重的。
Grad-CAM的原理如上图所示。
1)首先,在前向推理时,获取某层的特征层F和网络的预测值y(不进行softmax),在这一步我们就得到了激活图(特征图)
2)然后如果网络的最大输出类为cat,则以cat类别的预测值作为loss,在该loss上进行反向传播,得到特征层F在类别cat上的梯度值G。
3)然后将G计算每个通道上的均值,作为对应特征图通道的权重值,然后对特征图进行加权和,再通过ReLU(舍弃负值,论文中认为负值可能是帮助判断其他类的像素)即可。
2、代码实现
# 现在假设你已经准备好训练好的模型和预处理输入了
grad_block = [] # 存放grad图
feaure_block = [] # 存放特征图
# 获取梯度的函数
def backward_hook(module, grad_in, grad_out):
grad_block.append(grad_out[0].detach())
# 获取特征层的函数
def farward_hook(module, input, output):
feaure_block.append(output)
# 已知原图、梯度、特征图,开始计算可视化图
def cam_show_img(img, feature_map, grads):
cam = np.zeros(feature_map.shape[1:], dtype=np.float32) # 二维,用于叠加
grads = grads.reshape([grads.shape[0], -1])
# 梯度图中,每个通道计算均值得到一个值,作为对应特征图通道的权重
weights = np.mean(grads, axis=1)
for i, w in enumerate(weights):
cam += w * feature_map[i, :, :] # 特征图加权和
cam = np.maximum(cam, 0)
cam = cam / cam.max()
cam = cv2.resize(cam, (W, H))
# cam.dim=2 heatmap.dim=3
heatmap = cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET) # 伪彩色
cam_img = 0.3 * heatmap + 0.7 * img
cv2.imwrite("cam.jpg", cam_img)
# layer_name=model.features[18][1]
model.features[18][1].register_forward_hook(farward_hook)
model.features[18][1].register_backward_hook(backward_hook)
# forward
# 在前向推理时,会生成特征图和预测值
output = model(inp)
max_idx = np.argmax(output.cpu().data.numpy())
print("predict:{}".format(max_idx))
# backward
model.zero_grad()
# 取最大类别的值作为loss,这样计算的结果是模型对该类最感兴趣的cam图
class_loss = output[0, max_idx]
class_loss.backward() # 反向梯度,得到梯度图
# grads
grads_val = grad_block[0].cpu().data.numpy().squeeze()
fmap = feaure_block[0].cpu().data.numpy().squeeze()
# 我的模型中
# grads_cal.shape=[1280,2,2]
# fmap.shape=[1280,2,2]
raw_img = cv2.imread("../3.jpg")
# save cam
cam_show_img(raw_img, fmap, grads_val)