【深度学习系列(四)】:基于tensorflow的YOLOV3实现 (3):YOLOV3模型优化

通过上一篇文章:【深度学习系列(四)】:基于tensorflow的YOLOV3实现 (2):YOLOV3框架结构,我们基本了解了YOLOV3网络在进行前向运算的细节,这里具体说明下YOLOV3在预测过程中的流程:

  1. 输入一张任意大小图片,保持长宽比不变的情况下,缩放至 w 或 h 达到416,再覆盖在416*416的新图上,作为网络的输入。即网络的输入是一张416*416,3通道的RGB图。
  2. 运行网络。YOLO的CNN网络把图片分成 S*S 个网格(yolov3多尺度预测,输出3层,每层 S * S个网格,分别为 13*13 ,26 *26 ,52*52),然后每个单元格负责去检测那些中心点落在该格子内的目标,如图所示。每个单元格需要测 3*(4+1+B)个值。如果将输入图片划分为 S*S 网格,那么每层最终预测值为 S*S*3*(4+1+B) 大小的张量。B为类别数(coco集为80类),即B=80。3 为每层anchorbox数量,4 为边界框大小和位置(x , y , w , h )1 为置信度。 
  3.  通过NMS,非极大值抑制,筛选出框boxes,输出框class_boxes和置信度class_box_scores,再生成类别信息classes,生成最终的检测数据框,并返回。

当我们直到网络如何进行计算时,是否还在思考如何来学习这个网络呢?这篇文章将着重介绍下如何学习YOLOV3网络,即YOLOV3的网络的优化。


1、loss function

在YOLOV3的论文里没有明确提所用的损失函数,但我们可以从YOLOV1的论文中看到相关损失函数的,如下图所示。


 

图片信息来源:yolov--12--YOLOv3的原理深度剖析和关键点讲解

从公式中我们可以知道,YOLO系列的损失函数主要由三部分组成:方框位置损失、置信度损失以及分类损失组成。对于位置和置信度损失采用SSE计算,而对于分类损失,在预测对象类别时不使用softmax,YOLOV3采用改成使用logistic的输出进行预测。

为什么对象分类softmax改成logistic?

早期YOLO,作者曾用softmax获取类别得分并用最大得分的标签来表示包含再边界框内的目标,在YOLOv3中,这种做法被修正。
softmax来分类依赖于这样一个前提,即分类是相互独立的,换句话说,如果一个目标属于一种类别,那么它就不能属于另一种。
但是,当我们的数据集中存在人或女人的标签时,上面所提到的前提就是去了意义。这就是作者为什么不用softmax,而用logistic regression来预测每个类别得分并使用一个阈值来对目标进行多标签预测。比阈值高的类别就是这个边界框真正的类别。
用简单一点的语言来说,其实就是对每种类别使用二分类的logistic回归,即你要么是这种类别要么就不是,然后便利所有类别,得到所有类别的得分,然后选取大于阈值的类别就好了。
logistic回归用于对anchor包围的部分进行一个目标性评分(objectness score),即这块位置是目标的可能性有多大。这一步是在predict之前进行的,可以去掉不必要anchor,可以减少计算量。
如果模板框不是最佳的即使它超过我们设定的阈值,我们还是不会对它进行predict。
不同于faster R-CNN的是,yolo_v3只会对1个prior进行操作,也就是那个最佳prior。而logistic回归就是用来从9个anchor priors中找到objectness score(目标存在可能性得分)最高的那一个。logistic回归就是用曲线对prior相对于 objectness score映射关系的线性建模。


原文链接:https://blog.csdn.net/litt1e/article/details/88907542

接下来,我们将详细讨论该损失函数是怎样计算的。我们知道,在目标检测任务里,有几个关键信息是需要确定的:(x,y,w,h,confidence,classes),这也是我们前一篇文章中得到的预测结果。这里我们假设预测结果为y_pred,维数为batch*        grid_h*gride_w*n_box*80,其中batch表示预测的批量大小,grid_hgride_w表示网格的大小,n_box表示对应的锚点框个数,一般n_box=3对应的标签为y_true,维数为batch*80。整体的loss计算如下:

代码位于yololoss.py中:

ignore_thresh=0.5
grid_scale=1
obj_scale=5
noobj_scale=1
xywh_scale=1
class_scale=1
def lossCalculator(y_true, y_pred, anchors,image_size):
    '''
    计算预测与标签之间的损失
    :param y_true: 标签
    :param y_pred: 预测结果
    :param anchors: 锚点框
    :param image_size: 原图像大小[416,416]
    :return:
    '''
    #使预测与标签一一对应
    y_pred=tf.reshape(y_pred,y_true.shape)

    object_mask=tf.expand_dims(y_true[...,4],4) #置信度
    preds=adjust_pred_tensor(y_pred) #预测结果预处理

    conf_delta = conf_delta_tensor(y_true, preds, anchors, ignore_thresh)
    wh_scale = wh_scale_tensor(y_true[..., 2:4], anchors, image_size)
    
    #计算方框损失
    loss_box = loss_coord_tensor(object_mask, preds[..., :4], y_true[..., :4], wh_scale, xywh_scale)
    #计算置信度损失
    loss_conf = loss_conf_tensor(object_mask, preds[..., 4], y_true[..., 4], obj_scale, noobj_scale, conf_delta)
    #计算分类损失
    loss_class = loss_class_tensor(object_mask, preds[..., 5:], y_true[..., 5:], class_scale)
    loss = loss_box + loss_conf + loss_class

    return loss * grid_scale

#生成带序号的网格
def _create_mesh_xy(batch_size,grid_h,grid_w,n_box):
    mesh_x = tf.cast(tf.reshape(tf.tile(tf.range(grid_w), [grid_h]), (1, grid_h, grid_w, 1, 1)), tf.float32)
    mesh_y = tf.transpose(mesh_x, (0, 2, 1, 3, 4))
    mesh_xy = tf.tile(tf.concat([mesh_x, mesh_y], -1), [batch_size, 1, 1, n_box, 1])
    return mesh_xy

#将网格信息融入坐标,置信度做sigmoid。并重新组合
def adjust_pred_tensor(y_pred):
    grid_offset=_create_mesh_xy(*y_pred.shape[:4])
    # 计算该尺度矩阵上的坐标sigma(t_xy) + c_xy
    pred_xy=grid_offset+tf.sigmoid(y_pred[...,:2])
    # 取出预测物体的尺寸t_wh
    pred_wh=y_pred[...,2:4]
    # 对分类概率(置信度)做sigmoid转化
    pred_conf=tf.sigmoid(y_pred[...,4])
    # 取出分类结果
    pred_classes=y_pred[...,5:]

    #重新组合
    preds=tf.concat([pred_xy,pred_wh,tf.expand_dims(pred_conf,axis=-1),pred_classes],axis=-1)

    return preds

#生成一个矩阵。每个格子里放有3个候选框
def _create_mesh_anchor(anchors, batch_size, grid_h, grid_w, n_box):
    mesh_anchor = tf.tile(anchors, [batch_size*grid_h*grid_w])
    mesh_anchor = tf.reshape(mesh_anchor, [batch_size, grid_h, grid_w, n_box, 2])#每个候选框有2个值
    mesh_anchor = tf.cast(mesh_anchor, tf.float32)
    return mesh_anchor

def conf_delta_tensor(y_true, y_pred, anchors, ignore_thresh):

    pred_box_xy, pred_box_wh, pred_box_conf = y_pred[..., :2], y_pred[..., 2:4], y_pred[..., 4]
    #带有候选框的格子矩阵
    anchor_grid = _create_mesh_anchor(anchors, *y_pred.shape[:4])#y_pred.shape为(2,13,13,3,15)
    true_wh = y_true[:,:,:,:,2:4]
    true_wh = anchor_grid * tf.exp(true_wh)
    true_wh = true_wh * tf.expand_dims(y_true[:,:,:,:,4], 4)#还原真实尺寸,高和宽
    anchors_ = tf.constant(anchors, dtype='float', shape=[1,1,1,y_pred.shape[3],2])#y_pred.shape[3]为候选框个数
    true_xy = y_true[..., 0:2]#获取中心点
    true_wh_half = true_wh / 2.
    true_mins    = true_xy - true_wh_half#计算起始坐标
    true_maxes   = true_xy + true_wh_half#计算尾部坐标

    pred_xy = pred_box_xy
    pred_wh = tf.exp(pred_box_wh) * anchors_

    pred_wh_half = pred_wh / 2.
    pred_mins    = pred_xy - pred_wh_half#计算起始坐标
    pred_maxes   = pred_xy + pred_wh_half#计算尾部坐标

    intersect_mins  = tf.maximum(pred_mins,  true_mins)
    intersect_maxes = tf.minimum(pred_maxes, true_maxes)

    #计算重叠面积
    intersect_wh    = tf.maximum(intersect_maxes - intersect_mins, 0.)
    intersect_areas = intersect_wh[..., 0] * intersect_wh[..., 1]

    true_areas = true_wh[..., 0] * true_wh[..., 1]
    pred_areas = pred_wh[..., 0] * pred_wh[..., 1]
    #计算不重叠面积
    union_areas = pred_areas + true_areas - intersect_areas
    best_ious  = tf.truediv(intersect_areas, union_areas)#计算iou
    #ios小于阈值将作为负向的loss
    conf_delta = pred_box_conf * tf.cast(best_ious < ignore_thresh,tf.float32)
    return conf_delta

def wh_scale_tensor(true_box_wh, anchors, image_size):
    image_size_  = tf.reshape(tf.cast(image_size, tf.float32), [1,1,1,1,2])
    anchors_ = tf.constant(anchors, dtype='float', shape=[1,1,1,3,2])

    #计算高和宽的缩放范围
    wh_scale = tf.exp(true_box_wh) * anchors_ / image_size_
    #物体尺寸占整个图片的面积比
    wh_scale = tf.expand_dims(2 - wh_scale[..., 0] * wh_scale[..., 1], axis=4)
    return wh_scale
  • loss_coord_tensor() 计算方框损失
#位置loss为box之差乘缩放比,所得的结果,再进行平方求和
def loss_coord_tensor(object_mask, pred_box, true_box, wh_scale, xywh_scale):
    xy_delta=object_mask*(pred_box-true_box)*wh_scale*xywh_scale
    loss_xy=tf.reduce_sum(tf.square(xy_delta),list(range(1,5)))

    return loss_xy
  • loss_conf_tensor() 计算置信度损失
def loss_conf_tensor(object_mask, pred_box_conf, true_box_conf, obj_scale, noobj_scale, conf_delta):
    object_mask_ = tf.squeeze(object_mask, axis=-1)
    conf_delta=object_mask_*(pred_box_conf-true_box_conf) * obj_scale + (1-object_mask_) * conf_delta * noobj_scale
    loss_conf=tf.reduce_sum(tf.square(conf_delta),list(range(1,4)))
    return loss_conf
  • loss_class_tensor() 计算分类损失
    
def loss_class_tensor(object_mask, pred_box_class, true_box_class, class_scale):
    true_box_class_ = tf.cast(true_box_class, tf.int64)

    class_delta = object_mask *\
                  tf.expand_dims(tf.nn.softmax_cross_entropy_with_logits_v2(labels=true_box_class_, logits=pred_box_class), 4) * \
                  class_scale

    loss_class = tf.reduce_sum(class_delta, list(range(1, 5)))
    return loss_class

最终我们将三种尺寸矩阵的损失相见再开方得到最终的结果:

def loss_fn(list_y_trues, list_y_preds,anchors,image_size):
    inputanchors = [anchors[12:], anchors[6:12], anchors[:6]]

    losses = [lossCalculator(list_y_trues[i], list_y_preds[i], inputanchors[i], image_size) for i in
              range(len(list_y_trues))]

    return tf.sqrt(tf.reduce_sum(losses))  # 将三个矩阵的loss相加再开平方

YOLOV3在计算loss过程中的流程总结如下:

(1)遍历YOLOV3预测列表和样本标签列表;

(2)从两个列表中取出对应矩阵;

(3)将对应矩阵和对应的候选框一起传入loss函数中计算loss;

(4)将每个矩阵的loss值结果相加,再开方,得到最终的结果。

参考链接:

YOLOv3庖丁解牛(三):YOLOv3损失函数

yolo系列之yolo v3【深度解析】

YOLOv3 Loss构建详解

2、模型训练

当你读到这时,恭喜你已经对整个网络已经足够了解了。接下来,只是了解怎么训练这个网络,放轻松,这部分不需要太费脑子,只要看看怎么运行就ok了。

#coding:utf-8

import os
import tensorflow as tf
import glob
from tqdm import tqdm
import  tensorflow.contrib.eager as tfe

from generator import BatchGenerator
from yolov3 import Yolonet
from yololoss import loss_fn


tf.enable_eager_execution()

PROJECT_ROOT = os.path.dirname(__file__)#获取当前目录
print(PROJECT_ROOT)

#定义coco锚点候选框
COCO_ANCHORS = [10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90, 156,198, 373,326]

#定义预训练模型路径
YOLOV3_WEIGHTS = os.path.join(PROJECT_ROOT, "yolov3.weights")

#定义分类
LABELS = ['0',"1", "2", "3",'4','5','6','7','8', "9"]

#定义样本路径
ann_dir = os.path.join(PROJECT_ROOT,  "data", "ann", "*.xml")
img_dir = os.path.join(PROJECT_ROOT,  "data", "img")

train_ann_fnames = glob.glob(ann_dir)#获取该路径下的xml文件
imgsize =416
batch_size =2
#制作数据集
generator = BatchGenerator(train_ann_fnames,img_dir,
                           net_size=imgsize,
                           anchors=COCO_ANCHORS,
                             batch_size=2,
                             labels=LABELS,
                             jitter = False)#随机变化尺寸。数据增强

#定义训练参数
learning_rate = 1e-4  #定义学习率
num_epoches =85       #定义迭代次数
save_dir = "./model"  #定义模型路径

#循环整个数据集,进行loss值验证
def _loop_validation(model,generator):
    n_steps=generator.steps_per_epoch
    loss_value=0

    for _ in range(n_steps):#按批次循环
        xs,yolo_1,yolo_2,yolo_3=generator.next_batch()
        xs=tf.convert_to_tensor(xs)
        yolo_1 = tf.convert_to_tensor(yolo_1)
        yolo_2 = tf.convert_to_tensor(yolo_2)
        yolo_3 = tf.convert_to_tensor(yolo_3)
        ys=[yolo_1,yolo_2,yolo_3]
        ys_=model(xs)
        loss_value+=loss_fn(ys,ys_,anchors=COCO_ANCHORS,image_size=[imgsize,imgsize])

    loss_value/=generator.steps_per_epoch
    return loss_value

def _loop_train(model,optimizer,generator,grad):
    n_steps=generator.steps_per_epoch
    for _ in tqdm(range(n_steps)):
        xs, yolo_1, yolo_2, yolo_3 = generator.next_batch()
        xs = tf.convert_to_tensor(xs)
        yolo_1 = tf.convert_to_tensor(yolo_1)
        yolo_2 = tf.convert_to_tensor(yolo_2)
        yolo_3 = tf.convert_to_tensor(yolo_3)
        ys = [yolo_1, yolo_2, yolo_3]
        optimizer.apply_gradients(grad(model, xs, ys))

if not os.path.exists(save_dir):
    os.makedirs(save_dir)
save_fname=os.path.join(save_dir,"weights")

yolo_v3=Yolonet(n_classes=len(LABELS))
yolo_v3.load_darknet_params(YOLOV3_WEIGHTS,skip_detect_layer=True)#加载预训练模型

#定义优化器
optimizer=tf.train.AdamOptimizer(learning_rate=learning_rate)

#定义损失函数loss
def _grad_fn(yolo_v3,images_tensor,list_y_trues):
    logits=yolo_v3(images_tensor)
    loss=loss_fn(list_y_trues,logits,anchors=COCO_ANCHORS,image_size=[imgsize,imgsize])
    return loss

grad=tfe.tfe.implicit_gradients(_grad_fn)#获得计算梯度的函数

history=[]
for i in range(num_epoches):
    _loop_train(yolo_v3,optimizer,generator,grad)#训练

    loss_value=_loop_validation(yolo_v3,generator)
    print("{}-th loss = {}".format(i, loss_value))

    # 收集loss

    history.append(loss_value)

    if loss_value == min(history):  #只有loss创新低时再保存模型
        print("    update weight {}".format(loss_value))
        yolo_v3.save_weights("{}.h5".format(save_fname))



 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值