深度解析YoloV3的主干网络和Loss

您所在的位置:网站首页 yolov3主干网络 深度解析YoloV3的主干网络和Loss

深度解析YoloV3的主干网络和Loss

#深度解析YoloV3的主干网络和Loss| 来源: 网络整理| 查看: 265

 摘要

YoloV3作为物体检测模型中比较常用的模型之一,是广大算法工程师入门物体检测必学的算法之一,所以弄清楚Yolov3的主干网络和Loss很有必要。本文根据网络收集和自己的理解写的,如果有不对的地方,欢迎大家指正。代码推荐keras版本的,这个版本写的非常简单,容易上手。github地址:GitHub - qqwweee/keras-yolo3: A Keras implementation of YOLOv3 (Tensorflow backend)

主干网络DarkNet-53

上图是YoloV3的整体网络图,我可以到y1,y2,y3三个输出,y1的大小是13×13×255,y2的大小是26×26×255,y3的大小是52×52×255,13,26,52这三个值好理解,是通过卷积得来的(在这里要注意,DrakNet-53并没有使用池化,它降采样使用的是卷积,将步进设置为2得来的。),那么255是怎么得来的呢?以y1为例,y1的输出为13*13*255,表示整张图被分为13*13个格子,每个格子预测3个框,每个框的预测信息包括:80个类别+1个框的置信度+2个框的位置偏差+2个框的size偏差。输出可以理解为是13*13*(3*(80+1+2+2))。

注:每个格子上的3个预测框是,我们通过聚类得到的9个框,平均分配到y1,y2,y3这三个map上,每个map的格子就是3个预测框了,

下面这张图展示了,图像从输入到输出,每个阶段的大小,三个红色的框是对应y1,y2,y3的输出。

DarkNet-53的主要代码:

def DarknetConv2D(*args, **kwargs): """Wrapper to set Darknet parameters for Convolution2D.""" darknet_conv_kwargs = {'kernel_regularizer': l2(5e-4)} darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same' darknet_conv_kwargs.update(kwargs) return Conv2D(*args, **darknet_conv_kwargs) def DarknetConv2D_BN_Leaky(*args, **kwargs): """Darknet Convolution2D followed by BatchNormalization and LeakyReLU.""" no_bias_kwargs = {'use_bias': False} no_bias_kwargs.update(kwargs) return compose( DarknetConv2D(*args, **no_bias_kwargs), BatchNormalization(), LeakyReLU(alpha=0.1)) def resblock_body(x, num_filters, num_blocks): '''A series of resblocks starting with a downsampling Convolution2D''' # Darknet uses left and top padding instead of 'same' mode x = ZeroPadding2D(((1,0),(1,0)))(x) x = DarknetConv2D_BN_Leaky(num_filters, (3,3), strides=(2,2))(x) for i in range(num_blocks): y = compose( DarknetConv2D_BN_Leaky(num_filters//2, (1,1)), DarknetConv2D_BN_Leaky(num_filters, (3,3)))(x) x = Add()([x,y]) return x def darknet_body(x): '''Darknent body having 52 Convolution2D layers''' x = DarknetConv2D_BN_Leaky(32, (3,3))(x) x = resblock_body(x, 64, 1) x = resblock_body(x, 128, 2) x = resblock_body(x, 256, 8) x = resblock_body(x, 512, 8) x = resblock_body(x, 1024, 4) return x def make_last_layers(x, num_filters, out_filters): '''6 Conv2D_BN_Leaky layers followed by a Conv2D_linear layer''' x = compose( DarknetConv2D_BN_Leaky(num_filters, (1,1)), DarknetConv2D_BN_Leaky(num_filters*2, (3,3)), DarknetConv2D_BN_Leaky(num_filters, (1,1)), DarknetConv2D_BN_Leaky(num_filters*2, (3,3)), DarknetConv2D_BN_Leaky(num_filters, (1,1)))(x) y = compose( DarknetConv2D_BN_Leaky(num_filters*2, (3,3)), DarknetConv2D(out_filters, (1,1)))(x) return x, y def yolo_body(inputs, num_anchors, num_classes): """Create YOLO_V3 model CNN body in Keras.""" darknet = Model(inputs, darknet_body(inputs)) x, y1 = make_last_layers(darknet.output, 512, num_anchors*(num_classes+5)) x = compose( DarknetConv2D_BN_Leaky(256, (1,1)), UpSampling2D(2))(x) x = Concatenate()([x,darknet.layers[152].output]) x, y2 = make_last_layers(x, 256, num_anchors*(num_classes+5)) x = compose( DarknetConv2D_BN_Leaky(128, (1,1)), UpSampling2D(2))(x) x = Concatenate()([x,darknet.layers[92].output]) x, y3 = make_last_layers(x, 128, num_anchors*(num_classes+5)) return Model(inputs, [y1,y2,y3])

在网络的输入和输出,图片大小上很多人也有疑惑,比如测试的时候,我输入的图像是416×416的,那么在测试的时候必须是416×416的吗?

我们继续以y1为例,y1的默认输出为13*13*255,表示整张图被分为13*13个格子,每个格子预测3个框,每个框的预测信息包括:80个类别+1个框的置信度+2个框的位置偏差+2个框的size偏差。输出可以理解为是13*13*(3*(80+1+2+2))。如果我们在测试的时候,输入的图像是4160×4160的呢?那么y1的输出就变成130×130×255了,整张图被划分的格子数按照对应的倍数增加,所以不会发生变化。所以在处理大图像的时候,我可以采用小图训练大图测试的方式做,没有必要让测试的图像大小和训练的图像大小保持一致,但是最好保持等比例resize。

Loss函数以及构建过程

关于Loss函数的理解,我在网上查了查,版本不少,由于作者在写论文的时候,没有详细些Loss,导致很多人认为Yolov3的Loss和以前的版本一致,所以很多文章也是错误的。我参照代码总结对YoloV3的Loss做了一些总结。

我们先看代码里的Loss是如何写的?

def yolo_loss(args, anchors, num_classes, ignore_thresh=.5, print_loss=False): '''Return yolo_loss tensor Parameters ---------- yolo_outputs: list of tensor, the output of yolo_body or tiny_yolo_body y_true: list of array, the output of preprocess_true_boxes anchors: array, shape=(N, 2), wh num_classes: integer ignore_thresh: float, the iou threshold whether to ignore object confidence loss Returns ------- loss: tensor, shape=(1,) ''' num_layers = len(anchors)//3 # default setting yolo_outputs = args[:num_layers] y_true = args[num_layers:] anchor_mask = [[6,7,8], [3,4,5], [0,1,2]] if num_layers==3 else [[3,4,5], [1,2,3]] input_shape = K.cast(K.shape(yolo_outputs[0])[1:3] * 32, K.dtype(y_true[0])) grid_shapes = [K.cast(K.shape(yolo_outputs[l])[1:3], K.dtype(y_true[0])) for l in range(num_layers)] loss = 0 m = K.shape(yolo_outputs[0])[0] # batch size, tensor mf = K.cast(m, K.dtype(yolo_outputs[0])) for l in range(num_layers): object_mask = y_true[l][..., 4:5] true_class_probs = y_true[l][..., 5:] grid, raw_pred, pred_xy, pred_wh = yolo_head(yolo_outputs[l], anchors[anchor_mask[l]], num_classes, input_shape, calc_loss=True) pred_box = K.concatenate([pred_xy, pred_wh]) # Darknet raw box to calculate loss. 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]) raw_true_wh = K.switch(object_mask, raw_true_wh, K.zeros_like(raw_true_wh)) # avoid log(0)=-inf box_loss_scale = 2 - y_true[l][...,2:3]*y_true[l][...,3:4] # Find ignore mask, iterate over each of batch. ignore_mask = tf.TensorArray(K.dtype(y_true[0]), size=1, dynamic_size=True) object_mask_bool = K.cast(object_mask, 'bool') 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) ignore_mask = ignore_mask.write(b, K.cast(best_iou


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3