grad-cam用于3D分割网络的代码修改——以及特征层非常规输出的解决方法

这篇具有很好参考价值的文章主要介绍了grad-cam用于3D分割网络的代码修改——以及特征层非常规输出的解决方法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

首先,我们看下chatgpt写的Gradcam框架。

import torch
import torchvision.models as models
from pytorch_grad_cam import GradCAM

# Load your 3D segmentation model
model = models.segmentation3d()

# Define the target layer
target_layer = model.conv3

# Initialize the Grad-CAM class
grad_cam = GradCAM(model, target_layer)

# Load your input tensor
input_tensor = torch.randn(1, 1, 32, 32, 32)

# Get the Grad-CAM result
result = grad_cam(input_tensor)

# Save the Grad-CAM result
torch.save(result, "grad_cam.pt")

为了个性化设计输出的cam,我们需要修改源码。也就是GradCAM()函数。
先解读一下原版

class GradCAM(BaseCAM):
    def __init__(self, model, target_layers, use_cuda=False,
                 reshape_transform=None):
        super(
            GradCAM,
            self).__init__(
            model,
            target_layers,
            use_cuda,
            reshape_transform)

    def get_cam_weights(self,
                        input_tensor,
                        target_layer,
                        target_category,
                        activations,
                        grads):
    #这个函数放到后面说了
        return np.mean(grads, axis=(2, 3))
        

可以看到并没有太多函数方法,我们打开基类BaseCAM()

    def __init__(self,
                 model: torch.nn.Module,
                 target_layers: List[torch.nn.Module],
                 use_cuda: bool = False,
                 reshape_transform: Callable = None,
                 compute_input_gradient: bool = False,
                 uses_gradients: bool = True) -> None:
        self.model = model.eval()
        self.target_layers = target_layers
        self.cuda = use_cuda
        if self.cuda:
            self.model = model.cuda()
        self.reshape_transform = reshape_transform
        self.compute_input_gradient = compute_input_gradient
        self.uses_gradients = uses_gradients
        self.activations_and_grads = ActivationsAndGradients(
            self.model, target_layers, reshape_transform)
        # 实例化了ActivationsAndGradients类

看一下ActivationsAndGradients

class ActivationsAndGradients:
    # 自动调用__call__()函数,获取正向传播的特征层A和反向传播的梯度A'
    def __init__(self, model, target_layers, reshape_transform): 

        # 传入模型参数,申明特征层的存储空间(self.activations)
        # 和回传梯度的存储空间(self.gradients)
        self.model = model
        self.gradients = []
        self.activations = []
        self.reshape_transform = reshape_transform
        self.handles = []

        # 注意,上文指明目标网络层是是用列表存储的(target_layers = [model.down4])
        # 源码设计的可以得到多层cam图
        # 这里注册了一个前向传播的钩子函数“register_forward_hook()”,其作用是
        # 在不改变网络结构的情况下获取某一层的输出,也就是获取正向传播的特征层
        for target_layer in target_layers:
            self.handles.append(
                target_layer.register_forward_hook(
                    self.save_activation
                )
            )
        
        # hasattr(object,name)返回值:如果对象有该属性返回True,否则返回False
        # 其作用是判断当前环境中是否存在该函数(解决版本不匹配的问题)
        if hasattr(target_layer, 'register_full_backward_hook'):
            self.handles.append(
                target_layer.register_full_backward_hook(self.save_gradient))
        else:
            # 注册反向传播的钩子函数“register_backward_hook”,用于存储反向传播过程中梯度图
            self.handles.append(
                target_layer.register_backward_hook(self.save_gradient))
    
    # 官方API文档对于register_forward_hook()函数有着类似的用法,
    # self.activations中存储了正向传播过程中的特征层
    def save_activation(self, module, input, output):
        activation = output
        if self.reshape_transform is not None:
            activation = self.reshape_transform(activation)
        self.activations.append(activation.cpu().detach())
    
    # 与上述类似,只不过save_gradient()存储梯度信息,值得注意的是self.gradients的存储顺序
    def save_gradient(self, model, grad_input, grad_output):
        grad = grad_output[0]
        if self.reshape_transform is not None:
            grad = self.reshape_transform(grad)
        self.gradients = [grad.cpu().detach()] + self.gradients 
        # 反向传播的梯度A’放在最前,目的是与特征层顺序一致

    def __call__(self, x):
        # 自动调用,会self.model(x)开始正向传播,注意此时并没有反向传播的操作
        self.gradients = []
        self.activations = []
        return self.model(x)

    def release(self):
        for handle in self.handles:
            handle.remove()
            # handle要及时移除掉,不然会占用过多内存

可以看到,ActivationsAndGradients类主要的功能是通过钩子函数获取正向传播的特征层和反向传播的梯度图,分别应用了register_forward_hook(hook)和register_backward_hook(hook)方法。这两类钩子函数的作用是自动获取某些中间变量,因为pytorch会自动舍弃图计算中间结果。比如自变量x,中间变量y和结果z,我们在反向传播过程中输出y的梯度时会提示“None”,这就是pytorch自动舍弃的结果,我们可以通过注册钩子函数将这些中间结果获取。

register_forward_hook(hook):调用方法是“网络层结构.register_forward_hook(hook)”在相应的网络层结构正向传播时,获取其特征层,并执行自己定义好的hook函数中(其中包含model、input和output—输出特征层三个参数),来存储特征层信息。

register_backward_hook(hook):同样在指定的网络层结构执行完.backward()函数后调用钩子函数hook(model, grad_input, grad_output)。model是指定的网络层结构,grad_input是该层网络的所有输入的梯度(bias)、该层网络输入变量x的梯度(weight)和网络权重的梯度(x);而grad_output是指该层网络输出的梯度。

BaseCAM函数其他方法。先看2d是怎么处理的。

@staticmethod
    def get_loss(output, target):
        loss = output # 直接将预测值作为Loss回传,本文展示的是语义分割的结果
        return loss

    @staticmethod
    def get_cam_weights(grads): 
        # GAP全局平均池化,得到大小为[B,C,1,1]
        # 因为我们输入一张图,所以B=1,C为特征层的通道数
        return np.mean(grads, axis=(2,3), keepdims=True)

    @staticmethod
    def get_target_width_height(input_tensor):
        # 获取原图的高和宽
        width, height = input_tensor.size(-1), input_tensor.size(-2) 
        return width, height

    def get_cam_image(self, activations, grads):
        # 将梯度图进行全局平均池化,weights大小为[1, C, 1, 1],在通道上具有不同权重分布
        weights = self.get_cam_weights(grads) #对梯度图进行全局平均池化
        weighted_activations = weights[:, :, None, None] * activations #和原特征层加权乘
        cam = weighted_activations.sum(axis=1) # 在C维度上求和,得到大小为(1,h,w)
        return cam

    @staticmethod
    def scale_cam_img(cam, target_size=None):
        # 将cam缩放到与原始图像相同的大小,并将其值缩放到[0,1]之间
        result = []
        for img in cam: # 因为传入的目标层(target_layers)可能为复数,所以一层一层看
            img = img - np.min(img) #减去最小值
            img = img / (1e-7 + np.max(img))
            if target_size is not None:
                img = cv.resize(img, target_size) 
                # 注意:cv2.resize(src, (width, height)),width在height前
            result.append(img)
        result = np.float32(result)
        return result

    def compute_cam_per_layer(self, input_tensor):
        activations_list = [a.cpu().data.numpy() for a in 
                            self.activations_and_grads.activations] 
        grads_list = [a.cpu().data.numpy() for a in 
                      self.activations_and_grads.gradients]
        target_size = self.get_target_width_height(input_tensor)
        cam_per_target_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 #ReLU
            scaled = self.scale_cam_img(cam, target_size) 
            # 将CAM图缩放到原图大小,然后与原图叠加,这考虑到特征图可能小于或大于原图情况
            cam_per_target_layer.append(scaled[:, None, :]) 
             # 在None标注的位置加入一个维度,相当于scaled.unsqueeze(1),此时scaled大小为
             # [1,1,H,W]
        return cam_per_target_layer

    def aggregate_multi_layers(self, cam_per_layer):
        cam_per_layer = np.concatenate(cam_per_layer, axis=1) 
        # 在Channel维度进行堆叠,并没有做相加的处理
        cam_per_layer = np.maximum(cam_per_layer, 0) 
        # 当cam_per_layer任意位置值小于0,则置为0
        result = np.mean(cam_per_layer, axis=1) 
        # 在channels维度求平均,压缩这个维度,该维度返回为1
        # 也就是说如果最开始输入的是多层网络结构时,经过该方法会将这些网络结构
        # 在Channels维度上压缩,使之最后成为一张图
        return self.scale_cam_img(result)

    def __call__(self, input_tensor, target): # __init__()后自动调用__call__()方法
        # 这里的target就是目标的gt(双边缘)
        if self.use_cuda:
            input_tensor = input_tensor.cuda()
        # 正向传播的输出结果,创建ActivationsAndGradients类后调用__call__()方法,执行self.model(x)
        # 注意这里的output未经softmax,所以如果网络结构中最后的ouput不能经历激活函数
        output = self.activations_and_grads(input_tensor)[0]
        _output = output.detach().cpu()
        _output=_output.squeeze(0).squeeze(0)

        self.model.zero_grad()
        loss = self.get_loss(output, target)
        loss.backward(torch.ones_like(target), retain_graph=True)
        # 将输出结果作为Loss回传,记录回传的梯度图,
        # 梯度最大的说明在该层特征在预测过程中起到的作用最大,
        # 预测的部分展示出来就是整个网络预测时的注意力

        cam_per_layer = self.compute_cam_per_layer(input_tensor) 
        # 计算每一层指定的网络结构中的cam图
        return self.aggregate_multi_layers(cam_per_layer) 
        # 将指定的层结构中所有得到的cam图堆叠并压缩为一张图

    def __del__(self):
        self.activations_and_grads.release()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        self.activations_and_grads.release()
        if isinstance(exc_value, IndexError):
            # Handle IndexError here...
            print(
                f"An exception occurred in CAM with block: {exc_type}. Message: {exc_value}")
            return True

那么3d应该怎么修改呢,如果输出是[B,C,Z,Y,X]的话,可以一次输出Z中的一张图片。
首先在get_cam_weights函数中改成return np.mean(grads, axis=(3,4), keepdims=True)
然后get_cam_image中改成weighted_activations = weights[:, :, 10, :, :] * activations[:, :, 10, :, :]

那假如我的target_layers输出并不是单一数组,而是list或tuple怎么办呢?
那就在ActivationsAndGradients类中save_gradient和save_activation里面的output修改成你想输出的

官方代码
本文2d部分参考了:https://zhuanlan.zhihu.com/p/546875698

2024.1.15编辑:
看到有人需要代码,把代码上传到了github
如果有帮助,github可以给个star谢谢!

很多人对里面的10有误解,其实这个10只是一个举例,它可以是>=0,<Z轴最大值的任意数字,因为3d的特征图没办法可视化,只能输出2d图片可视化文章来源地址https://www.toymoban.com/news/detail-463707.html

到了这里,关于grad-cam用于3D分割网络的代码修改——以及特征层非常规输出的解决方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Grad-CAM简介

    论文名称:Grad-CAM: Visual Explanations from Deep Networks via Gradient-based Localization 论文下载地址:https://arxiv.org/abs/1610.02391 推荐代码(Pytorch):https://github.com/jacobgil/pytorch-grad-cam bilibili视频讲解:https://b23.tv/1kccjmb 对于常用的深度学习网络(例如CNN),普遍认为是个黑盒可解释性并不

    2024年02月02日
    浏览(33)
  • Pytorch使用Grad-CAM绘制热力图

    原理与代码学习自B站霹雳吧啦Wz老师 使用grad_cam对不同预测目标的图像做activate图。 效果见下图。 使用的是自己训练的MobileNetV2 需要模型feature的最后一层,模型训练权重。 代码如下: 还有别的图的效果。总之没有很精细,但也不错了。 大概就是在将本张图片分为感兴趣类

    2024年02月12日
    浏览(32)
  • 类别激活热力图grad-cam(pytorch)实战跑图

    类激活热力图:用于检查图像哪一部分对模型的最终输出有更大的贡献。具体某个类别对应到图片的那个区域响应最大,也就是对该类别的识别贡献最大 pytorch-grad-cam库代码GitHub代码 如果只想跑个图的话不用下! 作用:一是清晰直观的看看到底影响检测结果的特征;而是cv论

    2024年02月07日
    浏览(29)
  • 分类任务使用Pytorch实现Grad-CAM绘制热力图

    对于深度学习网络,在我们指定数据集类别的情况下,Grad-CAM能够绘制出相应的热力图,让我们能够非常直观的看出网络关注的主要区域与特征是什么。本文主要记录在绘制热力图过程中,自己碰到的一些实际问题,希望能对小伙伴们有所帮助。 以下是本文的参考视频和代码

    2024年02月04日
    浏览(34)
  • yolov5热力图可视化grad-cam踩坑经验分享

    最近在做热力图的可视化,网上搜了很多的资料,但是大部分都是需要在原网络结构上进行修改,非常的不方便。最后在网上找到一位博主分享的即插即用的模块,觉得效果还可以,但是中间有些细节,需要注意。 原博文地址: https://blog.csdn.net/qq_37706472/article/details/12871460

    2024年02月04日
    浏览(31)
  • 【PyTorch 实战2:UNet 分割模型】10min揭秘 UNet 分割网络如何工作以及pytorch代码实现(详细代码实现)

      U-Net,自2015年诞生以来,便以其卓越的性能在生物医学图像分割领域崭露头角。作为FCN的一种变体,U-Net凭借其Encoder-Decoder的精巧结构,不仅在医学图像分析中大放异彩,更在卫星图像分割、工业瑕疵检测等多个领域展现出强大的应用能力。UNet是一种常用于图像分割的卷

    2024年04月28日
    浏览(32)
  • (CVPR) PointNet:用于3D分类和分割的点集深度学习 - 详细解读

    目录 知识补充 网络解读 概括 局部和全局特征的融合(分割任务) 联合对准网络(T网络) 总结 点云存在的问题: 详细理解: 参考内容: 点云转换为体素的问题:数据庞大 本文设计了一种直接消耗点云的神经网络(保持了输入中心点的排列不变性) 刚性变换: 指保持物

    2024年03月21日
    浏览(64)
  • EPT-Net:用于3D医学图像分割的边缘感知转换器

    IEEE TRANSACTIONS ON MEDICAL IMAGING, VOL. 42, NO. 11, NOVEMBER 2023 卷积运算的 内在局部性 在建模长程依赖性方面存在局限性。尽管为序列到序列全局预测而设计的Transformer就是为了解决这个问题而诞生的,但由于 底层细节特征 不足,它可能会导致定位能力有限。此外,低级特征具有丰富

    2024年02月04日
    浏览(29)
  • 【3-D深度学习:肺肿瘤分割】创建和训练 V-Net 神经网络,并从 3D 医学图像中对肺肿瘤进行语义分割研究(Matlab代码实现)

     💥💥💞💞 欢迎来到本博客 ❤️❤️💥💥 🏆博主优势: 🌞🌞🌞 博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️ 座右铭: 行百里者,半于九十。 📋📋📋 本文目录如下: 🎁🎁🎁 目录 💥1 概述 📚2 运行结果 🎉3 参考文献 🌈4 Matlab代码实现 使用

    2024年02月15日
    浏览(33)
  • 论文阅读—2023.7.13:遥感图像语义分割空间全局上下文信息网络(主要为unet网络以及改unet)附加个人理解与代码解析

    前期看的文章大部分都是深度学习原理含量多一点,一直在纠结怎么改模型,论文看的很吃力,看一篇忘一篇,总感觉摸不到方向。想到自己是遥感专业,所以还是回归遥感影像去谈深度学习,回归问题,再想着用什么方法解决问题。 1、易丢失空间信息 在 Decoder 阶段输出多

    2024年02月16日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包