【CV中的Attention机制】BiSeNet中的FFM模块与ARM模块

前言:之前介绍过一个语义分割中的注意力机制模块-scSE模块,效果很不错。今天讲的也是语义分割中使用到注意力机制的网络BiSeNet,这个网络有两个模块,分别是FFM模块和ARM模块。其实现也很简单,不过作者对注意力机制模块理解比较深入,提出的FFM模块进行的特征融合方式也很新颖。

1. 简介

语义分割需要丰富的空间信息和相关大的感受野,目前很多语义分割方法为了达到实时推理的速度选择牺牲空间分辨率,这可能会导致比较差的模型表现。

BiSeNet(Bilateral Segmentation Network)中提出了空间路径和上下文路径:

  • 空间路径用于保留语义信息生成较高分辨率的feature map(减少下采样的次数)
  • 上下文路径使用了快速下采样的策略,用于获取充足的感受野。
  • 提出了一个FFM模块,结合了注意力机制进行特征融合。

本文主要关注的是速度和精度的权衡,对于分辨率为2048×1024的输入,BiSeNet能够在NVIDIA Titan XP显卡上达到105FPS的速度,做到了实时语义分割。

2. 分析

提升语义分割速度主要有三种方法,如下图所示:

  1. 通过resize的方式限定输入大小,降低计算复杂度。缺点是空间细节有损失,尤其是边界部分。
  2. 通过减少网络通道的个数来加快处理速度。缺点是会弱化空间信息。
  3. 放弃最后阶段的下采样(如ENet)。缺点是模型感受野不足以覆盖大物体,判别能力差。

语义分割中,U型结构也被广泛使用,如下图所示:

这种U型网络通过融合backbone不同层次的特征,在U型结构中逐渐增加空间分辨率,保留更多的细节特征。不过有两个缺点:

  1. 高分辨率特征图计算量非常大,影响计算速度。
  2. 由于resize或者减少网络通道而丢失的空间信息无法通过引入浅层而轻易复原。

3. 细节

下图是BiSeNet的架构图,从图中可看到主要包括两个部分:空间路径和上下文路径。

代码实现来自:https://github.com/ooooverflow/BiSeNet,其CP部分没有使用Xception39而使用的ResNet18。

空间路径SP

减少下采样次数,只使用三个卷积层(stride=2)获得1/8的特征图,由于它利用了较大尺度的特征图,所以可以编码比较丰富的空间信息。

class ConvBlock(torch.nn.Module):
    def __init__(self,
                 in_channels,
                 out_channels,
                 kernel_size=3,
                 stride=2,
                 padding=1):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels,
                               out_channels,
                               kernel_size=kernel_size,
                               stride=stride,
                               padding=padding,
                               bias=False)
        self.bn = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU()

    def forward(self, input):
        x = self.conv1(input)
        return self.relu(self.bn(x))

class Spatial_path(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.convblock1 = ConvBlock(in_channels=3, out_channels=64)
        self.convblock2 = ConvBlock(in_channels=64, out_channels=128)
        self.convblock3 = ConvBlock(in_channels=128, out_channels=256)

    def forward(self, input):
        x = self.convblock1(input)
        x = self.convblock2(x)
        x = self.convblock3(x)
        return x

上下文路径CP

为了增大感受野,论文提出上下文路径,在Xception尾部添加全局平均池化层,从而提供更大的感受野。可以看出CP中进行了32倍的下采样。(示例中CP部分使用的是ResNet18,不是论文中的xception39)

class resnet18(torch.nn.Module):
    def __init__(self, pretrained=True):
        super().__init__()
        self.features = models.resnet18(pretrained=pretrained)
        self.conv1 = self.features.conv1
        self.bn1 = self.features.bn1
        self.relu = self.features.relu
        self.maxpool1 = self.features.maxpool
        self.layer1 = self.features.layer1
        self.layer2 = self.features.layer2
        self.layer3 = self.features.layer3
        self.layer4 = self.features.layer4

    def forward(self, input):
        x = self.conv1(input)
        x = self.relu(self.bn1(x))
        x = self.maxpool1(x)
        feature1 = self.layer1(x)  # 1 / 4
        feature2 = self.layer2(feature1)  # 1 / 8
        feature3 = self.layer3(feature2)  # 1 / 16
        feature4 = self.layer4(feature3)  # 1 / 32
        # global average pooling to build tail
        tail = torch.mean(feature4, 3, keepdim=True)
        tail = torch.mean(tail, 2, keepdim=True)
        return feature3, feature4, tail

组件融合

为了SP和CP更好的融合,提出了特征融合模块FFM还有注意力优化模块ARM。

ARM:

ARM使用在上下文路径中,用于优化每一阶段的特征,使用全局平均池化指导特征学习,计算成本可以忽略。其具体实现方式与SE模块很类似,属于通道注意力机制。

class AttentionRefinementModule(torch.nn.Module):
    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1)
        self.bn = nn.BatchNorm2d(out_channels)
        self.sigmoid = nn.Sigmoid()
        self.in_channels = in_channels
        self.avgpool = nn.AdaptiveAvgPool2d(output_size=(1, 1))

    def forward(self, input):
        # global average pooling
        x = self.avgpool(input)
        assert self.in_channels == x.size(
            1), 'in_channels and out_channels should all be {}'.format(
                x.size(1))
        x = self.conv(x)
        # x = self.sigmoid(self.bn(x))
        x = self.sigmoid(x)
        # channels of input and x should be same
        x = torch.mul(input, x)
        return x

FFM:

特征融合模块用于融合CP和SP提供的输出特征,由于两路特征并不相同,所以不能对这两部分特征进行简单的加权。SP提供的特征是低层次的(8×down),CP提供的特征是高层语义的(32×down)。

将两个部分特征图通过concate方式叠加,然后使用类似SE模块的方式计算加权特征,起到特征选择和结合的作用。(这种特征融合方式值得学习)

class FeatureFusionModule(torch.nn.Module):
    def __init__(self, num_classes, in_channels):
        super().__init__()
        self.in_channels = in_channels
        self.convblock = ConvBlock(in_channels=self.in_channels,
                                   out_channels=num_classes,
                                   stride=1)
        self.conv1 = nn.Conv2d(num_classes, num_classes, kernel_size=1)
        self.relu = nn.ReLU()
        self.conv2 = nn.Conv2d(num_classes, num_classes, kernel_size=1)
        self.sigmoid = nn.Sigmoid()
        self.avgpool = nn.AdaptiveAvgPool2d(output_size=(1, 1))

    def forward(self, input_1, input_2):
        x = torch.cat((input_1, input_2), dim=1)
        assert self.in_channels == x.size(
            1), 'in_channels of ConvBlock should be {}'.format(x.size(1))
        feature = self.convblock(x)
        x = self.avgpool(feature)

        x = self.relu(self.conv1(x))
        x = self.sigmoid(self.conv2(x))
        x = torch.mul(feature, x)
        x = torch.add(x, feature)
        return x

BiSeNet网络整个模型:

class BiSeNet(torch.nn.Module):
    def __init__(self, num_classes, context_path):
        super().__init__()
        self.spatial_path = Spatial_path()
        self.context_path = build_contextpath(name=context_path)
        if context_path == 'resnet101':
            self.attention_refinement_module1 = AttentionRefinementModule(
                1024, 1024)
            self.attention_refinement_module2 = AttentionRefinementModule(
                2048, 2048)
            self.supervision1 = nn.Conv2d(in_channels=1024,
                                          out_channels=num_classes,
                                          kernel_size=1)
            self.supervision2 = nn.Conv2d(in_channels=2048,
                                          out_channels=num_classes,
                                          kernel_size=1)
            self.feature_fusion_module = FeatureFusionModule(num_classes, 3328)

        elif context_path == 'resnet18':
            self.attention_refinement_module1 = AttentionRefinementModule(
                256, 256)
            self.attention_refinement_module2 = AttentionRefinementModule(
                512, 512)
            self.supervision1 = nn.Conv2d(in_channels=256,
                                          out_channels=num_classes,
                                          kernel_size=1)
            self.supervision2 = nn.Conv2d(in_channels=512,
                                          out_channels=num_classes,
                                          kernel_size=1)
            self.feature_fusion_module = FeatureFusionModule(num_classes, 1024)
        else:
            print('Error: unspport context_path network \n')
        self.conv = nn.Conv2d(in_channels=num_classes,
                              out_channels=num_classes,
                              kernel_size=1)

    def forward(self, input):
        sx = self.spatial_path(input)
        cx1, cx2, tail = self.context_path(input)
        cx1 = self.attention_refinement_module1(cx1)
        cx2 = self.attention_refinement_module2(cx2)
        cx2 = torch.mul(cx2, tail)
        cx1 = torch.nn.functional.interpolate(cx1,
                                              size=sx.size()[-2:],
                                              mode='bilinear')
        cx2 = torch.nn.functional.interpolate(cx2,
                                              size=sx.size()[-2:],
                                              mode='bilinear')
        cx = torch.cat((cx1, cx2), dim=1)
        if self.training == True:
            cx1_sup = self.supervision1(cx1)
            cx2_sup = self.supervision2(cx2)
            cx1_sup = torch.nn.functional.interpolate(cx1_sup,
                                                      size=input.size()[-2:],
                                                      mode='bilinear')
            cx2_sup = torch.nn.functional.interpolate(cx2_sup,
                                                      size=input.size()[-2:],
                                                      mode='bilinear')
        result = self.feature_fusion_module(sx, cx)
        result = torch.nn.functional.interpolate(result,
                                                 scale_factor=8,
                                                 mode='bilinear')
        result = self.conv(result)
        if self.training == True:
            return result, cx1_sup, cx2_sup
        return result

4. 实验

使用了Xception39处理实时语义分割任务,在CityScapes, CamVid和COCO stuff三个数据集上进行评估。

消融实验:

测试了basemodel xception39,参数量要比ResNet18小得多,同时MIOU只略低于与ResNet18。

以上是BiSeNet各个模块的消融实验,可以看出,每个模块都是有效的。

统一使用了640×360分辨率的图片进行对比参数量和FLOPS状态。

上表对BiSeNet网络和其他网络就MIOU和FPS上进行比较,可以看出该方法相比于其他方法在速度和精度方面有很大的优越性。

在使用ResNet101等比较深的网络作为backbone的情况下,效果也是超过了其他常见的网络,这证明了这个模型的有效性。

5. 结论

BiSeNet 旨在同时提升实时语义分割的速度与精度,它包含两路网络:Spatial Path 和 Context Path。Spatial Path 被设计用来保留原图像的空间信息,Context Path 利用轻量级模型和全局平均池化快速获取大感受野。由此,在 105 fps 的速度下,该方法在 Cityscapes 测试集上取得了 68.4% mIoU 的结果。

原文地址:https://www.cnblogs.com/pprp/p/12288305.html

时间: 2024-07-30 21:38:04

【CV中的Attention机制】BiSeNet中的FFM模块与ARM模块的相关文章

【CV中的Attention机制】易于集成的Convolutional Block Attention Module(CBAM模块)

前言: 这是CV中的Attention机制专栏的第一篇博客,并没有挑选实现起来最简单的SENet作为例子,而是使用了CBAM作为第一个讲解的模块,这是由于其使用的广泛性以及易于集成.目前cv领域借鉴了nlp领域的attention机制以后生产出了很多有用的基于attention机制的论文,attention机制也是在2019年论文中非常火.这篇cbam虽然是在2018年提出的,但是其影响力比较深远,在很多领域都用到了该模块,所以一起来看一下这个模块有什么独到之处,并学着实现它. 1. 什么是注意

【从零开始学习YOLOv3】7. 教你在YOLOv3模型中添加Attention机制

前言:[从零开始学习YOLOv3]系列越写越多,本来安排的内容比较少,但是在阅读代码的过程中慢慢发掘了一些新的亮点,所以不断加入到这个系列中.之前都在读YOLOv3中的代码,已经学习了cfg文件.模型构建等内容.本文在之前的基础上,对模型的代码进行修改,将之前Attention系列中的SE模块和CBAM模块集成到YOLOv3中. 1. 规定格式 正如[convolutional],[maxpool],[net],[route]等层在cfg中的定义一样,我们再添加全新的模块的时候,要规定一下cfg

Deep Learning基础--理解LSTM/RNN中的Attention机制

导读 目前采用编码器-解码器 (Encode-Decode) 结构的模型非常热门,是因为它在许多领域较其他的传统模型方法都取得了更好的结果.这种结构的模型通常将输入序列编码成一个固定长度的向量表示,对于长度较短的输入序列而言,该模型能够学习出对应合理的向量表示.然而,这种模型存在的问题在于:当输入序列非常长时,模型难以学到合理的向量表示. 在这篇博文中,我们将探索加入LSTM/RNN模型中的attention机制是如何克服传统编码器-解码器结构存在的问题的. 通过阅读这篇博文,你将会学习到: 传

【CV中的Attention机制】CBAM的姊妹篇-BAM模块

1. BAM BAM全程是bottlenect attention module,与CBAM很相似的起名,还是CBAM的团队完成的作品. CBAM被ECCV18接受,BAM被BMVC18接收. CBAM可以看做是通道注意力机制和空间注意力机制的串联(先通道后空间),BAM可以看做两者的并联. 这个模块之所以叫bottlenect是因为这个模块放在DownSample 也就是pooling layer之前,如下图所示: 由于改论文与上一篇:CBAM的理论部分极为相似,下边直接进行实现部分. 2.

【CV中的Attention机制】Selective Kernel Networks(SE进化版)

1. SKNet SKNet是SENet的加强版,结合了SE opetator, Merge-and-Run Mappings以及attention on inception block的产物.其最终提出的也是与SE类似的一个模块,名为SK, 可以自适应调节自身的感受野.据作者说,该模块在超分辨率任务上有很大提升,并且论文中的实验也证实了在分类任务上有很好的表现. 这篇博客重画了SK模块示意图,详见下图,下图中上边的部分是重画的,下边的是论文中的图,虽然比较简洁,但是比较难理解.上边重画的部分分

[NLP/Attention]关于attention机制在nlp中的应用总结

原文链接: https://blog.csdn.net/qq_41058526/article/details/80578932 attention 总结 参考:注意力机制(Attention Mechanism)在自然语言处理中的应用 Attention函数的本质可以被描述为一个查询(query)到一系列(键key-值value)对的映射,如下图. 在计算attention时主要分为三步: 第一步是将query和每个key进行相似度计算得到权重,常用的相似度函数有点积,拼接,感知机等: 第二步

[转] 深度学习中的注意力机制

from: https://zhuanlan.zhihu.com/p/37601161 注意力模型最近几年在深度学习各个领域被广泛使用,无论是图像处理.语音识别还是自然语言处理的各种不同类型的任务中,都很容易遇到注意力模型的身影.所以,了解注意力机制的工作原理对于关注深度学习技术发展的技术人员来说有很大的必要. 人类的视觉注意力 从注意力模型的命名方式看,很明显其借鉴了人类的注意力机制,因此,我们首先简单介绍人类视觉的选择性注意力机制. 图1 人类的视觉注意力 视觉注意力机制是人类视觉所特有的大

Spring 中的事件机制

说到事件机制,可能脑海中最先浮现的就是日常使用的各种 listener,listener去监听事件源,如果被监听的事件有变化就会通知listener,从而针对变化做相应的动作.这些listener是怎么实现的呢?说listener之前,我们先从设计模式开始讲起. 观察者模式 观察者模式一般包含以下几个对象: Subject:被观察的对象.它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify().目标类可以是接口,也可以是抽象类或具体类. ConcreteSubject:具体的

关于js中的回收机制,通俗版

在前面的几篇文章中,我讲解过了js中的回收机制,但是对于当时的我来说,我自己对回收机制的这个概念也有些懵懵懂懂,现在对回收机制有了更深入的理解,所以特此发布此文给于总结,也好加深记忆. 如果你想学习闭包那么js中的回收机制是必不可少的,当然学习闭包除了需要理解js中的回收机制以外还需要了解其他的概念,我的其他文章有相关的说明,这里不做闭包的讲解. 为什么要有回收机制?why? 打个比方,我有一个内存卡,这个内存是8G的,我把文件,视频,音乐,都保存到了这个内存卡,随着我的储存的内容越来越多,这个