DCAMnet网络复现与讲解

距论文阅读完毕已经过了整整一周多。。。终于抽出时间来写这篇辣!~

论文阅读笔记放这里:

基于可变形卷积和注意力机制的带钢表面缺陷快速检测网络DCAM-Net(论文阅读笔记)-CSDN博客


 为了方便观看,我把结构图也拿过来了。

Overall architecture of the DCAM-Net.

众所周知,DCAMnet是在yolox的基础上改进、升级的。(应该只有博主这个憨憨对着yolov5网络改了两天,被学长一语道破天机后才幡然醒悟的吧?)

既然这样,我们就需要先对照着原yolox的网络结构,找出新增的或改动的结构,下面是yolox的网络结构图(温馨提示,在csdn页面观看图片很难受,下载后打开方式选择照片可以看得很清楚)。

yolox的项目请自行去github上获取,下面推荐一篇->

GitHub - renyuehe/bilibili-yolox_simple_voc_coco: yolox 详细注释-简化版,已配置好 voc + coco 数据集

YOLOx网络结构图

改动点

一 ,CSPLayer

相对于yolox,DCAMnet在所有CSPLayer层的右支路都加上了一个EDE-block,而EDE-block是论文作者新提出的一个模块,结构不算复杂,可以通过开头放的传送门去论文阅读笔记学一学。

EDE-block中的DFMConv即可变形卷积模块,本模块的代码如下:

此模块的代码放在该位置->yolox\models\network_blocks.py

class DeformConv(nn.Module):def __init__(self, in_channel, out_channel, kernel_size=3, padding=1, stride=1, bias=None, modulation=False):"""Args:modulation (bool, optional): If True, Modulated Defomable Convolution (Deformable ConvNets v2)."""super(DeformConv, self).__init__()self.kernel_size = kernel_sizeself.padding = paddingself.stride = strideself.zero_padding = nn.ZeroPad2d(padding)# conv则是实际进行的卷积操作,注意这里步长设置为卷积核大小,因为与该卷积核进行卷积操作的特征图是由输出特征图中每个点扩展为其对应卷积核那么多个点后生成的。self.conv = nn.Conv2d(in_channel, out_channel, kernel_size=kernel_size, stride=kernel_size, bias=bias)# p_conv是生成offsets所使用的卷积,输出通道数为卷积核尺寸的平方的2倍,代表对应卷积核每个位置横纵坐标都有偏移量。self.p_conv = nn.Conv2d(in_channel, 2 * kernel_size * kernel_size, kernel_size=3, padding=1, stride=stride)nn.init.constant_(self.p_conv.weight, 0)self.p_conv.register_full_backward_hook(self._set_lr)self.modulation = modulation  # modulation是可选参数,若设置为True,那么在进行卷积操作时,对应卷积核的每个位置都会分配一个权重。if modulation:self.m_conv = nn.Conv2d(in_channel, kernel_size * kernel_size, kernel_size=3, padding=1, stride=stride)nn.init.constant_(self.m_conv.weight, 0)self.m_conv.register_full_backward_hook(self._set_lr)@staticmethoddef _set_lr(module, grad_input, grad_output):grad_input = (grad_input[i] * 0.1 for i in range(len(grad_input)))grad_output = (grad_output[i] * 0.1 for i in range(len(grad_output)))def forward(self, x):offset = self.p_conv(x)if self.modulation:m = torch.sigmoid(self.m_conv(x))dtype = offset.data.type()ks = self.kernel_sizeN = offset.size(1) // 2if self.padding:x = self.zero_padding(x)# (b, 2N, h, w)p = self._get_p(offset, dtype)# (b, h, w, 2N)p = p.contiguous().permute(0, 2, 3, 1)q_lt = p.detach().floor()q_rb = q_lt + 1q_lt = torch.cat([torch.clamp(q_lt[..., :N], 0, x.size(2) - 1), torch.clamp(q_lt[..., N:], 0, x.size(3) - 1)],dim=-1).long()q_rb = torch.cat([torch.clamp(q_rb[..., :N], 0, x.size(2) - 1), torch.clamp(q_rb[..., N:], 0, x.size(3) - 1)],dim=-1).long()q_lb = torch.cat([q_lt[..., :N], q_rb[..., N:]], dim=-1)q_rt = torch.cat([q_rb[..., :N], q_lt[..., N:]], dim=-1)# clip pp = torch.cat([torch.clamp(p[..., :N], 0, x.size(2) - 1), torch.clamp(p[..., N:], 0, x.size(3) - 1)], dim=-1)# bilinear kernel (b, h, w, N)g_lt = (1 + (q_lt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_lt[..., N:].type_as(p) - p[..., N:]))g_rb = (1 - (q_rb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_rb[..., N:].type_as(p) - p[..., N:]))g_lb = (1 + (q_lb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_lb[..., N:].type_as(p) - p[..., N:]))g_rt = (1 - (q_rt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_rt[..., N:].type_as(p) - p[..., N:]))# (b, c, h, w, N)x_q_lt = self._get_x_q(x, q_lt, N)x_q_rb = self._get_x_q(x, q_rb, N)x_q_lb = self._get_x_q(x, q_lb, N)x_q_rt = self._get_x_q(x, q_rt, N)# (b, c, h, w, N)x_offset = g_lt.unsqueeze(dim=1) * x_q_lt + \g_rb.unsqueeze(dim=1) * x_q_rb + \g_lb.unsqueeze(dim=1) * x_q_lb + \g_rt.unsqueeze(dim=1) * x_q_rt# modulationif self.modulation:m = m.contiguous().permute(0, 2, 3, 1)m = m.unsqueeze(dim=1)m = torch.cat([m for _ in range(x_offset.size(1))], dim=1)x_offset *= mx_offset = self._reshape_x_offset(x_offset, ks)out = self.conv(x_offset)return outdef _get_p_n(self, N, dtype):# 由于卷积核中心点位置是其尺寸的一半,于是中心点向左(上)方向移动尺寸的一半就得到起始点,向右(下)方向移动另一半就得到终止点p_n_x, p_n_y = torch.meshgrid(torch.arange(-(self.kernel_size - 1) // 2, (self.kernel_size - 1) // 2 + 1),torch.arange(-(self.kernel_size - 1) // 2, (self.kernel_size - 1) // 2 + 1),indexing='ij')# (2N, 1)p_n = torch.cat([torch.flatten(p_n_x), torch.flatten(p_n_y)], 0)p_n = p_n.view(1, 2 * N, 1, 1).type(dtype)return p_ndef _get_p_0(self, h, w, N, dtype):# p0_y、p0_x就是输出特征图每点映射到输入特征图上的纵、横坐标值。p_0_x, p_0_y = torch.meshgrid(torch.arange(1, h * self.stride + 1, self.stride),torch.arange(1, w * self.stride + 1, self.stride),indexing='ij')p_0_x = torch.flatten(p_0_x).view(1, 1, h, w).repeat(1, N, 1, 1)p_0_y = torch.flatten(p_0_y).view(1, 1, h, w).repeat(1, N, 1, 1)p_0 = torch.cat([p_0_x, p_0_y], 1).type(dtype)return p_0# 输出特征图上每点(对应卷积核中心)加上其对应卷积核每个位置的相对(横、纵)坐标后再加上自学习的(横、纵坐标)偏移量。# p0就是将输出特征图每点对应到卷积核中心,然后映射到输入特征图中的位置;# pn则是p0对应卷积核每个位置的相对坐标;def _get_p(self, offset, dtype):N, h, w = offset.size(1) // 2, offset.size(2), offset.size(3)# (1, 2N, 1, 1)p_n = self._get_p_n(N, dtype)# (1, 2N, h, w)p_0 = self._get_p_0(h, w, N, dtype)p = p_0 + p_n + offsetreturn pdef _get_x_q(self, x, q, N):# 计算双线性插值点的4邻域点对应的权重b, h, w, _ = q.size()padded_w = x.size(3)c = x.size(1)# (b, c, h*w)x = x.contiguous().view(b, c, -1)# (b, h, w, N)index = q[..., :N] * padded_w + q[..., N:]  # offset_x*w + offset_y# (b, c, h*w*N)index = index.contiguous().unsqueeze(dim=1).expand(-1, c, -1, -1, -1).contiguous().view(b, c, -1)x_offset = x.gather(dim=-1, index=index).contiguous().view(b, c, h, w, N)return x_offset@staticmethoddef _reshape_x_offset(x_offset, ks):b, c, h, w, N = x_offset.size()x_offset = torch.cat([x_offset[..., s:s + ks].contiguous().view(b, c, h, w * ks) for s in range(0, N, ks)],dim=-1)x_offset = x_offset.contiguous().view(b, c, h * ks, w * ks)return x_offset

有了DFMConv模块后,就可以完成EDE-block,EDE-block的结构图和代码如下:

此模块的代码放在该位置->yolox\models\network_blocks.py

(其实我就放在可变形卷积的下面)

 

class EDE_block(nn.Module):def __init__(self, in_channel, out_channel):super(EDE, self).__init__()self.branch0 = nn.Sequential(BaseConv(in_channel, out_channel, ksize=1, stride=1),BaseConv(out_channel, out_channel, ksize=3, stride=1))#左支路分别经过1*1和3*3的两个BaseConv。self.branch1 = nn.Sequential(DeformConv(in_channel, out_channel),nn.BatchNorm2d(out_channel),nn.SiLU(inplace=True),BaseConv(out_channel, out_channel, ksize=3, stride=1))#中支路首先经过(可变形卷积,BN归一化,SiLU激活)这三块看作DFMConv模块,然后经过一个3*3的BaseConv。self.conv_cat = BaseConv(3*out_channel, out_channel, ksize=1, stride=1)#最后将左、中、和右(右支路就是原特征层)完成拼接后的结果压缩回需要的通道数即out_channeldef forward(self, x):x0 = self.branch0(x)x1 = self.branch1(x)x_cat = torch.cat((x, x0, x1), dim=1)#在维度1即通道上进行拼接return self.conv_cat(x_cat)

有了EDE-block后,可以完成CSPLayer层,结构图和代码如下:

#因为取的名字和原yolox网络相同,所以请注意修改或注释原网络的CSPLayer层,以便网络正确调用修改过后的层!~

此模块的代码放在该位置->yolox\models\network_blocks.py

 

 

class CSPLayer(nn.Module):"""C3 in yolov5, CSP Bottleneck with 3 convolutions"""def __init__(self,in_channels,out_channels,n=1,shortcut=True,expansion=0.5,depthwise=False,act="silu",):"""Args:in_channels (int): input channels.out_channels (int): output channels.n (int): number of Bottlenecks. Default value: 1."""# ch_in, ch_out, number, shortcut, groups, expansionsuper().__init__()hidden_channels = int(out_channels * expansion)  # hidden channelsself.conv1 = BaseConv(in_channels, hidden_channels, 1, stride=1, act=act)self.conv2 = BaseConv(in_channels, hidden_channels, 1, stride=1, act=act)self.edeblock = EDE_block(hidden_channels, hidden_channels)#与源代码唯一的区别,在右支路加了一个EDE-block。self.conv3 = BaseConv(2 * hidden_channels, out_channels, 1, stride=1, act=act)def forward(self, x):x_1 = self.conv1(x)x_1 = self.edeblock(x_1)x_2 = self.conv2(x)x = torch.cat((x_1, x_2), dim=1)#在维度1即通道上进行拼接return self.conv3(x)#拼接后再压缩回原通道

至此CSPLayer的改动就完成了!


二,backbone部分的dark5处

在此处添加了一个CA注意力模块来替换了源码的SPP结构,替换的原因请通过开头传送门移动论文阅读笔记自行学习。

CA即Coordinate Attention注意力模块,代码如下:

此模块的代码放在该位置->yolox\models\darknet.py

(其实就是backbone源码部分的上面,当然你放network_blocks.py文件里面也是没问题的,但是你就得在darknet.py导入写好的CA模块)

 

#CA注意模块
class h_sigmoid(nn.Module):def __init__(self, inplace=True):super(h_sigmoid, self).__init__()self.relu = nn.ReLU6(inplace=inplace)def forward(self, x):return self.relu(x + 3) / 6class h_swish(nn.Module):def __init__(self, inplace=True):super(h_swish, self).__init__()self.sigmoid = h_sigmoid(inplace=inplace)def forward(self, x):return x * self.sigmoid(x)class CA(nn.Module):def __init__(self, inp, oup, reduction=32):super(CA, self).__init__()self.pool_h = nn.AdaptiveAvgPool2d((None, 1))self.pool_w = nn.AdaptiveAvgPool2d((1, None))mip = max(8, inp // reduction)self.conv1 = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0)self.bn1 = nn.BatchNorm2d(mip)self.act = h_swish()self.conv_h = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)self.conv_w = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0)def forward(self, x):identity = xn, c, h, w = x.size()x_h = self.pool_h(x)x_w = self.pool_w(x).permute(0, 1, 3, 2)y = torch.cat([x_h, x_w], dim=2)y = self.conv1(y)y = self.bn1(y)y = self.act(y)x_h, x_w = torch.split(y, [h, w], dim=2)x_w = x_w.permute(0, 1, 3, 2)a_h = self.conv_h(x_h).sigmoid()a_w = self.conv_w(x_w).sigmoid()out = identity * a_w * a_hreturn out

有了CA模块就能在dark上进行添加了,如下:

就加这一行代码,替换原来的spp结构->

CA(base_channels * 16, base_channels * 16),

 三,检测头

DCAMnet把Head的右支路上的第一个BaseConv替换成了DFMConv,结构图如下:

 ​​​​

yolox的检测头
DCAMnet的检测头

 

所以先在->yolox\models\yolo_head.py

中导入DFMConv模块,如下图:

然后在reg_convs模块中用DFMConv模块替换原来的BaseConv模块,如下图:

DeformConv(int(256 * width),int(256 * width)),
nn.BatchNorm2d(int(256 * width)),
nn.SiLU(inplace=True),

 


至此,DCAMnet就完成复现了,祝愿各位代码成功跑通

o(* ̄▽ ̄*)ブ

博主自己对着自己写的教程,从头做了一遍,是没有任何问题的!

因为博主也是才学了不到2月半的小白,所以难免出现问题,如果有问题欢迎在评论区指出!~ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/186588.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软考:2024年软考高级:软件工程

软考:2024年软考高级: 提示:系列被面试官问的问题,我自己当时不会,所以下来自己复盘一下,认真学习和总结,以应对未来更多的可能性 关于互联网大厂的笔试面试,都是需要细心准备的 (1…

2023 年 IntelliJ IDEA下载、安装教程,附详细图文

大家好,今天为大家带来的是 2023年 IntelliJ IDEA 下载、安装教程,超详细的图文教程,亲测可用。 文章目录 1 IDEA 下载2 IDEA 安装3 IDEA 使用4 快捷键新手必须掌握:Ctrl:Alt:Shift:Ctrl Alt&a…

机械臂仿真之vrep如添加视觉传感器

基于视觉的机械臂作业任务,如何在vrep中加入视觉传感器,并获取画面?

认证鉴权方案

现在一般使用比较多的认证方式有四种: SessionTokenSSO单点登录OAtuth登录1.Cookie + Session 最常见的就是 Cookie + Session 认证。 Session,是一种有状态的会话管理机制,其目的就是为了解决HTTP无状态请求带来的问题。 当用户登录认证请求通过时,服务端会将用户的信息存…

【排序,直接插入排序 折半插入排序 希尔插入排序】

文章目录 排序排序方法的分类插入排序直接插入排序折半插入排序希尔插入排序 排序 将一组杂乱无章的数据按照一定规律排列起来。将无序序列排成一个有序序列。 排序方法的分类 储存介质: 内部排序:数据量不大,数据在内存,无需…

JeecgBoot低代码开发—Vue3版前端入门教程

JeecgBoot低代码开发—Vue3版前端入门教程 后端接口配置VUE3 必备知识1.vue3新特性a. https://v3.cn.vuejs.org/b.setup的用法c.ref 和 reactive 的用法d.新版 v-model 的用法e.script setup的用法 2.TypeScript基础 后端接口配置 如何修改后台项目路径 http://127.168.3.52:8…

【brpc学习实践】ParallelChannel的使用与并行请求

概览 ParallelChannel (有时被称为“pchan”)同时访问其包含的sub channel,并合并它们的结果。用户可通过CallMapper修改请求,通过ResponseMerger合并结果。ParallelChannel看起来就像是一个Channel: 支持同步和异步访问。 发起异步操作后可以立刻删除。 可以取消。 支持超…

ELFK集群部署(Filebeat+ELK) 本地收集nginx日志 远程收集多个日志

filebeat是一款轻量级的日志收集工具,可以在非JAVA环境下运行。 因此,filebeat常被用在非JAVAf的服务器上用于替代Logstash,收集日志信息。 实际上,Filebeat几乎可以起到与Logstash相同的作用, 可以将数据转发到Logst…

【产品功能】dolphinscheduler怎么修改,实现超时就结束掉当前工作流

超时就结束工作流 代码 代码 MasterExecThread类 的 runProcess方法 里面有超时告警,原本里面只有超时告警的,这时候我只要加上海豚自己写好的结束任务的方法endProcess()方法

pycharm编译报错处理

1.c生成工具下载 https://visualstudio.microsoft.com/visual-cpp-build-tools/ 在这里插入图片描述 pip install pycocotools

遭到美国做空机构“灰熊”做空后,人工智能公司商汤科技股价暴跌

来源:猛兽财经 作者:猛兽财经 猛兽财经获悉,在遭到美国做空机构Grizzly Research(灰熊)指控夸大收入后,商汤科技的股价在周二一度下跌了9.7%。 Grizzly Research在周二发布的一份报告中称,商汤…

基于java的职工便利系统【大学期末结课作业】

需求分析 背景和目标 基于现在的各类企业的职工在企业日常工作及生活中所需要的各类需求,通过使用Java语言开发一个职工便利系统,以方便职工完成公司内的日常事务,提高工作效率。 设计目的: 设计职工便利系统的主要目的是为了提升职工的工作…

FFmpeg之将视频转为16:9(横屏)或9:16(竖屏)(一)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…

全文检索[ES系列] - 第495篇

历史文章(文章累计490) 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 M…

知识蒸馏代码实现(以MNIST手写数字体为例,自定义MLP网络做为教师和学生网络)

dataloader_tools.py import torchvision from torchvision import transforms from torch.utils.data import DataLoaderdef load_data():# 载入MNIST训练集train_dataset torchvision.datasets.MNIST(root "../datasets/",trainTrue,transformtransforms.ToTens…

QT Day01 qt概述,创建项目,窗口属性,按钮,信号与槽

1.qt概述 1.什么是qt Qt 是一个跨平台的 C 图形用户界面应用程序框架。它为应用程序开发者提供建立艺 术级图形界面所需的所有功能。它是完全面向对象的,很容易扩展,并且允许真正的组 件编程。 2.支持的平台 Windows – XP 、 Vista 、 Win7 、 Win8…

Java(119):ExcelUtil工具类(org.apache.poi读取和写入Excel)

ExcelUtil工具类(XSSFWorkbook读取和写入Excel),入参和出参都是:List<Map<String,Object>> 一、读取Excel testdata.xlsx 1、new XSSFWorkbook对象 File file = new File(filePath); FileInputStream fis = new FileInputStream(file);…

8.二维数组——将一个二维数组行和列的元素互换,存到另一个二维数组中。

文章目录 前言一、题目描述 二、题目分析 三、解题 程序运行代码 前言 本系列为二维数组编程题&#xff0c;点滴成长&#xff0c;一起逆袭。 一、题目描述 将一个二维数组行和列的元素互换&#xff0c;存到另一个二维数组中。 二、题目分析 三、解题 程序运行代码 #incl…

玄学调参实践篇 | 深度学习模型 + 预训练模型 + 大模型LLM

&#x1f60d; 这篇主要简单记录一些调参实践&#xff0c;无聊时会不定期更新~ 文章目录 0、学习率与batch_size判断1、Epoch数判断2、判断模型架构是否有问题3、大模型 - 计算量、模型、和数据大小的关系4、大模型调参相关论文经验总结5、训练时模型的保存 0、学习率与batch_s…

压力测试+接口测试

jmeter是apache公司基于java开发的一款开源压力测试工具&#xff0c;体积小&#xff0c;功能全&#xff0c;使用方便&#xff0c;是一个比较轻量级的测试工具&#xff0c;使用起来非常简单。因 为jmeter是java开发的&#xff0c;所以运行的时候必须先要安装jdk才可以。jmeter是…