CBAM: Convolutional Block Attention Module
論文地址:https://arxiv.org/abs/1807.06521
簡介:我們提出了卷積塊注意模塊 (CBAM), 一個簡單而有效的注意模塊的前饋卷積神經網絡。給出了一個中間特征映射, 我們的模塊按照兩個獨立的維度、通道和空間順序推斷出注意力映射, 然后將注意力映射相乘為自適應特征細化的輸入特征映射。因為 CBAM 是一個輕量級和通用的模塊, 它可以無縫地集成到任何 CNN 架構只增加微不足道的間接開銷, 可以集成到端到端的CNN里面去。通過對 ImageNet-1K、COCO、MS 檢測和 VOC 2007 檢測數據集的廣泛實驗, 我們驗證了我們的 CBAM。我們的實驗表明, 各種模型的分類和檢測性能都有了一致的改進, 證明了 CBAM 的廣泛適用性。這些代碼和模型將公開提供。
2 相關工作
網絡架構的構建,一直是計算機視覺中最重要的研究之一, 因為精心設計的網絡確保了在各種應用中顯著的性能提高。自成功實施大型 CNN以來, 已經提出了一系列廣泛的體系結構。一種直觀而簡單的擴展方法是增加神經網絡的深度如 VGG-NET、ResNet及其變體,如 WideResNet和 ResNeXt。GoogLeNet展現了增加網絡的寬度對於結果的提升的幫助,典型的分類網絡都在提升深度與寬度上下了很大功夫。
眾所周知, 注意力在人的知覺中起着重要的作用。一個人並不是試圖一次處理整個場景。相反, 人類注意部分場景, 並有選擇地專注於突出部分, 以便更好地捕捉視覺結構。
最近, 有幾次嘗試加入注意處理, 以提高CNNs在大規模分類任務的性能。Residual attention network for image classification中使用 encoder-decoder 樣式的注意模塊的Residual attention network。通過細化特征映射,不僅網絡性能良好, 而且對噪聲輸入也很健壯。我們不直接計算3d 的注意力映射, 而是分解了單獨學習通道注意和空間注意的過程。對於3D 特征圖, 單獨的注意生成過程的計算和參數開銷要小得多, 因此可以作為CNN的前置基礎架構的模塊使用。
Squeeze-and-excitation networks引入一個緊湊模塊來利用通道間的關系。在他們的壓縮和激勵模塊中, 他們使用全局平均池功能來計算通道的注意力。然而, 我們表明, 這些都是次優特征, 以推斷良好的通道注意, 我們使用最大池化的特點。然而,他們也錯過了空間注意力機制, 在決定 "Where"。在我們的 CBAM 中, 我們利用一個有效的體系結構來開發空間和通道的注意力, 並通過經驗驗證, 利用兩者都優於僅使用通道的注意作為。此外, 我們的實驗表明, 我們的模塊在檢測任務 (MS COCO和 VOC2017)上是有效的。特別是, 我們通過將我們的模塊放在VOC2007 測試集中的現有的目標檢測器結合實現了最先進的性能。
3 Convolutional Block Attention Module
給定一個中間特征映射F∈RC xHxW作為輸入, CBAM的1維通道注意圖Mc ∈RC ×1×1 和2D 空間注意圖Ms ∈R1×HxW 如圖1所示。總的注意過程可以概括為:
表示逐元素相乘。在相乘過程中,注意值被廣播。相應地,通道注意值被沿着空間維度廣播,反之亦然。F’’是最終輸出。
3.1 Channel attention module
我們利用特征的通道間關系, 生成了通道注意圖。當一個特征圖的每個通道被考慮作為特征探測器, 通道注意聚焦於 ' what ' 是有意義的輸入圖像。為了有效地計算通道的注意力, 我們壓縮了輸入特征圖的空間維數。為了聚焦空間信息,我們同時使用平均池化和最大池化。我們的實驗證實, 同時使用這兩種功能大大提高了網絡的表示能力。下面將描述詳細操作。
我們首先使用平均池化和最大池化操作來聚合特征映射的空間信息, 生成兩個不同的空間上下文描述符:Fcavg 和Fcmax , 分別表示平均池化和最大池化。兩個描述符然后送到一個共享網絡, 以產生我們的通道注意力圖 Mc ∈ Rc×1×1。共享網絡由多層感知機(MLP) 和一個隱藏層組成。為了減少參數開銷, 隱藏的激活大小被設置為 rc/c++×1×1, 其中 r 是壓縮率。在將共享網絡應用於每個描述符之后, 我們使用逐元素求和合並輸出特征向量。簡而言之, 頻道的注意力被計算為:
Mc(f) = σ(MLP(AvgPool(f)) + MLP(MaxPool(f)))
= σ(W1(W0(Fcavg)) + W1(W0(Fcmax)))
σ是sigmoid function,W0 ∈ RC/r × C, W1 ∈ RC × C/r
W0 ,W1 是多層感知機的權重,共享輸入和W0 的RELU激活函數。
3.2 Spatial attention module
我們利用特征間的空間關系, 生成空間注意圖。與通道注意力不同的是, 空間注意力集中在 "where" 是一個信息的部分, 這是對通道注意力的補充。為了計算空間注意力, 我們首先在通道軸上應用平均池和最大池運算, 並將它們連接起來以生成一個有效的特征描述符。在串聯特征描述符上, 我們應用7×7的卷積生成空間注意圖的層Ms (F) ∈RH×W 。我們描述下面的詳細操作.
我們使用兩個池化操作來聚合功能映射的通道信息, 生成兩個2維映射:Fsavg∈R1×HxW 和Fsmax∈R1×HxW 每個通道都表示平均池化和最大池化。然后通過一個標准的卷積層連接和卷積混合, 產生我們的2D 空間注意圖。簡而言之, 空間注意力被計算為:
Ms (f) = σ( f7×7( AvgPool(f) ; MaxPool(F)] )))
= σ(f7×7 (Fsavg; Fsmax])),
σ是sigmoid function, f7×7 是7×7的卷積。
通過實驗我們發現串聯兩個注意力模塊的效果要優於並聯。通道注意力放在前面要優於空間注意力模塊放在前面。
4實驗
在本小節中,我們憑實驗證明了我們的設計選擇的有效性。 在這次實驗中,我們使用ImageNet-1K數據集並采用ResNet-50作為基礎架構。 ImageNet-1K分類數據集[1]由1.2組成用於訓練的百萬個圖像和用於1,000個對象類的驗證的50,000個圖像
我們采用相同的數據增強方案進行訓練和測試時間進行單一作物評估,大小為224×224。 學習率從0.1開始,每30個時期下降一次。 我們訓練網絡90迭代。
4.1 通道注意力和空間注意力機制實驗
4.2我們使用Grad-CAM進行網絡可視化
4.3 CBAM在目標檢測的結果
5總結
作者提出了一種提高 CNN 網絡表示的新方法--卷積瓶頸注意模塊 (CBAM)。作者將基於注意力的特征細化成兩個不同的模塊、通道和空間結合起來, 實現了顯著的性能改進, 同時保持了小的開銷。對於通道的關注,使用最大池化和平均池化,最終模塊 (CBAM) 學習了如何有效地強調或壓縮提取中間特征。為了驗證它的有效性, 我們進行了廣泛的實驗與並證實, CBAM 優於所有基線上的三不同的基准數據集: ImageNet-1K, COCO, 和 VOC 2007。此外, 我們可視化模塊如何准確推斷給定的輸入圖像。CBAM 或許會成為各種網絡體系結構的重要組成部分。
附上cbam的代碼:
pytorch_resnet50
1 from collections import OrderedDict 2 import math 3 import torch 4 import torch.nn as nn 5 # import torchvision.models.resnet 6 class CBAM_Module(nn.Module): 7 8 def __init__(self, channels, reduction): 9 super(CBAM_Module, self).__init__() 10 self.avg_pool = nn.AdaptiveAvgPool2d(1) 11 self.max_pool = nn.AdaptiveMaxPool2d(1) 12 self.fc1 = nn.Conv2d(channels, channels // reduction, kernel_size=1, 13 padding=0) 14 self.relu = nn.ReLU(inplace=True) 15 self.fc2 = nn.Conv2d(channels // reduction, channels, kernel_size=1, 16 padding=0) 17 self.sigmoid_channel = nn.Sigmoid() 18 self.conv_after_concat = nn.Conv2d(2, 1, kernel_size = 3, stride=1, padding = 1) 19 self.sigmoid_spatial = nn.Sigmoid() 20 21 def forward(self, x): 22 # Channel attention module:(Mc(f) = σ(MLP(AvgPool(f)) + MLP(MaxPool(f)))) 23 module_input = x 24 avg = self.avg_pool(x) 25 mx = self.max_pool(x) 26 avg = self.fc1(avg) 27 mx = self.fc1(mx) 28 avg = self.relu(avg) 29 mx = self.relu(mx) 30 avg = self.fc2(avg) 31 mx = self.fc2(mx) 32 x = avg + mx 33 x = self.sigmoid_channel(x) 34 # Spatial attention module:Ms (f) = σ( f7×7( AvgPool(f) ; MaxPool(F)] ))) 35 x = module_input * x 36 module_input = x 37 avg = torch.mean(x, 1, keepdim=True) 38 mx, _ = torch.max(x, 1, keepdim=True) 39 x = torch.cat((avg, mx), 1) 40 x = self.conv_after_concat(x) 41 x = self.sigmoid_spatial(x) 42 x = module_input * x 43 return x 44 45 46 class Bottleneck(nn.Module): 47 """ 48 Base class for bottlenecks that implements `forward()` method. 49 """ 50 def forward(self, x): 51 residual = x 52 53 out = self.conv1(x) 54 out = self.bn1(out) 55 out = self.relu(out) 56 57 out = self.conv2(out) 58 out = self.bn2(out) 59 out = self.relu(out) 60 61 out = self.conv3(out) 62 out = self.bn3(out) 63 64 if self.downsample is not None: 65 residual = self.downsample(x) 66 67 out = self.se_module(out) + residual 68 out = self.relu(out) 69 70 return out 71 72 class CBAMResNetBottleneck(Bottleneck): 73 """ 74 ResNet bottleneck with a CBAM_Module. It follows Caffe 75 implementation and uses `stride=stride` in `conv1` and not in `conv2` 76 (the latter is used in the torchvision implementation of ResNet). 77 """ 78 expansion = 4 79 80 def __init__(self, inplanes, planes, groups, reduction, stride=1, 81 downsample=None): 82 super(CBAMResNetBottleneck, self).__init__() 83 self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False, 84 stride=stride) 85 self.bn1 = nn.BatchNorm2d(planes) 86 self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, padding=1, 87 groups=groups, bias=False) 88 self.bn2 = nn.BatchNorm2d(planes) 89 self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) 90 self.bn3 = nn.BatchNorm2d(planes * 4) 91 self.relu = nn.ReLU(inplace=True) 92 self.se_module = CBAM_Module(planes * 4, reduction=reduction) 93 self.downsample = downsample 94 self.stride = stride 95 96 97 98 class CABMNet(nn.Module): 99 100 def __init__(self, block, layers, groups, reduction, dropout_p=0.2, 101 inplanes=128, input_3x3=True, downsample_kernel_size=3, 102 downsample_padding=1, num_classes=1000): 103 super(CABMNet, self).__init__() 104 self.inplanes = inplanes 105 if input_3x3: 106 layer0_modules = [ 107 ('conv1', nn.Conv2d(3, 64, 3, stride=2, padding=1, 108 bias=False)), 109 ('bn1', nn.BatchNorm2d(64)), 110 ('relu1', nn.ReLU(inplace=True)), 111 ('conv2', nn.Conv2d(64, 64, 3, stride=1, padding=1, 112 bias=False)), 113 ('bn2', nn.BatchNorm2d(64)), 114 ('relu2', nn.ReLU(inplace=True)), 115 ('conv3', nn.Conv2d(64, inplanes, 3, stride=1, padding=1, 116 bias=False)), 117 ('bn3', nn.BatchNorm2d(inplanes)), 118 ('relu3', nn.ReLU(inplace=True)), 119 ] 120 else: 121 layer0_modules = [ 122 ('conv1', nn.Conv2d(3, inplanes, kernel_size=7, stride=2, 123 padding=3, bias=False)), 124 ('bn1', nn.BatchNorm2d(inplanes)), 125 ('relu1', nn.ReLU(inplace=True)), 126 ] 127 # To preserve compatibility with Caffe weights `ceil_mode=True` 128 # is used instead of `padding=1`. 129 layer0_modules.append(('pool', nn.MaxPool2d(3, stride=2, 130 ceil_mode=True))) 131 self.layer0 = nn.Sequential(OrderedDict(layer0_modules)) 132 self.layer1 = self._make_layer( 133 block, 134 planes=64, 135 blocks=layers[0], 136 groups=groups, 137 reduction=reduction, 138 downsample_kernel_size=1, 139 downsample_padding=0 140 ) 141 self.layer2 = self._make_layer( 142 block, 143 planes=128, 144 blocks=layers[1], 145 stride=2, 146 groups=groups, 147 reduction=reduction, 148 downsample_kernel_size=downsample_kernel_size, 149 downsample_padding=downsample_padding 150 ) 151 self.layer3 = self._make_layer( 152 block, 153 planes=256, 154 blocks=layers[2], 155 stride=2, 156 groups=groups, 157 reduction=reduction, 158 downsample_kernel_size=downsample_kernel_size, 159 downsample_padding=downsample_padding 160 ) 161 self.layer4 = self._make_layer( 162 block, 163 planes=512, 164 blocks=layers[3], 165 stride=2, 166 groups=groups, 167 reduction=reduction, 168 downsample_kernel_size=downsample_kernel_size, 169 downsample_padding=downsample_padding 170 ) 171 self.avg_pool = nn.AvgPool2d(7, stride=1) 172 self.dropout = nn.Dropout(dropout_p) if dropout_p is not None else None 173 self.last_linear = nn.Linear(512 * block.expansion, num_classes) 174 175 for m in self.modules(): 176 if isinstance(m, nn.Conv2d): 177 n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels 178 m.weight.data.normal_(0, math.sqrt(2. / n)) 179 elif isinstance(m, nn.BatchNorm2d): 180 m.weight.data.fill_(1) 181 m.bias.data.zero_() 182 183 # for m in self.modules(): 184 # if isinstance(m, nn.Conv2d): 185 # nn.init.kaiming_normal(m.weight.data) 186 # elif isinstance(m, nn.BatchNorm2d): 187 # m.weight.data.fill_(1) 188 # m.bias.data.zero_() 189 190 def _make_layer(self, block, planes, blocks, groups, reduction, stride=1, 191 downsample_kernel_size=1, downsample_padding=0): 192 downsample = None 193 if stride != 1 or self.inplanes != planes * block.expansion: 194 downsample = nn.Sequential( 195 nn.Conv2d(self.inplanes, planes * block.expansion, 196 kernel_size=downsample_kernel_size, stride=stride, 197 padding=downsample_padding, bias=False), 198 nn.BatchNorm2d(planes * block.expansion), 199 ) 200 201 layers = [] 202 layers.append(block(self.inplanes, planes, groups, reduction, stride, 203 downsample)) 204 self.inplanes = planes * block.expansion 205 for i in range(1, blocks): 206 layers.append(block(self.inplanes, planes, groups, reduction)) 207 208 return nn.Sequential(*layers) 209 210 def features(self, x): 211 x = self.layer0(x) 212 x = self.layer1(x) 213 x = self.layer2(x) 214 x = self.layer3(x) 215 x = self.layer4(x) 216 return x 217 218 def logits(self, x): 219 x = self.avg_pool(x) 220 if self.dropout is not None: 221 x = self.dropout(x) 222 x = x.view(x.size(0), -1) 223 x = self.last_linear(x) 224 return x 225 226 def forward(self, x): 227 x = self.features(x) 228 x = self.logits(x) 229 return x 230 231 232 233 234 def cbam_resnet50(num_classes=1000): 235 model = CABMNet(CBAMResNetBottleneck, [3, 4, 6, 3], groups=1, reduction=16, 236 dropout_p=None, inplanes=64, input_3x3=False, 237 downsample_kernel_size=1, downsample_padding=0, 238 num_classes=num_classes) 239 print(model) 240 return model 241 cbam_resnet50()
制作的PPT方便大家理解