YOLOv3的网络构建是一个从配置文件(cfg文件)到创建模型并定义其前向传播过程的复杂过程,以下是详细说明:
1. cfg文件
- 作用和支持的层类型
- 网络结构可视化
2. 网络模型构建
2.1 module_defs
- 解析cfg文件得到module_defs
- 通过
parse_model_cfg
函数解析cfg文件,得到module_defs
对象。这个对象是一个包含多个字典的列表,每个字典保存了一个模块的相关信息。 - 例如,对于卷积层模块,字典中可能包含
batch_normalize
(是否进行BN操作)、filters
(输出特征图数量)、size
(卷积核尺寸)、stride
(卷积步长)、pad
(填充大小)、activation
(激活函数类型)等键值对。
- 通过
- 解析过程中的参数处理
- 在解析过程中,函数会去除cfg文件中以
#
开头的注释行,然后逐行解析。当遇到[
开头的行时,表示一个新模块的开始,会创建一个新的字典并添加到module_defs
列表中。 - 对于不同类型的模块,会根据其特定的参数格式进行解析。例如,对于
convolutional
层,如果batch_normalize
为1,则在字典中设置相应的值;对于anchors
参数,会将其解析为numpy
数组形式。同时,函数还会检查所有解析出的参数是否符合支持的参数类型列表,如果存在不支持的字段,会抛出异常。
- 在解析过程中,函数会去除cfg文件中以
2.2 module_list&routs
- 构建ModuleList和routs
- 通过
create_modules
函数,根据module_defs
构建ModuleList
和routs
。ModuleList
类似于一个模块的列表,用于存储网络中的各个层;routs
则用于存储一些层的索引信息,在route
和shortcut
操作中会用到。
- 通过
- 不同层类型的构建方式
- 卷积层:对于
convolutional
类型的模块,根据解析出的参数创建nn.Conv2d
层,并根据是否进行BN操作添加nn.BatchNorm2d
层,以及设置相应的激活函数层(如nn.LeakyReLU
)。同时,根据不同的激活函数类型和模型配置(如arc
参数),可能会有不同的设置。 - 最大池化层:对于
maxpool
类型的模块,创建nn.MaxPool2d
层,并根据size
和stride
参数设置池化大小和步长。如果size == 2
且stride == 1
(如在yolov3 - tiny
中某些情况),还会添加nn.ZeroPad2d
层进行填充。 - 上采样层:对于
upsample
类型的模块,创建nn.Upsample
层,并根据stride
参数设置上采样因子。 - Route层:对于
route
类型的模块,根据layers
参数指定的层索引,计算输出特征图的通道数(通过对相应层的输出通道数求和),并将指定的层索引添加到routs
中。 - Shortcut层:对于
shortcut
类型的模块,根据from
参数指定的索引,获取相应层的输出特征图通道数,并将相关索引添加到routs
中。 - yolo层:对于
yolo
类型的模块,创建YOLOLayer
层,设置anchors
、nc
(类别数量)、img_size
等参数。同时,根据不同的arc
参数,会对卷积层的bias
进行特殊设置。例如,对于default
或Fdefaultpw
等不同的arc
值,会设置不同的obj
和cls
的bias
值,以解决样本不平衡问题,具体是在原有bias
基础上进行调整,使其在初始训练阶段避免过度激活,提高模型训练的稳定性和性能。
- 卷积层:对于
2.3 yolo_layers
- 获取yolo层的序号
- 通过
get_yolo_layers
函数获取yolo
层在整个模型中的序号。该函数通过解析module_defs
,查找类型为yolo
的模块,并返回其在列表中的索引。例如,对于yolov3.cfg
文件,可能返回[82, 94, 106]
等序号,表示第83、94、106个层是yolo
层。
- 通过
3. forward函数
- 前向传播过程描述
- 在
forward
函数中,实现了模型的前向传播过程。它通过遍历module_defs
和module_list
中的模块,根据模块类型进行相应的操作。
- 在
- 不同层类型在前向传播中的处理
- 卷积层、上采样层、池化层:对于
convolutional
、upsample
、maxpool
类型的模块,直接调用相应模块的forward
函数进行前向传播,将输入数据传递给下一层。 - Route层:对于
route
类型的模块,如果layers
参数只有一个值,则直接获取对应层的输出作为当前层的输出;如果有多个值,则将指定的多层输出在通道维度上进行拼接(使用torch.cat
函数)作为当前层的输出。如果在拼接过程中遇到darknet reorg
层相关的情况,可能需要对其中一层进行下采样操作(使用F.interpolate
函数)后再进行拼接。 - Shortcut层:对于
shortcut
类型的模块,将当前层的输入与指定层(根据from
参数)的输出相加作为当前层的输出。 - yolo层:对于
yolo
类型的模块,调用YOLOLayer
的forward
函数进行处理,并将输出添加到output
列表中。如果是训练模式,直接返回output
列表;如果是测试模式或ONNX_EXPORT
模式,则根据不同的模式要求对output
列表进行进一步处理,如在测试模式下,对输出进行一些后处理操作(如对坐标进行激活函数处理、根据arc
参数对类别进行处理等),并将处理后的结果进行适当的形状调整后返回。
YOLOv3的前向传播过程是数据在网络中依次经过各个层进行处理的过程,以下是详细解释:
- 卷积层、上采样层、池化层:对于
YOLOv3__43">4. YOLOv3 的前向传播过程
在forward
函数中实现了前向传播过程。它通过遍历module_defs
(存储cfg文件解析后的模块信息)和module_list
(构建的网络模块列表)中的模块,根据模块类型进行相应的操作,最终得到YOLO层的输出。
5. 不同层类型的处理
卷积层、上采样层、池化层
- 对于
convolutional
(卷积层)、upsample
(上采样层)、maxpool
(最大池化层)类型的模块,在前向传播过程中只需要经过相应模块的forward
函数即可。 - 即直接将输入数据
x
传递给模块的forward
函数进行处理,得到输出后继续传递给下一层。例如对于卷积层,会进行卷积运算、BN操作(如果有)以及激活函数操作(如果有)。
Route层
- Route层用于将几个层的内容进行拼接。
- 首先获取
layers
参数指定的层索引,如果layers
只有一个值,那么直接获取对应层的输出作为当前层的输出,即x = layer_outputs[layers[0]]
。 - 如果
layers
有多个值,尝试将指定的多层输出在通道维度上进行拼接(使用torch.cat
函数)。如果在拼接过程中遇到问题(例如可能涉及darknet reorg
层相关的情况),可能需要对其中一层进行下采样操作(使用F.interpolate
函数),然后再进行拼接,最终得到x = torch.cat([layer_outputs[i] for i in layers], 1)
。
Shortcut层
- Shortcut层类似于ResNet的跨层连接操作。
- 它将当前层的输入与指定层(根据
from
参数)的输出相加作为当前层的输出,即x = x + layer_outputs[int(mdef['from'])]
。
yolo层
- 对于
yolo
类型的模块,调用YOLOLayer
的forward
函数进行处理,并将输出添加到output
列表中。 - 在训练模式下,如果
self.training
为True
,则直接返回output
列表,这个列表包含了各个yolo
层的输出,符合YOLO要求的Tensor
格式。 - 在测试模式或
ONNX_EXPORT
模式下:- 如果是
ONNX_EXPORT
模式,需要对output
进行一些特殊处理,例如将output
中的数据进行拼接和调整形状,最终返回符合ONNX
格式要求的结果。 - 在测试模式下,首先获取
output
列表中的数据,对其进行一些后处理操作。例如对于坐标部分,会根据公式进行激活函数处理(如io[..., :2] = torch.sigmoid(io[..., :2]) + self.grid_xy
),根据arc
参数对类别部分进行处理(如使用sigmoid
或softmax
等激活函数),然后将处理后的结果进行适当的形状调整(如return io.view(bs, -1, self.no), p
)后返回。
- 如果是
6. 记录中间结果
在整个前向传播过程中,使用layer_outputs
列表记录每个层的输出(除了route
层中不需要记录的部分),以便后续层使用。例如,shortcut
层和route
层(部分情况)会用到前面层的输出。同时,根据不同的阶段(训练、测试、ONNX_EXPORT
),对最终的输出进行相应的处理和返回。