草庐IT

yolov5 Grad-CAM可视化,以及对可视化过程的分析

匿名的魔术师 2023-04-10 原文

目录

一、Grad CAM 可视化  实施与效果

1 、 配置和效果

 2、 修改处

二、代码分析 

 1、debug 参数记录

 2、打印日志处

3、功能处接口

(1)实例化模型

(2) 对img 的处理

(3) YOLOV5 Grad-CAM

(4) 前向传播过程

(5)设置保存结果的路径 

(6)热力图的实现

(7)画标签和矩形框

三、创建的文件和 构造的类都是干什么的,它们的作用都是什么

1、yolov5_object_detect.py

2、gradcam.py

3、main_gradcam.py

四、Grad CAM 实现的过程和代码

五、一些需要注意的地方

1、model的定义

2、得到的效果图片为什么那么多,并且有好有坏


Grad CAM 为 神经网络的一种可解释算法。

一、Grad CAM 可视化  实施与效果

直接运行 main_gradcam.py

或者终端运行 

python main_gradcam.py --img-path 路径

 

1 、 配置和效果

yolov5 Grad-CAM可视化的修改可参考这两篇博客:YOLOv5结合GradCAM热力图可视化 和 【YOLOv5】结合GradCAM热力图可视化,我用的好像跟他不一样版本,不过问题不大,有几处需要改一下,见 2、修改处

实际效果如下所示

 2、 修改处

yolov5_object_detector.py ---31

self.model = attempt_load(model_weight, device=device, inplace=False, fuse=False)

注意 device 处修改了

main_gradcam.py --- 25

parser.add_argument('--model-path', type=str, default="yolov5s.pt", help='Path to the model')

加载模型的路径改了。 

二、代码分析 

 1、debug 参数记录

1) colors

2)

prediction

 logits

 __

2)x

 2、打印日志处

(1) main_gradcam.py --- 82

Loading the model

(2)gradcam.py --- 65

person, model-backward took:  90.6689 seconds

(3)main_gradcam.py --- 104

Saving the final image at outputs/bus/gradcam

 (4)main_gradcam.py --- 120

17_0.jpg done!!

(5)main_gradcam.py --- 121

print(f'Total time : {round(time.time() - tic, 4)} s')

3、功能处接口

(1)实例化模型

 man_gradcam.py --- 84

model = YOLOV5TorchObjectDetector(args.model_path, device, img_size=input_size, names=names)

用的是创建的 CAM 代码 ,但进入这里面会发现,初始化时依然用的是attemp_load函数来加载模型,如下所示

 self.model = attempt_load(model_weight, device=device, inplace=False, fuse=False)

注意这其中的

self.model.requires_grad_(True)

其为 nn.Model 模块中的方法。

(2) 对img 的处理

 main_gradcam.py --- 87 

torch_img = model.preprocessing(img[..., ::-1])
原img ----> 增加1维 ----> letterbox ----> 调换维度----> to Tensor ----> /255 ----> input

(3) YOLOV5 Grad-CAM

main_gradcam.py --- 93

saliency_method = YOLOV5GradCAM(model=model, layer_name=target_layer, img_size=input_size)

这个主要是挂hook,

(4) 前向传播过程

始 main_gradcam.py ---96

 masks, logits, [boxes, _, class_names, conf] = saliency_method(torch_img)

跳转到 gradcam.py ---55

preds, logits = self.model(input_img)

接着跳转到 yolov5_object_detector.py --- 147

即YOLOV5TorchObjectDetector类中的 前向传播 函数中,

prediction, logits, _ = self.model(img, augment=False)

接着跳转到 yolo.py  --- 233

    def forward(self, x, augment=False, profile=False, visualize=False):
        if augment:
            return self._forward_augment(x)  # augmented inference, None
        return self._forward_once(x, profile, visualize)  # single-scale inference, train

接着跳转到 yolo.py --- 137 

 def _forward_once(self, x, profile=False, visualize=False):
        y, dt = [], []  # outputs
        # print('================')
        for m in self.model:  # m 与 model <c-8>
            # print('====================')
            # print("i is {}".format(m.i))
            # print('====================')
            if m.f != -1:  # if not from previous layer
                # print("when i is {},f is {}".format(m.i, m.f))
                x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f]  # from earlier layers
            if profile:
                self._profile_one_layer(m, x, dt)
            x = m(x)  # run
            y.append(x if m.i in self.save else None)  # save output  这里注意 m.i 这个属性
            if visualize:
                feature_visualization(x, m.type, m.i, save_dir=visualize)
        return x

到这里与原来的 yolov5 推理过程接轨。这里注意,当 执行最后一个模块时,也就是当 m 的值为 Detect的时候,由于它是输出 head, 我们对它这里做了改变,所以 执行 Detect 的时候跟之前会有不同。

最终由于Detect函数种的改动

return x if self.training else (torch.cat(z, 1), torch.cat(logits_, 1), x)

所以返回值有三个,第一个是原yolov5的输出,第二个是输入到Detect head检测头的输入的类别信息, 第三个是 输入到Detect head 的输入。其具体信息见 debug 参数中的 2)

接下来进行非极大值抑制,yolov5_object_detector.py --- 148

prediction, logits = self.non_max_suppression(prediction, logits, self.confidence, self.iou_thresh,
                                                      classes=None,
                                                      agnostic=self.agnostic)

(5)设置保存结果的路径 

 main_gradcam.py --- 101

save_path = f'{args.output_dir}{imgae_name[:-4]}/{args.method}'

从这里可以看出主要由  args.output_dir 和 args.method 决定

(6)热力图的实现

 main_gradcam.py --- 113

res_img, heat_map = get_res_img(bbox, mask, res_img)

(7)画标签和矩形框

  main_gradcam.py --- 114

res_img = plot_one_box(bbox, res_img, label=label, color=colors[int(names.index(cls_name))],
                                   line_thickness=3)

三、创建的文件和 构造的类都是干什么的,它们的作用都是什么

1、yolov5_object_detect.py

 这个文件中 只 包含了一个类,即 YOLOV5TorchObjectDetector。

首先来看看它的初始化函数,最重要的地方是

self.model = attempt_load(model_weight, device=device, inplace=False, fuse=False)

这里 利用原 yolov5 中的 attempt_load 函数 来加载model。再来看看其他函数,

def non_max_suppression   # nms 函数
    def yolo_resize(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True):  # 利用 letterbox函数 对图片进行处理

        return letterbox(img, new_shape=new_shape, color=color, auto=auto, scaleFill=scaleFill, scaleup=scaleup)

yolo_resize 函数 也是 调用了 原yolov5 中的 letterbox 函数来进行 img 的 resize 和 填充。

    def preprocessing(self, img):
        if len(img.shape) != 4:  # 增加一维
            img = np.expand_dims(img, axis=0)  # 举例 ndarray:(1,1080,810,3)
        im0 = img.astype(np.uint8)
        img = np.array([self.yolo_resize(im, new_shape=self.img_size)[0] for im in im0])  # 利用letterbox函数对img进行处理,举例 ndarray:(1,640,480,3)
        img = img.transpose((0, 3, 1, 2))  # 举例 ndarray:(1,3,640,480)
        img = np.ascontiguousarray(img)  # 举例 ndarray:(1,3,640,480) ascontiguousarray函数将一个内存不连续存储的数组转换为内存连续存储的数组,使得运行速度更快。
        img = torch.from_numpy(img).to(self.device)  # numpy to tensor
        img = img / 255.0
        return img

processing函数 是对原图片进行处理, 从而得到输入到 model 的图片。这里调用了它自己的方法 yolo_resize,而yolo_resize实际上就是 letterbox函数。哈哈。

最后是它的前向传播函数,首先执行的是原yolov5的前向传播过程,不过在Detect head 做了修改,返回值改变了。然后进行NMS,与原来不同的是这里的logits也同步处理。最后返回

return [self.boxes, self.classes, self.class_names, self.confidences], logits

返回 [bbox信息,类别信息序号,类别的标签即名称,置信度分数] , logits 。

总的来说,这个文件中包含的 YOLOV5TorchObjectDetector 类实现的是一个接口,包括对原img的处理、yolov5的前向传播和NMS后处理。它联系原来的yolov5 对输入进行处理从而得到 Grad CAM 想要的输出。

2、gradcam.py

在这之前,首先需要了解两个关键的地方

一是 pytorch 的hook机制,二是 Grad CAM 论文中的原理。这两篇博客写的非常棒。

首先,看这个 

def find_yolo_layer(model, layer_name): 

函数,它的作用就是 得到需要的输出层的名字。 yolov5 中 Detect head 的输入有三个部分,分别来自 17,20,23,所以这里 它事先就设置好了这几个不同层,

target_layers = ['model_17_cv3_act', 'model_20_cv3_act', 'model_23_cv3_act']

从这里就可以看出 它可视化的层是 Detect 层之前的卷积层,而且是三个层,根据结果也会看到不同的层 可视化 后的效果不一样,越是靠后的层 效果 越好。

接下来就是 class YOLOV5GradCAM: 这个类了。初始化函数中,实例化model,最重要的就是 挂hook 操作。

# 定义 钩子函数
        def backward_hook(module, grad_input, grad_output):
            self.gradients['value'] = grad_output[0]
            return None

        def forward_hook(module, input, output):
            self.activations['value'] = output
            return None

# 挂 hook
        target_layer.register_forward_hook(forward_hook)  # 挂hook
        target_layer.register_full_backward_hook(backward_hook)

 这里  反向传播记录 梯度, 前向传播 记录的是 卷积层的输出,由于需要记录的输出的模块的最后一层 都是 SiLU 激活函数,所以命名为 self.activations。(详见yolov5前向传播过程

前向传播 forward 函数中,此时的 self.model 是 YOLOV5TorchObjectDetector 类,所以返回的是

return [self.boxes, self.classes, self.class_names, self.confidences], logits

这个 hook 是什么时候启动的呢?首先,当执行

preds, logits = self.model(input_img)

时,启动model 的前向传播,在这个过程中 forward_hook 被启动,记录了相关卷积层的输出。当执行

score.backward(retain_graph=True)

时,backward_hook被启动,记录反向传播时计算的梯度。至此, self.gradients 和 self.activations 记录了相关的数据。然后执行 Grad -CAM 算法,得到其 map ,然后进行线性插值和归一化,最后返回

 return saliency_maps, logits, preds

这只是一层的结果。注意在主文件中是分层进行的

for target_layer in target_layers:

总的来说, 该文件中主要的就是 Gram CAM 算法的实现,其中还包括 挂 hook 的操作。

3、main_gradcam.py

实现整个流程,

def get_res_img  # 为画热力图操作
def plot_one_box  # 为画bbox 和 label

四、Grad CAM 实现的过程和代码

gradcam.py ---70

b, k, u, v = gradients.size()  # 举例 1, 128, 80, 60
alpha = gradients.view(b, k, -1).mean(2)  # 按通道求权重

 实现的是

weights = alpha.view(b, k, 1, 1)
saliency_map = (weights * activations).sum(1, keepdim=True)
saliency_map = F.relu(saliency_map)

实现的是 

然后进行双线性插值将尺寸扩大到原img尺寸,再进行归一化将数值 调整到 0 ~ 1 范围之间。

五、一些需要注意的地方

1、model的定义

saliency_method -----> YOLOV5GradCAM 对象 
YOLOV5GradCAM 中的 model  -----> YOLOV5TorchObjectDetector 对象 YOLOV5TorchObjectDetector 中的model -----> 原yolov5 的 model

2、得到的效果图片为什么那么多,并且有好有坏

首先,它一共看了 三个 layer 的输出 

target_layers = ['model_17_cv3_act', 'model_20_cv3_act', 'model_23_cv3_act']

其次,每个层的输出可能会又包含多个不同的目标,有几个矩形框它就分别绘制不同的效果。

所以,不同层的输出 得到的效果是不一样的, 实验证明越靠后的卷积层输出 得到的效果越明显。

有关yolov5 Grad-CAM可视化,以及对可视化过程的分析的更多相关文章

  1. ruby - Ruby 中的波形可视化 - 2

    我即将开始一个将录制和编辑音频文件的项目,我正在寻找一个好的库(最好是Ruby,但会考虑Java或.NET以外的任何库)以进行实时可视化波形。有人知道我应该从哪里开始搜索吗? 最佳答案 要流入浏览器的数据量很大。Flash或Flex图表可能是唯一能提高内存效率的解决方案。Javascript图表往往会因大型数据集而崩溃。 关于ruby-Ruby中的波形可视化,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c

  2. Unity数据可视化图表插件XCharts3.0发布 - 2

    Unity数据可视化图表插件XCharts3.0发布历时8个多月,业余时间,断断续续,XCharts3.0总算发布了。如果要打个满意度,我给3.0版本来个80分。对于代码框架结构设计的调整改动,基本符合预期,甚是满意。相比之前的1.0和2.0版本,我认为3.0才是一个拿得出手给广大开发者使用的版本。1.0发布的时候,很兴奋,从0.1到1.0,也磨了一年,真的等不及想给大家试用了,还特地写过一篇文章以示庆祝。那个时候,1.0虽然还还不够完善,功能也不够丰富,但它是XCharts的开始,没有1.0,也就没有后面的2.0和3.0。后面的2.0发布,做了很多改进和优化,随着版本迭代,慢慢的发现有不少硬

  3. micropython复现经典单片机项目(二)可视化音频 频谱解析(基本搞定) - 2

    本人是音乐爱好者,从小就特别喜欢那个随着音乐跳动的方框效果,就是这个:arduino上一大把对,我忍你很久了,我就想用mpy做,全网没有,行我自己研究。果然兴趣是最好的老师,我之前有篇博客专门讲音频,有兴趣的可以回顾一下。提到可视化频谱,必然绕不开fft,大学学过这玩意,当时一心玩,老师讲的一个字都么听进去,网上教程简略扫了一下,大该就是把时域转频域的工具,我大mpy居然没有fft函数,奶奶的,先放着。音频信息如何收集?第一种傻瓜式的ADC,模拟转数字,原始粗暴,第二种,I2S库,我之前博客有讲过,数据是PCM编码。然后又去学PCM编码,一学豁然开朗,舒服,以代码为例:audio_in=I2S

  4. 关于yolov5训练时参数workers和batch-size的理解 - 2

    关于yolov5训练时参数workers和batch-size的理解yolov5训练命令workers和batch-size参数的理解两个参数的调优总结yolov5训练命令python.\train.py--datamy.yaml--workers8--batch-size32--epochs100yolov5的训练很简单,下载好仓库,装好依赖后,只需自定义一下data目录中的yaml文件就可以了。这里我使用自定义的my.yaml文件,里面就是定义数据集位置和训练种类数和名字。workers和batch-size参数的理解一般训练主要需要调整的参数是这两个:workers指数据装载时cpu所使

  5. ruby-on-rails - Ruby On Rails 4.2 的可视化日志查看器 - 2

    我以前在Laravel4上工作过,它有一个很棒的日志查看器工具laravellogviewer查看demo我正在寻找与Rubyonrails4.2非常相似的东西,如果你们知道Rails4.2的任何好的可视化日志记录GEM,请告诉我..从代码我需要记录不同的日志级别,这个工具应该直观地组织我的日志,谢谢.. 最佳答案 这应该可以帮助您入门https://github.com/shadabahmed/logstasher如其所说Thisgemisheavilyinspiredfromlograge,butit'sfocusedonone

  6. 通过可视化运维配置,实现故障秒级自愈 - 2

    急促的告警铃声响彻寂静的夜晚。对运维人来说,晚间值守耗费更大的精力,往往一个简单的磁盘使用率告警通知,就不得不爬起来进行处理,毕竟告警无小事,对于小问题,运维人也不能心存侥幸心理。虽然有着值班人员和团队的支撑,但频繁的告警还是让运维人员精疲力竭,如何让系统的稳定性提高,减轻一线人员的工作量,减轻一线人员的压力?通过智能运维,实现故障自愈将成为不可避免的选择。故障自愈是提升企业网络系统可用性和降低故障处理的人力投入,实现故障自愈从"人工处理"到"无人值守"的变革。通过实时发现告警,进行预诊断分析,判断告警类型和级别,如果是一般告警,平台进行自动恢复,如果是严重复杂告警则通过告警通知、运维工单等形

  7. 必看新手教程!一篇就够!pycharm链接云服务器--yolov5 yolov7训练自己的数据集(矩池云) - 2

    趁着寒假期间稍微尝试跑了一下yolov5和yolov7的代码,由于自己用的笔记本没有独显,台式机虽有独显但用起来并不顺利,所以选择了租云服务器的方式,选择的平台是矩池云(价格合理,操作便捷)需要特别指出的是,如果需要用pycharm链接云服务器训练,必须要使用pycharm的专业版而不是社区版,专业版可以使用SSH服务连接云服务器。关于专业版的获取,据我所知一是可以买,二是如果你是在校大学生,可以用学生证向JetBrain申请专业版使用权,我就是通过这种方式激活专业版账户的,我记得当时两三天官方就发激活邮件了,还是很人性化的,使用期一年。下面开始正题本教程只涉及将yolov5及yolov7跑通

  8. javascript - 谷歌可视化堆积面积图 : Add total to tooltip - 2

    我正在使用谷歌可视化API创建堆积面积图。当用户将鼠标悬停在图表内的一个点上时,我希望它显示该位置点的总和,以及这些点的值。第二点,我可以通过指定选项focusTarget:'category'轻松实现。我希望在类似的外观和感觉中,在total的工具提示中多一行。我尝试通过添加一个名为Total的额外列来实现此目的,该列的值为0,但工具提示等于总和。然而,这会向图例和图表本身添加一个空行,这在视觉上并不吸引人。我觉得这应该是开箱即用的东西,但我找不到解决这个问题的方法。如果有人知道解决这个问题的好方法,请回答。提前致谢。 最佳答案

  9. javascript - 用 Dart 可视化? - 2

    我的工作涉及大量的可视化。我一直在用D3.js和JavaScriptInfovistoolkit我最近了解到Dart如何成为开发Web应用程序的新方法。Q1。Dart是否提供任何用于可视化的库(某种级别的D3.js或JavaScriptInfovistoolkit)?Q2。如果我继续使用Dart,我可以使用D3.js吗?/JavascriptInfovistoolkit与Dart一起?编辑:我在互联网上发现wecanuseJavascriptalongwithDart.我经历了DartFAQ,但无法真正找到与可视化库或D3.js本身相关的任何内容。 最佳答案

  10. javascript - 谷歌可视化-点击事件 - 2

    我正在使用googlevisulaization绘制饼图。我面临的问题是,我无法捕获饼图上的点击事件。我正在这样做。functiondrawchartfromRe(){dashboard=newgoogle.visualization.Dashboard(document.getElementById('dashboard_div'));//alert("RefuelLength"+totrefuelList.length);//alert("Vehicleid:"+totrefuelList[0].vehicleId);//google.load("visualization","1

随机推荐