YOLOv8如何实现注意力?逐行解读ultralytics/nn/modules/conv.py(3)

   

目录

类:Focus (缩HW增C)

类:ChannelAttention (通道注意力)

 类:SpatialAttention (空间注意力)

类:CBAM (Convolutional Block Attention Module)

类:Concat (通道连接)


    算法小白从0学习YOLOv8,代码看不懂一点儿!呜呜呜~

    于是翻译成中文人话,方便自己理解,顺便记录一下自己的学习过程,说的不全面的地方请友友在评论区补充指正~

    这篇博客记录了YOLOv8里的注意力实现方式:Focus注意力 (缩HW增C)、通道注意力(ChannelAttention)、空间注意力(SpatialAttention)、CBAM注意力(Convolutional Block Attention Module),有需要请目录跳转。

    源代码摘自https://github.com/ultralytics/ultralytics/blob/main/ultralytics/nn/modules/conv.py

类:Focus (缩HW增C)

class Focus(nn.Module):
    """Focus wh information into c-space."""

    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
        """Initializes Focus object with user defined channel, convolution, padding, group and activation values."""
        super().__init__()
        self.conv = Conv(c1 * 4, c2, k, s, p, g, act=act)
        # self.contract = Contract(gain=2)

    def forward(self, x):
        """
        Applies convolution to concatenated tensor and returns the output.

        Input shape is (b,c,w,h) and output shape is (b,4c,w/2,h/2).
        """
        return self.conv(torch.cat((x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]), 1))
        # return self.conv(self.contract(x))

     Focus类的主要功能是降低输入张量的空间尺寸(高度和宽度),同时增加通道数Focus类的输入值是(B,C,H,W),输出值变为了(B,4C,H/2,W/2)。这可以降低空间分辨率,可以加快计算速度并降低内存使用量,特别是在处理大型图像时;通过连接每个通道中不同空间位置的元素,模型可以潜在捕捉到图像中特征的更全面表示。

return self.conv(torch.cat((x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]), 1))

    上面的代码中,x的形状是(B,C,H,W)x[... , ::2 , ::2]中的...指的是取了前两个维度BatchsizeChannel的所有值,按照一定步长取了HeightWidth维度的一部分值。

    x[... , ::2 , ::2]取了偶数行和偶数列的元素;

    x[... , 1::2 , ::2]取了奇数行和偶数列的元素;

    x[... , ::2 , 1::2]取了偶数行和奇数列的元素;

    x[... , 1::2 , 1::2]取了奇数行和奇数列的元素;

    torch.cat函数将这四个子张量沿通道维度(轴 1)进行拼接。这有效地将来自所有四种采样方式的信息组合成一个单一张量,该张量的通道数是原始通道数的4倍。

    Focus类在进行前向传播的时候,对每一个通道切片(,1,H,W)都进行四次采样,并在通道维度上叠加,得到(,4,H/2,W/2)的切片,然后再把整体(B,4C,H/2,W/2)的特征图送进卷积。

类:ChannelAttention (通道注意力)

class ChannelAttention(nn.Module):
    """Channel-attention module https://github.com/open-mmlab/mmdetection/tree/v3.0.0rc1/configs/rtmdet."""

    def __init__(self, channels: int) -> None:
        """Initializes the class and sets the basic configurations and instance variables required."""
        super().__init__()
        self.pool = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Conv2d(channels, channels, 1, 1, 0, bias=True)
        self.act = nn.Sigmoid()

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """Applies forward pass using activation on convolutions of the input, optionally using batch normalization."""
        return x * self.act(self.fc(self.pool(x)))

    在类的初始化过程中,channels代表输入张量的通道数;

self.pool = nn.AdaptiveAvgPool2d(1)

    self.pool使用自适应平均池化层 (nn.AdaptiveAvgPool2d),用于将输入特征图缩小到空间尺寸为 1x1 的张量。

self.fc = nn.Conv2d(channels, channels, 1, 1, 0, bias=True)

    self.fc用卷积层 (nn.Conv2d)实现全连接层的功能,用于将池化后的特征图中的信息转换为通道注意力权重。它具有和输入通道数相同的输出通道数,内核大小为 1x1,无填充 (padding=0),并带有偏置项 (bias=True)。

self.act = nn.Sigmoid()

    self.act为激活函数 (nn.Sigmoid),用于将注意力权重值压缩到 0 到 1 之间。

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return x * self.act(self.fc(self.pool(x)))

    前向推理的返回值中,self.act(self.fc(self.pool(x)))计算得到的是注意力权重,它以逐元素乘法的方式与原始输入特征图相乘,相当于放大模型关注的重要的通道信息,同时削弱不重要的通道信息。

操作输入输出
self.pool(x)(B,C,H,W)(B,C,1,1)
self.fc(x)(B,C,1,1)(B,C,1,1)
self.act(x)(B,C,1,1)(B,C,1,1)
x*self.act(self.fc(self.pool(x)))(B,C,H,W)

(B,C,H,W)

    在论文CBAM: Convolutional Block Attention Module中提出的channel attention使用了最大池化和平均池化的并联,但本文所述的模块中只使用了平均池化 (nn.AdaptiveAvgPool2d)。

 类:SpatialAttention (空间注意力)

class SpatialAttention(nn.Module):
    """Spatial-attention module."""

    def __init__(self, kernel_size=7):
        """Initialize Spatial-attention module with kernel size argument."""
        super().__init__()
        assert kernel_size in (3, 7), 'kernel size must be 3 or 7'
        padding = 3 if kernel_size == 7 else 1
        self.cv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)
        self.act = nn.Sigmoid()

    def forward(self, x):
        """Apply channel and spatial attention on input for feature recalibration."""
        return x * self.act(self.cv1(torch.cat([torch.mean(x, 1, keepdim=True), torch.max(x, 1, keepdim=True)[0]], 1)))

    这个类用于实现空间注意力机制,帮助模型在处理图像时更加关注重要的空间信息。

assert kernel_size in (3, 7), 'kernel size must be 3 or 7'

    这句话使用断言,规定了卷积核大小一定是3或者7,否则会报错

padding = 3 if kernel_size == 7 else 1

    k=3,p=1或者k=7,p=3可以保证卷积操作之后特征图H和W不变

self.cv1 = nn.Conv2d(2, 1, kernel_size, padding=padding, bias=False)

    self.cv1定义了一个输入通道数2,输出通道数1的普通卷积

    def forward(self, x):
        return x * self.act(self.cv1(torch.cat([torch.mean(x, 1, keepdim=True), torch.max(x, 1, keepdim=True)[0]], 1)))

    上面这句话当中,假如输入张量的大小是(B,C,H,W)torch.mean(x, 1, keepdim=True)指的是取x在通道维度(dim=1)的平均值,输出为(B, 1, H, W)torch.max(x, 1, keepdim=True)返回的是x的通道最大值(B, 1, H, W)和其对应的通道索引,torch.max(x, 1, keepdim=True)[0]取出了通道最大值,抛弃了最大值的索引,所以返回的是(B, 1, H, W)的张量。

    torch.cat([torch.mean(x, 1, keepdim=True), torch.max(x, 1, keepdim=True)[0]], 1)的操作将torch.mean(x, 1, keepdim=True)计算得到的(B, 1, H, W)平均值torch.max(x, 1, keepdim=True)[0]计算得到的(B, 1, H, W)最大值进行通道相加(dim=1),得到(B, 2, H, W)的张量

    然后再把(B, 2, H, W)的张量先后送入self.cv1self.act进行卷积和激活,得到(B, 1, H, W)的空间注意力权重值。卷积是为了浓缩空间信息,激活函数是为了把张量映射到0到1之间,成为权重值。

    最后再用原来(B, C, H, W)的x与(B, 1, H, W)的空间注意力权重值进行逐元素相乘,相当于放大模型关注的重要的空间信息,同时削弱不重要的空间信息。

操作输入输出
torch.mean(x, 1, keepdim=True)(B,C,H,W)(B,1,H,W)
torch.max(x, 1, keepdim=True)[0](B,C,H,W)(B,1,H,W)
torch.cat(y, 1)[(B,1,H,W),(B,1,H,W)](B,2,H,W)
self.cv1(x)(B,2,H,W)(B,1,H,W)
self.act(x)(B,1,H,W)(B,1,H,W)
x * self.act(self.cv1(torch.cat([torch.mean(x, 1, keepdim=True), torch.max(x, 1, keepdim=True)[0]], 1)))(B,C,H,W)(B,C,H,W)

 

    本类所实现的空间注意力就如在论文CBAM: Convolutional Block Attention Module中绘制的空间注意力图:

 

类:CBAM (Convolutional Block Attention Module)

class CBAM(nn.Module):
    """Convolutional Block Attention Module."""

    def __init__(self, c1, kernel_size=7):
        """Initialize CBAM with given input channel (c1) and kernel size."""
        super().__init__()
        self.channel_attention = ChannelAttention(c1)
        self.spatial_attention = SpatialAttention(kernel_size)

    def forward(self, x):
        """Applies the forward pass through C1 module."""
        return self.spatial_attention(self.channel_attention(x))

    类CBAM先定义了两个分支self.channel_attentionself.apatial_attention,分别使用了上文所定义的两个类,然后再前向传播的时候,先让输入x通过通道注意力self.channel_attention,再通过空间注意力self.apatial_attention。与论文CBAM: Convolutional Block Attention Module中绘制的CBAM整体结构图一模一样:

 

类:Concat (通道连接)

class Concat(nn.Module):
    """Concatenate a list of tensors along dimension."""

    def __init__(self, dimension=1):
        """Concatenates a list of tensors along a specified dimension."""
        super().__init__()
        self.d = dimension

    def forward(self, x):
        """Forward pass for the YOLOv8 mask Proto module."""
        return torch.cat(x, self.d)

    self.d指定了连接张量的维度,例如,self.d=1表示沿通道维度 (channel dimension) 进行连接,self.d=2表示沿高度维度 (height dimension) 进行连接。x是一个包含要连接的张量列表的输入,函数返回值指的是将x沿着self.d指定的方向进行连接。

  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值