深入理解并使用Grad-CAM

现如今,深度学习算法在许多领域都取得了巨大的成功,其中又以视觉领域最为突出。深度卷积神经网络具有强大的特征提取和模式识别能力,但模型的黑盒现象已经成为一个普遍的问题。因为很难理解它为何会得出特定的结果,以及它在图像中关注什么。

在这种情况下,研究人员提出了许多技术来解释卷积神经网络的工作,例如Grad-CAM(Gradient-weighted Class Activation Mapping)技术。Grad-CAM是一种可视化方法,它可以将卷积神经网络输出结果的可解释性可视化。它告诉我们卷积神经网络在哪里关注图像,以及这些区域如何帮助分类或回归任务。

一、Grad-CAM的原理

理解Grad-CAM的基本原理非常重要。Grad-CAM的核心思想是要找到一个能够反映网络输出概率的空间位置权重映射。具体而言,Grad-CAM的做法是将输出概率的梯度回传到卷积层上,并将卷积层的输出特征图和权重进行加权平均。通过这种方式,可以得到一个重要性分数,该分数与输出概率相关而能够反映图像区域的重要程度。

下面是Grad-CAM核心算法代码:

class GradCAM:
    def __init__(self, model, candidate_layers=None):
        self.model = model
        self.extractor = ModelOutputs(model, candidate_layers or model.outputs[0])
        
    def forward(self, input):
        return self.model(input)

    def __call__(self, input, index=None):
        features, output = self.extractor(input)

        if index is None:
            index = np.argmax(output.cpu().data.numpy())

        one_hot = np.zeros((1, output.size()[-1]), dtype=np.float32)
        one_hot[0][index] = 1

        one_hot = Variable(torch.from_numpy(one_hot), requires_grad=True)
        one_hot = torch.sum(one_hot.cuda() * output)

        self.model.zero_grad()
        one_hot.backward(retain_graph=True)

        grads_val = self.extractor.get_gradients()[-1].cpu().data.numpy()
        target = features[-1].cpu().data.numpy()[0, :]
        weights = np.mean(grads_val, axis=(2, 3))[0, :]
        cam = np.sum(target * weights[:, None, None], axis=0)
        cam = np.maximum(cam, 0)
        cam = cv2.resize(cam, (input.shape[3], input.shape[2]))
        cam = cam - np.min(cam)
        cam = cam / np.max(cam)
        return cam

其中的ModelOutputs类是一个包装类,它可以帮助我们同时获取卷积层和输出层。以下是ModelOutputs的代码:

class ModelOutputs:
    def __init__(self, model, candidate_layers):
        self.model = model
        self.gradients = None
        self.activation_maps = dict()

        for (name, module) in self.model.named_modules():
            if name in candidate_layers:
                module.register_backward_hook(self._get_gradients)
                module.register_forward_hook(self._get_activation(name))

    def _get_gradients(self, module, input_grad, output_grad):
        self.gradients = output_grad[0]

    def _get_activation(self, name):
        def hook(module, input, output):
            self.activation_maps[name] = output.detach()
        return hook

    def __call__(self, x):
        outputs = []
        for name, module in self.model.named_modules():
            x = module(x)
            if name in self.activation_maps:
                outputs.append(self.activation_maps[name])
        return outputs, x

该类中的_grads方法可以获取梯度。在我们使用Grad-CAM方法来实现可视化之前需要的基础就在这里。

二、Grad-CAM的优缺点

Grad-CAM具有多个优点。其中最重要的是,它是一个通用的可视化方法,可用于任何卷积神经网络架构。它并不需要重复训练或特殊的网络改造。它还不需要修改网络体系结构或模型体系结构,这意味着它可以很好地与其他机器学习工具一起使用。

此外,Grad-CAM并不难以实现。实际上,其是一个用于反向传播的标准技术。它只是使用了一些诸如箱形激活的技巧,以让输出分数和特征映射可用于可视化。它对于更复杂的架构和框架也很有效。

Grad-CAM的一个缺点是它假定模型完全是用卷积层和全连接层构建的。如果模型具有其他类型的层(例如循环或门层),那么该方法将不适用。此外,该方法局限于先前在模型中定义的卷积层或最终输出层。这意味着如果您想可视化网络中的其他层,您需要在代码中更改构建的层。

三、Grad-CAM的应用

1. 可视化图像分类结果

Grad-CAM最常见的应用是可视化图像分类结果。其方法非常简单,您只需要将Grad-CAM类与您的图像和分类器模型一起运行。下面是执行示例代码:

img = Image.open(image_path)

# 图像预处理
preprocessing = transforms.Compose([
    transforms.Resize((224,224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

img_tensor = preprocessing(img).unsqueeze(0).cuda()

# 获取Grad-CAM
grad_cam = GradCAM(model=model, candidate_layers=['layer4'])
output = model(img_tensor)

# 根据得到的概率分布,获得数字标签
pred_index = output.data.cpu().numpy().argmax()

# 获取Grad-CAM的热力图
cam = grad_cam(img_tensor)

# 将热力图(Grad-CAM)与原图像叠加
heatmap, result = visualize_cam(img_tensor.cpu().data.numpy()[0], cam)

# 显示结果
plt.figure(figsize=(10,10))
plt.subplot(2,1,1)
plt.imshow(heatmap)
plt.subplot(2,1,2)
plt.imshow(result)
plt.show()

2. 分析神经网络模型

分析神经网络的不同层:卷积层、池化层、批量标准化层(Batch Normalization)等在图像中的作用也是很有意义的。使用Grad-CAM可以很容易地以直观的方式分析每个层次的预测结果对输出的影响有哪些,并检查模型是否真正关注图像中的重要信息。

下面的代码演示了对特定卷积层进行可视化:

def get_cam(model, img_path, target_layer):
    """
    产生特定层的Grad-CAM
    :param model:
    :param img_path:
    :param target_layer: conv5_x, layer4, layer3, layer2, layer1
    """
    grad_cam = GradCAM(model=model, candidate_layers=[target_layer])
    img = Image.open(img_path)
    preprocessing = transforms.Compose([
        transforms.Resize((224,224)),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ])
    img_tensor = preprocessing(img).unsqueeze(0).cuda()
    target_index = None
    output = model(img_tensor)
    pred_class = output.argmax(dim=1, keepdim=True)
    # 如果有多个标签,则生成多个GradCAM
    if pred_class.size(0) > 1:
        for i in range(pred_class.size(0)):
            print(f'{i+1}-th categories with GradCAM:')
            # 注意GradCAM的标签需要int型,且此处要将标量变为int型,不能够用.item()方法
            cam = grad_cam(img_tensor, index=int(pred_class[i]))
            grad_img = cv2.resize(np.float32(img), (224,224))
            grad_img -= grad_img.min()
            grad_img /= grad_img.max()
            grad_map = torch.from_numpy(cam.transpose(2, 0, 1)).unsqueeze_(0)
            # 将GradCAM叠加到图像上
            show_cam_on_image(grad_img, grad_map.numpy()[0], f'Result{i+1}.jpg')
    else:
        # 获取Grad-CAM
        cam = grad_cam(img_tensor, index=target_index)
        grad_img = cv2.resize(np.float32(img), (224,224))
        grad_img -= grad_img.min()
        grad_img /= grad_img.max()
        grad_map = torch.from_numpy(cam.transpose(2, 0, 1)).unsqueeze_(0)
        # 将GradCAM叠加到图像上
        show_cam_on_image(grad_img, grad_map.numpy()[0], 'Result.jpg')

model = models.resnet50(pretrained=True).cuda()
_ = model.eval()

get_cam(model, image_path, "layer4")

四、结语

Grad-CAM是解释模型输出的强大工具,可以帮助我们理解卷积神经网络的特点、训练过程、优化以及如何通过调整超参数来提高模型的精度。

当将深度学习模型应用于实际问题时,人们通常要求精度和可解释性之间取得平衡。Grad-CAM作为一种可视化技术,为深度学习模型的可解释性和解释性提供了重要的信息。这种方法的优点是它易于实现,通用性强,可以应用于任何CNNs模型,缺点是存在局限性。

原创文章,作者:VWXK,如若转载,请注明出处:https://www.506064.com/n/134361.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
VWXKVWXK
上一篇 2024-10-04 00:05
下一篇 2024-10-04 00:05

相关推荐

  • 深入解析Vue3 defineExpose

    Vue 3在开发过程中引入了新的API `defineExpose`。在以前的版本中,我们经常使用 `$attrs` 和` $listeners` 实现父组件与子组件之间的通信,但…

    编程 2025-04-25
  • 深入理解byte转int

    一、字节与比特 在讨论byte转int之前,我们需要了解字节和比特的概念。字节是计算机存储单位的一种,通常表示8个比特(bit),即1字节=8比特。比特是计算机中最小的数据单位,是…

    编程 2025-04-25
  • 深入理解Flutter StreamBuilder

    一、什么是Flutter StreamBuilder? Flutter StreamBuilder是Flutter框架中的一个内置小部件,它可以监测数据流(Stream)中数据的变…

    编程 2025-04-25
  • 深入探讨OpenCV版本

    OpenCV是一个用于计算机视觉应用程序的开源库。它是由英特尔公司创建的,现已由Willow Garage管理。OpenCV旨在提供一个易于使用的计算机视觉和机器学习基础架构,以实…

    编程 2025-04-25
  • 深入了解scala-maven-plugin

    一、简介 Scala-maven-plugin 是一个创造和管理 Scala 项目的maven插件,它可以自动生成基本项目结构、依赖配置、Scala文件等。使用它可以使我们专注于代…

    编程 2025-04-25
  • 深入了解LaTeX的脚注(latexfootnote)

    一、基本介绍 LaTeX作为一种排版软件,具有各种各样的功能,其中脚注(footnote)是一个十分重要的功能之一。在LaTeX中,脚注是用命令latexfootnote来实现的。…

    编程 2025-04-25
  • 深入了解Python包

    一、包的概念 Python中一个程序就是一个模块,而一个模块可以引入另一个模块,这样就形成了包。包就是有多个模块组成的一个大模块,也可以看做是一个文件夹。包可以有效地组织代码和数据…

    编程 2025-04-25
  • 深入探讨冯诺依曼原理

    一、原理概述 冯诺依曼原理,又称“存储程序控制原理”,是指计算机的程序和数据都存储在同一个存储器中,并且通过一个统一的总线来传输数据。这个原理的提出,是计算机科学发展中的重大进展,…

    编程 2025-04-25
  • 深入理解Python字符串r

    一、r字符串的基本概念 r字符串(raw字符串)是指在Python中,以字母r为前缀的字符串。r字符串中的反斜杠(\)不会被转义,而是被当作普通字符处理,这使得r字符串可以非常方便…

    编程 2025-04-25
  • 深入剖析MapStruct未生成实现类问题

    一、MapStruct简介 MapStruct是一个Java bean映射器,它通过注解和代码生成来在Java bean之间转换成本类代码,实现类型安全,简单而不失灵活。 作为一个…

    编程 2025-04-25

发表回复

登录后才能评论