物体定位:Grad-CAM论文笔记——Grad-CAM: Visual Explanations from Deep Networks via Gradient-based Localization
综述
论文题目:《Grad-CAM: Visual Explanations from Deep Networks via Gradient-based Localization》
会议时间:IEEE International Conference on Computer Vision 2017 (ICCV, 2017)关键字:无监督定位
介绍
Grad-CAM的全称为梯度加权的类别响应图(Gradient-weighted Class Activation Mapping),常用于物体定位,适用于图像分类模型、图像描述模型(image captioning)以及可视化问答系统(visual question answering)等等。
生成响应图的核心就是获得基于指定类别的梯度,之后利用梯度计算神经元的重要性:
α
k
c
=
1
Z
∑
i
∑
j
∂
y
c
∂
A
i
,
j
k
\alpha_k^c=\frac1Z\sum_i\sum_j\frac{\partial y^c}{\partial A^k_{i,j}}
αkc=Z1i∑j∑∂Ai,jk∂yc
其中
α
k
c
\alpha^c_k
αkc是第
k
k
k张特征图
A
k
A^k
Ak对于预测类别
c
c
c的相对重要性,
y
c
y^c
yc表示类别
c
c
c的预测值(网络的输出,未经过softmax),之后将
α
\alpha
α与特征图
A
A
A相乘,再沿通道方向求和得到一张图,最后再传入ReLU激活函数,得到最终的响应图:
L
G
r
a
d
−
C
A
M
c
=
R
e
L
U
(
∑
k
α
k
c
A
k
)
L^c_{Grad-CAM}=ReLU(\sum_k \alpha^c_k A^k)
LGrad−CAMc=ReLU(k∑αkcAk)
后续可以利用响应图来执行定位任务,响应图上数据越大,表示原图响应位置上是物体所在区域。
注:
- Grad-CAM算法核心就在于提取指定类别的梯度,在PyTorch中可用hook模块提取指定中间变量的梯度;
- 在训练过程中,可用使用类别标签来定位,在测试过程中无标签,可以使用预测值最大的序号作为物体类别来定位;
- 响应图不能直接用于可视化,可以先经过一次归一化运算,之后使用
cv2.applyColorMap
将原单通道图像数据转为3色图像数据; - 如果网络有BN层或DropOut层,在测试过程中执行定位时,需要将模型设为
eval()
模式,调整网络运算方式。
代码实现
代码为个人实现,仅供参考,可视化效果如下:
import cv2
import torch
import numpy as np
import torch.nn.functional as F
from torchvision import models, transforms
# 定义反向传播和前向传播hook,分别用于提取梯度和特征
# 这里只提取网络模块输出元素的梯度和对应的特征数据
def backward_hook(module, inputs, outputs):
grad_outputs.append(outputs[0].detach())
def forward_hook(module, inputs, outputs):
feature_outputs.append(outputs)
# 数据归一化模块
def _normalize(cams):
min = cams.min()
max = cams.max()
return (cams - min) / (max - min)
torch.manual_seed(0)
img_path = 'dog.jpg'
grad_outputs = []
feature_outputs = []
input_size = (224, 224)
# 定义网络模型,注意测试阶段要将模型设置为eval()
net = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1).eval()
# 将定义好的hook加到网络模块上面,注意模块名称
net.layer4.register_full_backward_hook(backward_hook)
net.layer4.register_forward_hook(forward_hook)
tran = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
img = cv2.imread(img_path)
img_re = cv2.resize(img, input_size)
img_re = cv2.cvtColor(img_re, cv2.COLOR_BGR2RGB)
data = tran(img_re).unsqueeze(0)
# 将图片数据传入网络中,做前向传播,此时feature_outputs中已经有了特征数据
out = net(data)
cls_idx = torch.argmax(out).item()
# 提取到网络预测最大的概率序号,当成预测类别,设为c
score = out[:, cls_idx]
net.zero_grad()
# 反向传播,得到关于类别c的梯度,此时储存在grad_outputs中
score.backward(retain_graph=True)
# 梯度求均值->和特征数据相乘->相乘结果沿通道方向求和->经过relu->归一化,得到最终的定位响应图
weight = grad_outputs[-1].squeeze(0).mean(dim=(0)).unsqueeze(0)
grad_cam = (weight * feature_outputs[-1].squeeze(0)).sum(0)
grad_cam = _normalize(F.relu(grad_cam, inplace=True)).cpu().detach().numpy()
# 将响应图放大到和原图一样的大小,之后转为三色图,变成热图
grad_cam = cv2.resize(grad_cam, (img.shape[1], img.shape[0]))
heatmap = cv2.applyColorMap(np.uint8(255 * grad_cam), cv2.COLORMAP_JET)
# 热图和原图加权相加,得到最终的可视化图
heatmap = cv2.addWeighted(img, 0.5, heatmap, 0.5, 0)
cv2.imwrite('hotmap.jpg', heatmap)
注:以上内容仅是笔者的个人见解,若有问题或者不清楚的地方,欢迎沟通交流。