【深度学习】【目标检测】【Ultralytics-YOLO系列】YOLOV3核心文件common.py解读
文章目录
- 【深度学习】【目标检测】【Ultralytics-YOLO系列】YOLOV3核心文件common.py解读
- 前言
- autopad函数
- Conv类
- __init__成员函数
- forward成员函数
- forward_fuse成员函数
- Bottleneck类
- __init__成员函数
- forward成员函数
- Concat类
- __init__成员函数
- forward成员函数
- 总结
前言
在详细解析YOLOV3网络之前,首要任务是搭建Ultralytics–YOLOV3【Windows11下YOLOV3人脸检测】所需的运行环境,并完成模型的训练和测试,展开后续工作才有意义。
本博文对models/common.py代码进行解析,common.py文件存放着YOLOV3网络搭建常见的通用模块。其他代码后续的博文将会陆续讲解。这里只做YOLOV3相关模块的代码解析,其他的通用模块是YOLO后续系列中创新和提出的。
autopad函数
用于自动计算卷积层的填充值(padding),以确保当步幅为1时卷积操作后的输出特征图尺寸保持不变。
def autopad(k, p=None): # kernel, padding
"""
用于自动计算卷积层的填充值
:param k:卷积核大小
:param p:填充值
:return:计算得到的填充值
"""
if p is None: # 检查是否需要自动计算填充值,否则用户指定填充值
# 正方形卷积核:isinstance(k, int)表示k是一个整数,卷积核是正方形,填充值p被为k//2,即卷积核大小的一半向下取整
# 非正方形卷积核: 是一个列表或元组,则对每个维度分别计算填充值,同理计算填充值p
p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
return p
理论基础: 在二维卷积中,输出特征图的尺寸可以通过以下公式计算:
O
u
t
p
u
t
S
i
z
e
=
I
n
p
u
t
S
i
z
e
+
2
p
−
k
s
+
1
OutputSize = \frac{{InputSize + 2p - k}}{{\rm{s}}} + 1
OutputSize=sInputSize+2p−k+1
其中:
I
n
p
u
t
S
i
z
e
InputSize
InputSize是输入特征图的尺寸;
p
p
p是填充大小;
k
k
k是卷积核大小;
s
s
s是步长。
根据上述方程,当
s
=
1
s=1
s=1且输出尺寸等于输入尺寸时,解出
p
p
p的值:
p
=
⌈
k
−
1
2
⌉
=
⌊
k
2
⌋
p = \left\lceil {\frac{{k - 1}}{2}} \right\rceil = \left\lfloor {\frac{k}{2}} \right\rfloor
p=⌈2k−1⌉=⌊2k⌋
由于填充大小
p
p
p必须是整数,因此使用向下取整除法(//),即
p
=
k
/
/
2
p=k//2
p=k//2 来自动计算填充值,确保了无论卷积核大小
k
k
k是奇数还是偶数,都可以正确计算出合适的填充大小,使得在步长为1的情况下,输出尺寸尽可能接近输入尺寸。对于非对称的卷积核,分别对每个维度应用此规则以保证各自的输出尺寸匹配相应的输入尺寸。
Conv类
__init__成员函数
构造函数,初始化自定义的卷积模块,通常包含卷积层、批量归一化层和激活函数。调用了【models/common.py】的autopad函数
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
"""
构造函数,初始化自定义的卷积模块
:param c1:输入通道数
:param c2:输出通道数
:param k:卷积核大小,默认为 1
:param s:步幅,默认为 1
:param p:填充,默认为None(不指定为None会自动计算填充值)
:param g:分组卷积的分组数,默认为 1(标准卷积)
:param act:是否使用激活函数,默认为True(使用nn.SiLU()),False则使用nn.Identity()(相当于没使用任何函数),提供了指定激活函数对象则使用指定的激活函数
"""
super().__init__()
# 创建一个二维卷积层
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
# 创建一个批量归一化层
self.bn = nn.BatchNorm2d(c2)
# 设置激活函数
self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
本博文讲解的代码原论文的源码,是YOLOV5团队复现改进后的YOLOV3代码,因此自定义的卷积模块的结构与原论文的结构在激活函数的选择上有所不同。
在YOLOV5中,SiLU替换了早期版本中的Leaky ReLU,显著提升了模型的性能。在EfficientNet系列中,SiLU成为了默认激活函数。
forward成员函数
前向传播,执行自定义的卷积模块。
def forward(self, x):
"""
前向传播,执行模块
:param x:输入数据张量
:return:卷积块的输出
"""
return self.act(self.bn(self.conv(x)))
forward_fuse成员函数
融合前向传播,跳过了批量归一化层,推理阶段使用可以提高计算效率。
def forward_fuse(self, x):
"""
融合前向传播,跳过了批量归一化层,推理阶段使用可以提高计算效率
:param x:输入数据张量
:return:卷积块的输出
"""
return self.act(self.conv(x))
Bottleneck类
__init__成员函数
自定义的瓶颈模块,通常用于构建类似残差网络的残差块。
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
"""
构造函数,构建类似残差网络的残差块
:param c1:输入通道数
:param c2:输出通道数
:param shortcut:是否使用残差连接,默认为 True
:param g: 分组卷积的分组数,默认为1(标准卷积)
:param e:扩展比例,用于计算中间隐藏层的通道数默认为 0.5
"""
super().__init__()
c_ = int(c2 * e) # 计算中间隐藏层的通道数
self.cv1 = Conv(c1, c_, 1, 1) # 第一层1×1卷积层
self.cv2 = Conv(c_, c2, 3, 1, g=g) # 第二层3×3卷积层
self.add = shortcut and c1 == c2 # 判断是否添加残差连接:使用残差连接且输入通道数等于输出通道数
原论文中YOLOV3的瓶颈模块都是带有残差连接的,同时YOLOV3的结构中有很多1×1卷积+3×3卷积这种顺次排列的情况,与残差连接的瓶颈模块相似(但不是瓶颈模块)。因此,Ultralytics-YOLOV3代码为了yaml配置文件的简洁明了,将二则结合起来成了新的瓶颈模块,即可以选择是否激活残差连接。
如下图标记的配置文件部分,假设不结合在一起,每个Bottleneck就要单独写俩个Conv,分别是1×1 Conv和3×3 Conv。
forward成员函数
前向传播,执行自定义的瓶颈模块。
def forward(self, x):
"""
前向传播,执行模块
:param x:输入数据张量
:return:瓶颈模块的输出
"""
# 如果进行残差连接,则将输入x和瓶颈模块的输出结果相加
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
Concat类
__init__成员函数
用于在深度学习模型中实现张量的拼接操作。
def __init__(self, dimension=1):
"""
构造函数,用于实现张量在指定维度上的拼接操作
:param dimension: 指定拼接的维度,默认为 1
"""
super().__init__()
self.d = dimension
forward成员函数
前向传播,执行拼接。
def forward(self, x):
"""
前向传播,执行拼接
:param x:输入数据张量列表
:return:拼接后的张量
"""
# 将输入张量列表沿着指定维度拼接成新的单一张量
return torch.cat(x, self.d)
总结
尽可能简单、详细的介绍了核心文件common.py文件的作用:存放着YOLOV3网络搭建常见的通用模块。