DECO: Query-Based End-to-End Object Detection with ConvNets 学习笔记

论文地址:https://arxiv.org/pdf/2312.13735.pdf
源码地址:https://github.com/xinghaochen/DECO

近年来,Detection Transformer (DETR) 及其变体在准确检测目标方面显示出巨大的潜力。对象查询机制使DETR系列能够直接获得固定数量的目标预测,并简化了检测 pipeline。同时,最近的研究还表明,通过适当的架构设计,ConvNeXt这样的卷积网络(ConvNets)也可以与 transformers等变压器实现竞争性能。为此,在本文中,作者探讨了是否可以使用 ConvNet 而不是复杂的 transformer 架构构建基于查询的端到端目标检测框架。所提出的框架,即检测ConvNet(DECO),由骨干和卷积编码器-解码器架构组成。作者精心设计了DECO编码器,并为DECO解码器提出了一种新颖的机制,通过卷积层在对象查询和图像特征之间执行交互。将 DECO 与具有挑战性的 COCO 基准上的先前检测器进行了比较。尽管简单,但DECO在检测精度和运行速度方面具有竞争力。具体来说,使用 ResNet-50 和 ConvNeXt-Tiny 骨干网,DECO 在 COCO val 上分别获得 38.6% 和 40.8% 的 AP,分别为 35 和 28 FPS,优于 DETR 模型。作者的 DECO+ 集成了先进的多尺度功能模块,以 34 FPS 的速度实现了 47.8% 的 AP。作者希望提出的DECO为设计目标检测框架带来另一个视角。


网络架构

        DETR的主要特点是利用Transformer Encoder-Decoder的结构,对一张输入图像,利用一组Query跟图像特征进行交互,可以直接输出指定数量的检测框,从而可以摆脱对NMS等后处理操作的依赖。作者提出的DECO总体架构上跟DETR类似,也包括了Backbone来进行图像特征提取,一个Encoder-Decoder的结构跟Query进行交互,最后输出特定数量的检测结果。唯一的不同在于,DECO的Encoder和Decoder是纯卷积的结构,因此DECO是一个由纯卷积构成的Query-Based端对端检测器。


编码器

        DETR 的 Encoder 结构替换相对比较直接,我们选择使用4个ConvNeXt Block来构成Encoder结构。具体来说,Encoder的每一层都是通过叠加一个7x7的深度卷积、一个LayerNorm层、一个1x1的卷积、一个GELU激活函数以及另一个1x1卷积来实现的。此外,在DETR中,因为Transformer架构对输入具有排列不变性,所以每层编码器的输入都需要添加位置编码,但是对于卷积组成的Encoder来说,则无需添加任何位置编码。

代码示例:

class DecoEncoder(nn.Module):
    '''Define Deco Encoder'''
    def __init__(self, enc_dims=[120,240,480], enc_depth=[2,6,2]): 
        super().__init__()
        self._encoder = ConvNeXt(depths=enc_depth, dims=enc_dims) 

    def forward(self, src):    
        output = self._encoder(src)
        return output # [2, 480, 34, 31]


ConvNeXt部分:

class Block(nn.Module):
    r""" ConvNeXt Block. 
    Args:
        dim (int): Number of input channels.
        drop_path (float): Stochastic depth rate. Default: 0.0
        layer_scale_init_value (float): Init value for Layer Scale. Default: 1e-6.
    """
    def __init__(self, dim, drop_path=0., layer_scale_init_value=1e-6):
        super().__init__()
        self.dwconv = nn.Conv2d(dim, dim, kernel_size=7, padding=3, groups=dim)  # Conv2d(120, 120, kernel_size=(7, 7), stride=(1, 1), padding=(3, 3), groups=120) # Conv2d(240, 240, kernel_size=(7, 7), stride=(1, 1), padding=(3, 3), groups=240) # Conv2d(480, 480, kernel_size=(7, 7), stride=(1, 1), padding=(3, 3), groups=480)
        self.norm = LayerNorm(dim, eps=1e-6)
        self.pwconv1 = nn.Linear(dim, 4 * dim) 
        self.act = nn.GELU()
        self.pwconv2 = nn.Linear(4 * dim, dim)
        self.gamma = nn.Parameter(layer_scale_init_value * torch.ones((dim)), 
                                    requires_grad=True) if layer_scale_init_value > 0 else None
        self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()

    def forward(self, x):
        input = x  # [2, 120, 34, 31] # [2, 240, 34, 31] # [2, 480, 34, 31]
        x = self.dwconv(x)
        x = x.permute(0, 2, 3, 1) 
        x = self.norm(x)
        x = self.pwconv1(x)  # [2, 34, 31, 120] -> [2, 34, 31, 480] # [2, 34, 31, 240] -> [2, 34, 31, 960] # [2, 34, 31, 480] -> [2, 34, 31, 1920]
        x = self.act(x)
        x = self.pwconv2(x)  # [2, 34, 31, 480] -> [2, 34, 31, 120] # [2, 34, 31, 960] -> [2, 34, 31, 240] # [2, 34, 31, 1920] -> [2, 34, 31, 480]
        if self.gamma is not None:
            x = self.gamma * x
        x = x.permute(0, 3, 1, 2) 

        x = input + self.drop_path(x)
        return x  # [2, 120, 34, 31] # [2, 240, 34, 31] # [2, 480, 34, 31]

class ConvNeXt(nn.Module):
    r""" ConvNeXt
        A PyTorch impl of : `A ConvNet for the 2020s`  -
          https://arxiv.org/pdf/2201.03545.pdf
    Args:
        depths (tuple(int)): Number of blocks at each stage. Default: [2, 6, 2]
        dims (int): Feature dimension at each stage. Default: [120, 240, 480]
        drop_path_rate (float): Stochastic depth rate. Default: 0.
        layer_scale_init_value (float): Init value for Layer Scale. Default: 1e-6.
    """

    def __init__(self, 
                 depths=[2, 6, 2], dims=[120, 240, 480], drop_path_rate=0., 
                 layer_scale_init_value=1e-6, 
                 ):
        super().__init__()

        self.depths = depths

        self.downsample_layers = nn.ModuleList() 
        for i in range(len(depths)-1):
            downsample_layer = nn.Sequential(
                    LayerNorm(dims[i], eps=1e-6, data_format="channels_first"),
                    nn.Conv2d(dims[i], dims[i+1], kernel_size=1),
            )
            self.downsample_layers.append(downsample_layer)  # (0): Sequential((0): LayerNorm() (1): Conv2d(120, 240, kernel_size=(1, 1), stride=(1, 1)))
                                                             # (1): Sequential((0): LayerNorm() (1): Conv2d(240, 480, kernel_size=(1, 1), stride=(1, 1)))
        self.stages = nn.ModuleList() 
        dp_rates=[x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))] 
        cur = 0
        for i in range(len(depths)):
            stage = nn.Sequential(
                *[Block(dim=dims[i], drop_path=dp_rates[cur + j], 
                layer_scale_init_value=layer_scale_init_value) for j in range(depths[i])]
            )
            self.stages.append(stage)
            cur += depths[i]

        self.norm = nn.LayerNorm(dims[-1], eps=1e-6) 
        self.apply(self._init_weights)
        
    def with_pos_embed(self, tensor, pos: Optional[Tensor]):
        return tensor if pos is None else tensor + pos

    def forward(self, src):
        src=self.forward_features(src)
        return src   

    def forward_features(self, src):
        for i in range(len(self.depths)-1):
            src = self.stages[i](src)  # [2, 120, 34, 31]->[2, 120, 34, 31] # [2, 240, 34, 31]->[2, 240, 34, 31]
            src = self.downsample_layers[i](src) # [2, 120, 34, 31]->[2, 240, 34, 31] # [2, 240, 34, 31]->[2, 480, 34, 31]
        src = self.stages[len(self.depths)-1](src)
        return src  # [2, 480, 34, 31]

    def _init_weights(self, m):
        if isinstance(m, (nn.Conv2d, nn.Linear)):
            trunc_normal_(m.weight, std=.02)
            nn.init.constant_(m.bias, 0)

解码器

        相比而言,Decoder的替换则复杂得多。Decoder的主要作用为对图像特征和Query进行充分的交互,使得Query可以充分感知到图像特征信息,从而对图像中的目标进行坐标和类别的预测。Decoder主要包括两个输入:Encoder的特征输出和一组可学的查询向量(Query)。把Decoder的主要结构分为两个模块:自交互模块(Self-Interaction Module, SIM)和交叉交互模块(Cross-Interaction Module, CIM)。

        这里,SIM模块主要融合Query和上层Decoder层的输出,这部分的结构,可以利用若干个卷积层来组成,使用9x9 depthwise卷积和1x1卷积分别在空间维度和通道维度进行信息交互,充分获取所需的目标信息以送到后面的CIM模块进行进一步的目标检测特征提取。Query为一组随机初始化的向量,该数量决定了检测器最终输出的检测框数量,其具体的值可以随实际需要进行调节。对DECO来说,因为所有的结构都是由卷积构成的,因此我们把Query变成二维,比如100个Query,则可以变成10x10的维度。

        CIM模块的主要作用是让图像特征和Query进行充分的交互,使得Query可以充分感知到图像特征信息,从而对图像中的目标进行坐标和类别的预测。对于Transformer结构来说,利用cross attention机制可以很方便实现这一目的,但对于卷积结构来说,如何让两个特征进行充分交互,则是一个最大的难点。

        要把大小不同的SIM输出和encoder输出全局特征进行融合,必须先把两者进行空间对齐然后进行融合,首先我们对SIM的输出进行最近邻上采样:

        使得上采样后的特征与Encoder输出的全局特征有相同的尺寸,然后将上采样后的特征和encoder输出的全局特征进行融合,然后进入深度卷积进行特征交互后加上残差输入:

        最后将交互后的特征通过FNN进行通道信息交互,之后pooling到目标数量大小得到decoder的输出embedding:

        最后将得到的输出embedding送入检测头,以进行后续的分类和回归。

代码示例:

class DecoDecoder(nn.Module):
    '''Define Deco Decoder'''
    def __init__(self, decoder_layer, num_layers, norm=None, return_intermediate=False, qH=10, qW=10):
        super().__init__()
        self.layers = _get_clones(decoder_layer, num_layers)
        self.num_layers = num_layers
        self.norm = norm
        self.return_intermediate = return_intermediate
        self.qH = qH
        self.qW = qW

    def forward(self, tgt, memory, bs, d_model, query_pos: Optional[Tensor] = None):
        output = tgt
        intermediate = []

        for layer in self.layers:
            output=output.permute(1, 2, 0).view(bs, d_model,self.qH,self.qW)
            output = layer(output, memory, query_pos=query_pos)
            output=output.flatten(2).permute(2, 0, 1)
            if self.return_intermediate:
                intermediate.append(self.norm(output))

        if self.norm is not None:
            output = self.norm(output)
            if self.return_intermediate:
                intermediate.pop()
                intermediate.append(output)

        if self.return_intermediate:
            return torch.stack(intermediate)
        
        return output.unsqueeze(0)

class DecoDecoderLayer(nn.Module):
    '''Define a layer for Deco Decoder'''
    def __init__(self,d_model, normalize_before=False, qH=10, qW=10,
                 drop_path=0.,layer_scale_init_value=1e-6):
        super().__init__()
        self.normalize_before = normalize_before
        self.qH = qH
        self.qW = qW

        # The SIM module   
        self.dwconv1 = nn.Conv2d(d_model, d_model, kernel_size=9, padding=4, groups=d_model)  # Conv2d(480, 480, kernel_size=(9, 9), stride=(1, 1), padding=(4, 4), groups=480)
        self.norm1 = LayerNorm(d_model, eps=1e-6)
        self.pwconv1_1 = nn.Linear(d_model, 4 * d_model) 
        self.act1 = nn.GELU()
        self.pwconv1_2 = nn.Linear(4 * d_model, d_model)
        self.gamma1 = nn.Parameter(layer_scale_init_value * torch.ones((d_model)), 
                                    requires_grad=True) if layer_scale_init_value > 0 else None
        self.drop_path1 = DropPath(drop_path) if drop_path > 0. else nn.Identity()

        # The CIM module
        self.dwconv2 = nn.Conv2d(d_model, d_model, kernel_size=9, padding=4, groups=d_model)  # Conv2d(480, 480, kernel_size=(9, 9), stride=(1, 1), padding=(4, 4), groups=480)
        self.norm2 = LayerNorm(d_model, eps=1e-6)
        self.pwconv2_1 = nn.Linear(d_model, 4 * d_model) 
        self.act2 = nn.GELU()
        self.pwconv2_2 = nn.Linear(4 * d_model, d_model)
        self.gamma2 = nn.Parameter(layer_scale_init_value * torch.ones((d_model)), 
                                    requires_grad=True) if layer_scale_init_value > 0 else None
        self.drop_path2 = DropPath(drop_path) if drop_path > 0. else nn.Identity()


    def forward(self, tgt, memory, query_pos: Optional[Tensor] = None):
        # SIM
        b, d, h, w = memory.shape  # [2, 480, 34, 31]
        tgt2 = tgt + query_pos # tgt以及query_pos均由nn.Embedding(100,480)生成,并reshape到[2, 480, 10, 10]
        tgt2 = self.dwconv1(tgt2)
        tgt2 = tgt2.permute(0, 2, 3, 1) # (b,d,10,10)->(b,10,10,d)
        tgt2 = self.norm1(tgt2)
        tgt2 = self.pwconv1_1(tgt2) # [2, 10, 10, 480]->[2, 10, 10, 1920]
        tgt2 = self.act1(tgt2)
        tgt2 = self.pwconv1_2(tgt2)  # [2, 10, 10, 1920]->[2, 10, 10, 480]
        if self.gamma1 is not None:
            tgt2 = self.gamma1 * tgt2
        tgt2 = tgt2.permute(0,3,1,2) # (b,10,10,d)->(b,d,10,10)
        tgt = tgt + self.drop_path1(tgt2)

        # CIM
        tgt = F.interpolate(tgt, size=[h,w])  # [2, 480, 10, 10] -> [2, 480, 34, 31]
        tgt2 = tgt + memory 
        tgt2 = self.dwconv2(tgt2)
        tgt2 = tgt2+tgt 
        tgt2 = tgt2.permute(0, 2, 3, 1) # (b,d,h,w)->(b,h,w,d)
        tgt2=self.norm2(tgt2)
        
        # FFN
        tgt = tgt2
        tgt2 = self.pwconv2_1(tgt2)  # [2, 34, 31, 480]->[2, 34, 31, 1920]
        tgt2 = self.act2(tgt2)
        tgt2 = self.pwconv2_2(tgt2)  # [2, 34, 31, 1920]->[2, 34, 31, 480]
        if self.gamma2 is not None:
            tgt2 = self.gamma2 * tgt2
        tgt2 = tgt2.permute(0,3,1,2) # (b,h,w,d)->(b,d,h,w)
        tgt = tgt.permute(0,3,1,2) # (b,h,w,d)->(b,d,h,w)
        tgt = tgt + self.drop_path1(tgt2)  # [2, 480, 34, 31]

        # pooling
        m = nn.AdaptiveMaxPool2d((self.qH, self.qW))
        tgt = m(tgt) # [2, 480, 10, 10]

        return tgt

多尺度特征

        跟原始的DETR一样,上述框架得到的DECO有个共同的短板,即缺少多尺度特征,而这对于高精度目标检测来说是影响很大的。Deformable DETR通过使用一个多尺度的可变形注意力模块来整合不同尺度的特征,但这个方法是跟Attention算子强耦合的,因此没法直接用在我们的DECO上。为了让DECO也能处理多尺度特征,我们在Decoder输出的特征之后,采用了RT-DETR提出的一个跨尺度特征融合模块。


实验

        在COCO上进行实验,在保持主要架构不变的情况下将DECO和DETR进行了比较,比如保持Query数量一致,保持Decoder层数不变等,仅将DETR中的Transformer结构按上文所述换成卷积结构。可以看出,DECO取得了比DETR更好的精度和速度的Trade-off。

        把搭配了多尺度特征后的DECO跟更多目标检测方法进行了对比,其中包括了很多DETR的变体,从下图中可以看到,DECO取得了很不错的效果,比很多以前的检测器都取得了更好的性能。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

解决angualr13 form表单设置disabled不起作用问题

我的博客原文:解决angualr13 form表单设置disabled不起作用问题 问题 我们在angular项目中form中disabled 属性和 formControlName 结合使用时,会发现disabled 属性不会起作用,代码如下 ​ 效果却是 ​ 这是为什么呢? 原…

c语言基础笔记(1)进制转换以及++a,a++,取地址和解引用

一进制转换 OCT - 八进制 DEC - 十进制 HEX - 十六进制 0520&#xff0c;表示八进制 0x520表示16进制 unsigned 无符号&#xff0c;只有正的 signed 有正有负数 char默认是signed 类型 #include <stdio.h>int main(void) { //字符转换成数字char a 5;int a1 a- 4…

C语言 swab 函数学习

swab函数交换字符串中相邻两个字节&#xff1b; void _swab( char *src, char *dest, int n ); char *src&#xff1a; 要拷贝、转换的字符串&#xff0c; char *dest&#xff0c;转换后存储到dest所表示的字符串&#xff0c; int n要拷贝、转换的字节数&#xff1b; 所…

OKR如何与个人绩效评估和激励相结合?

在现代企业管理中&#xff0c;个人绩效评估与激励是提升员工积极性、推动企业发展的关键环节。而OKR&#xff08;目标与关键成果&#xff09;作为一种高效的目标管理方法&#xff0c;通过与个人绩效评估和激励相结合&#xff0c;可以进一步提升员工的工作动力和工作效率&#x…

银行量子金融系统应用架构设计

量子金融&#xff08;即Financial-Quantum&#xff0c;简称Fin-Q&#xff09;&#xff0c;特指量子科技在金融行业中的应用。 目前&#xff0c;量子科技中以量子保密通信、量子随机数和量子计算发展进度较快&#xff0c;取得了诸多阶段性重大技术突破和商用成果&#xff0c;这…

社科赛斯考研:二十二载岁月铸辉煌,穿越周期的生命力之源

在考研培训行业的浩瀚海洋中&#xff0c;社科赛斯考研犹如一艘稳健的巨轮&#xff0c;历经二十二载风礼&#xff0c;依然破浪前行。在考研市场竞争白热化与学生对于考研机构要求越来越高的双重影响下&#xff0c;社科赛斯考研却以一种分蘖成长的姿态&#xff0c;扎根、壮大&…

Hive SQL必刷练习题:留存率问题(*****)

留存率&#xff1a; 首次登录算作当天新增&#xff0c;第二天也登录了算作一日留存。可以理解为&#xff0c;在10月1号登陆了。在10月2号也登陆了&#xff0c;那这个人就可以算是在1号留存 今日留存率 &#xff08;今日登录且明天也登录的用户数&#xff09; / 今日登录的总…

再谈 Flink 的 “动态表” 和 “流表二象性”

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

UE5制作一条底部挂着物体的悬垂的绳子

主要涉及cable&#xff08;缆索&#xff09;组件、PhysicsConstraint&#xff08;物理约束&#xff09;组件的灵活运用&#xff0c;经过摸索&#xff0c;写下本文以供探讨。 一、关卡中制作 关卡中制作最简单 1. cable组件加入场景 打开放置Actor面板&#xff0c;在其中找到…

什么是物联网远程模块

在数字化和信息化的浪潮下&#xff0c;物联网技术正在以惊人的速度改变着我们的生活和生产方式。物联网远程模块&#xff0c;作为物联网技术的核心组件之一&#xff0c;正引领着这场变革。HiWoo Box就是这样一款出色的物联网远程模块&#xff0c;它通过支持远程透传、远程锁机、…

第六十一回 放冷箭燕青救主 劫法场石秀跳楼-编译安装飞桨paddlepaddle@openKylin+RISCV

卢俊义在水里被张顺抓住&#xff0c;用轿子抬到了梁山。宋江等人下马跪在地上迎接&#xff0c;请他坐第一把交椅。卢俊义宁死不从&#xff0c;大家只好说留他在山寨几天&#xff0c;先让李固带着马车货物回去。吴用对李固说&#xff0c;你的主人已经答应坐第二把交椅了&#xf…

中文数字与阿拉伯数字:数字符号的文化交融

title: 中文数字与阿拉伯数字&#xff1a;数字符号的文化交融 date: 2024/3/21 17:46:31 updated: 2024/3/21 17:46:31 tags: 数字起源符号系统差异中文数字特点阿拉伯数字优势转换技术应用文化交流融合数字表达互通 中文数字与阿拉伯数字的关系起源&#xff1a; 中文数字是古…

【Unity】从0到1的横版2d制作笔记-DAY3

确定碰撞体积 选择rigidbody2d&#xff0c;创建player重力 创建player碰撞体积 创建瓦片地图碰撞体积 使平台变成一个整体 ​​​​​ 设置Body Type为Static&#xff08;避免平台也因为重力影响下落&#xff09; 回到Player&#xff0c;在Rigidbody2D中设置为冻结旋转 Player设…

蓝桥杯-模拟-纸张尺寸

题目 思路 代码 qlist(str(input())) numint(q[1]) x,y1189,841 while num:num-1x//2if x<y:x,yy,x print(x) print(y)

elementUI Tree 树形控件单选实现

文章目录 展示效果代码实现elementui Tree树形控件其他详细数据 在Element UI中&#xff0c;树形控件&#xff08;el-tree&#xff09;本身不支持单选功能。但是&#xff0c;你可以通过监听节点点击事件并手动更新选中状态来实现单选树。 以下是一个简单的例子&#xff0c;展示…

【SpringBoot】登录校验之会话技术、统一拦截技术

真正的登录功能应该是&#xff1a; 登陆后才能访问后端系统页面&#xff0c;不登陆则跳转登陆页面进行登陆。 当我们没有设置登录校验&#xff0c;可以直接通过修改地址栏直接进入管理系统内部&#xff0c;跳过登录页。而后端系统的增删改查功能&#xff0c;没有添加判断用户是…

Java-SSM电影在线播放系统

Java-SSM电影在线播放系统 1.服务承诺&#xff1a; 包安装运行&#xff0c;如有需要欢迎联系&#xff08;VX:yuanchengruanjian&#xff09;。 2.项目所用框架: 前端:JSP、layui等 后端:SSM,即Spring、SpringMvc、Mybatis等。 3.项目功能点: 3-1.后端功能: - 所有后台管理展…

酷炫的粒子动态表白HTML源码

源码介绍 酷炫的粒子动态表白HTML源码&#xff0c;自己自定义文字&#xff0c;动态组合文字&#xff0c;进行表白&#xff0c;喜欢的朋友可以下载使用&#xff0c;很不错的表白HTML代码 下载地址 酷炫的粒子动态表白HTML源码

如何部署GPT模型至自有服务器:从零开始搭建你的智能聊天机器人

引言 GPT模型是自然语言处理领域的重要突破&#xff0c;它能够通过生成式的文本生成方式&#xff0c;实现与用户的智能交互。本文将详细介绍如何将GPT模型部署到自有服务器上&#xff0c;并编写一个基本的API接口来实现与聊天机器人的交互。 目录 引言 一、准备工作 首先&am…

类和对象-4

文章目录 前言const成员函数取地址及const取地址操作符重载构造函数续explicit static成员友元内部类匿名对象 前言 在前面的文章中&#xff0c;我们了解到了类的四个默认成员函数&#xff1a;构造、析构、拷贝构造和赋值重载。接下来我们会继续学习剩下的两个默认成员函数以及…