RT-DETR系列之AIFI

还是那张图,我们学习了Backbone后,接下来学习AIFI(Intra-scale feature interaction)部分是干什么的-----单尺度内的特征交互。简而言之就是如图。输入为S5,输出为F5。

首先我们来看模型的配置文件

nc: 80  # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n-cls.yaml' will call yolov8-cls.yaml with scale 'n'
  # [depth, width, max_channels]
  l: [1.00, 1.00, 1024]

backbone:
  # [from, repeats, module, args]
  - [-1, 1, ConvNormLayer, [32, 3, 2, None, False, 'relu']] # 0-P1/2
  - [-1, 1, ConvNormLayer, [32, 3, 1, None, False, 'relu']] # 1
  - [-1, 1, ConvNormLayer, [64, 3, 1, None, False, 'relu']] # 2
  - [-1, 1, nn.MaxPool2d, [3, 2, 1]] # 3-P2/4
  
  # [ch_out, block_type, block_nums, stage_num, act, variant]
  - [-1, 1, Blocks, [64, BasicBlock, 2, 2, 'relu']] # 4
  - [-1, 1, Blocks, [128, BasicBlock, 2, 3, 'relu']] # 5-P3/8
  - [-1, 1, Blocks, [256, BasicBlock, 2, 4, 'relu']] # 6-P4/16
  - [-1, 1, Blocks, [512, BasicBlock, 2, 5, 'relu']] # 7-P5/32

head:
  - [-1, 1, Conv, [256, 1, 1, None, 1, 1, False]]  # 8 input_proj.2
  - [-1, 1, AIFI, [1024, 8]] # 9
  - [-1, 1, Conv, [256, 1, 1]]  # 10, Y5, lateral_convs.0

  - [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 11
  - [6, 1, Conv, [256, 1, 1, None, 1, 1, False]]  # 12 input_proj.1
  - [[-2, -1], 1, Concat, [1]] # 13
  - [-1, 3, RepC3, [256, 0.5]]  # 14, fpn_blocks.0
  - [-1, 1, Conv, [256, 1, 1]]   # 15, Y4, lateral_convs.1

  - [-1, 1, nn.Upsample, [None, 2, 'nearest']] # 16
  - [5, 1, Conv, [256, 1, 1, None, 1, 1, False]]  # 17 input_proj.0
  - [[-2, -1], 1, Concat, [1]]  # 18 cat backbone P4
  - [-1, 3, RepC3, [256, 0.5]]    # X3 (19), fpn_blocks.1

  - [-1, 1, Conv, [256, 3, 2]]   # 20, downsample_convs.0
  - [[-1, 15], 1, Concat, [1]]  # 21 cat Y4
  - [-1, 3, RepC3, [256, 0.5]]    # F4 (22), pan_blocks.0

  - [-1, 1, Conv, [256, 3, 2]]   # 23, downsample_convs.1
  - [[-1, 10], 1, Concat, [1]]  # 24 cat Y5
  - [-1, 3, RepC3, [256, 0.5]]    # F5 (25), pan_blocks.1

  - [[19, 22, 25], 1, RTDETRDecoder, [nc, 256, 300, 4, 8, 3]]  # Detect(P3, P4, P5)

这次我们就来学习有关AIFI的这部分

  - [-1, 1, Conv, [256, 1, 1, None, 1, 1, False]]  # 8 input_proj.2
  - [-1, 1, AIFI, [1024, 8]] # 9
  - [-1, 1, Conv, [256, 1, 1]]  # 10, Y5, lateral_convs.0

这里的第一行代码很显然是一个卷积,但是我们在Backbone中也涉及到卷积操作,名字(ConvNormLayer)和这个(Conv)不一样 。所以首先学习一下,这两个有什么区别:

Backbone中卷积操作ConvNormLayer和head中卷积操作Conv的区别

先分别展示他们两个的源代码

 ConvNormLayer源代码
class ConvNormLayer(nn.Module):
    def __init__(self, ch_in, ch_out, kernel_size, stride, padding=None, bias=False, act=None):
        super().__init__()
        self.conv = nn.Conv2d(
            ch_in, 
            ch_out, 
            kernel_size, 
            stride, 
            padding=(kernel_size-1)//2 if padding is None else padding, 
            bias=bias)
        self.norm = nn.BatchNorm2d(ch_out)
        self.act = nn.Identity() if act is None else get_activation(act) 

    def forward(self, x):
        return self.act(self.norm(self.conv(x)))
    
    def forward_fuse(self, x):
        """Perform transposed convolution of 2D data."""
        return self.act(self.conv(x))
 Conv源代码

class Conv(nn.Module):
    """Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)."""
    default_act = nn.SiLU()  # default activation

    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
        """Initialize Conv layer with given arguments including activation."""
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

    def forward(self, x):
        """Apply convolution, batch normalization and activation to input tensor."""
        return self.act(self.bn(self.conv(x)))

    def forward_fuse(self, x):
        """Perform transposed convolution of 2D data."""
        return self.act(self.conv(x))
区别 

首先第一个区别,卷积nn.Conv2d 中参数设置不一样

ConvNormLayer中的nn.Conv2d

self.conv = nn.Conv2d(
            ch_in, 
            ch_out, 
            kernel_size, 
            stride, 
            padding=(kernel_size-1)//2 if padding is None else padding, 
            bias=bias)

 Conv中的nn.Conv2d

self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)

,前4个参数是一样的,都是输入通道数(ConvNormLayer中的ch_in和Conv中的c1),输出通道数(ch_out和c2),卷积核(kernel_size和k),步长(stride和s)。从第5个参数就不一样了。先看ConvNormLayer中剩下的参数,padding和bias。就是设置了简单步长和偏置。Conv中的就更加复杂。

autopad(k, p, d):自动填充计算
  • 根据卷积核大小 k、膨胀率 d 和用户指定的填充 p,自动计算卷积层的填充值,确保输出尺寸与输入尺寸匹配(当 stride=1 时)。

  • 当 p=None 时,默认填充方式为 "SAME" 填充(保持输入输出尺寸一致)。 

groups=g:分组卷积
  • 将输入和输出的通道分成 g 组,每组独立进行卷积操作。

  • 优势:减少参数量和计算量,提升模型效率。常用于深度可分类卷积

dilation=d:膨胀卷积
  • 通过控制卷积核的膨胀率(空洞间隔),扩大感受野而不增加参数数量。

  • 用于捕获大范围上下文信息(如焊接缺陷的全局分布)。

卷积核结构

  • 膨胀率 d=1:普通卷积核(无间隔)

  • 膨胀率 d=2:卷积核元素间隔1个像素(感受野等效于 k + (k-1)*(d-1)

示例

  • 卷积核 k=3,膨胀率 d=2

    • 实际覆盖区域:3x3 → 等效 5x5 的感受野(但仅用 9 个参数)。

像素间隔

区别二:

激活函数的配置

先看 ConvNormLayer的

self.act = nn.Identity() if act is None else get_activation(act) 

默认无激活(act=None 时使用 nn.Identity),需手动指定激活函数 

再看Conv的

 default_act = nn.SiLU()  # default activation
self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

默认使用 SiLU 激活(即 Swish 激活),兼顾 ReLU 的非线性和 Sigmoid 的平滑性 

总结: 
  •  Conv
    当需要灵活配置膨胀/分组卷积,或希望利用 SiLU 激活函数增强非线性表达能力时(尤其适合检测小目标)。

  •  ConvNormLayer
    在基础特征提取层(如主干网络前半部分),追求计算效率且无需复杂卷积配置时。

现在我们来看

配置文件第一行

 - [-1, 1, Conv, [256, 1, 1, None, 1, 1, False]]  # 8 input_proj.2

回忆一下,最开始我们的输入图片为3*640*640,经过Backbone后,最后一层图片输出为512x20x20。 

对应参数解析:

  • 输出通道数 c2=256

  • 卷积核大小 k=1×1k=1×1

  • 步长 s=1s=1

  • 填充 p=Nonep=None(自动计算为 00)

  • 分组数 g=1g=1

  • 膨胀率 d=1d=1

  • 激活函数 act=False

输出计算

  1. 空间尺寸
    输入尺寸为 Hin×Win=20×20,卷积核为 1×1,步长 s=1,填充 p=0,膨胀率 d=1。
    输出尺寸公式:

    Hout=[Hin+2p−d×(k−1)−1]/s+1=20

    同理 Wout=20。

  2. 通道数
    输入通道数 c1=512 → 输出通道数 c2​=256。

作用总结
  1. 通道降维:将通道数从 512 压缩至 256,减少后续计算量。

  2. 特征融合:通过 1×1卷积实现跨通道信息融合,增强特征表达能力。

  3. 保持分辨率:不改变空间尺寸(无下采样),保留缺陷定位精度。

  4. 归一化:通过内置的 BatchNorm2d 稳定训练过程。

配置文件第二行

  - [-1, 1, AIFI, [1024, 8]] # 9
  • 输入通道数为 1024。
  • 多头注意力机制中的头数为 8

接下来看AIFI的源码

class AIFI(TransformerEncoderLayer):
    """Defines the AIFI transformer layer."""

    def __init__(self, c1, cm=2048, num_heads=8, dropout=0, act=nn.GELU(), normalize_before=False):
        """Initialize the AIFI instance with specified parameters."""
        super().__init__(c1, cm, num_heads, dropout, act, normalize_before)

    def forward(self, x):
        """Forward pass for the AIFI transformer layer."""
        c, h, w = x.shape[1:]
        pos_embed = self.build_2d_sincos_position_embedding(w, h, c)
        # Flatten [B, C, H, W] to [B, HxW, C]
        x = super().forward(x.flatten(2).permute(0, 2, 1), pos=pos_embed.to(device=x.device, dtype=x.dtype))
        return x.permute(0, 2, 1).view([-1, c, h, w]).contiguous()

    @staticmethod
    def build_2d_sincos_position_embedding(w, h, embed_dim=256, temperature=10000.0):
        """Builds 2D sine-cosine position embedding."""
        grid_w = torch.arange(int(w), dtype=torch.float32)
        grid_h = torch.arange(int(h), dtype=torch.float32)
        grid_w, grid_h = torch.meshgrid(grid_w, grid_h, indexing='ij')
        assert embed_dim % 4 == 0, \
            'Embed dimension must be divisible by 4 for 2D sin-cos position embedding'
        pos_dim = embed_dim // 4
        omega = torch.arange(pos_dim, dtype=torch.float32) / pos_dim
        omega = 1. / (temperature ** omega)

        out_w = grid_w.flatten()[..., None] @ omega[None]
        out_h = grid_h.flatten()[..., None] @ omega[None]

        return torch.cat([torch.sin(out_w), torch.cos(out_w), torch.sin(out_h), torch.cos(out_h)], 1)[None]
初始化
  • c1: 输入通道数。
  • cm: 中间特征维度,默认为 2048。
  • num_heads: 多头注意力机制中的头数,默认为 8。
  • dropout: dropout 概率,默认为 0。
  • act: 激活函数,默认为 GELU。
  • normalize_before: 是否在注意力机制之前进行归一化,默认为 False。

super().__init__(c1, cm, num_heads, dropout, act, normalize_before) 调用基类 TransformerEncoderLayer 的初始化方法。

前向传播
  1. 获取形状信息
    • c, h, w = x.shape[1:] 提取输入特征图的通道数、高度和宽度。x.shape 将返回一个包含四个元素的元组 (B, C, H, W)x.shape[1:] 会跳过第一个元素(即跳过批量大小 B),取后面的部分,即 (C, H, W)c, h, w = x.shape[1:] 是一种 Python 的多变量赋值(解包)语法,它将元组 (C, H, W) 解包为三个独立的变量 ch 和 w,分别表示通道数、图像高度和图像宽度。
  2. 生成位置嵌入
    • pos_embed = self.build_2d_sincos_position_embedding(w, h, c) 生成 2D 正弦-余弦位置嵌入。
  3. 特征处理
    • x.flatten(2).permute(0, 2, 1) 将输入特征从 [B, C, H, W] 变换为 [B, H*W, C],以适应 transformer 输入格式。

      x.flatten(2) 会将张量 x 从第 2 个维度开始展平(flatten),这意味着它会将 H 和 W 维度展平为一个维度。展平后的张量形状会变成 [B, C, H*W]

      例如,如果 x 的形状是 [B, C, H, W]x.flatten(2) 的形状会变成 [B, C, H*W]permute 方法x.flatten(2).permute(0, 2, 1) 会对展平后的张量进行维度重排。permute 方法会改变张量的维度顺序。在这个例子中,permute(0, 2, 1) 表示将第 0 个维度保持不变,将第 2 个维度移到第 1 个位置,将第 1 个维度移到第 2 个位置。具体来说:0 代表批量大小 B2 代表展平后的维度 H*W1 代表通道数 C,因此,x.flatten(2).permute(0, 2, 1) 会将形状从 [B, C, H*W] 变成 [B, H*W, C]

    • super().forward() 调用基类的前向传播方法,传入处理后的特征和位置嵌入。
    • x.permute(0, 2, 1).view([-1, c, h, w]).contiguous() 将输出特征重整为 [B, C, H, W] 形状。

      x.permute(0, 2, 1) 会改变张量的维度顺序。permute(0, 2, 1) 表示将第 0 个维度保持不变,将第 2 个维度移到第 1 个位置,将第 1 个维度移到第 2 个位置。

      具体来说:0 代表批量大小 B2 代表特征维度 C1 代表序列长度 N。因此,x.permute(0, 2, 1) 会将形状从 [B, N, C] 变成 [B, C, N]view 方法view 方法用于重新整形张量。x.permute(0, 2, 1).view([-1, c, h, w]) 将张量重新整形为 [B, C, H, W]。假设:B 是批量大小。C 是通道数。H 和 W 是图像的高度和宽度,且 N = H * W。那么 x.permute(0, 2, 1) 的形状是 [B, C, N],即 [B, C, H * W]view([-1, c, h, w]) 将其重新整形为 [B, C, H, W]。其中 -1 表示自动推断出批量大小 Bcontiguous 方法contiguous 方法用于确保张量在内存中的存储是连续的。经过 permute 和 view 操作后,张量在内存中的存储可能不再是连续的,contiguous 会创建一个新的连续张量。

2D 正弦-余弦位置嵌入
  1. 生成网格
    • grid_w 和 grid_h 分别生成宽度和高度方向的网格坐标。torch.arange

      torch.arange 是 PyTorch 中生成序列数据的方法。其常见用法是 torch.arange(start, end, step),其中:start 是序列的起始值(默认为0)。end 是序列的终止值(不包含此值)。step 是序列中相邻值之间的间隔(默认为1)。在这行代码中,只用了一个参数 end,默认为从 0 开始,到 int(w) 结束(不包括 int(w) 本身),步长为 1。3. dtype=torch.float32dtype 参数指定生成的张量的数据类型。在这里,dtype=torch.float32 表示生成的张量中的元素将是 32 位浮点数。

    • grid_w, grid_h = torch.meshgrid(grid_w, grid_h, indexing='ij') 创建 2D 网格。torch.meshgrid 是 PyTorch 中用于生成网格坐标的方法。其常见用法是 torch.meshgrid(tensor1, tensor2, ..., indexing='xy' or 'ij')。它接受多个一维张量,并返回相应的多维网格张量。'ij':基于矩阵索引顺序,返回的网格是 (i, j) 形式,即网格的第一个维度是 i 方向,第二个维度是 j 方向。
  2. 计算 Omega
    • 检查 embed_dim 是否可以被 4 整除

      assert embed_dim % 4 == 0

      为了进行2D正弦-余弦位置嵌入,嵌入维度 embed_dim 必须是 4 的倍数。这个检查是为了确保后续的计算能够正确进行。

      pos_dim = embed_dim // 4

      把 embed_dim 除以 4 得到 pos_dim。这是因为对于 2D 正弦-余弦位置嵌入,每个维度会使用 4 个值来进行编码(sin 和 cos 各两个方向)。

    • omega = torch.arange(pos_dim, dtype=torch.float32) / pos_dim 生成 omega 值。这里使用 torch.arange(pos_dim, dtype=torch.float32) 生成一个从 0 到 pos_dim-1 的一维张量。然后将这个张量除以 pos_dim,以得到一个在 [0, 1) 范围内的值序列。
    • omega = 1. / (temperature ** omega) 通过温度参数进行缩放。

      temperature 这里通常是一个常数,用于控制位置编码的尺度。通常在位置编码中,temperature 被设定为一个较大的值,如 10000。这意味着 omega 序列中的每个值都是一个倒数,基于指数增长的方式。

    • 总结

      omega 是一个包含不同频率倒数的序列,这些频率用于生成 2D 正弦-余弦位置编码的频率成分。通过这些频率,位置编码能够有效地表示输入序列中每个位置的信息,并帮助模型捕捉序列中位置间的相对关系。

  3. 计算正弦和余弦嵌入
    • out_w 和 out_h 分别计算宽度和高度方向的正弦和余弦值。
    • torch.cat([...], 1)[None] 将所有嵌入值合并为 [1, H*W, embed_dim] 形状。

      @ 是矩阵乘法运算符。这里我们进行的是矩阵乘法,将两个二维张量相乘:grid_w.flatten()[..., None] 的形状为 (H * W, 1)omega[None] 的形状为 (1, pos_dim)。矩阵乘法的结果 out_w 的形状为 (H * W, pos_dim),因为 (H * W, 1) @ (1, pos_dim) = (H * W, pos_dim)。同样,out_h 的形状也是 (H * W, pos_dim)。用途和意义out_w 和 out_h 是位置编码的一部分,它们表示网格中每个位置在宽度和高度方向上的编码。grid_w.flatten()[..., None] @ omega[None] 计算得到的 out_w 表示网格中所有位置的宽度编码。grid_h.flatten()[..., None] @ omega[None] 计算得到的 out_h 表示网格中所有位置的高度编码。

输出为1024x20 x 20

配置文件第三行

- [-1, 1, Conv, [256, 1, 1]]

通过一个1*1的卷积,输出为256x20x20 

AIFI 作用总结(作用于P5(S5)层,生成F5层)

  1. Transformer 编码层: AIFI 类继承自 TransformerEncoderLayer,意味着它主要功能是实现一种基于 Transformer 的编码层。

  2. 位置嵌入: 在前向传播过程中,AIFI 类生成二维正弦-余弦位置嵌入(2D Sin-Cos Positional Embedding),这在处理图像数据时很常见,因为图像的空间信息对模型理解非常重要。

  3. 特征处理

    • 输入特征格式变换:输入特征的形状为 [B, C, H, W](批量大小 B,通道数 C,高度 H,宽度 W),在处理前被展平并转置为 [B, H*W, C] 的格式,这样更适合 Transformer 模型处理。
    • 位置嵌入添加:位置嵌入被添加到特征中,以提供位置信息。
    • Transformer 处理:经过 Transformer 编码层处理后,特征被转置回原始格式 [B, C, H, W]

### RT-DETR 的技术创新及特点 RT-DETR 是一种高效的目标检测模型,其设计旨在解决传统目标检测方法中存在的效率与精度之间的权衡问题。以下是该模型的主要技术创新和技术细节: #### 1. **混合编码器的设计** RT-DETR 提出了混合编码器的概念,用于实现高效的特征提取和处理。这种架构通过解耦尺度内交互和跨尺度融合的方式,显著提升了推理速度[^2]。 #### 2. **注意力的尺度内特征交互 (AIFI)** 为了进一步优化特征表示的质量,RT-DETR 引入了注意力机制来增强尺度内的特征交互。这种方法能够更好地捕捉局部区域的信息,从而提高模型对复杂场景的理解能力。 #### 3. **基于 CNN 的跨尺度特征融合 (CCFF)** 除了关注单个尺度上的特征外,RT-DETR 还采用了基于卷积神经网络 (CNN) 的跨尺度特征融合模块 CCFF。这一模块可以有效地整合来自多个层次的特征信息,使得模型能够在不同的空间分辨率下保持较高的检测性能。 #### 4. **不确定性最小查询选择** RT-DETR 创新性地提出了不确定性最小查询选择策略,为解码器提供高质量的初始查询向量。这些精心挑选的查询有助于减少后续迭代过程中的误差累积,并最终提升整个系统的预测精度。 #### 5. **灵活调整解码器数量** 另一个重要的特点是 RT-DETR 支持动态调节解码器层数以满足特定应用场景的需求。相比于固定配置的传统 Transformer 结构,这种方式不仅提高了资源利用率,还允许开发者根据实际需求平衡计算成本与输出质量之间关系而无需重新训练模型。 #### 6. **避免 NMS 后处理的影响** 值得注意的是,由于采用端到端可学习框架设计,RT-DETR 成功消除了非极大抑制 (Non-Maximum Suppression, NMS) 步骤可能带来的额外开销及其潜在副作用——即因候选框重叠而导致误判等问题的发生概率大大降低。 ```python # 示例代码展示如何加载预训练权重并运行推断流程 import torch from ultralytics import YOLO model = YOLO('rt-detr.pt') # 加载 RT-DETR 预训练模型 results = model.predict(source='image.jpg', conf=0.5, iou=0.7) for result in results: boxes = result.boxes.xyxy.cpu().numpy() # 获取边界框坐标 scores = result.boxes.conf.cpu().numpy() # 获取置信度分数 labels = result.names[result.boxes.cls.int()] # 类别名称映射 ``` 上述特性共同构成了 RT-DETR 在实时性和精确性方面的核心竞争力,使其成为当前最先进的目标检测解决方案之一。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值