深入理解 CSP 瓶颈模块的变种:Bottleneck、C3、C3k、C2f 和 C3k2
从 YOLOv3 到 YOLOv11,Ultralytics 团队结合当时的主流结构提出了各种适用于 YOLO 的模块,涵盖了不同的创新和优化思路,从而应对不断变化的目标检测需求。这些模块在每一代 YOLO 发展中扮演了重要角色,从特征提取的方式到模型的速度优化,体现了目标检测网络的逐步演进。从这些模块的发展中,我们可以看出目标检测网络在精度、速度和特征提取能力方面的逐步改进。这篇博客我们来探讨这些模块的演变过程:Bottleneck、C3、C3k、C2f 和 C3k2,理解它们之间的联系和区别,以及它们在不同应用场景中的适用性。
标准瓶颈模块:Bottleneck
Bottleneck
是最基础的模块,用于构建更复杂的 CSP 结构。它包含两个卷积层,能够有效地减少计算量并提取特征。这个模块还可以选择是否使用 shortcut 连接,以增强梯度传播。
class Bottleneck(nn.Module):
"""Standard bottleneck."""
def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
"""Initializes a standard bottleneck module with optional shortcut connection and configurable parameters."""
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):
"""Applies the YOLO FPN to input data."""
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
Bottleneck
模块通过两个卷积层来提取特征,并可以选择是否加入shortcut,使其在某些情况下具有更好的梯度传播效果。
基础模块:C3
C3
是 CSP 瓶颈模块的一个基础版本,它的目的是通过增加特征的传递路径来提升网络的表现。C3
包含三个卷积层和一系列瓶颈层,能够高效提取不同层次的特征。
class C3(nn.Module):
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1) # 第一个卷积层
self.cv2 = Conv(c1, c_, 1, 1) # 第二个卷积层
self.cv3 = Conv(2 * c_, c2, 1) # 第三个卷积层,用于融合特征
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g) for _ in range(n))) # 多个瓶颈层的组合
def forward(self, x):
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1)) # 合并特征
在 C3
中,特征被分为两条路径,一条路径通过多层瓶颈层来提取复杂的特征,另一条路径直接传递输入特征。最后,通过拼接两条路径的输出,来增加模型的表达能力。
可定制卷积核的 C3k
C3k
是 C3
模块的一个变体,主要改进在于它允许自定义卷积核的大小(kernel size)。这使得 C3k
可以更好地适应不同尺寸的图像特征,尤其是当我们需要捕捉更大范围的上下文信息时。
class C3k(C3):
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, k=3):
super().__init__(c1, c2, n, shortcut, g, e)
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, k=(k, k)) for _ in range(n)))
与基础的 C3
不同,C3k
在初始化时允许设置不同的卷积核大小 k
,以便更灵活地应对各种不同的特征提取需求。比如,在需要捕捉更大区域信息时,我们可以设置更大的 k
值。当 k
设置为 3
时,C3k
在功能上与 C3
相等。
加速模块:C2f
C2f
是一个为了提升处理速度而设计的 CSP 瓶颈模块。它通过减少卷积层的数量和采用更高效的特征合并策略来提高速度。
class C2f(nn.Module):
def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):
super().__init__()
self.c = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, 2 * self.c, 1, 1) # 用于分割特征
self.cv2 = Conv((2 + n) * self.c, c2, 1) # 最终融合层
self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g) for _ in range(n)) # 瓶颈层列表
def forward(self, x):
y = list(self.cv1(x).chunk(2, 1)) # 将特征分成两部分
y.extend(m(y[-1]) for m in self.m) # 逐步增加特征
return self.cv2(torch.cat(y, 1)) # 最终特征融合
在 C2f
中,通过减少瓶颈层的数量以及对特征的快速分割和合并,达到了加速网络的目的,非常适合在对速度要求较高的应用场景中使用。
灵活组合模块:C3k2
C3k2
结合了 C2f
的速度优势和 C3k
的灵活性。它允许在运行时选择是否使用 C3k
层来处理特征,提供了很高的可配置性。
class C3k2(C2f):
def __init__(self, c1, c2, n=1, c3k=False, e=0.5, g=1, shortcut=True):
super().__init__(c1, c2, n, shortcut, g, e)
self.m = nn.ModuleList(
C3k(self.c, self.c, 2, shortcut, g) if c3k else Bottleneck(self.c, self.c, shortcut, g) for _ in range(n)
)
当 c3k
参数设置为 True
时,C3k2
将使用 C3k
层,能够利用不同卷积核大小的灵活性;否则,它将使用标准的瓶颈层,与 C2f
类似。
C3k2
与 C2f
的不同之处在于,C3k2
通过 c3k
参数可以选择是否使用 C3k
模块中的可变卷积核大小。这使得 C3k2
在需要更灵活的特征提取时具备优势,例如在处理需要不同感受野的场景时,可以通过调整 C3k
来适应特定的特征提取需求。而 C2f
则保持固定的结构和速度优势,更适合对计算资源有严格限制的应用场景。
C3k2
的灵活性与 C3k
和 C3
的关系
对于 C3k2
模块,当 c3k=True
和 c3k=False
时,其内部模块的选择会有所不同,这影响了其与 C3
和 C3k
的关系:
-
C3k2
中c3k=True
:- 在这种情况下,
C3k2
将使用C3k
模块。C3k
模块允许使用不同大小的卷积核,这给模型带来了灵活性,特别是在需要不同感受野来处理复杂特征的场景。 - 这样一来,
C3k2
在内部使用C3k
的特性,通过可配置的卷积核大小来适应不同的任务需求。
- 在这种情况下,
-
C3k2
中c3k=False
:- 在这种情况下,
C3k2
使用的模块与标准的C2f
类似,即使用了固定的Bottleneck
层。 - 在这个配置下,
C3k2
在特性上与C2f
没有区别,主要强调的是速度优化,而不是灵活性。
- 在这种情况下,
因此,C3k2
在 c3k=True
和 c3k=False
时的区别在于是否使用了 C3k
模块的灵活卷积核。当 c3k=True
时,C3k2
可以提供更灵活的卷积核大小,因此它与 C3
、C3k
有区别。而当 c3k=False
时,C3k2
与 C2f
一致,关注的是加速特性。
总结来说,C3k2
在 c3k=True
时与 C3k
类似,强调灵活性,而在 c3k=False
时与 C2f
一致,强调速度和效率。这样,C3k2
既可以作为灵活的特征提取模块,也可以作为快速的特征融合模块,具体使用哪个取决于您的任务需求。
C3k2
与 C2f
的不同之处在于,C3k2
通过 c3k
参数可以选择是否使用 C3k
模块中的可变卷积核大小。这使得 C3k2
在需要更灵活的特征提取时具备优势,例如在处理需要不同感受野的场景时,可以通过调整 C3k
来适应特定的特征提取需求。而 C2f
则保持固定的结构和速度优势,更适合对计算资源有严格限制的应用场景。
总结
Bottleneck
是最基础的模块,用于特征提取和减少计算量。C3
是基础模块,包含三层卷积和多层瓶颈层,用于增强特征传递。C3k
在C3
的基础上增加了卷积核大小的可配置性,使其更适合不同的特征提取需求。C2f
通过简化结构来加速处理,适合对计算速度有较高要求的场景。C3k2
结合了C3k
和C2f
的优势,提供了更高的灵活性和速度。
在某些特定情况下,这些模块是等价的:
- 当
C3k
的卷积核大小k
设置为3
时,它在功能上与C3
相等。此时,C3k
提供了与C3
相同的特征提取能力,但增加了卷积核大小的可配置性。 - 当
C3k2
的c3k
参数设置为False
时,C3k2
的功能与C2f
相同。它在这种情况下只是在结构上提供了额外的灵活性,但并没有使用C3k
层的优势。
这些模块的设计思路各有侧重,使用时可以根据具体任务的需求来选择合适的变种。例如,在需要快速处理和灵活特征提取的情况下,可以选择 C3k2
模块;而在需要保持基础结构的情况下,C3
则是不错的选择。没有绝对的好与不好,只有适合还是不适合。