1.Bottleneck 模块介绍
Bottleneck 模块在YOLOv8中的作用是进行特征提取和增强,是网络中的核心构建模块之一。它的主要功能是通过卷积操作来处理输入特征图,并在适当情况下应用残差连接,使得信息能够有效地通过网络层进行传播。
2.Bottleneck 模块的位置和作用
在YOLOv8的网络结构中,Bottleneck 模块被多次使用,主要出现在以下几个部分:
- Backbone 部分:
在多个层次上,如P3、P4、P5等,都使用了Bottleneck模块来处理不同尺度的特征图。
这些特征图从输入图像中提取,逐层进行空间维度的缩减和通道数的增加,以便更好地捕捉图像中的高层次特征。 - Head 部分:
在YOLOv8的检测头中也使用了Bottleneck模块,尤其是在构建检测特征图时,用于进一步处理和融合来自不同层次的特征。
3.网络结构:
4.Bottleneck 模块的结构和功能
Bottleneck 模块主要包含以下几个部分:
-
两个卷积层(Conv):
第一个卷积层将输入通道数c1缩减到一个中间的通道数c_(通常是输出通道数c2的某个比例)。
第二个卷积层将中间通道数c_恢复到输出通道数c2,同时可以根据配置应用组卷积(group convolution)。 -
残差连接(Shortcut):
如果输入通道数c1与输出通道数c2相同,并且shortcut=True,则会启用残差连接,即将输入特征直接加到输出特征上,形成跳跃连接。
这种结构可以帮助网络更好地保留和利用输入特征,缓解梯度消失问题,并加速训练收敛。
5.代码详解
Bottleneck 代码实现如下:
class Bottleneck(nn.Module):
"""Standard bottleneck."""
def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
"""Initializes a bottleneck module with given input/output channels, shortcut option, group, kernels, and
expansion.
"""
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, k[0], 1)
self.cv2 = Conv(c_, c2, k[1], 1, g=g)
self.add = shortcut and c1 == c2
def forward(self, x):
"""'forward()' applies the YOLO FPN to input data."""
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
1. init 方法
def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, k[0], 1)
self.cv2 = Conv(c_, c2, k[1], 1, g=g)
self.add = shortcut and c1 == c2
- 参数解释:
c1 和 c2:c1 是输入通道数,c2 是输出通道数。
shortcut:这是一个布尔值,决定是否使用残差连接(shortcut connection)。默认值是 True,即使用残差连接。当 c1 == c2 时,残差连接才会被应用。
g:表示组卷积的组数,默认为 1,即普通卷积。当 g > 1 时,表示进行组卷积,这种卷积方式可以减少计算量并增加模型的表达能力。
k:表示卷积核的大小,k=(3, 3) 表示两个卷积层分别使用 3x3 的卷积核。
e:表示通道的扩展因子,默认值为 0.5。它用于计算中间层的通道数 c_,c_ = int(c2 * e)。通过将输出通道数缩小到 c_,然后再恢复到 c2,可以减少计算量和参数数量。
- self.cv1 和 self.cv2:
self.cv1 是第一个卷积层,将输入通道数从 c1 减少到 c_。
self.cv2 是第二个卷积层,将通道数从 c_ 恢复到 c2。
self.add:用于决定是否应用残差连接。如果 c1 == c2 且 shortcut=True,则 self.add 为 True,意味着会在前向传播时使用残差连接。
2. forward 方法
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
x:输入张量,形状为 [batch_size, c1, height, width]。
self.cv1(x):首先对输入 x 应用第一个卷积层 self.cv1,得到中间特征图,形状为 [batch_size, c_, height, width]。
self.cv2(self.cv1(x)):然后对中间特征图应用第二个卷积层 self.cv2,得到输出特征图,形状为 [batch_size, c2, height, width]。
残差连接:
如果 self.add 为 True,则返回 x + self.cv2(self.cv1(x)),即输入张量和卷积后的特征图相加。
如果 self.add 为 False,则只返回卷积后的特征图 self.cv2(self.cv1(x))。
3.输入输出示例
假设输入 x 的形状为 [batch_size, 64, height, width],且参数 c1=64, c2=128, e=0.5:
- 第一层卷积:
输入 x 经过 self.cv1 后,通道数从 64 减少到 64(c_ = int(128 * 0.5)),输出形状为 [batch_size, 64, height, width]。
- 第二层卷积:
输出经过 self.cv2,通道数从 64 增加到 128,输出形状为 [batch_size, 128, height, width]。
- 残差连接:
如果 self.add 为 True,且 c1 == c2,则输出为 x + self.cv2(self.cv1(x)),即 [batch_size, 128, height, width]。
如果 self.add 为 False,则直接输出 self.cv2(self.cv1(x)),也是 [batch_size, 128, height, width]。
总结
(shortcut=True)
输入:形状为 [4, 64, 128, 128]。
第一个卷积层:保持通道数不变 [4, 64, 128, 128]。
第二个卷积层:输入形状 [4, 64, 128, 128] 将通道数增加到 128,输出形状为 [4, 128, 128, 128]。
输出:最终输出的形状为 [4, 128, 128, 128]。
(shortcut=False)
输入:形状为 [4, 64, 128, 128]。
第一个卷积层:保持通道数不变 [4, 64, 128, 128]。
第二个卷积层:将通道数增加到 128,输出形状为 [4, 128, 128, 128]。
无残差连接:因为 shortcut = False,没有进行残差连接操作。
所以最终的输出就是第二个卷积层的结果,形状为 [4, 128, 128, 128]。
这种配置更适合用于需要显著改变通道数但不需要保持输入特征的情况下。