YOLOv3的源代码精度理解(五) Darknet

335 阅读5分钟

代码主要是参考bubbliiing的github YOLOv3的代码:github.com/bubbliiiing…

对于源代码的解读

网络部分

image.png

darknet.py文件

在这个地方我们可以我们完全可以学习他的关于网络结构的构建方式

最重点的部分是:残差网络

image.png

residual block 又名残差块

下面的BasicBlock_make_layer完成残差块的构建值得学习

def darknet53():
    model = DarkNet([1, 2, 8, 8, 4])
    return model

定义了darknet53的网络,实际是由 Darknet的骨干组成的我们就将我们的序列送入进去即可

  • DarkNet
class DarkNet(nn.Module):
    def __init__(self, layers):
        super(DarkNet, self).__init__()
        self.inplanes = 32
        # 416,416,3 -> 416,416,32
        
        # 和上面的图中我们见到的conv2d 32 * 3 * 3的部分一样,就是使用3 * 3 * 32(通道) 将输入图片的三通道转化成32通道
        self.conv1  = nn.Conv2d(3, self.inplanes, kernel_size=3, stride=1, padding=1, bias=False)
        # 一般我们的一个标准层就是 <conv2d - BN - relu>
        self.bn1    = nn.BatchNorm2d(self.inplanes)
        self.relu1  = nn.LeakyReLU(0.1)

        # 416,416,32 -> 208,208,64
        
        # 下面的部分我们实际上是构建残差块,我们以第一个残差块为例,进行详解
        # 我们向_make_layer中传递的参数是[32,64],1
        # 通过下面分析我们可以知道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]

        # 进行权值初始化
        # 权值初始化的部分分为两种:第一种我们对卷积进行初始化,使用微小参数进行构建
        # 第二部分是BN层,权重为1,偏置为0,简单粗暴
        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_()

    #---------------------------------------------------------------------#
    #   在每一个layer里面,首先利用一个步长为2的3x3卷积进行下采样
    #   然后进行残差结构的堆叠
    #---------------------------------------------------------------------#
    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", nn.LeakyReLU(0.1)))
        # 加入残差结构
        self.inplanes = planes[1]
        # 假设block
        for i in range(0, blocks):
            layers.append(("residual_{}".format(i), BasicBlock(self.inplanes, planes)))
        return nn.Sequential(OrderedDict(layers))

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu1(x)

        x = self.layer1(x)
        x = self.layer2(x)
        # 注意在这个地方,我们要获得三个层次的输出,我们需要将其保存成变量,在最终的部分返回即可,之前在unet中遇到过这种写法
        out3 = self.layer3(x)
        out4 = self.layer4(out3)
        out5 = self.layer5(out4)

        return out3, out4, out5
  • 调用

    • 详细分析:_make_layer
    # 可以复用
    def _make_layer(self, planes, blocks):
        layers = []
        # 下采样,步长为2,卷积核大小为3
    
        # 我们先用3 * 3 * 32的卷积 以步长为2进行操作,我们的分辨率 (416 - 3 + 2 * 1)// 2 + 1 = 208
        # 我们发现我们的分辨率进行了减半 416,416,32 -->208,208,64
        layers.append(("ds_conv", nn.Conv2d(self.inplanes, planes[1], kernel_size=3, stride=2, padding=1, bias=False)))
        # 进行BN层和relu层
        layers.append(("ds_bn", nn.BatchNorm2d(planes[1])))
        layers.append(("ds_relu", nn.LeakyReLU(0.1)))
    
        # 加入残差结构
        # 我们的self.inplanes更新到64
        self.inplanes = planes[1]
        # 对于我们的块结构,blocks决定了我们的残差模块堆叠几层
        for i in range(0, blocks):
            # 在列表中增加residual_{i}:BasicBlock的结构,这个地方传入参数是64,[32,64]
            layers.append(("residual_{}".format(i), BasicBlock(self.inplanes, planes)))
            # 在block = 2的中我们实际上是得到了两个相同的BasicBlock、我们进入第一个basicblock中的维度是
            # 例:208 * 208 * 64 输出的是208 * 208 *64 然后和我们输入第二个basicblock中的维度是一样的
            # 也还是输入208 * 208 * 64 输出的是208 * 208 *64
            # 其实这个地方我们是在上面进行了下采样,到这个地方我们就不用关注维度不统一的问题了,我们的输入和输入经过两次卷积
            # 得到的维度相同,所以多层BasicBlock的堆叠和一层BasicBlock的感觉是一样的,最终使用Sequential统一构建层
            # 这个方式比较简单,比之前的认知要更好理解一点
        # 最终将其转化成一个有序字典,放入到 Sequential中 
        return nn.Sequential(OrderedDict(layers))
    
    • 详细分析: BasicBlock
    # 可以复用
    class BasicBlock(nn.Module):
        def __init__(self, inplanes, planes):
            super(BasicBlock, self).__init__()
            # 首先是进行先进行通道数从64 到 32 的转化,卷积核是 1 * 1 步长是1,填充为0的卷积 
            # 卷积计算公式 (x - 1 + 2 * 0) / 1 + 1 = x  大小不变,只是改变通道数
            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)
    
            # 再次是进行通道数从32 到 64 的转化,卷积核是 3 * 3 步长是1,填充为1的卷积 
            # 卷积计算公式 (x - 3 + 2 * 1) / 1 + 1 = x  大小不变,只是改变通道数
            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):
            # 我们输入的是 《208 * 208 * 64》
            residual = x
    
            # 经过第一波卷积我们可以得到208 * 208 * 32
            out = self.conv1(x)
            out = self.bn1(out)
            out = self.relu1(out)
    
            # 经过第二波卷积我们可以得到<208 * 208 * 64>
            out = self.conv2(out)
            out = self.bn2(out)
            out = self.relu2(out)
    
            # 将最初的《208 * 208 * 64》 和经过两次的卷积变化后的<208 * 208 * 64>的结果进行加和计算
            out += residual
            # 最后将计算结果返回回去
            return out