当前位置:   article > 正文

SE通道注意力机制模块_通道注意模块

通道注意模块

简介

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

深度学习领域,提升模型的表征能力一直是一个关键的研究方向。SE(Squeeze-and-Excitation)模块是一种引入通道注意力机制的方法,旨在让神经网络更加关注对当前任务重要的特征。本文将介绍SE模块的原理、实现以及在深度学习中的应用。

SE模块结构

SE模块主要的两个操作就是Squeeze与Excitation操作。

首先是Squeeze操作,通过聚合跨空间维度(H × W)的特征映射来产生通道描述符,怎么理解呢?

假设有一个输入的特征映射,它的维度是H × W × C,对于每个通道,执行全局平均池化操作,具体来说,对于第i个通道,计算该通道上所有空间位置的平均值。可以通过对每个通道的所有元素取平均来实现,其中每个元素表示相应通道上的平均值,在对输入特征映射中的所有通道,都执行上述的操作,就会得到C个平均值,形成一个长度为C的向量。由此,Squeeze操作将输入的H × W × C特征映射压缩成一个C维的向量,其中每个元素表示对应通道上的平均值。这个向量可以被看作是对整个特征映射在通道维度上的"描述符"。


接着是Excitation操作,它是SE模块中的第二步,其主要目的是对上一步Squeeze得到的通道描述符进行加权,以强调重要的通道。

接着是将Squeeze得到的C维向量输入到一个全连接层(FC层),其目的是学习通道权重。这个全连接层包括一个非线性激活函数,如ReLU,以引入非线性变换。通过学习,全连接层得到的通道权重经过一个Sigmoid激活函数,将其范围限制在0到1之间。学到的权重可以被视为每个通道的激活程度。最后,将原始的C维向量与这学到的通道权重相乘,得到加权后的向量。这个加权后的向量反映了每个通道在任务中的重要性和贡献程度。这一过程使得网络能够动态调整通道的关注度,从而更有效地利用信息来完成任务。

  1. """
  2. Original paper addresshttps: https://arxiv.org/pdf/1709.01507.pdf
  3. Code originates from https://github.com/moskomule/senet.pytorch/blob/master/senet/se_module.py
  4. """
  5. import torch
  6. from torch import nn
  7. class SELayer(nn.Module):
  8. def __init__(self, channel, reduction=16):
  9. super().__init__()
  10. self.avg_pool = nn.AdaptiveAvgPool2d(1)
  11. self.fc = nn.Sequential(
  12. nn.Linear(channel, channel // reduction, bias=False),
  13. nn.ReLU(inplace=True),
  14. nn.Linear(channel // reduction, channel, bias=False),
  15. nn.Sigmoid()
  16. )
  17. def forward(self, x):
  18. b, c, _, _ = x.size()
  19. y = self.avg_pool(x).view(b, c)
  20. y = self.fc(y).view(b, c, 1, 1)
  21. return x * y.expand_as(x)
  22. if __name__ == '__main__':
  23. input = torch.randn(1, 64, 64, 64)
  24. se = SELayer(channel=64, reduction=8)
  25. output = se(input)
  26. print(output.shape)

关于这一部分的理解,我也是看了许多博主的博客,很多都是一步带过,我也是综合着才理解到原来是这样的,最后会过头来再看代码似乎有一点明悟,我的理解可以与其对的上。

这里还涉及到了一个超参数reduction,指的是 SE 模块的缩减比例。据作者论文里面说的是16是一个效果比较好的值,它表示的是通道数缩减的比例为 1/16。这个比例用于控制全连接层的通道数缩减,从而影响 SE 模块的参数量和计算复杂度,通过调整 reduction 参数,可以灵活控制通道数的缩减程度。

SE-Resnet网络

由于我也是刚学完这部分的知识,应用上面还需要进行实验,所以,还是来看看原作者提供的torch版本的网络。

  1. import torch.nn as nn
  2. from torchvision.models import ResNet
  3. __all__ = ["se_resnet18", "se_resnet34", "se_resnet50", "se_resnet101", "se_resnet152"]
  4. class SELayer(nn.Module):
  5. def __init__(self, channel, reduction=16):
  6. super().__init__()
  7. self.avg_pool = nn.AdaptiveAvgPool2d(1)
  8. self.fc = nn.Sequential(
  9. nn.Linear(channel, channel // reduction, bias=False),
  10. nn.ReLU(inplace=True),
  11. nn.Linear(channel // reduction, channel, bias=False),
  12. nn.Sigmoid()
  13. )
  14. def forward(self, x):
  15. b, c, _, _ = x.size()
  16. y = self.avg_pool(x).view(b, c)
  17. y = self.fc(y).view(b, c, 1, 1)
  18. return x * y.expand_as(x)
  19. def conv3x3(in_planes, out_planes, stride=1):
  20. return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False)
  21. class SEBasicBlock(nn.Module):
  22. expansion = 1
  23. def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
  24. base_width=64, dilation=1, norm_layer=None,
  25. *, reduction=16):
  26. super(SEBasicBlock, self).__init__()
  27. self.conv1 = conv3x3(inplanes, planes, stride)
  28. self.bn1 = nn.BatchNorm2d(planes)
  29. self.relu = nn.ReLU(inplace=True)
  30. self.conv2 = conv3x3(planes, planes, 1)
  31. self.bn2 = nn.BatchNorm2d(planes)
  32. self.se = SELayer(planes, reduction)
  33. self.downsample = downsample
  34. self.stride = stride
  35. def forward(self, x):
  36. residual = x
  37. out = self.conv1(x)
  38. out = self.bn1(out)
  39. out = self.relu(out)
  40. out = self.conv2(out)
  41. out = self.bn2(out)
  42. out = self.se(out)
  43. if self.downsample is not None:
  44. residual = self.downsample(x)
  45. out += residual
  46. out = self.relu(out)
  47. return out
  48. class SEBottleneck(nn.Module):
  49. expansion = 4
  50. def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
  51. base_width=64, dilation=1, norm_layer=None,
  52. *, reduction=16):
  53. super(SEBottleneck, self).__init__()
  54. self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
  55. self.bn1 = nn.BatchNorm2d(planes)
  56. self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
  57. padding=1, bias=False)
  58. self.bn2 = nn.BatchNorm2d(planes)
  59. self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
  60. self.bn3 = nn.BatchNorm2d(planes * 4)
  61. self.relu = nn.ReLU(inplace=True)
  62. self.se = SELayer(planes * 4, reduction)
  63. self.downsample = downsample
  64. self.stride = stride
  65. def forward(self, x):
  66. residual = x
  67. out = self.conv1(x)
  68. out = self.bn1(out)
  69. out = self.relu(out)
  70. out = self.conv2(out)
  71. out = self.bn2(out)
  72. out = self.relu(out)
  73. out = self.conv3(out)
  74. out = self.bn3(out)
  75. out = self.se(out)
  76. if self.downsample is not None:
  77. residual = self.downsample(x)
  78. out += residual
  79. out = self.relu(out)
  80. return out
  81. def se_resnet18(num_classes):
  82. model = ResNet(SEBasicBlock, [2, 2, 2, 2], num_classes=num_classes)
  83. model.avgpool = nn.AdaptiveAvgPool2d(1)
  84. return model
  85. def se_resnet34(num_classes):
  86. model = ResNet(SEBasicBlock, [3, 4, 6, 3], num_classes=num_classes)
  87. model.avgpool = nn.AdaptiveAvgPool2d(1)
  88. return model
  89. def se_resnet50(num_classes):
  90. model = ResNet(SEBottleneck, [3, 4, 6, 3], num_classes=num_classes)
  91. model.avgpool = nn.AdaptiveAvgPool2d(1)
  92. return model
  93. def se_resnet101(num_classes):
  94. model = ResNet(SEBottleneck, [3, 4, 23, 3], num_classes=num_classes)
  95. model.avgpool = nn.AdaptiveAvgPool2d(1)
  96. return model
  97. def se_resnet152(num_classes):
  98. model = ResNet(SEBottleneck, [3, 8, 36, 3], num_classes=num_classes)
  99. model.avgpool = nn.AdaptiveAvgPool2d(1)
  100. return model
  101. if __name__=="__main__":
  102. import torchsummary
  103. import torch
  104. input = torch.ones(2, 3, 224, 224).cuda()
  105. net = se_resnet50(num_classes=4)
  106. net = net.cuda()
  107. out = net(input)
  108. print(out)
  109. print(out.shape)
  110. torchsummary.summary(net, input_size=(3, 224, 224))
  111. # Total params: 26,031,172

这里面基本上是与torchvision中的源码相同的,只是对原本的BasicBlock与Bottleneck在最后的卷积层后加入了SE模块。

ResNet与SE-ResNet分类性能比较实验

这一部分我是在自己的数据集上进行分类的,一共四个类,每个类别大约在300张左右,ResNet添加了官方的预训练权重,SE-Resnet是重头开始训练。(本来是预计都跑50轮的,但是在跑ResNet的时候忘记了)

下面是ResNet50的训练损失记录:

下面是SE_ResNet50的训练损失记录:

这里还记录了se_resnet的错误率记录,因为是后加的功能,所以resnet50没有。

这里出现了过拟合的情况,不过也没关系,本身这个数据量在这里,这里我也只是想要测试它的性能效果而已。

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

闽ICP备14008679号