关于特征图可视化的计算大多在class GradCAM的实例化中完成
cam = GradCAM(model=model,
target_layers=target_layers,
use_cuda=False,
reshape_transform=ReshapeTransform(model))
target_category = 281 #281 tabby, tabby cat
# target_category = 254 # pug, pug-dog
grayscale_cam = cam(input_tensor=input_tensor, target_category=target_category)
grayscale_cam = grayscale_cam[0, :]
下面具体看一下GradCAM如何定义的
获取目标层中每个通道的权重向量。
返回权重通道的方法,
通常只需要实现此函数。
call方法
这段代码定义了一个 __call__
方法,用于执行 Saliency 属性算法。
if self.cuda:
input_tensor = input_tensor.cuda()
首先,若需要使用 GPU 计算,则将输入的张量 input_tensor
移到 GPU 上。
# 正向传播得到网络输出logits(未经过softmax)
output = self.activations_and_grads(input_tensor)
然后,通过调用 activations_and_grads
方法对输入进行前向传播,得到网络输出的 logits(未经 softmax 处理)。模型的activations_and_grads方法就是 ActivationsAndGradients(self.model, target_layers, reshape_transform) ,用于注册hook,并前向运行model推理,从指定中间层提取激活值和注册对应的梯度。详见:(27条消息) ViT的可解释化-深度解析GradCam源码(2)-注册hook获取梯度_白菜c的博客-CSDN博客
if isinstance(target_category, int):
target_category = [target_category] * input_tensor.size(0)
if target_category is None:
target_category = np.argmax(output.cpu().data.numpy(), axis=-1)
print(f"category id: {target_category}")
else:
assert (len(target_category) == input_tensor.size(0))
GradCAM是根据类别有关的注意力,target_category
是你想关注的输入类别,比如小狗对应的是281。
如果 target_category
是整数类型,则将其扩展为与输入样本数量相同的列表。
接下来,根据情况选择目标类别。如果 target_category
为 None,则通过在输出 logits 上取最大值确定目标类别,并打印出相应的类别 id。否则,检查传入的目标类别列表长度是否与输入样本数量相同。
self.model.zero_grad()
loss = self.get_loss(output, target_category)
loss.backward(retain_graph=True)
然后,对模型的参数梯度进行清零,计算损失函数 loss
,并执行反向传播以计算梯度。
cam_per_layer = self.compute_cam_per_layer(input_tensor)
return self.aggregate_multi_layers(cam_per_layer)
一般情况下,Saliency 属性的研究会选择单个目标层来计算显著图像,通常是最后一个卷积层。但这里支持传入多个目标层的列表,将为每个输入图像计算显著图,并对它们进行聚合(默认使用均值聚合)。最后,返回多层显著图的聚合结果。compute_cam_per_layer方法具体解释如下
compute_cam_per_layer
这段代码定义了一个名为 compute_cam_per_layer
的方法,用于计算每个目标层的 CAM(Class Activation Map)
activations_list = [a.cpu().data.numpy()
for a in self.activations_and_grads.activations]
grads_list = [g.cpu().data.numpy()
for g in self.activations_and_grads.gradients]
activations_and_grads.activations
和 activations_and_grads.gradients
中获取激活值和梯度,并将它们转换为 numpy 数组。这些激活值和梯度是通过执行 self.activations_and_grads
方法获得的。
target_size = self.get_target_width_height(input_tensor)
接下来,根据输入张量的大小确定目标尺寸 target_size
。
cam_per_target_layer = []
# Loop over the saliency image from every layer
for layer_activations, layer_grads in zip(activations_list, grads_list):
cam = self.get_cam_image(layer_activations, layer_grads)
cam[cam < 0] = 0 # works like mute the min-max scale in the function of scale_cam_image
scaled = self.scale_cam_image(cam, target_size)
cam_per_target_layer.append(scaled[:, None, :])
然后,通过迭代每个目标层的激活值和梯度数组,调用 get_cam_image
方法计算 CAM 图像。对于计算得到的 CAM 图像,将小于 0 的值设为 0,类似于在 scale_cam_image
函数中进行最小-最大缩放。将 CAM 图像缩放到目标尺寸,并将结果添加到 cam_per_target_layer
列表中。
return cam_per_target_layer
最终,返回每个目标层的 CAM 图像列表 cam_per_target_layer
。