• SK-Net eca注意力机制应用于ResNet (附代码)


    resnet发展历程

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

    代码地址:https://github.com/pppLang/SKNet

    1.是什么?

    SK-net网络是一种增加模块嵌入到一些网络中的注意力机制,它可以嵌入和Resnet中进行补强,嵌入和方法和Se-net类似。SKNet的全称是“Selective Kernel Network”,它和SENet都是一个团队提出来的。SENet是对通道执行注意力机制,而SKNet则是对卷积核执行注意力机制,即让网络自己选择合适的卷积核。SKNet的核心模块包括多分支卷积网络、组卷积、空洞卷积以及注意力机制。在该模块中,作者使用了多尺寸的卷积核,让网络自己选择合适的尺度。SK-net网络的优点是可以提高网络的准确性和泛化能力,同时减少了网络的参数数量和计算量。

    2.为什么?

    SKNet的设计在以下几个方面具有优势:

    多尺度信息融合
    通过选择性地应用不同尺度的卷积核,SKNet能够有效地融合多尺度的特征信息。这有助于网络捕捉不同层次的视觉特征,提高了特征的表征能力。

    自适应性
    选择模块使网络能够自适应地选择卷积核的尺度,从而适应不同任务和图像的特点。这种自适应性能够使网络在各种场景下都能表现出色。

    减少计算成本
    尽管引入了多尺度卷积核,但由于选择模块的存在,SKNet只会选择一部分卷积核进行计算,从而减少了计算成本,保持了网络的高效性。
     

    3.怎么样?

    3.1网络模型

    SK-net的整个流程如下:
    (1)Split划分
        首先使用不同的卷积核获得两个不同的特征图。
    (2)Fuse融合
        融合两个不同的特征图,然后将它们通过全局平均池化的方式提取出每一个通道的融合值。再通过线性层进行通过压缩,这里和Se-net的通道注意力是一样的。
    (3)Select选择
        在通过一个Softmax层进行选择,后续的操作和Se-net是一样的,得到通过挑选的特征图。这里的特征图可以看到每一个通道所使用的卷积核是不一样的,也是通过学习得到的。

    3.2 网络结构


     

    3.3 代码实现

    SKConv

    1. import torch.nn as nn
    2. import torch
    3. class SKConv(nn.Module):
    4. def __init__(self, features, WH, M, G, r, stride=1, L=32):
    5. super(SKConv, self).__init__()
    6. d = max(int(features / r), L)
    7. self.M = M
    8. self.features = features
    9. self.convs = nn.ModuleList([])
    10. for i in range(M):
    11. # 使用不同kernel size的卷积
    12. self.convs.append(
    13. nn.Sequential(
    14. nn.Conv2d(features,
    15. features,
    16. kernel_size=3 + i * 2,
    17. stride=stride,
    18. padding=1 + i,
    19. groups=G), nn.BatchNorm2d(features),
    20. nn.ReLU(inplace=False)))
    21. self.fc = nn.Linear(features, d)
    22. self.fcs = nn.ModuleList([])
    23. for i in range(M):
    24. self.fcs.append(nn.Linear(d, features))
    25. self.softmax = nn.Softmax(dim=1)
    26. def forward(self, x):
    27. for i, conv in enumerate(self.convs):
    28. fea = conv(x).unsqueeze_(dim=1)
    29. if i == 0:
    30. feas = fea
    31. else:
    32. feas = torch.cat([feas, fea], dim=1)
    33. fea_U = torch.sum(feas, dim=1)
    34. fea_s = fea_U.mean(-1).mean(-1)
    35. fea_z = self.fc(fea_s)
    36. for i, fc in enumerate(self.fcs):
    37. print(i, fea_z.shape)
    38. vector = fc(fea_z).unsqueeze_(dim=1)
    39. print(i, vector.shape)
    40. if i == 0:
    41. attention_vectors = vector
    42. else:
    43. attention_vectors = torch.cat([attention_vectors, vector],
    44. dim=1)
    45. attention_vectors = self.softmax(attention_vectors)
    46. attention_vectors = attention_vectors.unsqueeze(-1).unsqueeze(-1)
    47. fea_v = (feas * attention_vectors).sum(dim=1)
    48. return fea_v
    49. if __name__ == "__main__":
    50. t = torch.ones((32, 256, 24,24))
    51. sk = SKConv(256,WH=1,M=2,G=1,r=2)
    52. out = sk(t)
    53. print(out.shape)

    SKNet

    1. import torch.nn as nn
    2. import torch
    3. from functools import reduce
    4. class SKConv(nn.Module):
    5. def __init__(self,in_channels,out_channels,stride=1,M=2,r=16,L=32):
    6. '''
    7. :param in_channels: 输入通道维度
    8. :param out_channels: 输出通道维度 原论文中 输入输出通道维度相同
    9. :param stride: 步长,默认为1
    10. :param M: 分支数
    11. :param r: 特征Z的长度,计算其维度d 时所需的比率(论文中 特征S->Z 是降维,故需要规定 降维的下界)
    12. :param L: 论文中规定特征Z的下界,默认为32
    13. '''
    14. super(SKConv,self).__init__()
    15. d=max(in_channels//r,L) # 计算向量Z 的长度d
    16. self.M=M
    17. self.out_channels=out_channels
    18. self.conv=nn.ModuleList() # 根据分支数量 添加 不同核的卷积操作
    19. for i in range(M):
    20. # 为提高效率,原论文中 扩张卷积5x5为 (3X3,dilation=2)来代替。 且论文中建议组卷积G=32
    21. self.conv.append(nn.Sequential(nn.Conv2d(in_channels,out_channels,3,stride,padding=1+i,dilation=1+i,groups=32,bias=False),
    22. nn.BatchNorm2d(out_channels),
    23. nn.ReLU(inplace=True)))
    24. self.global_pool=nn.AdaptiveAvgPool2d(1) # 自适应pool到指定维度 这里指定为1,实现 GAP
    25. self.fc1=nn.Sequential(nn.Conv2d(out_channels,d,1,bias=False),
    26. nn.BatchNorm2d(d),
    27. nn.ReLU(inplace=True)) # 降维
    28. self.fc2=nn.Conv2d(d,out_channels*M,1,1,bias=False) # 升维
    29. self.softmax=nn.Softmax(dim=1) # 指定dim=1 使得两个全连接层对应位置进行softmax,保证 对应位置a+b+..=1
    30. def forward(self, input):
    31. batch_size=input.size(0)
    32. output=[]
    33. #the part of split
    34. for i,conv in enumerate(self.conv):
    35. #print(i,conv(input).size())
    36. output.append(conv(input))
    37. #the part of fusion
    38. U=reduce(lambda x,y:x+y,output) # 逐元素相加生成 混合特征U
    39. s=self.global_pool(U)
    40. z=self.fc1(s) # S->Z降维
    41. a_b=self.fc2(z) # Z->a,b 升维 论文使用conv 1x1表示全连接。结果中前一半通道值为a,后一半为b
    42. a_b=a_b.reshape(batch_size,self.M,self.out_channels,-1) #调整形状,变为 两个全连接层的值
    43. a_b=self.softmax(a_b) # 使得两个全连接层对应位置进行softmax
    44. #the part of selection
    45. a_b=list(a_b.chunk(self.M,dim=1))#split to a and b chunk为pytorch方法,将tensor按照指定维度切分成 几个tensor块
    46. a_b=list(map(lambda x:x.reshape(batch_size,self.out_channels,1,1),a_b)) # 将所有分块 调整形状,即扩展两维
    47. V=list(map(lambda x,y:x*y,output,a_b)) # 权重与对应 不同卷积核输出的U 逐元素相乘
    48. V=reduce(lambda x,y:x+y,V) # 两个加权后的特征 逐元素相加
    49. return V
    50. class SKBlock(nn.Module):
    51. '''
    52. 基于Res Block构造的SK Block
    53. ResNeXt有 1x1Conv(通道数:x) + SKConv(通道数:x) + 1x1Conv(通道数:2x) 构成
    54. '''
    55. expansion=2 #指 每个block中 通道数增大指定倍数
    56. def __init__(self,inplanes,planes,stride=1,downsample=None):
    57. super(SKBlock,self).__init__()
    58. self.conv1=nn.Sequential(nn.Conv2d(inplanes,planes,1,1,0,bias=False),
    59. nn.BatchNorm2d(planes),
    60. nn.ReLU(inplace=True))
    61. self.conv2=SKConv(planes,planes,stride)
    62. self.conv3=nn.Sequential(nn.Conv2d(planes,planes*self.expansion,1,1,0,bias=False),
    63. nn.BatchNorm2d(planes*self.expansion))
    64. self.relu=nn.ReLU(inplace=True)
    65. self.downsample=downsample
    66. def forward(self, input):
    67. shortcut=input
    68. output=self.conv1(input)
    69. output=self.conv2(output)
    70. output=self.conv3(output)
    71. if self.downsample is not None:
    72. shortcut=self.downsample(input)
    73. output+=shortcut
    74. return self.relu(output)
    75. class SKNet(nn.Module):
    76. '''
    77. 参考 论文Table.1 进行构造
    78. '''
    79. def __init__(self,nums_class=1000,block=SKBlock,nums_block_list=[3, 4, 6, 3]):
    80. super(SKNet,self).__init__()
    81. self.inplanes=64
    82. # in_channel=3 out_channel=64 kernel=7x7 stride=2 padding=3
    83. self.conv=nn.Sequential(nn.Conv2d(3,64,7,2,3,bias=False),
    84. nn.BatchNorm2d(64),
    85. nn.ReLU(inplace=True))
    86. self.maxpool=nn.MaxPool2d(3,2,1) # kernel=3x3 stride=2 padding=1
    87. self.layer1=self._make_layer(block,128,nums_block_list[0],stride=1) # 构建表中 每个[] 的部分
    88. self.layer2=self._make_layer(block,256,nums_block_list[1],stride=2)
    89. self.layer3=self._make_layer(block,512,nums_block_list[2],stride=2)
    90. self.layer4=self._make_layer(block,1024,nums_block_list[3],stride=2)
    91. self.avgpool=nn.AdaptiveAvgPool2d(1) # GAP全局平均池化
    92. self.fc=nn.Linear(1024*block.expansion,nums_class) # 通道 2048 -> 1000
    93. self.softmax=nn.Softmax(-1) # 对最后一维进行softmax
    94. def forward(self, input):
    95. output=self.conv(input)
    96. output=self.maxpool(output)
    97. output=self.layer1(output)
    98. output=self.layer2(output)
    99. output=self.layer3(output)
    100. output=self.layer4(output)
    101. output=self.avgpool(output)
    102. output=output.squeeze(-1).squeeze(-1)
    103. output=self.fc(output)
    104. output=self.softmax(output)
    105. return output
    106. def _make_layer(self,block,planes,nums_block,stride=1):
    107. downsample=None
    108. if stride!=1 or self.inplanes!=planes*block.expansion:
    109. downsample=nn.Sequential(nn.Conv2d(self.inplanes,planes*block.expansion,1,stride,bias=False),
    110. nn.BatchNorm2d(planes*block.expansion))
    111. layers=[]
    112. layers.append(block(self.inplanes,planes,stride,downsample))
    113. self.inplanes=planes*block.expansion
    114. for _ in range(1,nums_block):
    115. layers.append(block(self.inplanes,planes))
    116. return nn.Sequential(*layers)
    117. def SKNet50(nums_class=1000):
    118. return SKNet(nums_class,SKBlock,[3, 4, 6, 3]) # 论文通过[3, 4, 6, 3]搭配出SKNet50
    119. def SKNet101(nums_class=1000):
    120. return SKNet(nums_class,SKBlock,[3, 4, 23, 3])
    121. if __name__=='__main__':
    122. x = torch.rand(2, 3, 224, 224)
    123. model=SKNet50()
    124. y=model(x)
    125. print(y) # shape [2,1000]

    参考:SKNet学习和使用-pytorch

    【深度学习注意力机制系列】—— SKNet注意力机制(附pytorch实现)

  • 相关阅读:
    作为产品经理,你是如何分析和管理你的产品需求的?
    计算机网络 ——数据链路层(广域网)
    探索原味BFF模式
    文件系统考古4:如何支持多个文件系统
    数据库select语句基础
    如何通过C#/VB.NET设置Word文档段落缩进
    写个rpc调用,试试自己了解多少
    【从零学习python 】70.网络通信方式及其应用:从直接通信到路由器连接多个网络
    ModStartCMS v4.9.0 用户注册IP,后台登录优化
    leetcode 46. 全排列(经典)
  • 原文地址:https://blog.csdn.net/PLANTTHESON/article/details/133979403