【CV中的Attention機制】BiSeNet中的FFM模塊與ARM模塊

2021-02-14 GiantPandaCV

前言:之前介紹過一個語義分割中的注意力機制模塊-scSE模塊,效果很不錯。今天講的也是語義分割中使用到注意力機制的網絡BiSeNet,這個網絡有兩個模塊,分別是FFM模塊和ARM模塊。其實現也很簡單,不過作者對注意力機制模塊理解比較深入,提出的FFM模塊進行的特徵融合方式也很新穎。

1. 簡介

語義分割需要豐富的空間信息和相關大的感受野,目前很多語義分割方法為了達到實時推理的速度選擇犧牲空間解析度,這可能會導致比較差的模型表現。

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

空間路徑用於保留語義信息生成較高解析度的feature map(減少下採樣的次數)上下文路徑使用了快速下採樣的策略,用於獲取充足的感受野。提出了一個FFM模塊,結合了注意力機制進行特徵融合。

本文主要關注的是速度和精度的權衡,對於解析度為2048×1024的輸入,BiSeNet能夠在NVIDIA Titan XP顯卡上達到105FPS的速度,做到了實時語義分割。

2. 分析

提升語義分割速度主要有三種方法,如下圖所示:

通過resize的方式限定輸入大小,降低計算複雜度。缺點是空間細節有損失,尤其是邊界部分。通過減少網絡通道的個數來加快處理速度。缺點是會弱化空間信息。放棄最後階段的下採樣(如ENet)。缺點是模型感受野不足以覆蓋大物體,判別能力差。

語義分割中,U型結構也被廣泛使用,如下圖所示:

這種U型網絡通過融合backbone不同層次的特徵,在U型結構中逐漸增加空間解析度,保留更多的細節特徵。不過有兩個缺點:

由於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 的結果。


往期內容:

【CV中的Attention機制】ECCV 2018 Convolutional Block Attention Module

【CV中的Attention機制】Non-Local neural networks的理解與實現

【CV中的Attention機制】融合Non-Local和SENet的GCNet

【CV中的attention機制】scSE模塊

【CV中的Attention機制】並行版的CBAM-BAM模塊

相關焦點

  • CV中的Attention機制:簡單而有效的CBAM模塊
    什麼是注意力機制?注意力機制(Attention Mechanism)是機器學習中的一種數據處理方法,廣泛應用在自然語言處理、圖像識別及語音識別等各種不同類型的機器學習任務中。通俗來講:注意力機制就是希望網絡能夠自動學出來圖片或者文字序列中的需要注意的地方。
  • 【CV中的Attention機制】ShuffleAttention
    本文提出了Shuffle Attention(SA)模塊來解決這個問題,可以高效地結合兩種注意力機制。具體來講:對每個子特徵使用SA Unit同時使用空間和通道間注意力機制。最後,所有的子特徵會被匯集起來,然後使用Channel Shuffle操作讓不同組的特徵進行融合。
  • 品覽AI論技|論CCNet如何有效減輕Attention機制的計算量
    本次大會總結了過去一年cv領域的不同方向的發展,許多國內的大牛都分享了自己實驗室的一些最新的研究成果,當然也有安利自己的框架。當然,這個是Attention的nlp的應用思想,不過這並不影響Attention在cv領域的應用,因為我們看一張圖片的時候也是挑重點看,比如下面這張圖片,應該沒有人會重點看椰子吧。 CCNet的背景注意力最開始是14年應用在MT(機器翻譯)任務中的,並且顯著改善了MT的性能。
  • 重新思考深度學習中的Attention機制
    寫在前面: 本文是一篇關於深度學習中的 Attention機制 的文章。後面和同事討論,我們感覺Gate模塊本質上就是一個對專家輸出進行加權融合的模塊,那這個模塊為何不用主流的Attention來實現一下呢?
  • 注意力機制(Attention Mechanism)在自然語言處理中的應用
    基於注意力(attention)機制的神經網絡成為了最近神經網絡研究的一個熱點,本人最近也學習了一些基於attention機制的神經網絡在自然語言處理(NLP)領域的論文,現在來對attention在NLP中的應用進行一個總結,和大家一起分享。
  • 注意力機制Attention
    注意力機制(attention mechanism)Attention實質上是一種分配機制,其核心思想是突出對象的某些重要特徵。根據Attention對象的重要程度,重新分配資源,即權重,實現核心思想是基於原有的數據找到其之間的關聯性,然後突出其某些重要特徵。注意力可以被描述為將一個查詢和一組鍵值對映射到一個輸出,其中查詢、鍵、值和輸出都是向量。
  • Python3中常用模塊-sys模塊
    sys模塊和os模塊是Python系統相關工具集的核心部分,其主要處理系統相關的功能,下面首先來介紹下sys模塊。 1:獲取模塊文檔 1.1:使用sys.__doc__查看方法說明 print(sys.
  • python中的模塊詳解
    概念python中的模塊是什麼?簡而言之,在python中,一個文件(以「.py」為後綴名的文件)就叫做一個模塊,每一個模塊在python裡都被看做是一個獨立的文件。模塊可以被項目中的其他模塊、一些腳本甚至是交互式的解析器所使用,它可以被其他程序引用,從而使用該模塊裡的函數等功能,使用Python中的標準庫也是採用這種方法。分類在Python中模塊分為以下幾種:系統內置模塊,例如:sys、time、json模塊等等;自定義模塊,自定義模塊是自己寫的模塊,對某段邏輯或某些函數進行封裝後供其他函數調用。
  • 淺談 Python 中的模塊
    前言真實項目開發中,我們會對項目的常量、變量、以及函數進行統一管理,把這些聲明、定義放在一個以 .py 結尾的文件中,我們將這個文件稱為 模塊。當解釋器遇到 import 語句時,如果模塊在當前的搜索路徑中就會被導入,多次導入一個模塊時,只有最後一次生效,這樣可以防止導入模塊被多次執行。搜索路徑存儲在 sys 模塊中的 path 變量中,我們可以通過以下代碼查看。
  • Python中爬蟲框架或模塊的區別
    Python中爬蟲框架或模塊的區別 (1)爬蟲框架或模塊 Python自帶爬蟲模塊:urllib、urllib2 ; 第三方爬蟲模塊:requests,aiohttp;
  • Python3中常用模塊-random模塊
    random是Python中與隨機數相關的模塊,其本質就是一個偽隨機數生成器,我們可以利用random模塊基礎生成各種不同的隨機數,以及一些基於隨機數的操作。
  • 績效考核中的扣分,積分,模塊制
    其中,扣分制績效考核屬於木桶「」滿水機制」,先在木桶中裝滿水,做不到就往外舀水。績效考核積分制,屬於木桶添水機制。先給一隻空木桶,做到了就往桶加水。模塊化考核是直接把木桶拆成木板,然後把木板組合成木桶作為考核。
  • xposed模塊整理
    安裝我是用apk+recovery刷入arm包的方式安裝的版本是Xposed框架安裝器v3.0 alpha4 版arm包為xposed-v86-sdk23-arm64另類禁用Xposed在 XDA Forum 的帖子中,開發者提到了 safemode 這個概念。在手機重啟過程中,多次點按同一個實體鍵可將 Xposed 框架禁用。
  • 機器翻譯中的 Attention 機制
    這篇進一步研究 Attention 機制,即「注意力」模型,它首次在【1】中引入,接著【2】中做了提煉。Attention 機制核心思想是建立輸出序列和 encoder 歷史狀態之間的直接連接(shot-cut connections),在翻譯時將「注意力」集中在與當前輸出相關性強的輸入上。
  • l7805cv中的5指的什麼_l7805cv全稱是什麼
    打開APP l7805cv中的5指的什麼_l7805cv全稱是什麼 發表於 2017-10-23 10:27:37   l7805cv的全稱是串聯型三端穩壓模塊。
  • python中的Requests模塊
    講解對象:python中的Requests模塊作者:融水公子 rsgz介紹:1 Requests 是一個第三方 Python 模塊2 Requests 唯一的一個非轉基因的pip3執行命令:sudo apt-get install python3-pip  3 pip3安裝requests模塊
  • 【python】os 模塊使用筆記
    然後,通過作業系統提供的接口從這個文件對象中讀取數據,或者把數據寫入這個文件對象。)os模塊一般有以下功能:複製文件的函數居然在os模塊中不存在!原因是複製文件並非由作業系統提供的系統調用。理論上講,我們通過上一節的讀寫文件可以完成文件複製,只不過要多寫很多代碼。
  • Python 中的 Subprocess 模塊
    我們能從Python官方文檔裡讀到應該用subprocess 模塊來運行系統命令。subprocess模塊允許我們創建子進程,連接他們的輸入/輸出/錯誤管道,還有獲得返回值。subprocess模塊打算來替代幾個過時的模塊和函數,比如: os.system, os.spawn*, os.popen*, popen2.*命令。
  • Python中json模塊的使用介紹
    # json:數據交換用到json文件,json是特殊的字符串,一種輕量級的數據交換格式# json 本質就是字符串,區別在於json裡要使用雙引號表示字符串# 在js語言中,一切都是對象,因此,任何支持的類型都可以通過json 來表示,例如字符串、數字、對象、數組等# 在python中,有專門處理json格式的模塊:json模塊 和 picle模塊# Json 模塊提供了四個方法:dumps、dump、loads、load# pickle 模塊也提供了四個功能:dumps、dump、loads、load# 序列化:
  • Python 中的黑暗角落(三):模塊與包
    此時,這個 Python 腳本就是一個 Python 模塊(Module)。我們可以在解釋器中,或者在其他 Python 腳本中,通過 import 載入定義好的 Python 模塊。模塊的識別和 Python 中的其它對象一樣,Python 也為模塊定義了一些形如 __foo__ 的變量。對於模塊來說,最重要的就是它的名字 __name__ 了。