2023年4月13日
YOLOX-Darknet代码阅读
class Darknet(nn.Module): # number of blocks from dark2 to dark5. depth2blocks = {21: [1, 2, 2, 1], 53: [2, 8, 8, 4]} # ======================== darknet stem ======================== # Q: stem 是什么意思 # A: stem 是 darknet 的第一层,它是一个 Focus 层。 # Q: 什么是 Focus 层 # A: Focus 层是一个卷积层,它的作用是将输入的图片从 3 通道变成 64 通道。 # Q: stem_out_channels 为32,那Focus层输出的通道数应该是多少 # A: 32*4=128 # Q: 为什么要需要*4 # Q: stem_out_channels 是什么意思 # A: stem_out_channels 是 darknet 的输出通道数,它决定了 darknet layer2 到 layer5 的通道数。 # Q: layer2 和 layer5 是什么意思 # A: layer2 和 layer5 是 darknet 的层数,layer2 是 darknet 的第二层,layer5 是 darknet 的第五层。 # Q: 他们的结构是什么样的 # A: layer2 是一个卷积层,layer5 是一个 SPPBottleneck 层。 # ======================== darknet out_features ======================== # Q: out_features 是什么意思 # A: out_features 是 darknet 的输出层名字。 # Q: 为什么要有 out_features # A: 因为 darknet 有多个输出层,我们可以选择需要的输出层。 # Q: 这几个输出层的作用是什么 # A: dark2 的输出层用于检测小目标,dark3 的输出层用于检测中目标,dark4 的输出层用于检测大目标。 # Q: 这三个层会融合吗 # A: 会,darknet 的输出层会融合,然后再进行后处理。 # ======================== darknet 结构 ======================== # Q: 请帮我梳理一下 darknet 的结构,请详细描述每一层的作用 # A: darknet 的结构如下: # A: darknet stem (Focus) # A: darknet layer2 (BaseConv) # A: darknet layer3 (BaseConv) # A: darknet layer4 (BaseConv) # A: darknet layer5 (SPPBottleneck) # A: darknet out_features (Tuple[str])
def __init__( self, depth, in_channels=3, stem_out_channels=32, out_features=("dark3", "dark4", "dark5"), ): """ Args: depth (int): depth of darknet used in model, usually use [21, 53] for this param. in_channels (int): number of input channels, for example, use 3 for RGB image. stem_out_channels (int): number of output channels of darknet stem. It decides channels of darknet layer2 to layer5. out_features (Tuple[str]): desired output layer name. """ super().__init__() # Q: 为什么要 assert out_features # A: 因为 out_features 是 darknet 的输出层名字,我们需要它。 # Q: 这里的 assert 是什么意思 # A: assert 是断言,它的作用是判断 out_features 是否为 True,如果不为 True,就会报错。 # Q: assert的内容为空,会发生什么 # A: 会报错,报错内容是 "please provide output features of Darknet"。 assert out_features, "please provide output features of Darknet" self.out_features = out_features # ======================== nn.Sequential ======================== # Q: nn.Sequential 是什么意思 # A: nn.Sequential 是一个容器,它可以将多个层组合在一起。 # Q: 第二个参数是什么意思,为什么要加 *,为什么要加 self.make_group_layer,make_group_layer 是什么意思 # A: 第二个参数是 darknet 的第一层,它是一个 Focus 层。 # A: * 是解包操作,它的作用是将 self.make_group_layer 的返回值解包,然后传给 nn.Sequential。 # Q: 我认为第一个参数BaseConv是第一层,为什么你说是Focus层 # A: Focus 层是一个卷积层,它的作用是将输入的图片从 3 通道变成 64 通道。 # Q: 所以到底是哪一层是Focus层,是nn.Sequential的第一层还是第二层 # A: Focus 层是 nn.Sequential 的第一层。 # Q: 为什么要加 self.make_group_layer # A: 因为 darknet 的第二层到第五层都是由多个卷积层组成的,self.make_group_layer 的作用是创建一个 darknet 的层。 # Q: 可不可以理解成nn.Sequential中的第一个参数是Focus层,第二个参数是darknet的第二层到第五层 # A: 可以,nn.Sequential 中的第一个参数是 Focus 层,第二个参数是 darknet 的第二层到第五层。 # Q: 但是nn.Sequential中的第二个参数只有一个num_blocks,为什么 # A: 因为 Focus 层只有一个卷积层,所以只需要一个 num_blocks。 # ======================== self.make_group_layer ======================== # A: self.make_group_layer 是一个函数,它的作用是创建一个 darknet 的层。 # A: darknet 的层是由多个卷积层组成的,self.make_group_layer 的作用是创建一个 darknet 的层。 # Q: 关于 self.make_group_layer 的问题,我还有一些疑问,num_blocks 是什么意思,stride 是什么意思 # A: num_blocks 是 darknet 的层数,stride 是 darknet 的步长。 # Q: 在make_group_layer中,我没有看到卷积核的大小,为什么 # A: 卷积核的大小在 BaseConv 中定义了,这里不需要再定义。 # Q: 可不可以理解make_group_layer自动调用了BaseConv # A: 可以,make_group_layer 自动调用了 BaseConv。 # Q: 详细解释BaseConv这个方法的作用与参数 # A: BaseConv 是一个卷积层,它的作用是将输入的特征图通过卷积层进行处理,然后输出处理后的特征图。 # A: BaseConv 的参数有: # A: in_channels (int): number of input channels. # A: out_channels (int): number of output channels. # A: ksize (int): size of kernel. # A: stride (int): stride of kernel. # A: act (str): activation function, use "lrelu" for leaky relu. # Q: 为什么要用 leaky relu # A: leaky relu 的作用是增加梯度,使得模型训练更快。 # Q: leaky relu 的公式是什么,它与 relu 的区别是什么 # A: leaky relu 的公式是:max(0.1x, x),它与 relu 的区别类的初始化函数中被赋值的。是当 x < 0 时,leaky relu 的值不为 0。
self.stem = nn.Sequential( BaseConv(in_channels, stem_out_channels, ksize=3, stride=1, act="lrelu"), *self.make_group_layer(stem_out_channels, num_blocks=1, stride=2), ) # Q: 为什么要乘以 2,经过 stem 后,特征图的通道数是多少 # A: 乘以 2 是因为 darknet 的第二层到第五层的通道数都是前一层的两倍。 # Q: 其实不是的,是因为make_group_layer调用了BaseConv,BaseConv中的out_channels是前一层的两倍 # A: 对,是因为 make_group_layer 调用了 BaseConv,BaseConv 中的 out_channels 是前一层的两倍。
in_channels = stem_out_channels * 2 # 64
# ======================== num_blocks ======================== # Q: 下面的语法中,Darknet是否就是self # A: 是的,Darknet 就是 self。 # Q: depth2blocks是个字典吗? # A: 是的,depth2blocks 是一个字典。 # Q: depth这个参数是不是没有被赋值 # A: depth 这个参数是在 Darknet 类的初始化函数中被赋值的。 # Q: 请尝试写一个调用这个类的函数 # A: 请看下面的代码。 # A: def test(): # A: model = Darknet(depth=53) # A: print(model) num_blocks = Darknet.depth2blocks[depth] # create darknet with `stem_out_channels` and `num_blocks` layers. # to make model structure more clear, we don't use `for` statement in python. self.dark2 = nn.Sequential( *self.make_group_layer(in_channels, num_blocks[0], stride=2) ) in_channels *= 2 # 128 self.dark3 = nn.Sequential( *self.make_group_layer(in_channels, num_blocks[1], stride=2) ) in_channels *= 2 # 256 self.dark4 = nn.Sequential( *self.make_group_layer(in_channels, num_blocks[2], stride=2) ) in_channels *= 2 # 512
self.dark5 = nn.Sequential( *self.make_group_layer(in_channels, num_blocks[3], stride=2), *self.make_spp_block([in_channels, in_channels * 2], in_channels * 2), ) # ======================== self.make_group_layer ======================== # Q: ResLayer的方法是什么,它的参数是什么,它的网络结构是什么样子的 # A: ResLayer 是一个残差层,它的参数是 in_channels,它的网络结构是: # A: 1. 卷积层,卷积核大小为 1,步长为 1,激活函数为 leaky relu。 # A: 2. 卷积层,卷积核大小为 3,步长为 1,激活函数为 leaky relu。 # A: 3. 卷积层,卷积核大小为 1,步长为 1,激活函数为 leaky relu。 # A: 4. 残差连接。 # Q: 残差连接了那两个层 # A: 残差连接了第 1 个卷积层和第 3 个卷积层。 def make_group_layer(self, in_channels: int, num_blocks: int, stride: int = 1): "starts with conv layer then has `num_blocks` `ResLayer`" return [ BaseConv(in_channels, in_channels * 2, ksize=3, stride=stride, act="lrelu"), *[(ResLayer(in_channels * 2)) for _ in range(num_blocks)], ]
# ======================== SPPBottleneck ======================== # Q: SPPBottleneck的方法是什么,它的参数是什么,它的网络结构是什么样子的 # A: SPPBottleneck 是一个 SPP 层,它的参数是 in_channels,它的网络结构是: # A: 1. 卷积层,卷积核大小为 1,步长为 1,激活函数为 leaky relu。 # A: 2. 卷积层,卷积核大小为 3,步长为 1,激活函数为 leaky relu。 # A: 3. SPP 层。 # A: 4. 卷积层,卷积核大小为 3,步长为 1,激活函数为 leaky relu。 # A: 5. 卷积层,卷积核大小为 1,步长为 1,激活函数为 leaky relu。 # Q: 请详细阐述一下 SPP 层的作用,它的网络结构是什么样子的 # A: SPP 层是 Spatial Pyramid Pooling 层,它的作用是将特征图的高和宽降低,同时增加通道数。
def make_spp_block(self, filters_list, in_filters): m = nn.Sequential( *[ BaseConv(in_filters, filters_list[0], 1, stride=1, act="lrelu"), BaseConv(filters_list[0], filters_list[1], 3, stride=1, act="lrelu"), SPPBottleneck( in_channels=filters_list[1], out_channels=filters_list[0], activation="lrelu", ), BaseConv(filters_list[0], filters_list[1], 3, stride=1, act="lrelu"), BaseConv(filters_list[1], filters_list[0], 1, stride=1, act="lrelu"), ] ) return m
def forward(self, x): outputs = {} x = self.stem(x) outputs["stem"] = x x = self.dark2(x) outputs["dark2"] = x x = self.dark3(x) outputs["dark3"] = x x = self.dark4(x) outputs["dark4"] = x x = self.dark5(x) outputs["dark5"] = x return {k: v for k, v in outputs.items() if k in self.out_features}