損失函數
yolo損失分為3個部分類別損失、置信度損失、位置損失
1. 類別損失
只有有目標的地方才會有類別判斷,從而才會有類別損失,所以需要解決兩個問題:1.有目標的地方;2.類別損失
1.1有目標的地方:object_mask
object_mask根據 y_true(真實值)確定,如何通過前處理編碼y_true,通過計算實際框(ground_truth)與anchor框的iou來確定
achor的9個框分為3組,分別負責下小目標(8倍下采樣)、中目標(16倍下采樣)、大目標(32倍下采樣) ,計算每個框與anchor框的iou,iou最大的anchor負責回歸這個真實框,
以13*13 (32倍下采樣)為例,整幅圖被分為13*13個gird,真實框的信息應該放在哪個grid里面呢?答案是真實框的中心落在哪個grid里面,這個grid就負責存儲這個框的所有信息,
包括(x, y, h, w),(confidence=1),(class)。object_mask,顧名思義,目標掩碼,即有目標的地方為true,所以 object_mask = y_true[l][..., 4:5]
2.1類別損失
類型損失,一般采用交叉熵損失,在目標檢測中,采用二元交叉熵損失,對每一個類別計算交叉熵損失,進行求和;
class_loss = object_mask * K.binary_crossentropy(true_class_prob, raw_pred[..., 5:], from_logits=True)
class_loss = K.sum(class_loss) / mf
此處為什么不采用softmax交叉熵損失呢?此處筆者也不清楚,歡迎有答案的同學留言交流。我猜測是因為各個類別之間相互獨立,不是非A即B的關系
所以沒有采用softmax,將所有類別的概率之和調整為1。
2.位置損失
目標檢測的一項重要任務就是確定目標的位置,即(x, y, h, w),所以損失值的計算中包含位置損失
通常計算位置損失有
1. L1 Loss 平均絕對誤差(Mean Absolute Error, MAE
梯度值為1或-1,在接近准確值時,會以learning_rate在准確值附近波動,比較難獲得准確效果
2. L2 Loss 均方誤差損失(Mean Square Error, MSE
在距離准確值較遠的地方梯度值較大,訓練初期收斂難度大,容易受到噪聲的干擾
克服了以上兩種損失函數的缺點,但將x,y,h,w作為獨立的變量的看待,割裂了他們之間的相對關系
考慮重合面積,可以較好的反應預測框和真實框的接近程度,但是但二者不相交時,損失值恆為1
考慮了不重合部分的面積對於損失的影響
考慮了中心點的偏移,即兩個框中心的距離與兩個框最遠距離的比值
考慮了長和寬的比值
在yoloV4中,采用了CIou Loss
def box_ciou(b1, b2): b1_xy = b1[..., :2] b1_wh = b1[..., 2:4] b1_wh_half = b1_wh / 2. b1_mins = b1_xy - b1_wh_half b1_maxes = b1_xy + b1_wh_half b2_xy = b2[..., :2] b2_wh = b2[..., 2:4] b2_wh_half = b2_wh / 2. b2_mins = b2_xy - b2_wh_half b2_maxes = b2_xy + b2_wh_half intersect_mins = K.maximum(b1_mins, b2_mins) intersect_maxes = K.minimum(b1_maxes, b2_maxes) intersect_wh = K.maximum(intersect_maxes-intersect_mins, 0.) # 兩個框相交部分面積 intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1] b1_area = b1_wh[..., 0] * b1_wh[..., 1] b2_area = b2_wh[..., 0] * b2_wh[..., 1] union_area = b1_area + b2_area - intersect_area # 兩個框的iou iou = intersect_area / K.maximum(union_area, K.epsilon()) # 兩個框中心點的距離 center_distance = K.sum(K.square((b1_xy - b2_xy)), axis=-1) enclose_mins = K.minimum(b1_mins, b2_mins) enclose_maxes = K.maximum(b1_maxes, b2_maxes) enclose_wh = K.maximum(enclose_maxes - enclose_mins, 0.0) # 兩個框的最遠距離 enclose_diagonal = K.sum(K.square(enclose_wh), axis=-1) # 計算DIOU(CIOU的前半部分) ciou = iou - 1.0 * (center_distance) / K.maximum(enclose_diagonal, K.epsilon()) # 計算兩個框的長寬比的差值的平方 v = 4*K.square(tf.math.atan2(b1_wh[..., 0], K.maximum(b1_wh[..., 1],K.epsilon())) - tf.math.atan2(b2_wh[..., 0], K.maximum(b2_wh[..., 1],K.epsilon()))) / (math.pi * math.pi) alpha = v / K.maximum((1.0 - iou + v), K.epsilon()) ciou = ciou - alpha * v ciou = K.expand_dims(ciou, -1) return ciou
ciou_loss = object_mask * box_loss_scale * (1 - ciou)
ciou_loss = K.sum(ciou_loss) / mf
其中 box_loss_scale 為 (2 - 對應真實框的面積),范圍為(1-2),當真實框的面積越大,box_loss_scale越小,意味着對大框的偏差容忍度越大。
解決了以上問題以后,我們還有一個重要問題沒有解決,預測框的(x, y, h, w)如何得到?
在yolo中,我們的(x,y)是通過對應grid左上角偏移得到;(h,w)是通過對對應anchor的長寬縮放得到,
box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[..., ::-1], K.dtype(feats))
box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[..., ::-1], K.dtype(feats))
前半部分(斜體加粗),公式轉化如圖所示
后半部分,box_xy 除以grid_shape 將中心點坐標轉化為(0-1),與y_true中中心點的坐標的編碼對應,同樣轉化為(0-1),即中心點相對於輸入圖像的位置;同樣也是作歸一化處理;box_hw 除以input_shape 將長寬轉化為(0-1),與y_true中長寬的編碼對應。
3.置信度損失
置信度損失分為兩個部分,有目標的置信度損失,無目標的置信度損失
3.1 有目標的置信度損失
有目標的地方,即object_mask,
置信度損失:采用二元交叉熵損失
K.binary_crossentropy(object_mask, raw_pred[..., 4:5]
總的損失為
object_mask * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True)
3.2 無目標的置信度損失
無目標的地方,即(1-object_mask)
(1 - object_mask) * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True)
存在一個問題,因為一張圖中大部分是背景,即大部分是負樣本,如果所有的負樣本都參與計算,會極大的放大負樣本的損失,導致訓練結果偏向於負樣本,
所以在計算無目標的置信度損失時只會選擇部分負樣本,如何選擇負樣本?原則是計算預測值框和真實值框的iou,每個預測值的每個grid中都有三個框(以13*13)為例,每個真實值(一幅圖)中有那個框
依次計算iou,會得到(13*13*3*n)個iou,選取最大的iou作為預測值和真實值的iou,得到的維度為(13,13,3,1),設置一個iou閾值,小於此閾值的視為負樣本,其實質筆者認為是一種隨機取樣的方法。
def loop_body(b, ignore_mask): true_box = tf.boolean_mask(y_true[l][b, ..., 0:4], object_mask_bool[b, ..., 0]) iou = box_iou(pred_box[b], true_box) best_iou = K.max(iou, axis=-1) # 13, 13, 3 ignore_mask = ignore_mask.write(b, K.cast(best_iou<ignore_thresh, K.dtype(true_box))) return b+1, ignore_mask
得到ignore_mask ,所以無目標的置信度損失為:
confidence_loss = object_mask * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True) + \
(1 - object_mask) * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True) * ignore_mask
confidence_loss = K.sum(confidence_loss) / mf
所以最終的目標檢測損失為三者的損失之和:
loss += location_loss + confidence_loss + class_loss