YoLov3

这段时间对YOLOv3代码理解,记录这一过程

代码连接:https://github.com/YunYang1994/tensorflow-yolov3

一 .代码总体理解
代码主要在core目录下
backbone是darknet53的实现,commom是构成网络的基础单元,config是配置文件(方便超参数的调整)
dataset是标签处理文件, utils是数据增强等操作,yolov3是整个网络构架
代码的理解是从三个方面来理解,
(1) 真实的标签(dataset.py)
(2)网络输出的特征图(yolov3.py)
(3)真实标签与输出的特征图之间的误差最小化(train,py)
真实的标签与网络输出的特征图维度一致,为了最后求损失函数的优化

二 .代码详细讲解
2.1. yolov3.py
按代码顺序讲解
2.11第一部分

import numpy as np
import tensorflow as tf
import core.utils as utils
import core.common as common
import core.backbone as backbone
from core.config import cfg
class YOLOV3(object):
"""Implement tensoflow yolov3 here"""
	def __init__(self, input_data, trainable):

    self.trainable        = trainable
    self.classes          = utils.read_class_names(cfg.YOLO.CLASSES)
    self.num_class        = len(self.classes)
    self.strides          = np.array(cfg.YOLO.STRIDES)
    self.anchors          = utils.get_anchors(cfg.YOLO.ANCHORS)
    self.anchor_per_scale = cfg.YOLO.ANCHOR_PER_SCALE
    self.iou_loss_thresh  = cfg.YOLO.IOU_LOSS_THRESH
    self.upsample_method  = cfg.YOLO.UPSAMPLE_METHOD

    try:
        self.conv_lbbox, self.conv_mbbox, self.conv_sbbox = self.__build_nework(input_data)
    except:
        raise NotImplementedError("Can not build up yolov3 network!")

    with tf.variable_scope('pred_sbbox'):
        self.pred_sbbox = self.decode(self.conv_sbbox, self.anchors[0], self.strides[0])

    with tf.variable_scope('pred_mbbox'):
        self.pred_mbbox = self.decode(self.conv_mbbox, self.anchors[1], self.strides[1])

    with tf.variable_scope('pred_lbbox'):
        self.pred_lbbox = self.decode(self.conv_lbbox, self.anchors[2], self.strides[2])

该文件首先调用了nump,tensorflow,以及其它文件中定义的函数,然后定义了一个类,在构造方法
def init(self, input_data,trainable):
添加了input_data,trainable形参,以及以下公有成员;

try-execpt语句
定义了进行异常监控的一段代码,提供了处理异常的机制,如没有异常,程序正常运行,如有异常,可以自定义错误;

raise
而在本代码中采用了raise NotImplementedError(“Can not build up yolov3 network!”)
意义是raise可以实现报出错误的类型功能,报错条件有程序员自己设定.面向对象编程中,可以预留一个方法接口不实现,在其子类中实现。如果要求其子类一定要实现,不实现的时候就会导致错误,采用raise的方式就比较号,此刻产生的问题分类就是NotImplementedError
(子类是指必须是一个类继承了)

with tf.variable_scope(‘name’):
使用tf管理图给中的节点命名,在后续的变量名读取它。
with tf.name_scope(name):
主要用于管理图中的各种操作。
区别:tf.variable_scope可以让变量有相同的命名,包括tf.get_variable与tf.Variable的变量
tf.name_scope可以让变量有相同的命名,只是限制tf.Variable的变量
演示代码:
https://blog.csdn.net/UESTC_C2_403/article/details/72328815
self.pred_sbbox = self.decode()
作用在与将网络结构输出,将特征图的维度调整为与标签的维度一致;

2.12 第二部分

   def __build_nework(self, input_data):

   	 route_1, route_2, input_data = backbone.darknet53(input_data, self.trainable)

   	 input_data = common.convolutional(input_data, (1, 1, 1024,  512), self.trainable, 'conv52')
   	 input_data = common.convolutional(input_data, (3, 3,  512, 1024), self.trainable, 'conv53')
     input_data = common.convolutional(input_data, (1, 1, 1024,  512), self.trainable, 'conv54')
     input_data = common.convolutional(input_data, (3, 3,  512, 1024), self.trainable, 'conv55')
     input_data = common.convolutional(input_data, (1, 1, 1024,  512), self.trainable, 'conv56')

     conv_lobj_branch = common.convolutional(input_data, (3, 3, 512, 1024), self.trainable,  name='conv_lobj_branch')
     conv_lbbox = common.convolutional(conv_lobj_branch, (1, 1, 1024, 3*(self.num_class + 5)),
                                      trainable=self.trainable, name='conv_lbbox', activate=False, bn=False)

     input_data = common.convolutional(input_data, (1, 1,  512,  256), self.trainable, 'conv57')
     input_data = common.upsample(input_data, name='upsample0', method=self.upsample_method)

     with tf.variable_scope('route_1'):
          input_data = tf.concat([input_data, route_2], axis=-1)

     input_data = common.convolutional(input_data, (1, 1, 768, 256), self.trainable, 'conv58')
     input_data = common.convolutional(input_data, (3, 3, 256, 512), self.trainable, 'conv59')
     input_data = common.convolutional(input_data, (1, 1, 512, 256), self.trainable, 'conv60')
     input_data = common.convolutional(input_data, (3, 3, 256, 512), self.trainable, 'conv61')
     input_data = common.convolutional(input_data, (1, 1, 512, 256), self.trainable, 'conv62')

     conv_mobj_branch = common.convolutional(input_data, (3, 3, 256, 512),  self.trainable, name='conv_mobj_branch' )
     conv_mbbox = common.convolutional(conv_mobj_branch, (1, 1, 512, 3*(self.num_class + 5)),
                                      trainable=self.trainable, name='conv_mbbox', activate=False, bn=False)

     input_data = common.convolutional(input_data, (1, 1, 256, 128), self.trainable, 'conv63')
     input_data = common.upsample(input_data, name='upsample1', method=self.upsample_method)

     with tf.variable_scope('route_2'):
         input_data = tf.concat([input_data, route_1], axis=-1)

     input_data = common.convolutional(input_data, (1, 1, 384, 128), self.trainable, 'conv64')
     input_data = common.convolutional(input_data, (3, 3, 128, 256), self.trainable, 'conv65')
     input_data = common.convolutional(input_data, (1, 1, 256, 128), self.trainable, 'conv66')
     input_data = common.convolutional(input_data, (3, 3, 128, 256), self.trainable, 'conv67')
     input_data = common.convolutional(input_data, (1, 1, 256, 128), self.trainable, 'conv68')

     conv_sobj_branch = common.convolutional(input_data, (3, 3, 128, 256), self.trainable, name='conv_sobj_branch')
     conv_sbbox = common.convolutional(conv_sobj_branch, (1, 1, 256, 3*(self.num_class + 5)),
                                      trainable=self.trainable, name='conv_sbbox', activate=False, bn=False)

     return conv_lbbox, conv_mbbox, conv_sbbox

def __build_network
作用是构建YOLOv3的网络结构:(需要输入必要参数input_data)
代码第一行:从backbone文件中调用函数darknet53,在函数中输入两个必要的变量,分别为input_data与self.trainable,返回三张不同尺度和维度的特征图,
代码最后一行返回三张特征图,
维度分别为:batch_sise1313*(20+5),等等,主要是四个维度,其实优化就是优化每一个网格。

代码转到了commom.py下,讲解如何构建一个conv+bn+leaky_relu,以及残差层
conv+bn+leaky_relu
def convolutional(input_data, filters_shape, trainable, name, downsample=False, activate=True, bn=True):

with tf.variable_scope(name):
    if downsample:
        pad_h, pad_w = (filters_shape[0] - 2) // 2 + 1, (filters_shape[1] - 2) // 2 + 1
        paddings = tf.constant([[0, 0], [pad_h, pad_h], [pad_w, pad_w], [0, 0]])
        input_data = tf.pad(input_data, paddings, 'CONSTANT')
        strides = (1, 2, 2, 1)
        padding = 'VALID'
    else:
        strides = (1, 1, 1, 1)
        padding = "SAME"

    weight = tf.get_variable(name='weight', dtype=tf.float32, trainable=True,
                             shape=filters_shape, initializer=tf.random_normal_initializer(stddev=0.01))
    conv = tf.nn.conv2d(input=input_data, filter=weight, strides=strides, padding=padding)

    if bn:
        conv = tf.layers.batch_normalization(conv, beta_initializer=tf.zeros_initializer(),
                                             gamma_initializer=tf.ones_initializer(),
                                             moving_mean_initializer=tf.zeros_initializer(),
                                             moving_variance_initializer=tf.ones_initializer(), training=trainable)
    else:
        bias = tf.get_variable(name='bias', shape=filters_shape[-1], trainable=True,
                               dtype=tf.float32, initializer=tf.constant_initializer(0.0))
        conv = tf.nn.bias_add(conv, bias)

    if activate == True: conv = tf.nn.leaky_relu(conv, alpha=0.1)

return conv

def convolutional()包含了标准的卷积、batch normalization和激活函数
在网络层中存在下采样过程,故设置一个条件判断来确定是否下采样还是正常的卷积操作,
写了两种卷积的方式,通过卷积的strides=2可以减少池化的特征损失。
然后定义了一个权重值,使用

 tf..get_variable(tf.get_variable(
name,   #name是一个必填参数
shape=None,
dtype=None,
initializer=None,
regularizer=None,
trainable=True,
collections=None,
caching_device=None,
partitioner=None,
validate_shape=True,
use_resource=None,
custom_getter=None)

其中None表示默认是不设置的,name为必填参数,True表示默认是可训练的
其中shape是指变量的形状或维度。
dtype是指变量的字符类型。元祖的形式
initializer是指:初始化变量的初始化器,初始化方式有不同的方式;一般选择随机初始化
regularizer:一个函数(张量 - >张量或无);将其应用于新创建的变量的结果将被添加到集合 tf.GraphKeys.REGULARIZATION_LOSSES 中,并可用于正则化。
其它的几个参数不常使用;
常见的初始化方式为:

tf.constant_initializer:常量初始化函数
tf.random_normal_initializer:正态分布
tf.truncated_normal_initializer:截取的正态分布
tf.random_uniform_initializer:均匀分布
tf.zeros_initializer:全部是0
tf.ones_initializer:全是1

例子如下:

import numpy as np;  
import matplotlib.pyplot as plt;  

a1 = tf.get_variable(name='a1', shape=[2,3], initializer=tf.random_normal_initializer(mean=0, stddev=1))
a2 = tf.get_variable(name='a2', shape=[1], initializer=tf.constant_initializer(1))
a3 = tf.get_variable(name='a3', shape=[2,3], initializer=tf.ones_initializer())

with tf.Session() as sess:
	sess.run(tf.initialize_all_variables())
	print sess.run(a1)
	print sess.run(a2)
	print sess.run(a3)
#输出
[[ 0.42299312 -0.25459203 -0.88605702]
 [ 0.22410156  1.34326422 -0.39722782]]
[ 1.]
[[ 1.  1.  1.]
 [ 1.  1.  1.]]

然后就是卷积操作;
def conv2d(input, filter, strides, padding, use_cudnn_on_gpu=True, data_format=“NHWC”, dilations=[1, 1, 1, 1], name=None)
其中input是指:输入数据
filter是指:权重值也就是weights
padding是指:输入特征图边缘补的0
use_cudnn_on_gpu:默认是True
data_format:也就是[batch, in_channels, in_height, in_width],即为输入数据的形式
dilations是指:卷积扩张因子,默认值是[1, 1, 1, 1],如果设置k大于1,则卷积的时候会跳过k-1个元素卷积,相当于扩张了卷积面积?这个不清楚
(An optional list of ints. Defaults to [1, 1, 1, 1]. 1-D tensor of length 4. The dilation factor for each dimension of input. If set to k > 1, there will be k-1 skipped cells between each filter element on that dimension. The dimension order is determined by the value of data_format, see above for details. Dilations in the batch and depth dimensions must be 1.)这个也不清楚

然后就是batch_normalization()同样设置了了条件函数;
tf.layer.batch_normalization(
)
其中的包含的参数太多就另写一篇博客这里只是讲解在使用过程应注意的事项
###BN层的训练
注意把tf.layers.batch_normalization(x,training=is_training, name=scope)输入参数training=True,在训练的时候要添加update_ops,以便每一次训练完后及时更新BN的参数。

 update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
 with tf.control_dependencies(update_ops):#保证train_op在update_op执行之后再执行;
 	train_op = optimize.minimize(loss)

###正确保存带BN的模型
保存模型的时候不能只保存trainable_variables,因为BN的参数不属于trainable_variables,可以使用tf.flobal_variables().如下

saver = tf.trainSaver(var_list=tf.global_variables())
savepath = saver.save(sess."model of path")

###正确读取BN的模型

saver= tf.train.Saver() or saver= tyf.train.Saver(tf.global_variables())
saver.restrore(sess, "path of model")

###测试时还需要将training变为False,
在设置的时候一定要设置不同的变量
一个是当前batch的mean、variance,一个是滑动平均的mean、variance。
(如果测试时选了training=True,可能输出却“很不如意”,因为如果用batch自己的mean和variance,输出总是规范的在一个区间,他总是将自身先缩放到标准范围,然后缩放偏移,但是BN的目的就是消除batch自己的偏差,使用统一的滑动mean和variance!)
tf.nn.bias_add()是指:将卷积操作与偏置相加
tf.nn,leaky_relu()是指:一种类型的激活函数

2.1.3 代码第三部分

def decode(self, conv_output, anchors, stride):
return tf.concat([pred_xywh, pred_conf, pred_prob], axis=-1)
函数的目的在与将四维度变为五维度,目的是为了画网格

def decode(self, conv_output, anchors, stride):
    """
    return tensor of shape [batch_size, output_size, output_size, anchor_per_scale, 5 + num_classes]
           contains (x, y, w, h, score, probability)
    """

    conv_shape       = tf.shape(conv_output)
    batch_size       = conv_shape[0]
    output_size      = conv_shape[1]
    anchor_per_scale = len(anchors)

    conv_output = tf.reshape(conv_output, (batch_size, output_size, output_size, anchor_per_scale, 5 + self.num_class))

    conv_raw_dxdy = conv_output[:, :, :, :, 0:2]
    conv_raw_dwdh = conv_output[:, :, :, :, 2:4]
    conv_raw_conf = conv_output[:, :, :, :, 4:5]
    conv_raw_prob = conv_output[:, :, :, :, 5: ]

    y = tf.tile(tf.range(output_size, dtype=tf.int32)[:, tf.newaxis], [1, output_size])
    x = tf.tile(tf.range(output_size, dtype=tf.int32)[tf.newaxis, :], [output_size, 1])

    xy_grid = tf.concat([x[:, :, tf.newaxis], y[:, :, tf.newaxis]], axis=-1)
    xy_grid = tf.tile(xy_grid[tf.newaxis, :, :, tf.newaxis, :], [batch_size, 1, 1, anchor_per_scale, 1])
    xy_grid = tf.cast(xy_grid, tf.float32)

    pred_xy = (tf.sigmoid(conv_raw_dxdy) + xy_grid) * stride
    pred_wh = (tf.exp(conv_raw_dwdh) * anchors) * stride
    pred_xywh = tf.concat([pred_xy, pred_wh], axis=-1)

    pred_conf = tf.sigmoid(conv_raw_conf)
    pred_prob = tf.sigmoid(conv_raw_prob)

    return tf.concat([pred_xywh, pred_conf, pred_prob], axis=-1)

首先第一步是得到卷积输出的结果
使用tf.reshape()将原来的四维变为五个维度。
conv_raw_dxdy 的目的在于得到该点的中心坐标,
conv_raw_dwdh 的目的在于得到该点的长和宽,
conv_raw_conf 的目的在于得到该点的置信度
conv_raw_prob 的目的在于得到该点的类别;
y,x 的目的在于得到网格的横竖坐标;
xy_grid 的目的在于画出网格;

pred_xy 的目的在于得到实际图片中的xy坐标;
pred_wh 的目的在于得到实际的w,h
pred_xywh的目的在于将其在最后的维度拼接
最后添加激活函数tf.sigmoid()的目的在于防止最后优化中出现0,将其限制在1的范围之内;
用sigmoid将tx,ty压缩到[0,1]区间內,可以有效的确保目标中心处于执行预测的网格单元中,防止偏移过多
使用指数的目的在与:因为tw,th是log尺度缩放到对数空间了,当然要指数回来,而且这样可以保证大于0。至于左边乘以Pw或者Ph是因为tw=log(Gw/Pw)当然应该乘回来得到真正的宽高。
在这里插入图片描述
边框回归最简单的想法就是通过平移加尺度缩放进行微调嘛。
2.1.4 Focal loss与bbox_giou 与 bbox_iou

def focal(self, target, actual, alpha=1, gamma=2):
    focal_loss = alpha * tf.pow(tf.abs(target - actual), gamma)
    return focal_loss

def bbox_giou(self, boxes1, boxes2):

    boxes1 = tf.concat([boxes1[..., :2] - boxes1[..., 2:] * 0.5,
                        boxes1[..., :2] + boxes1[..., 2:] * 0.5], axis=-1)
    boxes2 = tf.concat([boxes2[..., :2] - boxes2[..., 2:] * 0.5,
                        boxes2[..., :2] + boxes2[..., 2:] * 0.5], axis=-1)

    boxes1 = tf.concat([tf.minimum(boxes1[..., :2], boxes1[..., 2:]),
                        tf.maximum(boxes1[..., :2], boxes1[..., 2:])], axis=-1)
    boxes2 = tf.concat([tf.minimum(boxes2[..., :2], boxes2[..., 2:]),
                        tf.maximum(boxes2[..., :2], boxes2[..., 2:])], axis=-1)

    boxes1_area = (boxes1[..., 2] - boxes1[..., 0]) * (boxes1[..., 3] - boxes1[..., 1])
    boxes2_area = (boxes2[..., 2] - boxes2[..., 0]) * (boxes2[..., 3] - boxes2[..., 1])

    left_up = tf.maximum(boxes1[..., :2], boxes2[..., :2])
    right_down = tf.minimum(boxes1[..., 2:], boxes2[..., 2:])

    inter_section = tf.maximum(right_down - left_up, 0.0)
    inter_area = inter_section[..., 0] * inter_section[..., 1]
    union_area = boxes1_area + boxes2_area - inter_area
    iou = inter_area / union_area

    enclose_left_up = tf.minimum(boxes1[..., :2], boxes2[..., :2])
    enclose_right_down = tf.maximum(boxes1[..., 2:], boxes2[..., 2:])
    enclose = tf.maximum(enclose_right_down - enclose_left_up, 0.0)
    enclose_area = enclose[..., 0] * enclose[..., 1]
    giou = iou - 1.0 * (enclose_area - union_area) / enclose_area

    return giou

def bbox_iou(self, boxes1, boxes2):

    boxes1_area = boxes1[..., 2] * boxes1[..., 3]
    boxes2_area = boxes2[..., 2] * boxes2[..., 3]

    boxes1 = tf.concat([boxes1[..., :2] - boxes1[..., 2:] * 0.5,
                        boxes1[..., :2] + boxes1[..., 2:] * 0.5], axis=-1)
    boxes2 = tf.concat([boxes2[..., :2] - boxes2[..., 2:] * 0.5,
                        boxes2[..., :2] + boxes2[..., 2:] * 0.5], axis=-1)

    left_up = tf.maximum(boxes1[..., :2], boxes2[..., :2])
    right_down = tf.minimum(boxes1[..., 2:], boxes2[..., 2:])

    inter_section = tf.maximum(right_down - left_up, 0.0)
    inter_area = inter_section[..., 0] * inter_section[..., 1]
    union_area = boxes1_area + boxes2_area - inter_area
    iou = 1.0 * inter_area / union_area

    return iou

2.1.5 定义损失层
def loss_layer():
return giou, conf_loss, prob_loss
目的是定义出损失层;
def loss_layer(self, conv, pred, label, bboxes, anchors, stride):

    conv_shape  = tf.shape(conv)
    batch_size  = conv_shape[0]
    output_size = conv_shape[1]
    input_size  = stride * output_size
    conv = tf.reshape(conv, (batch_size, output_size, output_size,
                             self.anchor_per_scale, 5 + self.num_class))
    conv_raw_conf = conv[:, :, :, :, 4:5]
    conv_raw_prob = conv[:, :, :, :, 5:]

    pred_xywh     = pred[:, :, :, :, 0:4]
    pred_conf     = pred[:, :, :, :, 4:5]

    label_xywh    = label[:, :, :, :, 0:4]
    respond_bbox  = label[:, :, :, :, 4:5]
    label_prob    = label[:, :, :, :, 5:]

    giou = tf.expand_dims(self.bbox_giou(pred_xywh, label_xywh), axis=-1)
    input_size = tf.cast(input_size, tf.float32)

    bbox_loss_scale = 2.0 - 1.0 * label_xywh[:, :, :, :, 2:3] * label_xywh[:, :, :, :, 3:4] / (input_size ** 2)
    giou_loss = respond_bbox * bbox_loss_scale * (1- giou)

    iou = self.bbox_iou(pred_xywh[:, :, :, :, np.newaxis, :], bboxes[:, np.newaxis, np.newaxis, np.newaxis, :, :])
    max_iou = tf.expand_dims(tf.reduce_max(iou, axis=-1), axis=-1)

    respond_bgd = (1.0 - respond_bbox) * tf.cast( max_iou < self.iou_loss_thresh, tf.float32 )

    conf_focal = self.focal(respond_bbox, pred_conf)

    conf_loss = conf_focal * (
            respond_bbox * tf.nn.sigmoid_cross_entropy_with_logits(labels=respond_bbox, logits=conv_raw_conf)
            +
            respond_bgd * tf.nn.sigmoid_cross_entropy_with_logits(labels=respond_bbox, logits=conv_raw_conf)
    )

    prob_loss = respond_bbox * tf.nn.sigmoid_cross_entropy_with_logits(labels=label_prob, logits=conv_raw_prob)

    giou_loss = tf.reduce_mean(tf.reduce_sum(giou_loss, axis=[1,2,3,4]))
    conf_loss = tf.reduce_mean(tf.reduce_sum(conf_loss, axis=[1,2,3,4]))
    prob_loss = tf.reduce_mean(tf.reduce_sum(prob_loss, axis=[1,2,3,4]))

    return giou_loss, conf_loss, prob_loss

conv_shape 的目的在与得到conv的形状
batch_size 的目的在与得到它的批次
output_size 的目的在与得到输出的尺度
input_size的目的在与得到输入的尺度
conv 的目的在与将其再一次重塑为4个维度
giou 求出了真实与标签的标签;并且扩充一个维度
input_size 是转化字符为float32
bbox_loss_scale 的目的在与?

bbox_loss_sacle的目的在是作为一个所有边界框损失的惩罚项,在对小目标框优化时,增加的损失更加地大,越容易优化目标,而对大目标优化时,惩罚项较小,优化不是很明显.所以这一项对于小目标优化更加明显一些.

其它的都使用了二值交叉熵损失函数计算损失;

2.1.6 def compute_loss()
这个函数的目的就是给损失函数层中输入数据,计算损失。

参考博客
1.https://blog.csdn.net/grey_csdn/article/details/77074707
2.https://blog.csdn.net/UESTC_C2_403/article/details/72328815
3.https://blog.csdn.net/zz2230633069/article/details/81414330
4.https://blog.csdn.net/computerme/article/details/80836060
5.https://zhuanlan.zhihu.com/p/49995236

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值