YOLOv3中设计了Darknet-53网络,对图像进行特征提取,是之前YOLOv2中使用的Darknet-19网络中添加了残差块。原文:
We use a new network for performing feature extraction. Our new network is a hybrid approach between the network used in YOLOv2, Darknet-19, and that newfangled residual network stuff. Our network uses successive 3×3 and 1×1 convolutional layers but now has some shortcut connections as well and is significantly larger. It has 53 convolutional layers so we call it… wait for it… Darknet-53!
最后一句真有意思,哈哈。
Darknet-53网络结构是这样的:
这个网络中一共添加了5组残差结构。粉色标注中的是×1,表示添加了一个残差块。×2,表示使用了两个残差块的累加。具体说下这么多的残差结构是怎么嵌入到网络中的。
Darknet-53构建
def darknet53(pretrained, **kwargs):
model = DarkNet([1, 2, 8, 8, 4])
在最开始的Darknet-53网络的实例化中,给了一组参数[1, 2, 8, 8, 4],这表示构建5组残差结构,其中每一个残差结构包含的残差块分别为1个,2个,8个,8个,4个。这正好对应网络的所有残差。
调用实例化的残差网络
输入x
的维度为(416,416,32)
,经过一次残差网络的结果为(208,208,64)
class DarkNet(nn.Module):
def __init__(self, layers): # layers = [1 2 8 8 4]
super(DarkNet, self).__init__()
self.inplanes = 32 # 卷积核个数
# (416,416,3) -> (416,416,32)
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(self.inplanes)
self.relu1 = nn.LeakyReLU(0.1)
# 416,416,32 -> 208,208,64
self.layer1 = self._make_layer([32, 64], layers[0])
def forward(self, x): # 网络的残差块结构
# (416,416,3) -> (416,416,32)
x = self.conv1(x)
x = self.bn1(x)
x = self.relu1(x)
# (416,416,32) -> (208,208,64)
x = self.layer1(x)
残差网络实例化
self.layer1 = self._make_layer([32, 64], layers[0])
定义一个残差块为_make_layer()
函数,其中[32, 64]
是输出的通道数,layers[0]
是残差的块数
残差网络函数定义
def _make_layer(self, planes, blocks):
layers = [] # 初始化一个列表,里面存放定义好的CBAPD模块
# 经历一个CBA,卷积核大小为3*3,stride=2,即进行了下采样,
layers.append(("ds_conv", nn.Conv2d(self.inplanes, planes[1], kernel_size=3,stride=2, padding=1, bias=False)))
layers.append(("ds_bn", nn.BatchNorm2d(planes[1])))
layers.append(("ds_relu", nn.LeakyReLU(0.1)))
# 利用for循环构建残差块
self.inplanes = planes[1]
for i in range(0, blocks):
layers.append(("residual_{}".format(i), BasicBlock(self.inplanes, planes)))
return nn.Sequential(OrderedDict(layers)) # layers是一个列表,包含卷积过程
- 利用
layers.append()
函数,不断在列表中添加卷积模块。 "residual_{}".format(i)
是一种输出方式,如果i = 1
,则输出residual_1
,对残差块进行了命名。BasicBlock()
是基础残差块,利用i的数量构建叠加在一起的残差块的数量。- 这里的
for
循环就可以根据之前的[1,2,8,8,4]
自动快速生成对应数量的残差块。
基础残差块——BasicBlock()
基础残差块的模型如下:
class BasicBlock(nn.Module):
def __init__(self, inplanes, planes):
super(BasicBlock, self).__init__()
# 首先进行一次卷积,通道数变成32
self.conv1 = nn.Conv2d(inplanes, planes[0], kernel_size=1, stride=1, padding=0, bias=False)
self.bn1 = nn.BatchNorm2d(planes[0])
self.relu1 = nn.LeakyReLU(0.1)
# 再进行一次卷积,通道数又变成64
self.conv2 = nn.Conv2d(planes[0], planes[1], kernel_size=3,
stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes[1])
self.relu2 = nn.LeakyReLU(0.1)
def forward(self, x): # 残差块
# 备份残差块的输入X
# (208,208,64) -> (208,208,32)
residual = x
# (208,208,32) -> (208,208,32)
out = self.conv1(x)
out = self.bn1(out)
out = self.relu1(out)
# (208,208,32) -> (208,208,64)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu2(out)
# 残差累加
# (208,208,64) + (208,208,64) -> (208,208,64)
out += residual
# 返回经历一个残差块的结果
return out
参考文章:【链接】