CSPNet
CSPNet出至论文:CSPNet: A New Backbone that can Enhance Learning Capability of CNN, 近来yolo-v4,yolo-v5都使用其作为主干网络的结构,其主要用于降低计算量的同时保持甚至提升网络的特征提取能力. 本文主要对其实现作小结,理论分析不深入。
优点
- 增强CNN的学习能力,能够在轻量化的同时保持准确性。
- 降低计算瓶颈
- 降低内存成本
实现
a 是原始的densenet 特征融合方式,b 是cspdensenet特征融合方式(trainsition->concatenation->transition),c d分别是作者尝试的另两种融合方式,b是c、d的结合. Fustion First的方式是对两个分支的feature map先进行concatenation操作,这样梯度信息可以被重用。 Fusion Last的方式是对Dense Block所在分支先进性transition操作,然后再进行concatenation, 梯度信息将被截断,因此不会重复使用梯度信息 。 transition为过渡层, 一般是1 * 1卷积.
对于作者提出的三种融合方式,实验结果表明:
- 使用Fusion First有助于降低计算代价,但是准确率有显著下降。
- 使用Fusion Last也是极大降低了计算代价,top-1 accuracy仅仅下降了0.1个百分点。
- 同时使用Fusion First和Fusion Last的CSP所采用的融合方式可以在降低计算代价的同时,提升准确率.
上图是DenseNet的示意图以及CSPDenseNet的改进,改进点在于CSPNet将浅层特征映射为两个部分,一部分经过Dense模块(图中的Partial Dense Block),另一部分直接与Partial Dense Block输出进行concate。
论文中的CSP是将特征图分为两部分,一部分进行进一步的操作,另一部分最后concat, 通道变少,计算量变少。 在实际实现中,如u版的yolo-v5中,没有对特征进行划分, 但shortcut以add方式非concat,也能降低通道数,降低计算量:
class Bottleneck(nn.Module):
# Standard bottleneck
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
super(Bottleneck, self).__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_, c2, 3, 1, g=g)
self.add = shortcut and c1 == c2
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
class BottleneckCSP(nn.Module):
# CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super(BottleneckCSP, self).__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
self.cv4 = Conv(2 * c_, c2, 1, 1)
self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3)
self.act = nn.LeakyReLU(0.1, inplace=True)
self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])
def forward(self, x):
y1 = self.cv3(self.m(self.cv1(x)))
y2 = self.cv2(x)
return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))
netron 可视化如下: