• YOLOX backbone——CSPDarknet的实现


    YOLOX所使用的主干特征提取网络为CSPDarknet,如下图左侧框所示。

    图片来源: Pytorch 搭建自己的YoloX目标检测平台(Bubbliiiing 深度学习 教程)_哔哩哔哩_bilibili

    CSPDarknet的几个要点总结如下。

    1. Focus网络结构

    Focus结构的具体操作是,在一幅图像中行和列的方向进行隔像素抽取,组成新的特征层,每幅图像可重组为4个特征层,然后将4个特征层进行堆叠,将输入通道扩展为4倍。堆叠后的特征层相对于原先的3通道变为12通道,如下图所示:

     PyTorch代码实现如下:

    1. class Focus(nn.Module):
    2. """Focus width and height information into channel space."""
    3. def __init__(self, in_channels, out_channels, ksize=1, stride=1, act="silu"):
    4. super().__init__()
    5. self.conv = BaseConv(in_channels * 4, out_channels, ksize, stride, act=act)
    6. def forward(self, x):
    7. # shape of x (b,c,w,h) -> y(b,4c,w/2,h/2)
    8. patch_top_left = x[..., ::2, ::2]
    9. patch_top_right = x[..., ::2, 1::2]
    10. patch_bot_left = x[..., 1::2, ::2]
    11. patch_bot_right = x[..., 1::2, 1::2]
    12. x = torch.cat(
    13. (
    14. patch_top_left,
    15. patch_bot_left,
    16. patch_top_right,
    17. patch_bot_right,
    18. ),
    19. dim=1,
    20. )
    21. return self.conv(x)

    2. 残差网络Residual

    CSPDarknet中的残差网络分为两个分支,主干分支做一次1x1卷积和一次3x3卷积,残差边部分不做任何处理,相当于直接将主干的输入和输出结合。

     代码如下,

    1. class Bottleneck(nn.Module):
    2. # Standard bottleneck
    3. def __init__(
    4. self,
    5. in_channels,
    6. out_channels,
    7. shortcut=True,
    8. expansion=0.5,
    9. depthwise=False,
    10. act="silu",
    11. ):
    12. super().__init__()
    13. hidden_channels = int(out_channels * expansion)
    14. Conv = DWConv if depthwise else BaseConv
    15. self.conv1 = BaseConv(in_channels, hidden_channels, 1, stride=1, act=act)
    16. self.conv2 = Conv(hidden_channels, out_channels, 3, stride=1, act=act)
    17. self.use_add = shortcut and in_channels == out_channels
    18. def forward(self, x):
    19. y = self.conv2(self.conv1(x))
    20. if self.use_add:
    21. y = y + x
    22. return y

    其中的DWConv指的是Depthwise Convolution,在轻量级网络如YOLOX-Nano和YOLOX-Tiny会用到。

    DWConv和BaseConv的定义如下:

    1. class DWConv(nn.Module):
    2. """Depthwise Conv + Conv"""
    3. def __init__(self, in_channels, out_channels, ksize, stride=1, act="silu"):
    4. super().__init__()
    5. self.dconv = BaseConv(
    6. in_channels,
    7. in_channels,
    8. ksize=ksize,
    9. stride=stride,
    10. groups=in_channels,
    11. act=act,
    12. )
    13. self.pconv = BaseConv(
    14. in_channels, out_channels, ksize=1, stride=1, groups=1, act=act
    15. )
    16. def forward(self, x):
    17. x = self.dconv(x)
    18. return self.pconv(x)
    1. class BaseConv(nn.Module):
    2. """A Conv2d -> Batchnorm -> silu/leaky relu block"""
    3. def __init__(
    4. self, in_channels, out_channels, ksize, stride, groups=1, bias=False, act="silu"
    5. ):
    6. super().__init__()
    7. # same padding
    8. pad = (ksize - 1) // 2
    9. self.conv = nn.Conv2d(
    10. in_channels,
    11. out_channels,
    12. kernel_size=ksize,
    13. stride=stride,
    14. padding=pad,
    15. groups=groups,
    16. bias=bias,
    17. )
    18. self.bn = nn.BatchNorm2d(out_channels)
    19. self.act = get_activation(act, inplace=True)
    20. def forward(self, x):
    21. return self.act(self.bn(self.conv(x)))
    22. def fuseforward(self, x):
    23. return self.act(self.conv(x))

    3. CSPNet网络结构

    CSPNet的结构跟Residual有点像,也是分成左右两部分,主干部分进行残差块的堆叠,另一部分则像残差边一样,经过少量处理后连接到主干部分的最后。图示如下:

     图片来源于网络。

    上图最右侧部分即为CSPNet的分解结构,其中,Bottleneck的数目根据不同的层可配置不同的数目 。该结构的代码实现如下:

    1. class CSPLayer(nn.Module):
    2. """C3 in yolov5, CSP Bottleneck with 3 convolutions"""
    3. def __init__(
    4. self,
    5. in_channels,
    6. out_channels,
    7. n=1,
    8. shortcut=True,
    9. expansion=0.5,
    10. depthwise=False,
    11. act="silu",
    12. ):
    13. """
    14. Args:
    15. in_channels (int): input channels.
    16. out_channels (int): output channels.
    17. n (int): number of Bottlenecks. Default value: 1.
    18. """
    19. # ch_in, ch_out, number, shortcut, groups, expansion
    20. super().__init__()
    21. hidden_channels = int(out_channels * expansion) # hidden channels
    22. # 主干部分第一次卷积
    23. self.conv1 = BaseConv(in_channels, hidden_channels, 1, stride=1, act=act)
    24. # 大的残差边部分第一次卷积
    25. self.conv2 = BaseConv(in_channels, hidden_channels, 1, stride=1, act=act)
    26. # 对堆叠结果进行卷积操作,注意堆叠后,输入的channels变成了两倍
    27. self.conv3 = BaseConv(2 * hidden_channels, out_channels, 1, stride=1, act=act)
    28. # 根据循环次数构建Bottleneck残差结构
    29. module_list = [
    30. Bottleneck(
    31. hidden_channels, hidden_channels, shortcut, 1.0, depthwise, act=act
    32. )
    33. for _ in range(n)
    34. ]
    35. self.m = nn.Sequential(*module_list)
    36. def forward(self, x):
    37. # X_1为主干部分
    38. x_1 = self.conv1(x)
    39. # x_2为大的残差边部分
    40. x_2 = self.conv2(x)
    41. # 主干部分利用残差结构堆叠进行特征提取
    42. x_1 = self.m(x_1)
    43. # 主干部分和大的残差边部分进行堆叠
    44. x = torch.cat((x_1, x_2), dim=1)
    45. # 对堆叠结果进行卷积处理
    46. return self.conv3(x)

    4. SiLU激活函数

    SiLU激活函数是Signoid和ReLU的改进版,具有有下界无上界、平滑、非单调的特性,在深层模型上的效果优于ReLU。类似这种图形:

    在这里插入图片描述

    实现代码如下:

    1. class SiLU(nn.Module):
    2. """export-friendly version of nn.SiLU()"""
    3. @staticmethod
    4. def forward(x):
    5. return x * torch.sigmoid(x)

    5. SPP结构

    SPP是Spatial Pyramid Pooling的缩写。在CSPDarknet中,使用了不同池化核大小的MaxPool进行特征提取,以提高网络的感受野。与在YOLOv4中将SPP用在FPN里面不同,在YOLOX中,SPP模块被用在了主干特征提取网络中。示意图如下:

    实现代码如下:

    1. class SPPBottleneck(nn.Module):
    2. """Spatial pyramid pooling layer used in YOLOv3-SPP"""
    3. def __init__(
    4. self, in_channels, out_channels, kernel_sizes=(5, 9, 13), activation="silu"
    5. ):
    6. super().__init__()
    7. hidden_channels = in_channels // 2
    8. self.conv1 = BaseConv(in_channels, hidden_channels, 1, stride=1, act=activation)
    9. self.m = nn.ModuleList(
    10. [
    11. nn.MaxPool2d(kernel_size=ks, stride=1, padding=ks // 2)
    12. for ks in kernel_sizes
    13. ]
    14. )
    15. conv2_channels = hidden_channels * (len(kernel_sizes) + 1)
    16. self.conv2 = BaseConv(conv2_channels, out_channels, 1, stride=1, act=activation)
    17. def forward(self, x):
    18. x = self.conv1(x)
    19. x = torch.cat([x] + [m(x) for m in self.m], dim=1)
    20. x = self.conv2(x)
    21. return x

    6. CSPDarknet完整实现

    好了,CSPDarknet的组成部分介绍完了,接下来,需要将以上子模块拼装成最终的CSPDarknet。代码如下:

    1. class CSPDarknet(nn.Module):
    2. def __init__(
    3. self,
    4. dep_mul,
    5. wid_mul,
    6. out_features=("dark3", "dark4", "dark5"),
    7. depthwise=False,
    8. act="silu",
    9. ):
    10. super().__init__()
    11. assert out_features, "please provide output features of Darknet"
    12. self.out_features = out_features
    13. Conv = DWConv if depthwise else BaseConv
    14. # 输入图片大小是640x640x3
    15. # 初始基本通道为64
    16. base_channels = int(wid_mul * 64) # 64
    17. base_depth = max(round(dep_mul * 3), 1) # 3
    18. # 利用focus网络结构进行特征提取
    19. # 640x640x3 -> 320x320x12 -> 320x320x64
    20. self.stem = Focus(3, base_channels, ksize=3, act=act)
    21. # dark2
    22. # Conv: 320x320x64 -> 160x160x128
    23. # CSPLayer: 160x160x128 -> 160x160x128
    24. self.dark2 = nn.Sequential(
    25. Conv(base_channels, base_channels * 2, 3, 2, act=act),
    26. CSPLayer(
    27. base_channels * 2,
    28. base_channels * 2,
    29. n=base_depth,
    30. depthwise=depthwise,
    31. act=act,
    32. ),
    33. )
    34. # dark3
    35. # Conv: 160x160x128 -> 80x80x256
    36. # CSPLayer: 80x80x256 -> 80x80x256
    37. self.dark3 = nn.Sequential(
    38. Conv(base_channels * 2, base_channels * 4, 3, 2, act=act),
    39. CSPLayer(
    40. base_channels * 4,
    41. base_channels * 4,
    42. n=base_depth * 3,
    43. depthwise=depthwise,
    44. act=act,
    45. ),
    46. )
    47. # dark4
    48. # Conv: 80x80x256 -> 40x40x512
    49. # CSPLayer: 40x40x512 -> 40x40x512
    50. self.dark4 = nn.Sequential(
    51. Conv(base_channels * 4, base_channels * 8, 3, 2, act=act),
    52. CSPLayer(
    53. base_channels * 8,
    54. base_channels * 8,
    55. n=base_depth * 3,
    56. depthwise=depthwise,
    57. act=act,
    58. ),
    59. )
    60. # dark5
    61. # Conv: 40x40x512 -> 20x20x1024
    62. # SPPConv: 20x20x1024 -> 20x20x1024
    63. # CSPLayer: 20x20x1024 -> 20x20x1024
    64. self.dark5 = nn.Sequential(
    65. Conv(base_channels * 8, base_channels * 16, 3, 2, act=act),
    66. SPPBottleneck(base_channels * 16, base_channels * 16, activation=act),
    67. CSPLayer(
    68. base_channels * 16,
    69. base_channels * 16,
    70. n=base_depth,
    71. shortcut=False,
    72. depthwise=depthwise,
    73. act=act,
    74. ),
    75. )
    76. def forward(self, x):
    77. outputs = {}
    78. x = self.stem(x)
    79. outputs["stem"] = x
    80. x = self.dark2(x)
    81. outputs["dark2"] = x
    82. # dark3的输出为80x80x256的有效特征层
    83. x = self.dark3(x)
    84. outputs["dark3"] = x
    85. # dark4的输出为40x40x512的有效特征层
    86. x = self.dark4(x)
    87. outputs["dark4"] = x
    88. # dark5的输出为20x20x1024的有效特征层
    89. x = self.dark5(x)
    90. outputs["dark5"] = x
    91. return {k: v for k, v in outputs.items() if k in self.out_features}

  • 相关阅读:
    c-实用调试技巧-day5
    iNFTnews | Web3时代,用户将拥有数据自主权
    Oracle 19c LISTAGG 函数中distinct
    一张图带你了解.NET终结(Finalize)流程
    RNN笔记(刘二大人)
    【微软技术栈】使用新的C#功能减少内存分配
    MySQL向Es数据同步策略
    Matlab基本语法(一)
    使用 Next.js、Langchain 和 OpenAI 构建 AI 聊天机器人
    为了简写这行代码,我竟使用静态和动态编译技术
  • 原文地址:https://blog.csdn.net/DeliaPu/article/details/125414211