YOLOv3中的backbone,DarkNet53。不同于YOLOv2中的DarkNet19,它由大量的残差组成,DarkNet53在ImageNet上面的表现也证明了该网络的有效行。DarkNet53的网络结构如下。
YOLO版本的如下:
Darknet-53中总共有6个单独的卷积层和23个Residual,每个Residual包含2个卷积层(一个1×1,一个3×3),所以Darknet-53中共有52层卷积,可为什么叫做Darknet-53呢?因为Darknet-53在YOLO v3中,前52层只用作特征提取,最后一层是用于输出预测值的,故加上输出那一层称为Darknet-53。
如上图所示,DarkNet53主要由基本的卷积块(ConvBlock = Conv2d + BN +LeakyRelu)、残差块等组成。残差结构如下,它由1x1卷积、3x3卷积组成。
残差块的输入首先经过一个1×1的卷积层Conv(1×1,stride=1)将通道数降为原来的一半,接着通过3x3卷积将通道数恢复为In_channels。最后3×3卷积的输出与经过Shorcut传递过来的输入Input相加得到最终的Output(此时3×3卷积的输出与Input的形状(In_channels,h,w)相同,可以直接相加)。我们看到,经过Residual运算之后,输入的特征图形状保持不变。
#封装CBL
class Conv(nn.Module):
def __init__(self, c_in, c_out, k, s, p, bias=True):
'''
自定义卷积块,一次性完成卷积+归一化+池化
:param c_in: 输入通道数
:param c_in: 输出通道数
:param k: 卷积核大小
:param s: 步长
:param p: 填充
:param bias: 偏置
'''
super(Conv, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(c_in, c_out, k, s, p, bias=bias),
nn.BatchNorm2d(c_out),
nn.LeakyReLU(0.1),
)
def forward(self, x):
return self.conv(x)
class ConvResidual(nn.Module):
def __init__(self, c_in):
'''
自定义残差单元,只需给出通道数,该单元完成两次卷积,并进行加残差后返回相同维度的特征图
'''
super(ConvResidual, self).__init__()
c = c_in/2
# 采用1*1 + 3*3 的形式加深网络深度,加强特征抽取
self.conv = nn.Sequential(
Conv(c_in, c, 1, 1, 0), #1x1卷积降通道
Conv(c, c_in, 3, 1, 1), #3x3拉回通道
)
def forward(self, x):
return x + self.conv(x) #残差
Step3:根据DarkNet53网络图将卷积块与残差块进行组合,以搭建最终的网络。
class DarkNet53(nn.Module):
def __init__(self):
super(DarkNet53, self).__init__()
self.conv1 = Conv(3, 32, 3, 1, 1) # 一个卷积块 = 1层卷积
self.conv2 = Conv(32, 64, 3, 2, 1)
self.conv3_4 = ConvResidual(64) # 一个残差块 = 2层卷积 1
self.conv5 = Conv(64, 128, 3, 2, 1)
self.conv6_9 = nn.Sequential( #4层卷积 2
ConvResidual(128),
ConvResidual(128),
)
self.conv10 = Conv(128, 256, 3, 2, 1)
self.conv11_26 = nn.Sequential(*[ConvResidual(256) for i in range(8)]) # 8
self.conv27 = Conv(256, 512, 3, 2, 1)
self.conv28_43 = nn.Sequential(*[ConvResidual(512) for i in range(8)]) # 8
self.conv44 = Conv(512, 1024, 3, 2, 1)
self.conv45_52 = nn.Sequential(*[ConvResidual(1024) for i in range(4)]) # 4
def forward(self, x):
conv1 = self.conv1(x)
conv2 = self.conv2(conv1)
conv3_4 = self.conv3_4(conv2)
conv5 = self.conv5(conv3_4)
conv6_9 = self.conv6_9(conv5)
conv10 = self.conv10(conv6_9)
conv11_26 = self.conv11_26(conv10)
conv27 = self.conv27(conv11_26)
conv28_43 = self.conv28_43(conv27)
conv44 = self.conv44(conv28_43)
conv45_52 = self.conv45_52(conv44)
return conv45_52, conv28_43, conv11_26 # YOLOv3用,所以输出了3次特征