Darknet_Yolov3模型搭建


Darknet_Yolov3模型搭建

YOLO(You only look once)是目前流行的目標檢測模型之一,目前最新已經發展到V3版本了,在業界的應用也很廣泛。YOLO的特點就是“快”,但由於YOLO對每個網格只預測一個物體,就容易造成漏檢,對物體的尺度相對比較敏感,對於尺度變化較大的物體泛化能力較差。YOLO的基本原理是:首先對輸入圖像划分成7x7的網格,對每個網格預測2個邊框,然后根據閾值去除可能性比較低的目標窗口,最后再使用邊框合並的方式去除冗余窗口,得出檢測結果,如下圖:

 

 

 Darknet卷積模塊

Yolo系列的作者把yolo網絡叫做Darknet,其實其他神經網絡庫都已經把卷積層寫好了,直接堆疊起來即可。

darknet卷積模塊是這個模型里最基本的網絡單元,包括卷積層、batch norm(BN)層、激活函數,因此類型命名為 DarknetConv2D_BN_Leaky。原keras實現是卷積層加了L2正則化預防過擬合,Pytorch是把這個操作放到了Optimizer中,所以將在第三部分講解。

用Pytorch需要注意, 如果訓練的時候GPU顯存不大,batch size設的很小,這時候就要考慮訓練數據集的分布情況。舉個例子,加入的batch size設成了1,但數據每張圖差別都很大,這會導致的網絡一直在震盪,即使網絡能夠訓練到很低的training loss,

在做預測的時候效果也不好,這主要是BN造成的。因為每批數據的統計量(均值和方差)都不同,而且差別大,這就導致網絡訓練學不到好的BN層的統計量。如果直接去掉BN層,會發現網絡訓練非常慢,所以BN層還是要加的,好在Pytorch里的BN有個接口來控制要不要記住每批訓練的統計量,即track_running_stats=True,如果訓練的batch size不能設特別大,就把它改成False。

卷積層、BN層說完了,激活函數Yolo里用的是0.1的LeakReLU,本實驗與ReLU沒什么明顯的區別。

結構很簡答,這部分直接上代碼,不畫圖了。

 

import torch.nn as nn

import torch

class DarknetConv2D_BN_Leaky(nn.Module):

    def __init__(self, numIn, numOut, ksize, stride = 1, padding = 1):

        super(DarknetConv2D_BN_Leaky, self).__init__()

        self.conv1 = nn.Conv2d(numIn, numOut, ksize, stride, padding)#regularizer': l2(5e-4)

        self.bn1 = nn.BatchNorm2d(numOut)

        self.leakyReLU = nn.LeakyReLU(0.1)

 

    def forward(self, x):

        x = self.conv1(x)

        x = self.bn1(x)

        x = self.leakyReLU(x)

        return x

 

殘差模塊

殘差模塊是借鑒了ResNet,殘差模塊是為了保證深的模型能夠得到很好的訓練。殘差模塊ResidualBlock,對外接口有numIn, numOut, numBlock,分別控制模塊的輸入通道數,輸出通道數(卷積核數)和殘差模塊的堆疊次數。下圖是一個numBlock = 2 的模型,注意這里CONV是指上一部分說的Darknet卷積模塊,第一個模塊(D2)表示是這個卷積模塊stride = 2,順便執行了2倍降采樣操作。也就是說特征每經過一個殘差模塊,分辨率降為原來的一半。

 

 

 class ResidualBlock(nn.Module):

    def __init__(self, numIn, numOut, numBlock):

        super(ResidualBlock, self).__init__()

        self.numBlock = numBlock

        self.dark_conv1 = DarknetConv2D_BN_Leaky(numIn, numOut, ksize = 3, stride = 2, padding = 1)

        self.dark_conv2 = []

        for i in range(self.numBlock):

            layers = []

            layers.append(DarknetConv2D_BN_Leaky(numOut, numOut//2, ksize = 1, stride = 1, padding = 0))

            layers.append(DarknetConv2D_BN_Leaky(numOut//2, numOut, ksize = 3, stride = 1, padding = 1))

            self.dark_conv2.append(nn.Sequential(*layers))

        self.dark_conv2 = nn.ModuleList(self.dark_conv2)

    def forward(self, x):

        x = self.dark_conv1(x)

        for convblock in self.dark_conv2:

            residual = x

            x = self.convblock(x)

            x = x + residual

        return x

  

后端輸出模塊

后端輸出模塊是一個三次降采樣(三次升采樣在下一部分介紹),這三次降采樣+三次升采樣,類似Encoder-Decoder的FCN模型。這是為了在三種不同尺度上預測。本系列將在voc2007上訓練,訓練前輸入圖片要resize到256x256,那么這三種尺度分別是32x32,16x16,8x8。這一部分是因為圖片中的目標有大有小,為了保證從不同尺度上找到最好尺度的特征圖來進行預測。當然准確提升的同時,由於分辨率有提升,計算量又有一定的增加,索性這里的分辨率不大。下圖所示為最后輸出模塊,這個模塊有兩個輸出,一個是用作下一個模塊的輸入,一個是用於輸出目標檢測結果,即坐標、類別和目標置信度,這一部分將在下一篇詳細介紹。注意紅色的Conv不是DarknetConv2D_BN_Leaky,而是指普通的卷積模塊。

 

 

 class LastLayer(nn.Module):

    def __init__(self, numIn, numOut, numOut2):

        super(LastLayer, self).__init__()

        self.dark_conv1 = DarknetConv2D_BN_Leaky(numIn, numOut, ksize = 1, stride = 1, padding = 0)

        self.dark_conv2 = DarknetConv2D_BN_Leaky(numOut, numOut*2, ksize = 3, stride = 1, padding = 1)

        self.dark_conv3 = DarknetConv2D_BN_Leaky(numOut*2, numOut, ksize = 1, stride = 1, padding = 0)

        self.dark_conv4 = DarknetConv2D_BN_Leaky(numOut, numOut*2, ksize = 3, stride = 1, padding = 1)

        self.dark_conv5 = DarknetConv2D_BN_Leaky(numOut*2, numOut, ksize = 1, stride = 1, padding = 0)

       

        self.dark_conv6 = DarknetConv2D_BN_Leaky(numOut, numOut*2, ksize = 3, stride = 1, padding = 1)

        self.conv7 = nn.Conv2d(numOut*2, numOut2, 1, stride = 1, padding = 0)

   

    def forward(self, x):

        x = self.dark_conv1(x)

        x = self.dark_conv2(x)

        x = self.dark_conv3(x)

        x = self.dark_conv4(x)

        x = self.dark_conv5(x)

        y = self.dark_conv6(x)

        y = self.conv7(y)

        return x,y

  

Yolov3模型

基本的模塊已經定義好,Yolov3的模型就是把這些模型疊加起來。注意下圖就是Yolov3的簡化模型,數字表示該上一個模塊的輸出特征尺寸(CxHxW),相應的顏色對應相應的模塊。

 

 

 class Yolov3(nn.Module):

    def __init__(self, numAnchor, numClass):

        super(Yolov3, self).__init__()

        self.dark_conv1 = DarknetConv2D_BN_Leaky(3, 32, ksize = 3, stride = 1, padding = 1)

        self.res1 = ResidualBlock(32, 64, 1)

        self.res2 = ResidualBlock(64, 128, 2)

        self.res3 = ResidualBlock(128, 256, 8)

        self.res4 = ResidualBlock(256, 512, 8)

        self.res5 = ResidualBlock(512, 1024, 4)

        self.last1 = LastLayer(1024, 512, numAnchor*(numClass+5))

        self.up1 = nn.Sequential(DarknetConv2D_BN_Leaky(512, 256, ksize = 1, stride = 1, padding = 0),

                                 nn.Upsample(scale_factor=2))

        self.last2 = LastLayer(768, 256, numAnchor*(numClass+5))

        self.up2 = nn.Sequential(DarknetConv2D_BN_Leaky(256, 128, ksize = 1, stride = 1, padding = 0),

                                 nn.Upsample(scale_factor=2))

        self.last3 = LastLayer(384, 128, numAnchor*(numClass+5))

       def forward(self, x):

        x = self.dark_conv1(x)#32x256x256

        x = self.res1(x)#64x128x128

        x = self.res2(x)#128x64x64

        x3 = self.res3(x)#256x32x32

        x4 = self.res4(x3)#512x16x16

        x5 = self.res5(x4)#1024x8x8

       

        x,y1 = self.last1(x5)#512x8x8,

        x = self.up1(x)#256x16x16

        x = torch.cat((x, x4), 1)#768x16x16

        x,y2 = self.last2(x)#256x16x16

        x = self.up2(x)#128x32x32

        x = torch.cat((x, x3), 1)#384x32x32

        x,y3 = self.last3(x)#128x32x32

       return y1,y2,y3

 

 到這里模型已經完成,模型代碼結構非常清晰。有人可能會問,為什么要這種堆疊方式,其實自己根據新的需求定義網絡結構完全可以,但是要注意模型深度增加時如何保證收斂,如何加速模型訓練,同時輸出特征的分辨率要計算好。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM