【源碼解讀】YOLO v3 訓練 - 02 網絡結構


  首先,看一下YOLO v3 中的網絡結構。

YOLO v3 的整體流程

  番外步驟: 對訓練集圖片標記后產生的數據進行K-Means處理,篩選9個anchor-box

  詳見:https://www.cnblogs.com/monologuesmw/p/12761653.html

  進入YOLO v3的結構中:

  1. 圖像縮放。將訓練集的圖片縮放至416*416中,包括兩種縮放的方式:等比縮放和非等比縮放(非等比縮放一般只在訓練中)圖像縮放至416*416以后,便可以作為YOLO v3 結構的輸入。

  2. Darknet-53下采樣過程,其內部的結構可以看成一個3*3,步長(1*1)(fitters:32)的same卷積 和 若干個(一個3*3,步長(2*2),fitters總是比前面的fitters多一倍的窄卷積 和 fitters減半的1*1卷積、fitters恢復的3*3同卷積 )組成的殘差組合塊(組合的意思是,一個3*3步長(2*2)其實不是卷積塊的內容,但二者有依存關系)。 這樣就會將416*416*3 的圖像轉換為13*13*1024的feature_map。過程中會包含26*26*512的feature_map 和 52*52*256 的feature_map。其內部是這樣定義的,步長(1,1)的為同卷積,步長為(2*2)的為窄卷積(使用步長為2的卷積代替pool操作)。

   3. 多尺度預測與特征融合上采樣,YOLO v3與v2結構上的不同點就在於v3增添了多尺度的預測特征融合的上采樣(不同大小的特征適應於不同大小物體的檢測)。並且卷積的結構沒有池化層,在卷積層后都會搭載一個Leaky-ReLU的激活函數,並且在激活函數的輸入中不使用偏執bias

  在生成13*13*1024后,會再經歷兩對1*1和3*3卷積,使得三種尺度的feature_map變為13*13*512, 26*26*256。(因為最大尺度的是52*52*256),然后通過上采樣的方式,13*13*512變為26*26*512 ,26*26*256變為52*52*256, 這樣便可以與高尺度的拼接融合。生成最終的13*13*255、26*26*255 、52*52*255的feature_map輸出。

  P.S. 上采樣的內部機制實際上是最近鄰域插值的方式使維度翻倍。

  先一睹YOLOv3具體的網絡結構:

代碼解析

1. Darknet53部分

1 darknet = Model(inputs, darknet_body(inputs)) # 此處返回了第一個下采樣13*13 (?,416*416*32)->(?,13*13*1024)

  darknet_body(x)內部

 1 def darknet_body(x):
 2     '''Darknent body having 52 Convolution2D layers
 3     有52個卷積層(2D)
 4     '''
 5     x = DarknetConv2D_BN_Leaky(32, (3, 3))(x)      # (內置same卷積)輸出的x-》416*416*32
 6     x = resblock_body(x, 64, 1)   # num_filters = 64 , num_blocks = 1(重復次數)  返回結果208*208*64
 7     x = resblock_body(x, 128, 2)    # 返回結果 104*104*128
 8     x = resblock_body(x, 256, 8)    # 返回結果 52*52*256
 9     x = resblock_body(x, 512, 8)    # 返回結果 26*26*512
10     x = resblock_body(x, 1024, 4)    # 返回結果 13*13*1024     5組重復的resblock_body()單元
11     return x         # 第一個下采樣特征生成

  上述代碼是Darknet53的主體部分。

 a. 其中,第5行是 一個3*3,步長(1*1)(fitters:32)的same卷積。 其中包含:

  •  1個Darknet的2維卷積Conv2D層,即DarknetConv2D();
  •  1個批歸一化(BN)層,即BatchNormalization();
  •  1個LeakyReLU層,斜率是0.1,LeakyReLU是ReLU的變換;
1 def DarknetConv2D_BN_Leaky(*args, **kwargs):
2     """Darknet Convolution2D followed by BatchNormalization and LeakyReLU."""
3     no_bias_kwargs = {'use_bias': False}
4     no_bias_kwargs.update(kwargs)
5     return compose(
6         DarknetConv2D(*args, **no_bias_kwargs),
7         BatchNormalization(),
8         LeakyReLU(alpha=0.1))  

 b. 第6-9行,為若干個殘差塊組。(1+2+8+8=19個重復次數)fitters的通道數會逐漸倍增(深度逐漸加深)。最終返回的x為13*13*1024

  • ZeroPadding2D():填充x的邊界為0,由(?, 416, 416, 32)轉換為(?, 417, 417, 32)。

  因為下一步卷積操作的步長為2,所以圖的邊長需要是奇數;--- 需要padding

  • DarknetConv2D_BN_Leaky()是DarkNet的2維卷積操作,核是(3,3),步長是(2,2),

  注意,這會導致特征尺寸變小,由(?, 417, 417, 32)轉換為(?, 208, 208, 64)。由於num_filters是64,所以產生64個通道。

  • compose():輸出預測圖y,功能是組合函數,先執行1x1的卷積操作,再執行3x3的卷積操作,filter先降低2倍后恢復,最后與輸入相同,都是64; --- 殘差塊的另一分支
  • x = Add()([x, y])是殘差(Residual)操作,將x的值與y的值相加。殘差操作可以避免,在網絡較深時所產生的梯度彌散問題(Vanishing Gradient Problem)。
 1 def resblock_body(x, num_filters, num_blocks):
 2     '''A series of resblocks starting with a downsampling Convolution2D
 3     '''
 4     # Darknet uses left and top padding instead of 'same' mode
 5     x = ZeroPadding2D(((1, 0), (1, 0)))(x)   # 增加一圈0 x有(?,416,416,32)-> (?,417,417,32)  Keras中的方法
 6     x = DarknetConv2D_BN_Leaky(num_filters, (3, 3), strides=(2, 2))(x)   # 步長上下2,則是窄卷積, 因此上述需padding(才會沒有邊緣遺失)
 7     for i in range(num_blocks):               # 此時,x的輸出為208*208*64   (重復次數的循環)
 8         y = compose(
 9                 DarknetConv2D_BN_Leaky(num_filters//2, (1, 1)),   # 此時的輸出為208*208*32  降一半
10                 DarknetConv2D_BN_Leaky(num_filters, (3, 3)))(x)      # 此時的輸出為208 * 208 *64  恢復
11         x = Add()([x, y])   # 將殘差塊的結果加到源網絡的結構上  (不影響運算) 208*208*64
12     return x

2. Darknet輸出至y1,y2,y3部分(即13*13*1024至13*13*255. etc)

  A. 生成y1 ----> 13*13*255

1 x, y1 = make_last_layers(darknet.output, 512, num_anchors*(num_classes+5))

  make_last_layers()的內部

  該函數已經是 特征圖輸出的計算部分,結合結構圖來觀察,在13*13*1024的地方至13*13*255的y1處的運算:

  • 第1步,x執行多組1x1的卷積操作和3x3的卷積操作,filter先縮小再恢復,最后與輸入的filter保持不變,仍為512(num_filters),則x由(?, 13, 13, 1024)轉變為(?, 13, 13, 512);
  • 第2步,x先執行3x3的卷積操作,再執行不含BN和Leaky的1x1的卷積操作,作用類似於全連接操作,生成預測矩陣y;
  •  從結構上可以看出,x此時有兩個任務,一個是繼續y的運算,作為y1特征的輸出;另一個是需要上采樣到26*26的y2中。,所以分為了x和y兩個部分,同時也會返回這兩個部分。
 1 def make_last_layers(x, num_filters, out_filters):
 2     '''6 Conv2D_BN_Leaky layers followed by a Conv2D_linear layer
 3     '''
 4     x = compose(
 5             DarknetConv2D_BN_Leaky(num_filters, (1, 1)),
 6             DarknetConv2D_BN_Leaky(num_filters*2, (3, 3)),
 7             DarknetConv2D_BN_Leaky(num_filters, (1, 1)),
 8             DarknetConv2D_BN_Leaky(num_filters*2, (3, 3)),
 9             DarknetConv2D_BN_Leaky(num_filters, (1, 1)))(x)   # 此時13*13*512
10     y = compose(
11             DarknetConv2D_BN_Leaky(num_filters*2, (3, 3)),
12             DarknetConv2D(out_filters, (1,1)))(x)
13     return x, y  

  B. 生成y2 ---》26*26*255 此部分會涉及到尺度特征融合的部分,即13*13*512的上采樣過程。

  • 13*13*512--》13*13*256
  • 13*13*256 上采樣 26*26*256
  • 融合后的結果 26*26*768 (原來的是26*26*512,融合后即512+256 = 768)
1 # 第二部分 26*26*256 的y2
2 x = compose(
3         DarknetConv2D_BN_Leaky(256, (1,1)),          # 此時x的為13*13*256
4         UpSampling2D(2))(x)   # x需要參加的上采樣   此時的x為26*26*256
5 x = Concatenate()([x, darknet.layers[152].output])  # 把之前結果的第152層的輸出提取,即26*26*512的輸出 即13*13*1024的前一層輸出
6 x, y2 = make_last_layers(x, 256, num_anchors*(num_classes+5))    

  C. 生成y3 ----》52*52*255 此部分包含26*26*256的上采樣過程

  • 26*26*256 --》 26*26*128
  • 26*26*128上采樣到52*52*128
  • 融合后的結果52*52*384 (128+256=384)
1 # 第三部分 52*52*128*18(18=3*(1+5)1個類別,3個錨框) 的y3
2 x = compose(
3         DarknetConv2D_BN_Leaky(128, (1,1)),
4         UpSampling2D(2))(x)      # 同樣需要26的x 的上采樣
5 x = Concatenate()([x,darknet.layers[92].output])
6 x, y3 = make_last_layers(x, 128, num_anchors*(num_classes+5))

  當然,此時返回的這個x就沒有什么任務了。

  模型結構創建完畢。

  可以看出,Darknet的框架是由多組Conv+Conv+Residual的結構疊加而成的。

  即1+1+1*2+1+2*2+1+8*2+1+8*2+1+4*2 = 52

  配置文件中 ############# 的分割 就是結構中darknet的結束。

  

  

  

  


免責聲明!

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



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