上篇内容:
YOLOV3提出论文:《Yolov3: An incremental improvement》
模型创建函数 位置:项目 / utils / models.py / def create_modules
这是网络的所有层的构造函数部分
def create_modules(module_defs):
hyperparams = module_defs.pop(0)
output_filters = [int(hyperparams["channels"])]
module_list = nn.ModuleList()
for module_i, module_def in enumerate(module_defs):
modules = nn.Sequential()
if module_def["type"] == "convolutional":(暂时省略)
elif module_def["type"] == "maxpool":(暂时省略)
elif module_def["type"] == "upsample":(暂时省略)
elif module_def["type"] == "route":(暂时省略)
elif module_def["type"] == "shortcut":(暂时省略)
elif module_def["type"] == "yolo":(暂时省略)
module_list.append(modules)
output_filters.append(filters)
return hyperparams, module_list
module_list.append(modules)
这一行代码将当前处理的模块的层次结构 modules 添加到神经网络模型的 module_list 中。module_list 是一个 PyTorch ModuleList 对象,它用于存储神经网络的各个模块(例如卷积层、池化层、激活函数等)output_filters.append(filters)
:这一行代码将当前处理的模块的输出通道数 filters 添加到 output_filters 列表中。output_filters 用于跟踪每个模块的输出通道数,以便在构建下一个模块时确定输入通道数在4中介绍了模型创建函数,但是if判断语句中没有给出内容,在5介绍一下卷积层是如何处理的
if module_def["type"] == "convolutional":
bn = int(module_def["batch_normalize"])
filters = int(module_def["filters"])
kernel_size = int(module_def["size"])
pad = (kernel_size - 1) // 2
modules.add_module(
f"conv_{module_i}",
nn.Conv2d(
in_channels=output_filters[-1],
out_channels=filters,
kernel_size=kernel_size,
stride=int(module_def["stride"]),
padding=pad,
bias=not bn,
),
)
if bn:
modules.add_module(f"batch_norm_{module_i}", nn.BatchNorm2d(filters, momentum=0.9, eps=1e-5))
if module_def["activation"] == "leaky":
modules.add_module(f"leaky_{module_i}", nn.LeakyReLU(0.1))
elif module_def["type"] == "maxpool":
kernel_size = int(module_def["size"])
stride = int(module_def["stride"])
if kernel_size == 2 and stride == 1:
modules.add_module(f"_debug_padding_{module_i}", nn.ZeroPad2d((0, 1, 0, 1)))
maxpool = nn.MaxPool2d(kernel_size=kernel_size, stride=stride, padding=int((kernel_size - 1) // 2))
modules.add_module(f"maxpool_{module_i}", maxpool)
elif module_def["type"] == "maxpool":
:这个条件语句检查当前模块是否为最大池化层kernel_size = int(module_def["size"])
:解析模块配置中的池化核大小,将其转换为整数stride = int(module_def["stride"])
:解析模块配置中的池化层步幅,将其转换为整数if kernel_size == 2 and stride == 1:
:这一行代码检查是否满足一个特定条件,即池化核大小为2且步幅为1。如果满足这个条件,就执行下面的操作
modules.add_module(f"_debug_padding_{module_i}", nn.ZeroPad2d((0, 1, 0, 1)))
:这里添加了一个用于调试的零填充层。具体来说,它使用 nn.ZeroPad2d
在输入图像的底部和右侧各添加一列零填充。这个填充的目的可能是为了处理特定情况下的池化层,以确保输出大小与输入大小一致。这个步骤通常是为了处理某些特殊情况而添加的modules
容器中:
maxpool = nn.MaxPool2d(kernel_size=kernel_size, stride=stride, padding=int((kernel_size - 1) // 2))
:创建最大池化层,其中包括池化核大小、步幅和填充大小的配置modules.add_module(f"maxpool_{module_i}", maxpool)
:将最大池化层添加到 modules
容器中,命名为 maxpool_{module_i}
,其中 module_i
是当前模块的索引通过上述操作,当前最大池化层的配置被解析,并相应地创建了相应的 PyTorch 模块,这些模块将在神经网络模型中用于后续的前向传播计算。最大池化层用于减小特征图的尺寸,以帮助网络提取更高级别的特征。
elif module_def["type"] == "upsample":
upsample = Upsample(scale_factor=int(module_def["stride"]), mode="nearest")
modules.add_module(f"upsample_{module_i}", upsample)
这段代码处理上采样层(upsample)的情况。让我逐步解释这段代码的主要步骤:
elif module_def["type"] == "upsample":
:这个条件语句检查当前模块是否为上采样层modules
容器中:
upsample = Upsample(scale_factor=int(module_def["stride"]), mode="nearest")
:创建一个上采样层,其中包括上采样的尺度因子(由模块配置中的 “stride” 参数指定)和上采样的模式。在这里,使用了 “nearest” 模式,表示最近邻插值。modules.add_module(f"upsample_{module_i}", upsample)
:将上采样层添加到 modules
容器中,命名为 upsample_{module_i}
,其中 module_i
是当前模块的索引上采样层:
class Upsample(nn.Module):
""" nn.Upsample is deprecated """
def __init__(self, scale_factor, mode="nearest"):
super(Upsample, self).__init__()
self.scale_factor = scale_factor
self.mode = mode
def forward(self, x):
x = F.interpolate(x, scale_factor=self.scale_factor, mode=self.mode)
return x
elif module_def["type"] == "route": # 输入1:26*26*256 输入2:26*26*128 输出:26*26*(256+128)
layers = [int(x) for x in module_def["layers"].split(",")]
filters = sum([output_filters[1:][i] for i in layers])
modules.add_module(f"route_{module_i}", EmptyLayer())
这段代码处理路由层(route)的情况。路由层通常用于连接前面的某些层的输出。让我逐步解释这段代码的主要步骤:
elif module_def["type"] == "route":
:这个条件语句检查当前模块是否为路由层layers
参数,该参数指定了要连接的前一层的索引列表:
layers = [int(x) for x in module_def["layers"].split(",")]
:将模块配置中的 layers
参数按逗号分隔并解析为整数列表。这些整数表示要连接的前一层的索引filters
):
filters = sum([output_filters[1:][i] for i in layers])
:根据前一层的输出通道数列表 output_filters
,通过索引列表 layers
计算路由层的输出通道数。通常,路由层的输出通道数等于要连接的前一层的输出通道数之和modules
容器中:
modules.add_module(f"route_{module_i}", EmptyLayer())
:添加路由层到 modules
容器中,命名为 route_{module_i}
,其中 module_i
是当前模块的索引。EmptyLayer()
可能是一个占位符层,用于表示路由层的存在,但不执行具体的计算。这种情况下,通常会在后续的代码中处理路由层的连接操作路由层通常用于将不同尺度或分辨率的特征图连接在一起,以提供网络更丰富的信息,用于目标检测等任务。在这里,路由层的具体操作可能在后续的代码中进行,以确保正确地连接前一层的输出。
在介绍yolov3的时候,有讲过为了适应不同需要检测的物体的尺寸,在网络的最后几层将不同尺寸的输出拼接在一起,shortcut就是完成这个过程。
elif module_def["type"] == "shortcut":
filters = output_filters[1:][int(module_def["from"])]
modules.add_module(f"shortcut_{module_i}", EmptyLayer())
这段代码处理快捷连接层(shortcut)的情况。快捷连接层通常用于跨层级连接输出。让我逐步解释这段代码的主要步骤:
elif module_def["type"] == "shortcut":
:这个条件语句检查当前模块是否为快捷连接层from
参数,该参数指定了要连接的前一层的索引:
filters = output_filters[1:][int(module_def["from"])]
:通过索引参数 from
,获取要连接的前一层的输出通道数。通常,快捷连接层的输出通道数等于要连接的前一层的输出通道数modules
容器中:
modules.add_module(f"shortcut_{module_i}", EmptyLayer())
:添加快捷连接层到 modules
容器中,命名为 shortcut_{module_i}
,其中 module_i
是当前模块的索引。EmptyLayer()
可能是一个占位符层,用于表示快捷连接层的存在,但不执行具体的计算。这种情况下,通常会在后续的代码中处理快捷连接层的连接操作快捷连接层通常用于实现跳跃连接,以便在网络的不同层之间传递信息。这有助于模型学习更丰富的特征表示,并有助于提高性能,尤其是在深层神经网络中。在这里,快捷连接层的具体操作可能在后续的代码中进行,以确保正确地连接前一层的输出。
空层:
class EmptyLayer(nn.Module):
"""Placeholder for 'route' and 'shortcut' layers"""
def __init__(self):
super(EmptyLayer, self).__init__()
elif module_def["type"] == "yolo":
anchor_idxs = [int(x) for x in module_def["mask"].split(",")]
# Extract anchors
anchors = [int(x) for x in module_def["anchors"].split(",")]
anchors = [(anchors[i], anchors[i + 1]) for i in range(0, len(anchors), 2)]
anchors = [anchors[i] for i in anchor_idxs]
num_classes = int(module_def["classes"])
img_size = int(hyperparams["height"])
# Define detection layer
yolo_layer = YOLOLayer(anchors, num_classes, img_size)
modules.add_module(f"yolo_{module_i}", yolo_layer)
这段代码处理YOLO层(yolo)的情况,YOLO层通常是目标检测模型中的输出层,用于生成检测框。让我逐步解释这段代码的主要步骤:
elif module_def["type"] == "yolo":
:这个条件语句检查当前模块是否为YOLO层anchor_idxs = [int(x) for x in module_def["mask"].split(",")]
:解析YOLO层的掩码(mask)参数,它指定了要使用哪些锚框来生成检测框。anchors = [int(x) for x in module_def["anchors"].split(",")]
:解析YOLO层的锚框(anchors)参数,它包含一组用于检测的锚框的尺寸。anchor_idxs
选择要使用的锚框num_classes = int(module_def["classes"])
:解析YOLO层的类别数。img_size = int(hyperparams["height"])
:解析图像的尺寸(高度),这通常用于检测框坐标的归一化modules
容器中:
yolo_layer = YOLOLayer(anchors, num_classes, img_size)
:创建YOLO层,该层接受锚框、类别数和图像尺寸作为参数。modules.add_module(f"yolo_{module_i}", yolo_layer)
:将YOLO层添加到 modules
容器中,命名为 yolo_{module_i}
,其中 module_i
是当前模块的索引YOLO层是目标检测模型的关键组成部分,它负责生成检测框并预测每个框的类别和置信度。具体的操作在 YOLOLayer
类中实现,通常包括锚框处理、边界框回归、类别预测等。这个层级的输出包含了目标检测任务的预测结果。