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}
发表回复