所有“蒙蔽”都来自于不知道yolov3训练的时候,数据是长什么样子,所以在这主要记录的是对于一张图片,如何生成yolov3的label数据,并不是主要分析网络结构,想看网络结构的要失望了~~~
keras yolov3源代码
论文
数据打标
# box_data: (batch, 416, 416, 3)
image_data = np.array(image_data)
# box_data: (batch, 20, 5)
box_data = np.array(box_data)
y_true = preprocess_true_boxes(box_data, input_shape, anchors, num_classes)
yolov3 默认每个图像最多检测20个物体,y_true就是生成的训练label。y_true的格式为 :
- 13,26,52分别代表三种维度的feature-map,也就相当于将原图片分别分为13×13,26×26,52×52个cell。
- 3表示每个cell需要预测三个大小不同的框(anchor)。
- 25代表4个坐标,1个置信度还有20个分类。
所以对于一张图像,一共会映射( 13×13×3 + 26×26×3 + 52×52×3 )个anchor,其中(13×13)中每个cell对应的3个anchor大小为 [116. 90.],[156. 198.],[373. 326.];(26×26)中每个cell对应的3个anchor大小为 [ 30. 61.],[ 62. 45.],[ 59. 119.];(52×52)中每个cell对应的3个anchor为[ 10. 13.],[ 16. 30.],[ 33. 23.]。
首先,加入一张图片中有一个物体需要检测,如下图所示:
- 我们需要知道这个框是由三种维度(13×13),(26×26),(52×52)的feature-map中哪一个来进行预测的。
- 然后需要知道是对应feature-map中哪个cell预测的。
- 最后需知道是与这个cell相关联的三个anchor中哪个回归得到的。
首先求黄框与9种anchor的iou,并且找出最大的那个iou对应的anchor,如下图所示:
- 13×13的特征图对应 [116. 90.],[156. 198.],[373. 326.]三个anchor;
- 26×26对应 [ 30. 61.],[ 62. 45.],[ 59. 119.]三个anchor;
- 52×52对应 [ 10. 13.],[ 16. 30.],[ 33. 23.]三个anchor。
由图像可知,best anchor是[116. 90.]也就是13×13特征图中所产生的第一个anchor,这样就解决了问题中(1)和(3)。接下来需要将原始图片中的黄框定位到13×13的特征图中,也就是寻找13×13的特征图中是哪个cell在预测这个黄框。第几行i第几列j,如下图所示:
yolov3网络的构建
yolov3接收两个输入一个是图片数据:(batch,416,416,3),另一个是上文中提到的打标数据[ (batch,13, 13, 3, 25), (batch,26, 26, 3, 25), (batch,52, 52, 3, 25) ]。首先构建yolo_body,也就是主干网络,用来提取图片的特征并且进行3个尺度的类别预测和坐标回归,最终返回 Model(inputs, [y1,y2,y3]) y1,y2,y3分别对应三个feature-map!首先image通过DarkNet网络提取图片特征,将得到的feature-map进行卷积和concat一系列操作后,得到三个大小的特征图。
图片参考: yolov3框架
yolo_body 输出即为[ (batch,13, 13, 75), (batch,26, 26, 75), (batch,52, 52, 75) ]的三个feature-map。
构建损失函数yolo_loss
yolo_loss接收来在yolo_body的输出,与上文产生的y_true。首先将每个yolo_body的输出reshape为(batch,H,W,3,25)的tensor。这里我们需要知道yolov3中25的前四个分别代表什么意思。首先前两个(tx,ty)代表的是预测box的中心坐标相对于所属cell左上角坐标的偏移量,使用sigmod映射到(0,1)之间保证预测box的中心还在cell中。同时(tw,th)分别表示预测box的宽,高相对于对应anchor的比例。最后anchor的宽高乘以tw,th的e次方得到预测框的宽,高。原论文中如下图所示:
raw_true_xy = y_true[l][..., :2]*grid_shapes[l][::-1] - grid
raw_true_wh = K.log(y_true[l][..., 2:4] / anchors[anchor_mask[l]] * input_shape[::-1])
最后yolov3的损失一共有四部分组成:
- 回归中心坐标损失:xy_loss
- 回归W,H的损失:wh_loss
- 置信度损失:confidence_loss
- 物体分类损失:class_loss
xy_loss = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[...,0:2], from_logits=True)
wh_loss = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh-raw_pred[...,2:4])
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
class_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[...,5:], from_logits=True)
其中object_mask 为选中labe和predict中具有box的数据,ignore_mask为没有box的数据。其中要注意的是xy_loss和confidence_loss,binary_crossentropy 使用的是二分类交叉损失,wh_loss使用的平方损失。论文中说binary_crossentropy这个损失要比多分类的交叉损失效果要好,至于为啥,还在探索。有一篇讲解yolov3损失很棒的文章:yolov3损失详解
最后对这四个损失进行一些取平均得到最后的损失:
xy_loss = K.sum(xy_loss) / mf
wh_loss = K.sum(wh_loss) / mf
confidence_loss = K.sum(confidence_loss) / mf
class_loss = K.sum(class_loss) / mf
loss += xy_loss + wh_loss + confidence_loss + class_loss