yolov3源碼分析keras(二)損失函數計算


一、前言

     損失函數計算主要分析兩部分一部分是yolo_head函數的分析另一部分為ignore_mask的生成的分析。

二、重要細節分析

      2.1損失函數計算具體代碼及部分分析

 1 def yolo_loss(args, anchors, num_classes, ignore_thresh=.5, print_loss=False):
 2     #args前三個元素為yolov3輸出的預測值,后三個維度為保存的label 值
 3     '''Return yolo_loss tensor
 4 
 5     Parameters
 6     ----------
 7     yolo_outputs: list of tensor, the output of yolo_body or tiny_yolo_body
 8     y_true: list of array, the output of preprocess_true_boxes
 9     anchors: array, shape=(N, 2), wh
10     num_classes: integer
11     ignore_thresh: float, the iou threshold whether to ignore object confidence loss
12 
13     Returns
14     -------
15     loss: tensor, shape=(1,)
16 
17     '''
18     num_layers = len(anchors)//3 # default setting
19     yolo_outputs = args[:num_layers]
20     y_true = args[num_layers:]
21     anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]]
22     input_shape = K.cast(K.shape(yolo_outputs[0])[1:3] * 32, K.dtype(y_true[0])) #13*32=416  input_shape--->[416,416]
23     grid_shapes = [K.cast(K.shape(yolo_outputs[l])[1:3], K.dtype(y_true[0])) for l in range(num_layers)]#(13,13),(26,26),(52,52)
24     loss = 0
25     m = K.shape(yolo_outputs[0])[0] # batch size, tensor
26     mf = K.cast(m, K.dtype(yolo_outputs[0])) #mf為batchsize大小
27    #逐層計算損失 
28     for l in range(num_layers):  
29         object_mask = y_true[l][..., 4:5] # 取出置信度
30         true_class_probs = y_true[l][..., 5:]#取出類別信息
31         #yolo_head講預測的偏移量轉化為真實值,這里的真實值是用來計算iou,並不是來計算loss的,loss使用偏差來計算的
32         grid, raw_pred, pred_xy, pred_wh = yolo_head(yolo_outputs[l],
33              anchors[anchor_mask[l]], num_classes, input_shape, calc_loss=True) #anchor_mask[0]=[6,7,8] 
34         pred_box = K.concatenate([pred_xy, pred_wh])                            #anchors[anchor_mask[l]]=array([[  116.,   90.],
35                                                                                                         # [  156., 198.],
36                                                                                                         # [  373., 326.]])
37         # Darknet raw box to calculate loss.
38         raw_true_xy = y_true[l][..., :2]*grid_shapes[l][::-1] - grid #根據公式將boxes中心點x,y的真實值轉換為偏移量
39         raw_true_wh = K.log(y_true[l][..., 2:4] / anchors[anchor_mask[l]] * input_shape[::-1])#計算寬高的偏移量
40         raw_true_wh = K.switch(object_mask, raw_true_wh, K.zeros_like(raw_true_wh)) # avoid log(0)=-inf(后邊有詳細解釋為什么這么操作)
41         box_loss_scale = 2 - y_true[l][...,2:3]*y_true[l][...,3:4]  #(2-box_ares)避免大框的誤差對loss 比小框誤差對loss影響大
42 
43         # Find ignore mask, iterate over each of batch.
44         ignore_mask = tf.TensorArray(K.dtype(y_true[0]), size=1, dynamic_size=True)#定義一個size可變的張量來存儲不含有目標的預測框的信息
45         object_mask_bool = K.cast(object_mask, 'bool')#映射成bool類型  1=true 0=false
46         def loop_body(b, ignore_mask):
47             true_box = tf.boolean_mask(y_true[l][b,...,0:4], object_mask_bool[b,...,0]) #剔除為0的行
48             iou = box_iou(pred_box[b], true_box) #一張圖片預測出的所有boxes與所有的ground truth boxes計算iou 計算過程與生成label類似利用了廣播特性這里不詳細描述
49             best_iou = K.max(iou, axis=-1)#找出最大iou
50             ignore_mask = ignore_mask.write(b, K.cast(best_iou<ignore_thresh, K.dtype(true_box)))#當iou小於閾值時記錄,即認為這個預測框不包含物體
51             return b+1, ignore_mask
52         _, ignore_mask = K.control_flow_ops.while_loop(lambda b,*args: b<m, loop_body, [0, ignore_mask])#傳入loop_body函數初值為b=0,ignore_mask
53         ignore_mask = ignore_mask.stack()
54         ignore_mask = K.expand_dims(ignore_mask, -1) #擴展維度用來后續計算loss
55 
56         # K.binary_crossentropy is helpful to avoid exp overflow.
57         #僅計算包含物體框的x,y,w,h的損失
58         xy_loss = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[...,0:2], from_logits=True)
59         wh_loss = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh-raw_pred[...,2:4]) 
60         #置信度損失既包含有物體的損失 也包含無物體的置信度損失
61         confidence_loss = object_mask * K.binary_crossentropy(object_mask, raw_pred[...,4:5], from_logits=True)+ \
62             (1-object_mask) * K.binary_crossentropy(object_mask, raw_pred[...,4:5], from_logits=True) * ignore_mask
63         #分類損失只計算包含物體的損失
64         class_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[...,5:], from_logits=True)
65         #取平均值
66         xy_loss = K.sum(xy_loss) / mf
67         wh_loss = K.sum(wh_loss) / mf
68         confidence_loss = K.sum(confidence_loss) / mf
69         class_loss = K.sum(class_loss) / mf
70         loss += xy_loss + wh_loss + confidence_loss + class_loss
71         if print_loss:
72             loss = tf.Print(loss, [loss, xy_loss, wh_loss, confidence_loss, class_loss, K.sum(ignore_mask)], message='loss: ')
73     return loss

 2.2 yolo_head代碼分析

         yolo_head主要作用是將預測出的數據轉換為真實值。代碼如下:

 1 def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):
 2     """Convert final layer features to bounding box parameters."""
 3     num_anchors = len(anchors) # num_anchors = 3
 4     # Reshape to batch, height, width, num_anchors, box_params.
 5     anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2])#  [[[[[30.,   61.]                                                                                       
 6     grid_shape = K.shape(feats)[1:3] # height, width                                 [62.,   45.]
 7     grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),  #    [59.,  119.]]]]]
 8         [1, grid_shape[1], 1, 1])
 9     grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),
10         [grid_shape[0], 1, 1, 1])
11     grid = K.concatenate([grid_x, grid_y])
12     grid = K.cast(grid, K.dtype(feats))
13 
14     feats = K.reshape(
15         feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])#featuremap [N,13,13,3(20+5)]-->[N,13,13,3,(20+5)]
16 
17     # Adjust preditions to each spatial grid point and anchor size.
18     box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))#grid 為偏移 ,將x,y相對於featuremap尺寸進行了歸一化 
19     box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))#real box_wh
20     box_confidence = K.sigmoid(feats[..., 4:5])
21     box_class_probs = K.sigmoid(feats[..., 5:])
22     if calc_loss == True:
23         return grid, feats, box_xy, box_wh
24     return box_xy, box_wh, box_confidence, box_class_probs

          box真實值與預測值轉換公式及示意圖:

                                

        轉換代碼如下:    

 box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))#grid 為偏移   
 box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))#real box_wh

                對於初學者這個圖也有一定的迷惑性質,可以把上圖的每個網格想象成feature map上的一個點,則第一個像素對應的偏移為(0,0),第一行第二個偏移為(1,0)以此類推。圖中標注的點偏移量為(1,1)。

 yolo_head中轉換為真實值時gride偏移相對於特征圖尺寸做了歸一化。

           代碼對於預測出的值進行了Sigmoid操作目的是為了讓坐標值在0-1之間。

           假設藍色點為13*13的feature map 中的cell預測的中心點坐標為x,y,取sigmoid后其坐標為 (0.3, 0.5),則真實框在這個尺度上的中心點坐標值為(0.3+1, 0.5+1),映射到原圖尺度為(1.3,1,5)*scale。

在這里scale=32。

              其中grid為相對於feature map左上角的偏移量。以13*13的feature map為例來說明box 中心點x,y以及寬高w,h的計算過程。

              K.arange(0, stop=grid_shape[0]) ---->[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12]

              K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]) ---->  -1表示為變化的維度,放在第一維度表示第一位維是變化的則 shape=[13,1,1,1]  一共包含13行值分別為0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12。

              具體形式如下:[ [ [ [ 0 ] ] ]

                                           ..........

                                            [ [ [12 ] ] ] ]

             grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),  [1, grid_shape[1], 1, 1]) ----> 表示在第二個維度重復13次   shape=[13,13,1,1]

            具體形式如下:[ [ [ [ 0 ] ]

                                            ...........

                                              [ [ 0 ] ]    重復13次
                                            [ [ [ 1 ] ]

                                            ............
                                              [ [ 1 ] ]    重復13次
                                            .............
                                            .............

                                           [ [ [ 12 ] ]

                                            ...............

                                              [ [12 ] ] ] ]

            同理可得到grid_x的具體形式:[ [ [ [  0 ] ]                 shape=[13,13,1,1]
                                                                           [ [  1 ] ]

                                                                          ............

                                                                           [ [12 ] ] ]

                                                                          .............

                                                                          .............

                                                                       [ [ [ [  0 ] ] 藍色部分共重復了13次
                                                                           [ [  1 ] ]

                                                                          ............

                                                                          [ [ 12 ] ] ]        

                      grid最終形式為        [ [ [ [ 0  0 ] ]                           shape=[13,13,1,2]     
                                                               [ [ 1  0 ] ]     
                                                              ................
                                                               [ [12  1 ] ] ]   

                                                             [ [ [ 0  1 ] ]                    
                                                               [ [ 1  1 ] ]     
                                                              ................                                                                                                                      

                                                               [ [12  1] ] ]

                                                              ..................

                                                              ..................   

                                                              [ [ [ 0  12 ] ]                    
                                                               [ [ 1  12 ] ]     
                                                              ................                                                                                                                      

                                                               [ [12  12] ] ]

三、有關損失函數的一些注意事項

     ps:   損失函數計算的為偏移量的損失,作者將真實的標簽寬高轉換為對應特征圖尺寸上寬高的偏移量,然后與預測出的寬高偏移量計算誤差。並不是將預測出的偏移轉換為真實值和標簽計算誤差。  即計算的為偏移量的誤差不是真實值之間的誤差。同理中心點誤差計算也是特征圖上的中心點坐標。

        

 

 

 

 

實際公式中xi,yi尖,為網絡預測出的中心點值計算sigmoid之后的值。原版darknet計算中心點損失使用的是方差。keras作者使用的是交叉熵,這點有所不同。

                                                             


免責聲明!

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



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