【卷积神经网络系列】十八、CBAM


参考资料:

论文:

  CBAM: Convolutional Block Attention Module

博客:

  【注意力机制 】 经典网络模型2——CBAM 详解与复现

  【注意力机制】CBAM详解


一、前言

 为提高 CNN性能 ,最近的研究主要研究了网络的三个重要因素:

  • depth(深度)
  • width(宽度)
  • cardinality(基数)

 从20世纪90年代 LeNet 网络的提出,网络的 深度 不断增加;后来 VGG 网络表明,相同形状的块堆叠 效果良好;GoogLeNet 网络的提出,提出宽度 也是提高模型性能的另一个重要因素;同样的,ResNet残差块 以相同拓扑与跳跃式连接堆叠在一起,构建了一个非常深的架构,达到了不错的效果;XceptionResNeXt 网络表明,增加网络 基数 不仅减少了参数量,而且比另两个因素(深度和宽度) 具有更强的表示能力;

 除了这些因素之外,作者还研究了网络设计的另一个方面—— 注意力 ;“注意力” 也是人类视觉系统的一个很有趣的地方;通过注意力机制来增加网络的表征力:关注重要特征,抑制不必要特征

 卷积运算是通过将 跨通道信息和空间信息混合 在一起来提取信息特征的 ;

 作者因此提出了 CBAM(Convolutional Block Attention Module) 来强调通道轴空间轴这两个主要维度上的有意义特征 ,创新提出了Channel Attention Module (通道注意模块)Spatial Attention Module (空间注意模块)

在这里插入图片描述

 假设 输入特征图为 : F ∈ R C × H × W F ∈ R^{C\times H\times W} FRC×H×W ,利用 CBAM 依此推导出:

  • 一维通道注意图 : M c ∈ R C × 1 × 1 M_c ∈ R^{C\times 1\times 1} McRC×1×1
  • 二维空间注意图 : M s ∈ R 1 × H × W M_s ∈ R^{1\times H\times W} MsR1×H×W

 总的注意过程可以概括为 :

在这里插入图片描述

 可以看到 CBAM 包含2个独立的子模块, CAM和SAM,分别进行通道与空间上的Attention, 这样不只能够节约参数和计算力,并且保证了其能够做为即插即用的模块集成到现有的网络架构中去。

 通道上的 Attention 机制在 2017 年的 SENet 就被提出。事实上,CAM 与 SENet 相比,只是多了一个并行的 Max Pooling 层。至于为何如此更改,论文也给出了解释和实验数据支持。


二、网络结构

2.1 Channel Attention Module(CAM)

 利用 特征间的通道关系 来生成通道注意图:由于feature map的每个channel都被认为是 一个feature检测器 ,因此 channel 的注意力集中在 给定输入图像的 "什么" 是有意义的

 为了有效地计算通道注意力,采用 压缩输入特征映射的空间维度 的方法 :文中同时使用 AvgPool (平均池化)MaxPool (最大池化) 的方法,并证明了这种做法比单独使用一种池化方法更具有表征力;

在这里插入图片描述

 式中, σ σ σsigmoid 函数 , W 0 ∈ R C r × C W_0 ∈ R^{\frac{C}{r\times C}} W0Rr×CC W 1 ∈ R C × C r W_1 ∈ R^{\frac{C×C}{r}} W1RrC×C,MLP的权重 W 0 W_0 W0 W 1 W_1 W1 共享,在 W 0 W_0 W0 前是 ReLU 激活函数 ;

在这里插入图片描述

具体流程如下

  • (1)将输入的特征图 F ( H × W × C ) F(H×W×C) F(H×W×C)分别经过基于 W W W H H H 的Global Max Pooling(全局最大池化)和Global Average Pooling(全局平均池化),得到两个 1 × 1 × C 1×1×C 1×1×C 的特征图。
  • (2)再将它们分别送入一个两层的神经网络(MLP),第一层神经元个数为 C r \frac{C}{r} rC(r为减少率),激活函数为 R e L U ReLU ReLU,第二层神经元个数为 C C C,这个两层的神经网络是共享的。
  • (3)将MLP输出的特征进行基于element-wise的加和操作,再经过sigmoid激活操作,生成最终的channel attention feature,即 M c M_c Mc
  • (4)将 M c M_c Mc 和输入特征图 F F F 做element-wise乘法操作,生成Spatial attention模块需要的输入特征。

pooling的使用

 在channel attention中,表1对于pooling的使用进行了实验对比,发现avg & max的并行池化的效果要更好。这里也有可能是池化丢失的信息太多,avg&&max的并行连接方式比单一的池化丢失的信息更少,所以效果会更好一点。

在这里插入图片描述


2.2 Spatial Attention Module(SAM)

 利用 特征间的空间关系 生成空间注意图:与通道注意模块不同的是,空间注意模块关注的是 信息部分 "在哪里"

 作为通道注意模块的补充;为了计算空间注意力,首先沿着通道轴应用 平均池化和最大池化 操作,并将它们连接起来以生成一个有效的 特征描述符

 文中使用两个池化操作聚合一个feature map的通道信息,生成两个2D maps :

  • F a v g s ∈ R 1 × H × W F^s_{avg} ∈ R^{1×H×W} FavgsR1×H×W :表示通道的 平均池化特性
  • F m a x s ∈ R 1 × H × W F^s_{max} ∈ R^{1×H×W} FmaxsR1×H×W :表示通道的 最大池化特性

 然后利用一个标准的卷积层进行连接和卷积操作,得到二维空间注意力图;

在这里插入图片描述

式中, σ σ σsigmoid 函数 , f 7 x 7 f^{7x7} f7x7 7 x 7 7 x 7 7x7 大小的卷积核;

在这里插入图片描述

具体流程如下

  • (1)先将Channel attention模块输出的特征图 F ′ F' F 作为本模块的输入特征图。
  • (2)做一个基于channel的global max pooling 和global average pooling,得到两个H×W×1 的特征图
  • (3)将这2个特征图基于channel 做concat操作(通道拼接)。
  • (4)经过一个 7 × 7 7×7 7×7 卷积(7×7比3×3效果要好)操作,降维为1个channel,即 H × W × 1 H×W×1 H×W×1。再经过sigmoid生成spatial attention feature,即 M s M_s Ms
  • (5)最后将该feature和该模块的输入feature做乘法,得到最终生成的特征。

三、CBAM 的应用

 下面是将 CBAM 结合 ResBlock 应用于ResNet中 ;两个模块可以以并行或顺序的方式放置,实验测试发现 顺序排列并行排列 有更好的结果

在这里插入图片描述
在这里插入图片描述


四、CBAM可视化

 最后,分别使用 ResNet50ResNet50+SENetResNet50+CBAM 进行实验得到可视化结果 :

在这里插入图片描述


五、总结

  • 论文提出了一种基于注意力机制的轻量型结构 CBAM ,基本上可以添加到所有常规的卷积层中
  • 文中验证了 Channel Attention Module 中 avg 与 max 并行的方式最好,接下来通过实验验证了 Channel Attention Module 和 Spatial Attention Module 的最佳先后顺序,还对比了 CBAM 与 SENet 的性能。
  • 文章还可视化了 CBAM 的关注区域,使得 CBAM 具有更好的解释性。最后在目标检测任务上进行实验,验证了 CBAM 的通用性好。

六、论文复现

参考

  【注意力机制 】 经典网络模型2——CBAM 详解与复现


(1)Channel Attention Module:

在这里插入图片描述

class ChannelAttention(nn.Module):           # Channel Attention Module
    def __init__(self, in_planes):
        super(ChannelAttention, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.max_pool = nn.AdaptiveMaxPool2d(1)

        self.fc1 = nn.Conv2d(in_planes, in_planes // 16, kernel_size=1, bias=False)
        self.relu = nn.ReLU()
        self.fc2 = nn.Conv2d(in_planes // 16, in_planes, kernel_size=1, bias=False)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        avg_out = self.avg_pool(x)
        avg_out = self.fc1(avg_out)
        avg_out = self.relu(avg_out)
        avg_out = self.fc2(avg_out)

        max_out = self.max_pool(x)
        max_out = self.fc1(max_out)
        max_out = self.relu(max_out)
        max_out = self.fc2(max_out)

        out = avg_out + max_out
        out = self.sigmoid(out)
        return out

(2)Spatial Attention Module:

在这里插入图片描述

class SpatialAttention(nn.Module):           # Spatial Attention Module
    def __init__(self):
        super(SpatialAttention, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=2, out_channels=1, 
                               kernel_size=7, padding=3, bias=False)
        self.sigmoid = nn.Sigmoid()

    def forward(self, x):
        # 并行的两个通道维度上的池化,然后拼接
        avg_out = torch.mean(x, dim=1, keepdim=True)
        max_out, _ = torch.max(x, dim=1, keepdim=True)
        out = torch.cat([avg_out, max_out], dim=1)
        
        out = self.conv1(out)
        out = self.sigmoid(out)
        return out

(3)BasicBlock:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class BasicBlock(nn.Module):      # 左侧的 residual block 结构(18-layer、34-layer)
    expansion = 1

    def __init__(self, in_planes, planes, stride=1):      # 两层卷积 Conv2d + Shutcuts
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_planes, self.expansion*planes, kernel_size=3,
                               stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        
        self.conv2 = nn.Conv2d(self.expansion*planes, self.expansion*planes, kernel_size=3,
                               stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(self.expansion*planes)

        self.channel = ChannelAttention(self.expansion*planes)     # Channel Attention Module
        self.spatial = SpatialAttention()                          # Spatial Attention Module

        self.shortcut = nn.Sequential()
        if stride != 1 or in_planes != self.expansion*planes:      # Shutcuts用于构建 Conv Block 和 Identity Block
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, self.expansion*planes,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(self.expansion*planes)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        
        out = self.bn2(self.conv2(out))
        
        # 加入通道注意力,并在原始输入上加权
        CBAM_Cout = self.channel(out)
        out = out * CBAM_Cout
        
        # 加入空间注意力,并在原始输入上加权
        CBAM_Sout = self.spatial(out)
        out = out * CBAM_Sout
        
        # 残差连接
        out += self.shortcut(x)
        out = F.relu(out)
        return out

(4)Bottleneck:

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

class Bottleneck(nn.Module):      # 右侧的 residual block 结构(50-layer、101-layer、152-layer)
    expansion = 4

    def __init__(self, in_planes, planes, stride=1):      # 三层卷积 Conv2d + Shutcuts
        super(Bottleneck, self).__init__()
        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)
        self.bn1 = nn.BatchNorm2d(planes)
        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3,
                               stride=stride, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(planes)
        self.conv3 = nn.Conv2d(planes, self.expansion*planes,
                               kernel_size=1, bias=False)
        self.bn3 = nn.BatchNorm2d(self.expansion*planes)

        self.channel = ChannelAttention(self.expansion*planes)     # Channel Attention Module
        self.spatial = SpatialAttention()                          # Spatial Attention Module

        self.shortcut = nn.Sequential()
        if stride != 1 or in_planes != self.expansion*planes:      # Shutcuts用于构建 Conv Block 和 Identity Block
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, self.expansion*planes,
                          kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(self.expansion*planes)
            )

    def forward(self, x):
        out = F.relu(self.bn1(self.conv1(x)))
        out = F.relu(self.bn2(self.conv2(out)))
        
        out = self.bn3(self.conv3(out))
        
        CBAM_Cout = self.channel(out)
        out = out * CBAM_Cout
        
        CBAM_Sout = self.spatial(out)
        out = out * CBAM_Sout
        
        out += self.shortcut(x)
        out = F.relu(out)
        return out

(5)主体结构:

在这里插入图片描述

class CBAM_ResNet(nn.Module):
    def __init__(self, block, num_blocks, num_classes=1000):
        super(CBAM_ResNet, self).__init__()
        self.in_planes = 64

        self.conv1 = nn.Conv2d(3, 64, kernel_size=3,
                               stride=1, padding=1, bias=False)                  # conv1
        self.bn1 = nn.BatchNorm2d(64)
        
        self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)       # conv2_x
        self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)      # conv3_x
        self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)      # conv4_x
        self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)      # conv5_x
        
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.linear = nn.Linear(512 * block.expansion, num_classes)

    def _make_layer(self, block, planes, num_blocks, stride):
        strides = [stride] + [1]*(num_blocks-1)
        layers = []
        for stride in strides:
            layers.append(block(self.in_planes, planes, stride))
            self.in_planes = planes * block.expansion
        return nn.Sequential(*layers)

    def forward(self, x):
        x = F.relu(self.bn1(self.conv1(x)))
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        out = self.linear(x)
        return out

(6)构建不同的网络:

在这里插入图片描述

def make_net(net, num_classes):
    if net == 'CBAM_ResNet18':
        return CBAM_ResNet(BasicBlock, [2, 2, 2, 2], num_classes)
    if net == 'CBAM_ResNet34':
        return CBAM_ResNet(BasicBlock, [3, 4, 6, 3], num_classes)
    if net == 'CBAM_ResNet50':
        return CBAM_ResNet(Bottleneck, [3, 4, 6, 3], num_classes)
    if net == 'CBAM_ResNet101':
        return CBAM_ResNet(Bottleneck, [3, 4, 23, 3], num_classes)
    if net == 'CBAM_ResNet152':
        return CBAM_ResNet(Bottleneck, [3, 8, 36, 3], num_classes)
        
def test():
    CBAM_ResNet50 = make_net('CBAM_ResNet50', num_classes=2)
    #创建模型,部署gpu
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    # CBAM_ResNet50.to(device)
    # GPU显存不够,只能放在CPU上
    summary(CBAM_ResNet50, (3, 224, 224), batch_size=-1, device="cpu")
if __name__ == '__main__':
    test()
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要在Keras卷神经网络中加入CBAM注意力机制,需要按照以下步骤进行操作: 1. 导入必要的Keras库和模块,例如:Conv2D、Activation、Add、Lambda等。 2. 定义CBAM注意力机制的两个模块:通道注意力模块和空间注意力模块。 3. 将通道注意力模块和空间注意力模块组合成CBAM模块。 4. 在卷神经网络的每个卷层后添加CBAM模块。 下面是一个使用Keras实现CBAM注意力机制的示例代码: ```python from keras.layers import Conv2D, Activation, Add, Lambda def channel_attention(input_feature, ratio=8): # 计算通道注意力权重 channel_axis = 1 if K.image_data_format() == "channels_first" else -1 channel = input_feature._keras_shape[channel_axis] shared_layer_one = Dense(channel//ratio, activation='relu', kernel_initializer='he_normal', use_bias=True, bias_initializer='zeros') shared_layer_two = Dense(channel, kernel_initializer='he_normal', use_bias=True, bias_initializer='zeros') avg_pool = GlobalAveragePooling2D()(input_feature) avg_pool = Reshape((1,1,channel))(avg_pool) avg_pool = shared_layer_one(avg_pool) avg_pool = shared_layer_two(avg_pool) max_pool = GlobalMaxPooling2D()(input_feature) max_pool = Reshape((1,1,channel))(max_pool) max_pool = shared_layer_one(max_pool) max_pool = shared_layer_two(max_pool) cbam_feature = Add()([avg_pool,max_pool]) cbam_feature = Activation('sigmoid')(cbam_feature) if K.image_data_format() == "channels_first": cbam_feature = Permute((3, 1, 2))(cbam_feature) return multiply([input_feature, cbam_feature]) def spatial_attention(input_feature): # 计算空间注意力权重 kernel_size = 7 if K.image_data_format() == "channels_first": channel = input_feature._keras_shape[1] cbam_feature = Permute((2,3,1))(input_feature) else: channel = input_feature._keras_shape[-1] cbam_feature = input_feature avg_pool = Lambda(lambda x: K.mean(x, axis=3, keepdims=True))(cbam_feature) max_pool = Lambda(lambda x: K.max(x, axis=3, keepdims=True))(cbam_feature) concat = Concatenate(axis=3)([avg_pool, max_pool]) cbam_feature = Conv2D(filters = 1, kernel_size=kernel_size, strides=1, padding='same', activation='sigmoid', kernel_initializer='he_normal', use_bias=False)(concat) if K.image_data_format() == "channels_first": cbam_feature = Permute((3, 1, 2))(cbam_feature) return multiply([input_feature, cbam_feature]) def cbam_block(cbam_feature,ratio=8): # 定义CBAM模块 cbam_feature = channel_attention(cbam_feature,ratio) cbam_feature = spatial_attention(cbam_feature) return cbam_feature ``` 可以在需要添加CBAM注意力机制的卷层后添加CBAM模块,例如: ```python input_data = Input(shape=(224, 224, 3)) x = Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1), padding='same')(input_data) x = cbam_block(x) x = Activation('relu')(x) ``` 这样就可以在卷神经网络中加入CBAM注意力机制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

travellerss

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

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

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

打赏作者

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

抵扣说明:

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

余额充值