摘要:
Grad-CAM(Gradient-weighted Class Activation Mapping)是由Selvaraju等人在2017年的论文《Grad-CAM: Visual Explanations from Deep Networks via Gradient-based Localization》中提出的。Grad-CAM是一种可解释性方法,用于解释深度神经网络在图像分类任务中的预测结果。
传统的可视化方法,如热力图和类激活映射(CAM),通常只能对网络中的全局信息进行可视化,而无法提供更细粒度的局部信息。Grad-CAM通过结合梯度信息和全局池化层的权重来解决这个问题,从而生成更准确的可视化结果。
Grad-CAM的关键思想是将卷积神经网络(CNN)的梯度信息与全局池化层的权重相乘,以获得每个空间位置对于特定类别的重要性。这些重要性权重被用于生成类激活映射,即热力图,来可视化网络对于不同类别的关注点。
通过Grad-CAM,我们可以了解深度神经网络是如何在图像中关注和权重不同区域的,从而提供了对网络决策的解释能力。这对于理解和解释模型的预测结果以及进行模型的可解释性分析非常有用。
那么,我们要如何做到对一个输入图像做Grad-CAM可视化呢?
一、实现原理
首先我们需要通过网络的前向传播得到输入图像的特征图,计算目标类别对于最后一个卷积层特征图的梯度,为了实现这一点,可以用目标类别的预测分数作为损失函数,然后通过反向传播计算梯度,反向传播过程中保留梯度信息。接下来,对输入图像做一次前向传播,得到模型输出,将模型梯度清零,同时创建一个与输出相同形状的one-shot张量作为梯度的权重,计算得到最后一个卷积层特征图相对于目标类别的梯度。通过自适应平均池化对梯度进行空间平均池化,得到每个特征图通道的权重。然后,我们将特征图和权重进行逐通道相乘,得到每个通道的加权特征图。我们将加权特征图进行逐通道求和,得到一个二维热力图(cam),表示每个像素对于目标类别的重要性。最后,我们对热力图进行 ReLU 操作,并使用双线性插值将热力图的大小调整为输入图像的大小。 最后的最后,我们将热力图进行归一化处理,将像素值缩放到 0 到 1 之间,并将其转换为 numpy 数组,并返回结果。
二、实现代码
讲完了原理,接下来上代码,代码基本是按照原理对照着实现的。这里用到了上一篇文章里训练的水果识别模型,类别是0:猕猴桃,1:柠檬,2:石榴
#热力图可视化
import torch
import torch.nn.functional as F
from PIL import Image
from torchvision import transforms
import numpy as np
import cv2
import matplotlib.pyplot as plt
class GradCAM:
def __init__(self, model):
self.model = model
self.model.eval()
self.features = None
self.gradients = None
self.model.features.register_forward_hook(self.save_features_hook)
self.model.features.register_backward_hook(self.save_gradients_hook)
def save_features_hook(self, module, input, output):
self.features = output
def save_gradients_hook(self, module, grad_input, grad_output):
self.gradients = grad_output[0]
def calculate_cam(self, input_tensor, target_class):
output = self.model(input_tensor)
self.model.zero_grad()
one_hot = torch.zeros_like(output)
one_hot[0][target_class] = 1
output.backward(gradient=one_hot, retain_graph=True)
pooled_gradients = F.adaptive_avg_pool2d(self.gradients, 1)
cam = torch.mul(self.features, pooled_gradients).sum(dim=1, keepdim=True)
cam = F.relu(cam)
cam = F.interpolate(cam, input_tensor.size()[2:], mode="bilinear", align_corners=False)
cam = cam.squeeze().detach().cpu().numpy()
cam = 1 - ((cam - cam.min()) / (cam.max() - cam.min())) # 归一化
return cam
#加载训练好的模型
model = torch.load('model.pth')
model =model.cuda()
grad_cam = GradCAM(model)
# Load and preprocess the image
image_path = 'data/mihoutao/7.jpg'
image = Image.open(image_path).convert('RGB')
preprocess = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
input_tensor = preprocess(image).unsqueeze(0).cuda()
target_class = 0 # 训练模型时的类别索引
# Calculate Grad-CAM
cam = grad_cam.calculate_cam(input_tensor, target_class)
cam = np.uint8(255*cam)
cam = np.uint8(cv2.resize(cam,(image.size[0],image.size[1])))
heatmap = cv2.applyColorMap(cam,cv2.COLORMAP_JET)
heatmap_pil = Image.fromarray(heatmap)
image_pil = Image.fromarray(np.uint8(image))
result = Image.blend(image_pil,heatmap_pil,alpha=0.5)
plt.imshow(result)
plt.axis('off')
plt.show()
举个例子:当我输入一张猕猴桃的图片时,
输出的Grad-ACM可视化为:
使用 OpenCV 的 `applyColorMap` 函数时,使用的是 Jet 颜色映射。在 Jet 颜色映射中,较红的区域表示较高的值,而较蓝的区域表示较低的值。因此,在热力图中,较红的区域表示更重要的区域,较蓝的区域表示相对不重要的区域。
可以发现,模型很好的找到了整张图片的重点,这也为我们提供了模型的可解释性,帮我们检查模型是否根据要求进行学习。
以上是本文的全部内容!