Tensorflow2.0—YOLO V4-tiny网络原理及代码解析(三)- 损失函数的构建
YOLO V4中的损失函数与V3还是有比较大的区别的,具体的可以看YOLOV4与YOLOV3的区别。
代码是在nets文件夹下面的loss.py文件中,在train.py中引用的是:
model_loss = Lambda(yolo_loss, output_shape=(1,), name='yolo_loss',arguments={'anchors': anchors, 'num_classes': num_classes, 'ignore_thresh': 0.5, 'label_smoothing': label_smoothing})(loss_input)
先来看下yolo_loss的参数:
def yolo_loss(args, anchors, num_classes, ignore_thresh=.5, label_smoothing=0.1, print_loss=False, normalize=True):
把预测的与真实的进行分割出来,
y_true是一个列表,包含两个特征层,shape分别为(m,13,13,75),(m,26,26,75)
yolo_outputs是一个列表,包含两个特征层,shape分别为(m,13,13,3,25),(m,26,26,3,25)
y_true = args[num_layers:]
yolo_outputs = args[:num_layers]
然后就开始以特征层数开始循环,这里就以(m,13,13,3,25)为例:
先获得真实框(m,13,13,3,25)的第5个位置的数据,如果在编码中该位置存在gt框,那么就设置为1,表示第(i,j)特征图中第k个锚点框包含物体,否则一切都设置为0
object_mask = y_true[l][..., 4:5]
然后,再获得真实框(m,13,13,3,25)的第6-26个位置的数据:
true_class_probs = y_true[l][..., 5:]
接着,进行标签平滑:
if label_smoothing:true_class_probs = _smooth_labels(true_class_probs, label_smoothing)
def _smooth_labels(y_true, label_smoothing):num_classes = tf.cast(K.shape(y_true)[-1], dtype=K.floatx())label_smoothing = K.constant(label_smoothing, dtype=K.floatx())return y_true * (1.0 - label_smoothing) + label_smoothing / num_classes
接着,就是yolo_head:
yolo_head(yolo_outputs[l], anchors[anchor_mask[l]], num_classes, input_shape, calc_loss=True)
下面这一段代码有点难理解,其实它的作用就是创建(13,13,1,2)的网格
grid_shape = K.shape(feats)[1:3]
grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),[1, grid_shape[1], 1, 1])
grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),[grid_shape[0], 1, 1, 1])
grid = K.concatenate([grid_x, grid_y])
grid = K.cast(grid, K.dtype(feats))
举个例子:
在这幅图片中就可以很好的看到最终其实就是生成了(13,13,1,2)的网格。
feats = K.reshape(feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])
将预测结果(m,13,13,75)分割成(m,13,13,3,25)。
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_confidence = K.sigmoid(feats[..., 4:5])
box_class_probs = K.sigmoid(feats[..., 5:])
这四行代码的目的是:将预测值转换为真实值!
第一行:将预测结果的xy(None,13,13,3,2)先加上grid,然后除以(13,13),就得到了转化后的进行归一化的xy,shape=(None,13,13,3,2)。
第二行:将预测结果的wh(None,13,13,3,2)先乘anchor的尺寸,然后除以输入大小,最后再进行指数计算,得到转换后的wh,shape同xy。
第三行和第四行:将预测得到的confidence和class_prob进行sigmoid转化。
最后,就可以将预测结果进行decode为预测真实框的形式~
if calc_loss == True:return grid, feats, box_xy, box_whreturn box_xy, box_wh, box_confidence, box_class_probs
在计算loss的时候返回grid, feats, box_xy, box_wh
在预测的时候返回box_xy, box_wh, box_confidence, box_class_probs
pred_box = K.concatenate([pred_xy, pred_wh])
将上述box_xy, box_wh进行合并,shape=(None,13,13,3,4)
下面,还有一个loop_body函数,来看看它是干嘛的。
def loop_body(b, ignore_mask):true_box &#61; tf.boolean_mask(y_true[l][b,...,0:4], object_mask_bool[b,...,0])iou &#61; box_iou(pred_box[b], true_box)best_iou &#61; K.max(iou, axis&#61;-1)ignore_mask &#61; ignore_mask.write(b, K.cast(best_iou<ignore_thresh, K.dtype(true_box)))return b&#43;1, ignore_mask_, ignore_mask &#61; tf.while_loop(lambda b,*args: b<m, loop_body, [0, ignore_mask])
步骤解释&#xff1a;
1.先取出真实框存在物体的框的xywh
2.与预测框进行iou计算
3.找到对应每个真实框最大的iou的预测框&#xff0c;best_iou &#61; &#xff08;13&#xff0c;13&#xff0c;3&#xff09;
4.判断预测框和真实框的最大iou小于ignore_thresh&#xff0c;则认为该预测框没有与之对应的真实框&#xff0c;这么做的目的是&#xff1a;忽略预测结果与真实框非常对应特征点&#xff0c;因为这些框已经比较准了&#xff0c;不适合当作负样本&#xff0c;所以忽略掉
5.把这些框放入ignore_mask中
box_loss_scale &#61; 2 - y_true[l][...,2:3]*y_true[l][...,3:4]raw_true_box &#61; y_true[l][...,0:4]ciou &#61; box_ciou(pred_box, raw_true_box)ciou_loss &#61; object_mask * box_loss_scale * (1 - ciou)
这一部分是进行预测与真实的ciou损失&#xff01;但是代码实现中与论文中还是有一点区别的&#xff0c;在代码中还考虑到了真实框大小的因素&#xff1a;真实框越大&#xff0c;比重越小&#xff0c;小框的比重更大。&#xff08;这里我就不写ciou的代码了&#xff0c;有机会单独写一个blog~&#xff09;&#xff0c;得到的ciou_loss &#61; &#xff08;None,13,13,3,1&#xff09;。
最后&#xff0c;就是置信度损失和类别损失的计算了&#xff1a;
confidence_loss &#61; object_mask * K.binary_crossentropy(object_mask, raw_pred[...,4:5], from_logits&#61;True)&#43; \(1-object_mask) * K.binary_crossentropy(object_mask, raw_pred[...,4:5], from_logits&#61;True) * ignore_maskclass_loss &#61; object_mask * K.binary_crossentropy(true_class_probs, raw_pred[...,5:], from_logits&#61;True)
第一行&#xff1a;先计算真实框存在的confidence_loss&#xff0c;加上不存在真实框的confidence_loss&#xff08;这里要忽略那些ignore_mask里面的框&#xff09;。
第二行&#xff1a;直接计算预测和真实的类别损失。
num_pos &#43;&#61; tf.maximum(K.sum(K.cast(object_mask, tf.float32)), 1)loss &#43;&#61; location_loss &#43; confidence_loss &#43; class_lossloss &#61; K.expand_dims(loss, axis&#61;-1)if normalize:loss &#61; loss / num_poselse:loss &#61; loss / mfreturn loss
最后就是进行损失求和&#xff0c;若进行归一化&#xff0c;就将总损失除以正样本&#xff0c;要是不进行归一化&#xff0c;那就除以批次大小。最终得到最后的loss~