当前位置:   article > 正文

SENet注意力机制学习笔记 (附代码)_注意力模块怎么获取全局特征

注意力模块怎么获取全局特征

前言

论文地址:https://arxiv.org/pdf/1709.01507.pdf

代码地址: https://gitcode.net/mirrors/hujie-frank/senet?utm_source=csdn_github_accelerator

1.是什么?

SE注意力机制通过引入一个可学习的门控机制来提高模型的性能和泛化能力。该机制包含两个步骤:压缩(squeeze)和激励(excitation)。在压缩步骤中,SE注意力机制使用全局平均池化操作来捕捉特征图中每个通道的全局信息,得到一个通道维度的向量表示。在激励步骤中,该向量会经过两个全连接层,其中包括一个非线性激活函数,来生成通道维度上的权重。这些权重能够动态地调整特征图中不同通道的重要性,从而使网络能够更加关注和利用重要的特征。

通过引入SE注意力机制,模型能够更加有效地整合特征图的空间和通道维度的信息,提高了模型对重要特征的关注程度。这样可以增强模型的表达能力,使其更好地适应不同的输入数据,并提高模型的性能和泛化能力。同时,SE注意力机制具有较少的计算开销,能够在不增加太多参数和计算成本的情况下提升模型性能。

2.为什么?

2.1深层架构

大量的工作已经表明,以易于学习深度特征的方式重构卷积神经网络的架构可以大大提高性能。VGGNets和Inception模型证明了深度增加可以获得的好处,明显超过了ILSVRC 2014之前的方法。批标准化(BN)通过插入单元来调节层输入稳定学习过程,改善了通过深度网络的梯度传播,这使得可以用更深的深度进行进一步的实验。ResNet通过重构架构来训练更深层次的网络是有效的,通过使用基于恒等映射的跳跃连接来学习残差函数,从而减少跨单元的信息流动。

2.2网络模块化


另一种研究方法探索了调整网络模块化组件功能形式的方法。可以用分组卷积来增加基数(一组变换的大小)以学习更丰富的表示。多分支卷积可以解释为这个概念的概括,使得卷积算子可以更灵活的组合。跨通道相关性通常被映射为新的特征组合,或者独立的空间结构,或者联合使用标准卷积滤波器和1×1卷积,然而大部分工作的目标是集中在减少模型和计算复杂度上面。这种方法反映了一个假设,即通道关系可以被表述为具有局部感受野的实例不可知的函数的组合。相比之下,我们声称为网络提供一种机制来显式建模通道之间的动态、非线性依赖关系,使用全局信息可以减轻学习过程,并且显著增强网络的表示能力。

3.怎么样?


 

 3.1 Transformation(Ftr)—传统卷积操作

Squeeze-and-Excitation块是一个计算单元,可以为任何给定的变换构建:。为了简化说明,在接下来的表示中,我们将Ftr看作一个标准的卷积算子。V=[V1,V2,…,Vc]表示学习到的一组滤波器核,Vc指的是第c个滤波器的参数。然我们可以将FtrFtr的输出写作U=[U1,U2,…,Uc],其中这里 ∗表示卷积,(为了简洁表示,忽略偏置项)。这里 是 2D空间核,因此表示的一个单通道,作用于对应的通道 X。由于输出是通过所有通道的和来产生的,所以通道依赖性被隐式地嵌入到中,但是这些依赖性与滤波器捕获的空间相关性纠缠在一起。我们的目标是确保能够提高网络对信息特征的敏感度,以便后续转换可以利用这些功能,并抑制不太有用的功能。我们建议通过显式建模通道依赖性来实现这一点,以便在进入下一个转换之前通过两步重新校准滤波器响应,两步为: squeeze和 excitation。


对于一个C×W×H的输入X,在经过Ftr卷积操作之后,得到的输出Uc​(也就是C个大小为H×W的特征图)。


3.2 Squeeze: Global Information Embedding(Fsq)—压缩:全局信息嵌入

每一个学习到的卷积核,有一个局部感受野,因此每个卷积单元只能够关注到它所在区域内的空间信息,这样的话,这个感受野区域之外的信息就无法利用,卷积核只是在一个局部空间内进行操作, 输出特征图就很难获得足够的信息来提取通道之间的关系。

 Squeeze操作 

Fsq操作就是使用通道的全局平均池化,将包含全局信息的W×H×C 的特征图直接压缩成一个1×1×C的特征向量,即将每个二维通道变成一个具有全局感受野的数值,此时1个像素表示1个通道,屏蔽掉空间上的分布信息,更好的利用通道间的相关性。

输出的维度和输入的特征通道数相匹配(C)

Zc​ : 经Squeeze操作对Uc进行全局平均池化(GAP)得到的特征信息分布的局部描述算子,可以理解为在该层得到的C个特征图的数值分布情况。

3.3 Excitation: Adaptive Recalibration(Fex)—激励:自适应重新校准

目的:为了利用在"squeeze"操作中聚合的信息,接着进行Excitation操作,来完全捕获通道依赖关系。

方法:为实现上述目标,函数必须符合两个标准:

(1)灵活性:它必须能够学习通道之间的非线性相互作用

(2)必须学习一种非互斥关系:因为我们希望确保允许强调多个通道不同重要程度(而不是强制一个one-hot激活)。因为我们不光要学习特征,还要学习通道之间信息的相关性。

为了满足这两个条件,这篇论文这里采用两个全连接层+两个激活函数组成的结构输出和输入特征同样数目的权重值,也就是每个特征通道的权重系数。
 

 Excitation操作 

基于特征通道间的相关性,每个特征通道生成一个权重,用来代表特征通道的重要程度。由原本全为白色的C个通道的特征,得到带有不同深浅程度的颜色的特征向量,也就是不同的重要程度。 

Q1:加入全连接层的作用?

这是为了利用通道间的相关性来训练出真正的scale。一次mini-batch个样本的squeeze输出并不代表通道真实要调整的scale值,真实的scale要基于全部数据集来训练得出,而不是基于单个batch,所以后面要加个全连接层来进行训练。

Q2:为什么加两个全连接层?

应该是类似bottleneck的设计,增加非线性(model capacity),减少参数和运算量,不压缩的话这块儿的参数量和运算量会多r^2倍,比如1024个特征到1024个特征,直接全连接运算量是1024×1024,如果中间插入一个256层,那么它的运算量是1024×256×2,运算量降低了一半。

Q3:为什么前面用ReLU激活,后面为什么要改用Sigmoid呢?

(1)具有更多的非线性:可以更好地拟合通道间复杂的相关性。

(2)极大地减少了参数量和计算量:降维参数 r 用于控制第一个FC层中的神经元个数,在论文中也是经过多次对比实验得出r=16 时,模型得到的效果是最好的。

(3)由于Sigmoid函数图像的特点,它的值域在0—1之间,那么这样很符合概率分布的特点,最后能够获得 在0—1 之间归一化的权重参数,这样的话再通过乘法逐通道加权到先前的特征图上,使得有用的信息的注意力更趋向于1,而没有用的信息则更趋向于0,得到最后带有注意力权重的特征图。


3.4 Scale:Reweight(Fscale​ )—权重

将原本全为白色的注意力权重变为不同颜色的状态,也就代表着不同程度的重要性,对于某些颜色较深的attention map,神经网络就会重点关注这些特征通道。

 Reweight操作
将Excitation输出的权重看做每个特征通道的重要性,也就是对于U每个位置上的所有H×W上的值都乘上对应通道的权值,完成对原始特征的重校准。

公式 Fscale(uc​ ,sc​ )即为特征映射uc和标量sc​(这里由于是1×1可看做标量)之间的对应通道乘积操作。


3.5 Instantiations—实例

(1)SEInception

将转换Ftr作为一个完整的Inception模块,通过对架构中的每个这样的模块进行更改,获得了一个SE-Inception网络。

 SE块详细过程

1.首先由 Inception结构 或 ResNet结构处理后的C×W×H特征图开始,通过Squeeze操作对特征图进行全局平均池化(GAP),得到1×1×C 的特征向量

2.紧接着两个 FC 层组成一个 Bottleneck 结构去建模通道间的相关性:

  经过第一个FC层,将C个通道变成 C/ r​ ,减少参数量,然后通过ReLU的非线性激活,到达第二个FC层

  经过第二个FC层,再将特征通道数恢复到C个,得到带有注意力机制的权重参数

3.最后经过Sigmoid激活函数,最后通过一个 Scale 的操作来将归一化后的权重加权到每个通道的特征上。

(2)SEResNet

SE模块可以直接与ResNet一起使用。SE块变换Ftr被认为是残差模块的非恒等分支。挤压和激励都是在同分支相加之前起作用的。

 (3)SE和其他网络集成

将SE块与ResNeXt、Inception-ResNet、MobileNet和ShuffleNet整合的进一步变体可以通过类似的方案构建。

3.6 代码实现

(1)SENet pytorch
  1. class SELayer(nn.Module):
  2. def __init__(self, channel, reduction=16):
  3. super(SELayer, self).__init__()
  4. self.avg_pool = nn.AdaptiveAvgPool2d(1)
  5. self.fc = nn.Sequential(
  6. nn.Linear(channel, channel // reduction, bias=False),
  7. nn.ReLU(inplace=True),
  8. nn.Linear(channel // reduction, channel, bias=False),
  9. nn.Sigmoid()
  10. )
  11. def forward(self, x):
  12. b, c, _, _ = x.size()
  13. y = self.avg_pool(x).view(b, c)
  14. y = self.fc(y).view(b, c, 1, 1)
  15. return x * y.expand_as(x)

(2)SE-ResNet

  1. # Here is the code :
  2. import torch
  3. import torch.nn as nn
  4. import torch.nn.functional as F
  5. from torchinfo import summary
  6. class SE_Block(nn.Module): # Squeeze-and-Excitation block
  7. def __init__(self, in_planes):
  8. super(SE_Block, self).__init__()
  9. self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
  10. self.conv1 = nn.Conv2d(in_planes, in_planes // 16, kernel_size=1)
  11. self.relu = nn.ReLU()
  12. self.conv2 = nn.Conv2d(in_planes // 16, in_planes, kernel_size=1)
  13. self.sigmoid = nn.Sigmoid()
  14. def forward(self, x):
  15. x = self.avgpool(x)
  16. x = self.conv1(x)
  17. x = self.relu(x)
  18. x = self.conv2(x)
  19. out = self.sigmoid(x)
  20. return out
  21. class BasicBlock(nn.Module): # 左侧的 residual block 结构(18-layer、34-layer)
  22. expansion = 1
  23. def __init__(self, in_planes, planes, stride=1): # 两层卷积 Conv2d + Shutcuts
  24. super(BasicBlock, self).__init__()
  25. self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3,
  26. stride=stride, padding=1, bias=False)
  27. self.bn1 = nn.BatchNorm2d(planes)
  28. self.conv2 = nn.Conv2d(planes, planes, kernel_size=3,
  29. stride=1, padding=1, bias=False)
  30. self.bn2 = nn.BatchNorm2d(planes)
  31. self.SE = SE_Block(planes) # Squeeze-and-Excitation block
  32. self.shortcut = nn.Sequential()
  33. if stride != 1 or in_planes != self.expansion*planes: # Shutcuts用于构建 Conv Block 和 Identity Block
  34. self.shortcut = nn.Sequential(
  35. nn.Conv2d(in_planes, self.expansion*planes,
  36. kernel_size=1, stride=stride, bias=False),
  37. nn.BatchNorm2d(self.expansion*planes)
  38. )
  39. def forward(self, x):
  40. out = F.relu(self.bn1(self.conv1(x)))
  41. out = self.bn2(self.conv2(out))
  42. SE_out = self.SE(out)
  43. out = out * SE_out
  44. out += self.shortcut(x)
  45. out = F.relu(out)
  46. return out
  47. class Bottleneck(nn.Module): # 右侧的 residual block 结构(50-layer、101-layer、152-layer)
  48. expansion = 4
  49. def __init__(self, in_planes, planes, stride=1): # 三层卷积 Conv2d + Shutcuts
  50. super(Bottleneck, self).__init__()
  51. self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)
  52. self.bn1 = nn.BatchNorm2d(planes)
  53. self.conv2 = nn.Conv2d(planes, planes, kernel_size=3,
  54. stride=stride, padding=1, bias=False)
  55. self.bn2 = nn.BatchNorm2d(planes)
  56. self.conv3 = nn.Conv2d(planes, self.expansion*planes,
  57. kernel_size=1, bias=False)
  58. self.bn3 = nn.BatchNorm2d(self.expansion*planes)
  59. self.SE = SE_Block(self.expansion*planes) # Squeeze-and-Excitation block
  60. self.shortcut = nn.Sequential()
  61. if stride != 1 or in_planes != self.expansion*planes: # Shutcuts用于构建 Conv Block 和 Identity Block
  62. self.shortcut = nn.Sequential(
  63. nn.Conv2d(in_planes, self.expansion*planes,
  64. kernel_size=1, stride=stride, bias=False),
  65. nn.BatchNorm2d(self.expansion*planes)
  66. )
  67. def forward(self, x):
  68. out = F.relu(self.bn1(self.conv1(x)))
  69. out = F.relu(self.bn2(self.conv2(out)))
  70. out = self.bn3(self.conv3(out))
  71. SE_out = self.SE(out)
  72. out = out * SE_out
  73. out += self.shortcut(x)
  74. out = F.relu(out)
  75. return out
  76. class SE_ResNet(nn.Module):
  77. def __init__(self, block, num_blocks, num_classes=1000):
  78. super(SE_ResNet, self).__init__()
  79. self.in_planes = 64
  80. self.conv1 = nn.Conv2d(3, 64, kernel_size=3,
  81. stride=1, padding=1, bias=False) # conv1
  82. self.bn1 = nn.BatchNorm2d(64)
  83. self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1) # conv2_x
  84. self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2) # conv3_x
  85. self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2) # conv4_x
  86. self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2) # conv5_x
  87. self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
  88. self.linear = nn.Linear(512 * block.expansion, num_classes)
  89. def _make_layer(self, block, planes, num_blocks, stride):
  90. strides = [stride] + [1]*(num_blocks-1)
  91. layers = []
  92. for stride in strides:
  93. layers.append(block(self.in_planes, planes, stride))
  94. self.in_planes = planes * block.expansion
  95. return nn.Sequential(*layers)
  96. def forward(self, x):
  97. x = F.relu(self.bn1(self.conv1(x)))
  98. x = self.layer1(x)
  99. x = self.layer2(x)
  100. x = self.layer3(x)
  101. x = self.layer4(x)
  102. x = self.avgpool(x)
  103. x = torch.flatten(x, 1)
  104. out = self.linear(x)
  105. return out
  106. def SE_ResNet18():
  107. return SE_ResNet(BasicBlock, [2, 2, 2, 2])
  108. def SE_ResNet34():
  109. return SE_ResNet(BasicBlock, [3, 4, 6, 3])
  110. def SE_ResNet50():
  111. return SE_ResNet(Bottleneck, [3, 4, 6, 3])
  112. def SE_ResNet101():
  113. return SE_ResNet(Bottleneck, [3, 4, 23, 3])
  114. def SE_ResNet152():
  115. return SE_ResNet(Bottleneck, [3, 8, 36, 3])
  116. def test():
  117. net = SE_ResNet50()
  118. y = net(torch.randn(1, 3, 224, 224))
  119. print(y.size())
  120. summary(net, (1, 3, 224, 224))
  121. if __name__ == '__main__':
  122. test()

参考:

 SENet 算法的介绍经典神经网络论文超详细解读(七)——SENet(注意力机制)学习笔记(翻译+精读+代码复现)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/290839
推荐阅读
相关标签
  

闽ICP备14008679号