最近在做课题用到了YOLOv3,之前在B站听bulingbuling大神讲的课收益颇丰,所以用的代码依然选择了bulingbuling的,先说说总体的感受吧,代码结构清晰,阅读起来非常方便,而且代码中已经有了很多注释,整体的逻辑就更加明朗,本人为了更加深刻理解代码,同时也为了备份,在大神注释的基础上进行添砖加瓦,解释错误的地方请各路大神多多指正。
本篇文章主要讲解yolov3中的darknet.py文件!
import math
from collections import OrderedDict
import torch.nn as nn
#---------------------------------------------------------------------#
# 残差结构
# 利用一个1x1卷积下降通道数,然后利用一个3x3卷积提取特征并且上升通道数
# 最后接上一个残差边
首先对残差边进行了类定义,残差边中包含包含了两个卷积层,其中一个大小1*1,另外一个大小为3*3,
两个卷积层步长均为1,采用的是LeakyRelu函数
#---------------------------------------------------------------------#
class BasicBlock(nn.Module):
def __init__(self, inplanes, planes):
super(BasicBlock, self).__init__()
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)
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)
#---------------------------------------------------------------------#
# 这里本人简单的认为是feature map在上述残差边的传递顺序
#---------------------------------------------------------------------#
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu1(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu2(out)
#---------------------------------------------------------------------#
# 将上述经过残差边的feature map(out)与输入进来的x进行加和,输出为out
#---------------------------------------------------------------------#
out += residual
return out
class DarkNet(nn.Module):
def __init__(self, layers):
super(DarkNet, self).__init__()
self.inplanes = 32
#---------------------------------------------------------------------#
# 输出进来的RGB图片首先进行第一次卷积,大小不变,通道数由3变为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)
#---------------------------------------------------------------------#
# 如果查看过YOLOv3的网络结构图可以知道在主干网络(不包括残差边)上,一共包含6次卷积计算(上面的
# 通道3变32算一次)下面就是剩余下的五次(在接下来的卷积层中均说的是这五个)
#---------------------------------------------------------------------#
# 416,416,32 -> 208,208,64
self.layer1 = self._make_layer([32, 64], layers[0])
# 208,208,64 -> 104,104,128
self.layer2 = self._make_layer([64, 128], layers[1])
# 104,104,128 -> 52,52,256
self.layer3 = self._make_layer([128, 256], layers[2])
# 52,52,256 -> 26,26,512
self.layer4 = self._make_layer([256, 512], layers[3])
# 26,26,512 -> 13,13,1024
self.layer5 = self._make_layer([512, 1024], layers[4])
self.layers_out_filters = [64, 128, 256, 512, 1024]
# 进行权值初始化
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
#---------------------------------------------------------------------#
# 在每一个卷积层中(即上述的五个卷积层)里面,首先利用一个步长为2的3x3卷积进行下采样(ds)
# 然后进行残差结构的堆叠
# 第一眼看到下面的代码时候我有点晕,首先是在self.inplanes处,通过上边的定义知道是32,那么
# 在ds_conv里只有一次输入是32,有时候也是64、128、256、512啊,原来在下面的代码中将输出的
# 通道数重新赋值给了self.inplanes实现了输入通道数64、128、256、512
#---------------------------------------------------------------------#
def _make_layer(self, planes, blocks):
layers = []
# 下采样,步长为2,卷积核大小为3
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",ds_conv nn.LeakyReLU(0.1)))
# 加入残差结构
self.inplanes = planes[1]
for i in range(0, blocks):
layers.append(("residual_{}".format(i), BasicBlock(self.inplanes, planes)))
return nn.Sequential(OrderedDict(layers))
#---------------------------------------------------------------------#
# 仍然采用forward方法进行feature map的传递顺序,或与有人想要采用增加第四尺度104*104的预测,仅
需将x = self.layer2(x)变为out2 = self.layer2(x), x = self.layer2(out2)即可,return增加
一个out2,这种方法慎用,可能反而起到反作用,
#---------------------------------------------------------------------#
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu1(x)
x = self.layer1(x)
x = self.layer2(x)
out3 = self.layer3(x)
out4 = self.layer4(out3)
out5 = self.layer5(out4)
return out3, out4, out5
#---------------------------------------------------------------------#
# 定义残差结构每阶段的个数
#---------------------------------------------------------------------#
def darknet53():
model = DarkNet([1, 2, 8, 8, 4])
return model
上述就是我对darknet.py的理解,有错误的地方请大佬多多指正,谢谢大家
更多细节请阅读大佬的博客