学习资源站

27-添加RFAConv注意力卷积(感受野注意力卷积运算)

YOLOv5改进系列(26)——添加RFAConv注意力卷积(感受野注意力卷积运算)

🚀 一、RFAConv介绍 

1.1 RFAConv简介 

空间注意力机制的局限性

空间注意力机制就是寻找网络中最重要的部位进行处理。旨在提升关键区域的特征表达,本质上是将原始图片中的空间信息通过空间转换模块,变换到另一个空间中并保留关键信息,为每个位置生成权重掩膜(mask)并加权输出,从而增强感兴趣的特定目标区域同时弱化不相关的背景区域。

空间注意力机制从本质上解决了卷积核参数共享问题。然而,空间注意力生成的注意图所包含的信息对于大尺寸卷积核是不够的。

RFAConv的贡献

针对以上不足,作者提出了一种新的注意机制——感受野注意力(Receptive-Field Attention, RFA)现有的空间注意力,如卷积块注意力模块(CBAM)和协调注意力(CA),都存在着只关注空间特征,并没有完全解决卷积核参数共享的问题

相反,RFA不仅关注感受野空间特征,而且为大尺寸卷积核提供了有效的注意力权重。

由RFA开发的感受野注意卷积运算(RFAConv)代表了一种取代标准卷积运算的新方法。它可以显著提高网络性能,但是几乎可以忽略不计的计算成本和参数增量。RFAConv的核心思想是将空间注意力机制与卷积操作相结合,与感受野特征信息交互以学习注意力图,从而提高卷积神经网络(CNN)的性能。


1.2 RFAConv网络结构 

(1)RFAConv

具有3×3大小卷积核的RFAConv的总体结构如下图所示:

我们先看上半部分: 

  • 首先,通过使用AvgPool池化每个感受野特征的全局信息。
  • 然后,通过1×1的组卷积运算与信息交互。
  • 最后,softmax用于强调感受野特征中每个特征的重要性。

目的:为了减少额外的的计算开销和参数数量。

计算公式:

g表示分组卷积,k表示卷积核的大小,Norm代表规范化,X表示输入特征图,F是通过将注意力图A_{rf}与变换的感受野空间特征F_{rf}相乘而获得的。 

下半部分:

通过快速分组卷积提取感受野特征,替换了原来比较慢的提取感受野特征的方法。


(2)RFCAConv

与RFA类似,使用stride为k的k×k的最终卷积运算来提取特征信息。


(3)RFCBAMConv

为了比原始的CBAM减少计算开销,可以使用SE注意力来代替RFCBAM中的CAM。


🚀二、RFAConv核心代码讲解

 源码太长读不下去,找了核心代码读一读吧~

  1. from einops import rearrange
  2. class Bottleneck1(nn.Module):
  3. """Standard bottleneck."""
  4. # __init__ 方法:初始化函数
  5. def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
  6. """Initializes a bottleneck module with given input/output channels, shortcut option, group, kernels, and
  7. expansion.
  8. """
  9. super().__init__()
  10. # 计算隐藏通道数
  11. c_ = int(c2 * e) # hidden channels
  12. # 两个卷积层,分别是输入通道数到隐藏通道数和隐藏通道数到输出通道数的卷积。
  13. self.cv1 = Conv(c1, c_, k[0], 1)
  14. self.cv2 = Conv(c_, c2, k[1], 1, g=g)
  15. # 判断是否使用快捷连接,条件是启用快捷连接并且输入通道数等于输出通道数。
  16. self.add = shortcut and c1 == c2
  17. def forward(self, x):
  18. """'forward()' applies the YOLO FPN to input data."""
  19. return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
  20. class RFAConv(nn.Module):
  21. def __init__(self, in_channel, out_channel, kernel_size, stride=1):
  22. super().__init__()
  23. # 存储卷积核的尺寸
  24. self.kernel_size = kernel_size
  25. # 生成权重
  26. self.get_weight = nn.Sequential(nn.AvgPool2d(kernel_size=kernel_size, padding=kernel_size // 2, stride=stride),
  27. nn.Conv2d(in_channel, in_channel * (kernel_size ** 2), kernel_size=1,
  28. groups=in_channel, bias=False))
  29. # 生成特征
  30. self.generate_feature = nn.Sequential(
  31. nn.Conv2d(in_channel, in_channel * (kernel_size ** 2), kernel_size=kernel_size, padding=kernel_size // 2,
  32. stride=stride, groups=in_channel, bias=False),
  33. nn.BatchNorm2d(in_channel * (kernel_size ** 2)),
  34. nn.ReLU())
  35. self.conv = Conv(in_channel, out_channel, k=kernel_size, s=kernel_size, p=0)
  36. def forward(self, x):
  37. b, c = x.shape[0:2]
  38. weight = self.get_weight(x)
  39. h, w = weight.shape[2:]
  40. weighted = weight.view(b, c, self.kernel_size ** 2, h, w).softmax(2) # b c*kernel**2,h,w -> b c k**2 h w
  41. feature = self.generate_feature(x).view(b, c, self.kernel_size ** 2, h,
  42. w) # b c*kernel**2,h,w -> b c k**2 h w
  43. weighted_data = feature * weighted
  44. conv_data = rearrange(weighted_data, 'b c (n1 n2) h w -> b c (h n1) (w n2)', n1=self.kernel_size,
  45. # b c k**2 h w -> b c h*k w*k
  46. n2=self.kernel_size)
  47. return self.conv(conv_data)
  48. # Bottleneck1的子类
  49. class Bottleneck_RFAConv(Bottleneck1):
  50. """Standard bottleneck with RFAConv."""
  51. def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5): # ch_in, ch_out, shortcut, groups, kernels, expand
  52. super().__init__(c1, c2, shortcut, g, k, e)
  53. c_ = int(c2 * e) # hidden channels
  54. self.cv1 = Conv(c1, c_, k[0], 1)
  55. self.cv2 = RFAConv(c_, c2, k[1])
  56. # C3的子类
  57. class C3_RFAConv(C3):
  58. def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):
  59. super().__init__(c1, c2, n, shortcut, g, e)
  60. c_ = int(c2 * e) # hidden channels
  61. self.m = nn.Sequential(*(Bottleneck_RFAConv(c_, c_, shortcut, g, k=(1, 3), e=1.0) for _ in range(n)))

① class Bottleneck1

这是一个标准的残差块,在以前代码讲解中我们讲过很多次。

首先,通过__init__ 方法初始化函数,然后计算隐藏通道数 c_,即第一个卷积层的输出通道数。

再定义两个卷积层 cv1 cv2分别将输入通道数映射到隐藏通道数,接着从隐藏通道数映射到输出通道数。

最后通过self.add判断是否使用shortcut

② class RFAConv

这个类就是实现了具有区域注意力机制的卷积操作通过生成权重并将其应用到输入特征上,以获得加权的特征表示。最终,通过卷积操作将这些加权的特征映射到输出通道数

其中,self.get_weight包含两个子模块的顺序模块,用于生成权重。

  • 第一个子模块是一个平均池化层,用于降低空间分辨率。
  • 第二个子模块是一个 1x1 的卷积层,用于生成权重,并通过设置 groups=in_channel 实现通道间的独立

③ Bottleneck_RFAConv 

这个类是①的子类,参数也和①一样,就不细讲了~

目的是残差块结构中引入区域注意力机制,从而提高模型对输入数据的关注度。

④class C3_RFAConv

它是 C3 类的子类,通过引入 Bottleneck_RFAConv 模块实现了区域注意力机制。

目的是C3 模块结构中引入区域注意力机制,通过使用 Bottleneck_RFAConv 模块替代标准的瓶颈模块。这样可以在模块内引入区域注意力,从而在整个 C3 模块内提高模型对输入数据的关注度。 


🚀三、具体添加方法 

3.1 添加顺序 

(1)models/common.py    -->  加入新增的网络结构

(2)     models/yolo.py       -->  设定网络结构的传参细节,将RFAConv系列类名加入其中。(当新的自定义模块中存在输入输出维度时,要使用qw调整输出维度)
(3) models/yolov5*.yaml  -->  新建一个文件夹,如yolov5s_RFAConv.yaml,修改现有模型结构配置文件。(当引入新的层时,要修改后续的结构中的from参数)
(4)         train.py                -->  修改‘--cfg’默认参数,训练时指定模型结构配置文件


3.2 具体添加步骤  

第①步:在common.py中添加RFAConv模块  

将下面的RFAConv代码复制粘贴到common.py文件的末尾  

  1. from einops import rearrange
  2. class Bottleneck1(nn.Module):
  3. """Standard bottleneck."""
  4. def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
  5. """Initializes a bottleneck module with given input/output channels, shortcut option, group, kernels, and
  6. expansion.
  7. """
  8. super().__init__()
  9. c_ = int(c2 * e) # hidden channels
  10. self.cv1 = Conv(c1, c_, k[0], 1)
  11. self.cv2 = Conv(c_, c2, k[1], 1, g=g)
  12. self.add = shortcut and c1 == c2
  13. def forward(self, x):
  14. """'forward()' applies the YOLO FPN to input data."""
  15. return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
  16. class RFAConv(nn.Module):
  17. def __init__(self, in_channel, out_channel, kernel_size, stride=1):
  18. super().__init__()
  19. self.kernel_size = kernel_size
  20. self.get_weight = nn.Sequential(nn.AvgPool2d(kernel_size=kernel_size, padding=kernel_size // 2, stride=stride),
  21. nn.Conv2d(in_channel, in_channel * (kernel_size ** 2), kernel_size=1,
  22. groups=in_channel, bias=False))
  23. self.generate_feature = nn.Sequential(
  24. nn.Conv2d(in_channel, in_channel * (kernel_size ** 2), kernel_size=kernel_size, padding=kernel_size // 2,
  25. stride=stride, groups=in_channel, bias=False),
  26. nn.BatchNorm2d(in_channel * (kernel_size ** 2)),
  27. nn.ReLU())
  28. self.conv = Conv(in_channel, out_channel, k=kernel_size, s=kernel_size, p=0)
  29. def forward(self, x):
  30. b, c = x.shape[0:2]
  31. weight = self.get_weight(x)
  32. h, w = weight.shape[2:]
  33. weighted = weight.view(b, c, self.kernel_size ** 2, h, w).softmax(2) # b c*kernel**2,h,w -> b c k**2 h w
  34. feature = self.generate_feature(x).view(b, c, self.kernel_size ** 2, h,
  35. w) # b c*kernel**2,h,w -> b c k**2 h w
  36. weighted_data = feature * weighted
  37. conv_data = rearrange(weighted_data, 'b c (n1 n2) h w -> b c (h n1) (w n2)', n1=self.kernel_size,
  38. # b c k**2 h w -> b c h*k w*k
  39. n2=self.kernel_size)
  40. return self.conv(conv_data)
  41. class Bottleneck_RFAConv(Bottleneck1):
  42. """Standard bottleneck with RFAConv."""
  43. def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5): # ch_in, ch_out, shortcut, groups, kernels, expand
  44. super().__init__(c1, c2, shortcut, g, k, e)
  45. c_ = int(c2 * e) # hidden channels
  46. self.cv1 = Conv(c1, c_, k[0], 1)
  47. self.cv2 = RFAConv(c_, c2, k[1])
  48. class C3_RFAConv(C3):
  49. def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):
  50. super().__init__(c1, c2, n, shortcut, g, e)
  51. c_ = int(c2 * e) # hidden channels
  52. self.m = nn.Sequential(*(Bottleneck_RFAConv(c_, c_, shortcut, g, k=(1, 3), e=1.0) for _ in range(n)))

第②步:在yolo.py文件里的parse_model函数加入类名

首先找到yolo.py里面parse_model函数的这一行

加入 RFAConvC3_RFAConv 这两个模块


 第③步:创建自定义的yaml文件   

 第1种,更改Conv模块

  1. # YOLOv5 🚀 by Ultralytics, GPL-3.0 license
  2. # Parameters
  3. nc: 80 # number of classes
  4. depth_multiple: 0.33 # model depth multiple
  5. width_multiple: 0.50 # layer channel multiple
  6. anchors:
  7. - [10,13, 16,30, 33,23] # P3/8
  8. - [30,61, 62,45, 59,119] # P4/16
  9. - [116,90, 156,198, 373,326] # P5/32
  10. # YOLOv5 v6.0 backbone
  11. backbone:
  12. # [from, number, module, args]
  13. [[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
  14. [-1, 1, RFAConv, [128, 3, 2]], # 1-P2/4
  15. [-1, 3, C3, [128]],
  16. [-1, 1, RFAConv, [256, 3, 2]], # 3-P3/8
  17. [-1, 6, C3, [256]],
  18. [-1, 1, RFAConv, [512, 3, 2]], # 5-P4/16
  19. [-1, 9, C3, [512]],
  20. [-1, 1, RFAConv, [1024, 3, 2]], # 7-P5/32
  21. [-1, 3, C3, [1024]],
  22. [-1, 1, SPPF, [1024, 5]], # 9
  23. ]
  24. # YOLOv5 v6.0 head
  25. head:
  26. [[-1, 1, Conv, [512, 1, 1]],
  27. [-1, 1, nn.Upsample, [None, 2, 'nearest']],
  28. [[-1, 6], 1, Concat, [1]], # cat backbone P4
  29. [-1, 3, C3, [512, False]], # 13
  30. [-1, 1, Conv, [256, 1, 1]],
  31. [-1, 1, nn.Upsample, [None, 2, 'nearest']],
  32. [[-1, 4], 1, Concat, [1]], # cat backbone P3
  33. [-1, 3, C3, [256, False]], # 17 (P3/8-small)
  34. [-1, 1, RFAConv, [256, 3, 2]],
  35. [[-1, 14], 1, Concat, [1]], # cat head P4
  36. [-1, 3, C3, [512, False]], # 20 (P4/16-medium)
  37. [-1, 1, RFAConv, [512, 3, 2]],
  38. [[-1, 10], 1, Concat, [1]], # cat head P5
  39. [-1, 3, C3, [1024, False]], # 23 (P5/32-large)
  40. [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
  41. ]

 第2种,更改C3模块

  1. # YOLOv5 🚀 by Ultralytics, GPL-3.0 license
  2. # Parameters
  3. nc: 80 # number of classes
  4. depth_multiple: 0.33 # model depth multiple
  5. width_multiple: 0.50 # layer channel multiple
  6. anchors:
  7. - [10,13, 16,30, 33,23] # P3/8
  8. - [30,61, 62,45, 59,119] # P4/16
  9. - [116,90, 156,198, 373,326] # P5/32
  10. # YOLOv5 v6.0 backbone
  11. backbone:
  12. # [from, number, module, args]
  13. [[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
  14. [-1, 1, Conv, [128, 3, 2]], # 1-P2/4
  15. [-1, 3, C3_RFAConv, [128]],
  16. [-1, 1, Conv, [256, 3, 2]], # 3-P3/8
  17. [-1, 6, C3_RFAConv, [256]],
  18. [-1, 1, Conv, [512, 3, 2]], # 5-P4/16
  19. [-1, 9, C3_RFAConv, [512]],
  20. [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
  21. [-1, 3, C3_RFAConv, [1024]],
  22. [-1, 1, SPPF, [1024, 5]], # 9
  23. ]
  24. # YOLOv5 v6.0 head
  25. head:
  26. [[-1, 1, Conv, [512, 1, 1]],
  27. [-1, 1, nn.Upsample, [None, 2, 'nearest']],
  28. [[-1, 6], 1, Concat, [1]], # cat backbone P4
  29. [-1, 3, C3_RFAConv, [512, False]], # 13
  30. [-1, 1, Conv, [256, 1, 1]],
  31. [-1, 1, nn.Upsample, [None, 2, 'nearest']],
  32. [[-1, 4], 1, Concat, [1]], # cat backbone P3
  33. [-1, 3, C3_RFAConv, [256, False]], # 17 (P3/8-small)
  34. [-1, 1, Conv, [256, 3, 2]],
  35. [[-1, 14], 1, Concat, [1]], # cat head P4
  36. [-1, 3, C3_RFAConv, [512, False]], # 20 (P4/16-medium)
  37. [-1, 1, Conv, [512, 3, 2]],
  38. [[-1, 10], 1, Concat, [1]], # cat head P5
  39. [-1, 3, C3_RFAConv, [1024, False]], # 23 (P5/32-large)
  40. [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
  41. ]

第④步:验证是否加入成功

运行yolo.py   

第1种

第2种 

这样就OK啦!