YOLOv5引入密集连接卷积网络DenseNet思想
CVPR 2017最佳论文 D e n s e N e t DenseNet DenseNet 论文地址:https://arxiv.org/pdf/1608.06993.pdf
最近的研究表明,如果卷积网络在接近输入和接近输出的层之间包含更短的连接,那么卷积网络可以更深入、更准确、更有效地训练。在本文中,我们采用了这种观察结果,并引入了密集卷积网络(DenseNet),它以前馈的方式将每一层与每一层连接起来。传统的L层卷积网络有lconnections(每层和随后的层之间有一个连接),而我们的网络有L(L+1) 2个直接连接。对于每一层,前面所有层的特征映射用作输入,它自己的特征映射用作所有后续层的输入。DenseNets有几个令人信服的优点:它们缓解消失梯度问题,加强特征传播,鼓励特征重用,并大大减少参数的数量。我们在四个竞争激烈的目标识别基准任务(CIFAR-10、CIFAR-100、SVHN和ImageNet)上评估我们提出的架构。DenseNets在大多数技术上获得了显著的改进,同时需要更少的计算来实现高性能。代码和预先训练的模型可以通过https://github.com/liuzhuang13/DenseNet获得。
论文思想
DenseNet主要有以下三个优点:
- 缓解了梯度消失问题;
- 加强了特征传递;
- 减少参数数量。
Dense Block:像GoogLeNet网络由Inception
模块组成、ResNet网络由残差块Residual Building Block
组成一样,DenseNet网络由Dense Block
组成,论文截图如下所示:每个层从前面的所有层获得额外的输入,并将自己的特征映射传递到后续的所有层,使用级联(Concatenation
)方式,每一层都在接受来自前几层的”集体知识(collective knowledge
)”
设计理念
既然 DenseNet DenseNet的效果这么好,那么为什么不能在YOLOv5
中引入这个思想呢?
YOLOv5
的网络结构非常简洁,能动的地方也就只有C3
模块了,所以我就在C3
模块中尝试了这个思想,搭建了一个Dense_C3
模块。
- 参数量方面:模块里面使用了大量的残差分支,引入到YOLOv5中参数量有略微的上涨,但是有很多的改进空间,因为我在原本
C3
的Bottleneck
里面加了两个3×3卷积,这几个卷积完全可以换成其它的变种卷积;也可以通过改变Bottleneck
数量降低参数量。 - 计算量方面:上涨了很多,目前没想出有什么好的解决方案,这种结构带来计算量的上涨是在所难免的。
参数量计算量对比
模型 | 参数量parameters | 计算量GFLOPs |
---|---|---|
yolov5s | 7235389 | 16.5 |
yolov5s-Dense_C3 | 9766333 | 35.5 |
源码
yolov5s-Dense_C3
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# Parameters
nc: 80 # number of classes
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
# YOLOv5 v6.0 backbone
backbone:
# [from, number, module, args]
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
[-1, 3, Dense_C3, [128]],
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 6, Dense_C3, [256]],
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, Dense_C3, [512]],
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 3, Dense_C3, [1024]],
[-1, 1, SPPF, [1024]]
]
# YOLOv5 v6.0 head
head:
[[-1, 1, Conv, [512, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 6], 1, Concat, [1]], # cat backbone P4
[-1, 3, C3, [512, False]], # 13
[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 4], 1, Concat, [1]], # cat backbone P3
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
[-1, 1, Conv, [256, 3, 2]],
[[-1, 14], 1, Concat, [1]], # cat head P4
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
[-1, 1, Conv, [512, 3, 2]],
[[-1, 10], 1, Concat, [1]], # cat head P5
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
common.py
class Dense_Bottleneck(nn.Module):
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5):
super().__init__()
c_ = int(c2 * e) # 计算隐藏层通道数
self.cv1 = Conv(c1, c_, 1, 1) # 第一个卷积层,1x1卷积
self.cv2 = Conv(c_, c_, 3, 1, g=g) # 第二个卷积层,3x3卷积
self.cv3 = Conv(c_, c_, 3, 1, g=g) # 第三个卷积层,3x3卷积
self.cv4 = Conv(c_, c2, 3, 1, g=g) # 第四个卷积层,3x3卷积
self.add = shortcut and c1 == c2 # 是否使用快捷连接(shortcut connection)
def forward(self, x):
x1 = self.cv1(x) + x
x2 = self.cv2(x1) + x + self.cv1(x)
x3 = self.cv3(x2) + x + self.cv1(x) + self.cv2(x1)
x4 = self.cv4(x3) + x + self.cv1(x) + self.cv2(x1) + self.cv3(x2)
return x4 if self.add else self.cv4(self.cv3(self.cv2(self.cv1(x))))
class Dense_C3(nn.Module):
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
super().__init__()
c_ = int(c2 * e) # 计算隐藏层通道数
self.cv1 = Conv(c1, c_, 1, 1) # 第一个卷积层,1x1卷积
self.cv2 = Conv(c1, c_, 1, 1) # 第二个卷积层,1x1卷积
self.cv3 = Conv(2 * c_, c2, 1) # 第三个卷积层,1x1卷积
self.m = nn.Sequential(*(Dense_Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n))) # 创建包含多个Dense_Bottleneck的序列
def forward(self, x):
# 将输入分成两个分支,一个通过cv1和多个Dense_Bottleneck,另一个通过cv2
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))
yolo.py