目标检测Backbone系列(2)CBAM —— Spatial Attention空间注意力及Resnet_cbam实现

本文介绍了如何在ResNet中实现Spatial Attention(空间注意力)和Resnet_CBAM结构,通过改进的通道注意力和空间注意力模块,提升模型对图像特征的关注精度。学习者可以通过代码实例了解如何在CNN中结合这两种注意力机制以增强特征表示能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spatial Attention空间注意力及Resnet_cbam实现


前言

上一次介绍Renest时,介绍了CNN里的通道注意力Channel-Wise的Split Attention及其block实现

这一次介绍一下另外一种注意力,空间注意力和CBAM结构。

下面是我实现resnet中加入CBAM结构的代码,可以给大家学习一下:Resnet_CBAM 


一、Attention表达改进

这里插播一下CBAM中对于注意力的改进。下图为我之前介绍过的通道注意力的具体实现形式,也即通过AvgPool来汇聚每一个通道中的信息,然后将每一个通道中的信息经过两个全连接层来提取通道间的相关关系,来构造注意力信息(全连接有激活函数来构造非线性,图未画出)

而在CBAM中,作者认为单单使用AvgPool来提取每个通道中的信息,并不能很好的去表示这个通道的信息,于是又加入了MaxPool来补充这个通道的信息。所以改进后整体的结构图如下 

注意,这里经过AvgPool和MaxPool的两个表达通道特征的信息是共用全连接层的参数的。最后的输出,则为两个计算出来的注意力信息相加。

二、Spatial Attention空间注意力

其实我们从通道注意力,其实就可以推断出空间注意力具体的实现。因为通道注意力是通过压缩每个通道中wxh的信息来构造的,那么空间注意力就是去压缩通道,来构造在空间维度上的信息

具体的实现即如下图所示,在通道维度上进行最大值和平均值的汇聚,CxWxH的feature map压缩成1xWxH的信息,然后通过带有注意力权重的卷积来提取注意力信息,最后,如果是单分支结构,通过sigmoid来使注意力权重非负,如果是多分支结构则应用softmax来使注意力权重非负。

                

如果我们和通道注意力相比,通道注意力中的注意力信息,是筛选出哪些通道的信息是和目前这个认为是相关的。空间注意力则是去关心对于Feature map来说哪些位置的信息是和目前认为相关的。

所以当我们去提取空间注意力的权重,然后和原图进行相乘操作,会发现空间注意力可以highlight到我们需要识别的物体在这幅图片的位置。 

下面给出改进后的通道注意力和空间注意力的代码,对照着看,理解会更加容易。

class ChannelAttention(nn.Module):
    def __init__(self, inplanes):
        super(ChannelAttention, self).__init__()
        self.max_pool = nn.MaxPool2d(1)
        self.avg_pool = nn.AvgPool2d(1)
        # 通道注意力,即两个全连接层连接
        self.fc = nn.Sequential(
            nn.Conv2d(in_channels=inplanes, out_channels=inplanes // 16, kernel_size=1, bias=False),
            nn.ReLU(),
            nn.Conv2d(in_channels=inplanes // 16, out_channels=inplanes, kernel_size=1, bias=False)
        )
        self.sigmoid = nn.Sigmoid()
    def forward(self, x):
        max_out = self.fc(self.max_pool(x))
        avg_out = self.fc(self.avg_pool(x))
        # 最后输出的注意力应该为非负
        out = self.sigmoid(max_out + avg_out)
        return out


class SpatialAttention(nn.Module):
    def __init__(self):
        super(SpatialAttention, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=2, out_channels=1, kernel_size=7, padding=7 // 2, bias=False)
        self.sigmoid = nn.Sigmoid()
    def forward(self, x):
        # 压缩通道提取空间信息
        max_out, _ = torch.max(x, dim=1, keepdim=True)
        avg_out = torch.mean(x, dim=1, keepdim=True)
        # 经过卷积提取空间注意力权重
        x = torch.cat([max_out, avg_out], dim=1)
        out = self.conv1(x)
        # 输出非负
        out = self.sigmoid(out)
        return out

三、Resnet_CBAM

 CBAM结构其实就是将通道注意力信息核空间注意力信息在一个block结构中进行运用。具体实现方法,以在resnet中添加CBAM结构为例。

其中的channel attention和Spatial attention即我上文所实现的方法,所以我们想要在resnet中实现这个方法其实也十分简单,即在原始block和残差结构连接前,依次通过channel attention和spatial attention即可。

class Bottleneck(nn.Module):

 
    def __init__(
        self,
        inplanes: int,
        planes: int,
        stride: int = 1,
        downsample: Optional[nn.Module] = None,
        groups: int = 1,
        base_width: int = 64,
        dilation: int = 1,
        norm_layer: Optional[Callable[..., nn.Module]] = None
    ):
        super(Bottleneck, self).__init__()
        expansion: int = 4    
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        width = int(planes * (base_width / 64.)) * groups
        # Both self.conv2 and self.downsample layers downsample the input when stride != 1
        self.conv1 = conv1x1(inplanes, width)
        self.bn1 = norm_layer(width)
        self.conv2 = conv3x3(width, width, stride, groups, dilation)
        self.bn2 = norm_layer(width)
        self.conv3 = conv1x1(width, planes * self.expansion)
        self.bn3 = norm_layer(planes * self.expansion)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.stride = stride

        # attention
        self.spatial_atten = SpatialAttention()
        self.channel_atten = ChannelAttention(planes * self.expansion)

   def forward(self, x: Tensor):
        identity = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        # attention
        atten = self.channel_atten(out) * out
        atten = self.spatial_atten(atten) * atten

        if self.downsample is not None:
            identity = self.downsample(x)

        atten += identity
        out = self.relu(atten)

        return out

总结

如果对我上一篇讲解Resnest的文章中注意力的讲解比较熟悉的话,对应CBAM这个结构是比较容易理解和接受的。

但是网络的具体性能如何,大家可以用我的代码测试一下,个人在实验和认知上都觉得CBAM的结构其实还有很多需要优化的地方。

<think>好的,我现在需要解决用户的问题:如何将CBAM注意力模块集成到ResNet类型的backbone中。用户还要求方法及实现教程。首先,我得回忆一下CBAM的结构和ResNet的基本架构。 CBAM,也就是卷积块注意力模块,包含通道注意力空间注意力两个部分。根据引用[3],CBAM会依次沿着通道和空间维度生成注意力图,然后与输入特征相乘,进行特征细化。而ResNet的残差块结构,比如BasicBlock或Bottleneck,通常由多个卷积层和残差连接组成。根据引用[2],添加CBAM的位置是在原始block和残差结构连接前,依次通过通道和空间注意力。 接下来,我需要明确在ResNet中哪些地方可以插入CBAM模块。通常,ResNet的每个残差块内部会有卷积层,之后可能添加注意力机制。比如,在BasicBlock中,可能在两个卷积层之后,将特征输入CBAM,然后再与残差连接相加。这样,注意力机制可以增强重要特征,抑制不重要的。 然后,具体实现步骤可能包括:修改ResNet的BasicBlock或Bottleneck类,在forward函数中加入CBAM的处理。比如,在卷积操作之后,将得到的特征传入CBAM模块,然后再进行残差连接。这样,每个残差块在处理特征后,都会经过注意力模块的调整。 代码实现方面,用户提供的示例是Resnet_CBAM,需要参考这个结构。根据引用[2],实现的关键是在原始block和残差连接前添加CBAM。所以,在ResNet的每个残差块中,前向传播时,特征在卷积层处理后,先经过通道注意力,然后是空间注意力,最后再与输入相加。 需要确保CBAM模块的正确实现,包括通道注意力空间注意力的结构。通道注意力通常使用全局平均池化和最大池化,后接多层感知机;空间注意力则使用池化后的结果,通过卷积层生成空间注意力图。然后将两者依次应用到特征上。 可能遇到的问题包括:如何正确地将CBAM插入到现有结构中,确保维度匹配,尤其是在残差连接时。例如,如果输入和输出的通道数不同,可能需要调整残差边的处理,比如使用1x1卷积调整通道数,这在ResNet的Bottleneck结构中已经有类似处理。 另外,还需要考虑预训练模型的兼容性。如果是在现有的预训练ResNet上添加CBAM,可能需要重新训练整个网络,或者进行微调。根据引用[3],CBAM是轻量级的,可以端到端训练,所以集成后需要重新训练模型。 总结步骤:1. 实现CBAM模块;2. 修改ResNet的残差块,在卷积层后添加CBAM;3. 确保残差连接正确处理;4. 训练模型,验证性能提升。同时,需要参考用户提供的代码示例和引用资料中的实现方法,确保正确性。</think>### 如何将CBAM集成到ResNet Backbone中? #### 一、CBAM模块结构 CBAM(Convolutional Block Attention Module)包含**通道注意力**和**空间注意力**两个子模块,其数学表达式为: $$ \text{CBAM}(F) = \sigma(M_c(F) \otimes M_s(F \otimes M_c(F))) $$ 其中$F$为输入特征图,$M_c$为通道注意力,$M_s$为空间注意力,$\otimes$表示逐元素相乘[^3]。 #### 二、ResNet集成方法 1. **插入位置** 在ResNet的每个残差块(BasicBlock/Bottleneck)中,将CBAM模块**添加在卷积层与残差连接之间**,即在特征图与跳跃连接相加前进行注意力增强[^2]。 2. **实现步骤** - **步骤1**:定义CBAM模块 ```python class CBAM(nn.Module): def __init__(self, channels, reduction=16): super().__init__() # 通道注意力 self.channel_att = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(channels, channels//reduction, 1), nn.ReLU(), nn.Conv2d(channels//reduction, channels, 1), nn.Sigmoid() ) # 空间注意力 self.spatial_att = nn.Sequential( nn.Conv2d(2, 1, 7, padding=3), nn.Sigmoid() ) def forward(self, x): # 通道注意力 mc = self.channel_att(x) x = x * mc # 空间注意力 avg_out = torch.mean(x, dim=1, keepdim=True) max_out, _ = torch.max(x, dim=1, keepdim=True) ms = self.spatial_att(torch.cat([avg_out, max_out], dim=1)) return x * ms ``` - **步骤2**:修改ResNet残差块 ```python class BasicBlock_CBAM(nn.Module): def __init__(self, inplanes, planes, stride=1): super().__init__() self.conv1 = nn.Conv2d(inplanes, planes, 3, stride, padding=1) self.bn1 = nn.BatchNorm2d(planes) self.relu = nn.ReLU() self.conv2 = nn.Conv2d(planes, planes, 3, padding=1) self.bn2 = nn.BatchNorm2d(planes) # 添加CBAM self.cbam = CBAM(planes) # 注意通道数匹配 # 下采样处理 self.downsample = ... def forward(self, x): identity = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) # 添加注意力 out = self.cbam(out) # ← 关键插入点 # 残差连接 if self.downsample is not None: identity = self.downsample(x) out += identity return self.relu(out) ``` #### 三、训练建议 1. **初始化策略**:建议使用预训练ResNet权重,CBAM模块采用He初始化 2. **学习率设置**:CBAM部分的学习率应为Backbone的10倍(因Backbone已预训练) 3. **性能验证**:在ImageNet上集成CBAM可使Top-1准确率提升0.5-1.2%[^3]
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lzzzzzzm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值