当前位置: 首页 > news >正文

人工智能学习70-Yolo损失函数

人工智能学习70-Yolo损失函数 —快手视频
人工智能学习71-Yolo损失函数 —快手视频
人工智能学习72-Yolo损失函数 —快手视频
人工智能学习73-Yolo损失函数 —快手视频

Yolo算法损失函数

损失函数是指定Yolo网络训练的标尺,故此损失函数定义非常关键,Yolo网络主要完成目标检测,目标检测涉及量化指标包括包含物体的预测框Box(Box的中心坐标x,y,Box的宽高数据);每个规格特征图提供三个先验框,先验框中是否包含物体的置信度;预测框Box中包含物体的类别。因此损失函数需要度量这三方面量化指标预测框Box位置及大小,是否包含物体置信度confidence,预测框Box中物体类别指标。
在这里插入图片描述

损失函数定义

yolo_training.py

import math
from functools import partialimport tensorflow as tf
from keras import backend as K
from utils_bbox import get_anchors_and_decode# ---------------------------------------------------#
#   box_ciou包含两个矩形交并比例,还有一个修正值,修正值具体含义有待深究
#   box_iou与box_ciou优缺点可参考
#   https://zhuanlan.zhihu.com/p/648882134
# ---------------------------------------------------#
def box_ciou(b1, b2):"""输入为:----------b1: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywhb2: tensor, shape=(batch, feat_w, feat_h, anchor_num, 4), xywh返回为:-------ciou: tensor, shape=(batch, feat_w, feat_h, anchor_num, 1)"""# -----------------------------------------------------------##   求出预测框左上角右下角#   b1_mins     (batch, feat_w, feat_h, anchor_num, 2)#   b1_maxes    (batch, feat_w, feat_h, anchor_num, 2)# -----------------------------------------------------------#b1_xy = b1[..., :2]  # 矩形b1中心的坐标 0=x,1=yb1_wh = b1[..., 2:4]  # 取2-4两个元素,对应宽高 2=w,3=hb1_wh_half = b1_wh/2.  # 宽与高都取半数b1_mins = b1_xy - b1_wh_half  # 矩形框左上角坐标b1_maxes = b1_xy + b1_wh_half  # 矩形框右下角坐标# -----------------------------------------------------------##   求出真实框左上角右下角#   b2_mins     (batch, feat_w, feat_h, anchor_num, 2)#   b2_maxes    (batch, feat_w, feat_h, anchor_num, 2)# -----------------------------------------------------------#b2_xy = b2[..., :2]  # 矩形b2中心的坐标 0=x,1=yb2_wh = b2[..., 2:4]  # 矩形b2宽高 2=2,3=hb2_wh_half = b2_wh/2.b2_mins = b2_xy - b2_wh_half  # 左上角坐标b2_maxes = b2_xy + b2_wh_half  # 右下角坐标# -----------------------------------------------------------##   求真实框和预测框所有的iou#   iou         (batch, feat_w, feat_h, anchor_num)# -----------------------------------------------------------#intersect_mins = K.maximum(b1_mins, b2_mins)  # 取两个矩形框最左上角坐标最大值intersect_maxes = K.minimum(b1_maxes, b2_maxes)  # 取两个矩形框最右下角坐标最小值intersect_wh = K.maximum(intersect_maxes - intersect_mins, 0.)  # 取两个矩形框叠加后宽与高较大者intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]  # 计算两个矩形框组合后面积b1_area = b1_wh[..., 0] * b1_wh[..., 1]  # 矩形框1的面积b2_area = b2_wh[..., 0] * b2_wh[..., 1]  # 矩形框2的面积union_area = b1_area + b2_area - intersect_area  # 取两个矩形框面积的并集,去掉一份重回部分面积iou = intersect_area / K.maximum(union_area, K.epsilon())  # 计算两个矩形框组合后面积与重叠面积比例iou# -----------------------------------------------------------##   计算中心的差距#   center_distance (batch, feat_w, feat_h, anchor_num)# -----------------------------------------------------------#center_distance = K.sum(K.square(b1_xy - b2_xy), axis=-1)  # 计算两矩形框中心间距离的平方enclose_mins = K.minimum(b1_mins, b2_mins)  # 获取两矩形框最左上角坐标enclose_maxes = K.maximum(b1_maxes, b2_maxes)  # 获取两矩形框最右下角坐标enclose_wh = K.maximum(enclose_maxes - enclose_mins, 0.0)  # 获取两矩形框组合最大宽与高# -----------------------------------------------------------##   计算预测框与真实框组合最大矩形对角线距离平方和#   enclose_diagonal (batch, feat_w, feat_h, anchor_num)#   K.epsilon() 返回数值表达式中使用的模糊因子的值# -----------------------------------------------------------#enclose_diagonal = K.sum(K.square(enclose_wh), axis=-1)  # 获取两矩形框组合宽与高平方和(w^2+h^2)# 其中 center_distance / K.maximum(enclose_diagonal, K.epsilon()) 代表# "两矩形中心之间距离平方" 与 "两矩形组合后对角线平方和" 之比# 可理解为两矩形中心偏离程度比例# iou代表两矩形交集与并集面积之比# ciou代表交并比例减去两矩形中心偏离程度比例,ciou = iou - 1.0 * center_distance / K.maximum(enclose_diagonal, K.epsilon())# (atan2计算 矩形1的w/h反正切值, 与矩形2的w/h反正切值之差)的平方# 变量v是此平方值与math.pi平方之比的4倍# 变量v代表两个矩形宽与高比例平均值的4倍v = 4 * K.square(tf.math.atan2(b1_wh[..., 0], K.maximum(b1_wh[..., 1], K.epsilon())) - tf.math.atan2(b2_wh[..., 0],K.maximum(b2_wh[..., 1],K.epsilon()))) / (math.pi * math.pi)#alpha为一比例值,计算为 v/(1.0-iou+v)alpha = v / K.maximum((1.0 - iou + v), K.epsilon())#ciou重新赋值,其值减去alpha*vciou = ciou - alpha * v#变量ciou在最后轴添加一维度,具体含义包含两矩形交并比例,还有一定修正值,更深层含义待分析ciou = K.expand_dims(ciou, -1)return ciou# ---------------------------------------------------#
#   用于计算两个矩形交并比例
#   当预测框与真实框不存在重叠时,方法box_iou存在缺陷
# ---------------------------------------------------#
def box_iou(b1, b2):# ---------------------------------------------------##   num_anchor,1,4#   计算左上角的坐标和右下角的坐标# ---------------------------------------------------#b1          = K.expand_dims(b1, -2)b1_xy       = b1[..., :2]  # 取前两个元素b1_wh       = b1[..., 2:4]  # 取2-4两个元素b1_wh_half  = b1_wh/2.b1_mins     = b1_xy - b1_wh_halfb1_maxes    = b1_xy + b1_wh_half# ---------------------------------------------------##   1,n,4#   计算左上角和右下角的坐标# ---------------------------------------------------#b2          = K.expand_dims(b2, 0)b2_xy       = b2[..., :2]b2_wh       = b2[..., 2:4]b2_wh_half  = b2_wh/2.b2_mins     = b2_xy - b2_wh_halfb2_maxes    = b2_xy + b2_wh_half# ---------------------------------------------------##   计算重合面积# ---------------------------------------------------#intersect_mins  = K.maximum(b1_mins, b2_mins)intersect_maxes = K.minimum(b1_maxes, b2_maxes)intersect_wh    = K.maximum(intersect_maxes - intersect_mins, 0.)intersect_area  = intersect_wh[..., 0] * intersect_wh[..., 1]b1_area         = b1_wh[..., 0] * b1_wh[..., 1]  # 计算矩形框1的面积b2_area         = b2_wh[..., 0] * b2_wh[..., 1]  # 计算矩形框2的面积iou             = intersect_area / (b1_area + b2_area - intersect_area) #两矩形交叉面积与总面积比例return iou#---------------------------------------------------#
#   loss值计算
#---------------------------------------------------#
def yolo_loss(args, # 由nets/yolo.py中[*model_body.output, *y_true]打包成的列表input_shape, anchors, anchors_mask, num_classes, ignore_thresh   = 0.5,balance         = [0.4, 1.0, 4],box_ratio       = 0.05, obj_ratio       = 1, cls_ratio       = 0.5 / 4, ciou_flag       = True, print_loss      = False
):print('2...args=', (args))#2...args= [<tf.Tensor 'conv2d_59/BiasAdd:0' shape=(?, ?, ?, 255) dtype=float32>,# <tf.Tensor 'conv2d_67/BiasAdd:0' shape=(?, ?, ?, 255) dtype=float32>,# <tf.Tensor 'conv2d_75/BiasAdd:0' shape=(?, ?, ?, 255) dtype=float32>,# <tf.Tensor 'input_2:0' shape=(?, 13, 13, 3, 85) dtype=float32>,# <tf.Tensor 'input_3:0' shape=(?, 26, 26, 3, 85) dtype=float32>,# <tf.Tensor 'input_4:0' shape=(?, 52, 52, 3, 85) dtype=float32>]print('yolo_training.py yolo_loss() anchors_mask=',anchors_mask)#yolo_training.py yolo_loss() anchors_mask= [[6, 7, 8], [3, 4, 5], [0, 1, 2]]num_layers      = len(anchors_mask)# ---------------------------------------------------------------------------------------------------##   将预测结果和实际ground truth分开,args是[*model_body.output, *y_true]#   y_true是一个标签列表,包含三个特征层,shape分别为:#   (m,13,13,3,85)#   (m,26,26,3,85)#   (m,52,52,3,85)#   yolo_outputs是一个预测列表,包含三个特征层,shape分别为:#   (m,13,13,3,85)#   (m,26,26,3,85)#   (m,52,52,3,85)#   y_true中x,y是离散取值的;yolo_outputs中x,y是连续取值的# ---------------------------------------------------------------------------------------------------#y_true          = args[num_layers:]  # 列表中后三个数据,标签数据yolo_outputs    = args[:num_layers]  # 列表中前三个数据,模型预测数据# -----------------------------------------------------------##   得到input_shape为416,416#   k.cast 将张量强制转换为不同的类型并返回# -----------------------------------------------------------#input_shape = K.cast(input_shape, K.dtype(y_true[0]))# -----------------------------------------------------------##   得到网格的shape为[13,13]; [26,26]; [52,52]#   循环遍历每层# -----------------------------------------------------------#grid_shapes = [K.cast(K.shape(yolo_outputs[l])[1:3], K.dtype(y_true[0])) for l in range(num_layers)]#yolo_training.py yolo_loss() grid_shapes=# [<tf.Tensor 'yolo_loss/Cast_1:0' shape=(2,) dtype=float32>,# <tf.Tensor 'yolo_loss/Cast_2:0' shape=(2,) dtype=float32>,# <tf.Tensor 'yolo_loss/Cast_3:0' shape=(2,) dtype=float32>]# -----------------------------------------------------------##   取出图片数量#   m的值就是batch_size#   K.shape 返回张量或变量的符号形状# -----------------------------------------------------------#m = K.shape(yolo_outputs[0])[0]loss    = 0  #损失标量# ---------------------------------------------------------------------------------------------------##   遍历3个特征层#   y_true是标签列表,包含三个特征层,shape分别为(m,13,13,3,85),(m,26,26,3,85),(m,52,52,3,85)。离散取值的#   yolo_outputs是预测列表,包含三个特征层,shape分别为(m,13,13,3,85),(m,26,26,3,85),(m,52,52,3,85)。连续取值的# ---------------------------------------------------------------------------------------------------#for l in range(num_layers):# -----------------------------------------------------------##   以第一个特征层(m,13,13,3,85)为例子#   取出该特征层中存在目标的点的位置。(m,13,13,3,1)# -----------------------------------------------------------#object_mask         = y_true[l][..., 4:5]  # y_true张量中第5个元素,真实框内是否存在物体置信度#  object_mask=== Tensor("yolo_loss/strided_slice_4:0", shape=(?, 13, 13, 3, 1), dtype=float32)#  object_mask=== Tensor("yolo_loss/strided_slice_41:0", shape=(?, 26, 26, 3, 1), dtype=float32)#  object_mask=== Tensor("yolo_loss/strided_slice_78:0", shape=(?, 52, 52, 3, 1), dtype=float32)# -----------------------------------------------------------##   取出其对应的种类(m,13,13,3,80)#   y_true张量中第5个元素以后所有元素,真实框内存在物体的种类,最后一维度第5元素以后是物体种类数据,一共80个物体分类# -----------------------------------------------------------#true_class_probs    = y_true[l][..., 5:]#  true_class_probs= Tensor("yolo_loss/strided_slice_5:0", shape=(?, 13, 13, 3, 80), dtype=float32)#  true_class_probs= Tensor("yolo_loss/strided_slice_42:0", shape=(?, 26, 26, 3, 80), dtype=float32)#  true_class_probs= Tensor("yolo_loss/strided_slice_79:0", shape=(?, 52, 52, 3, 80), dtype=float32)# -----------------------------------------------------------##   根据yolo_outputs的特征层和先验框anchors获取预测框归一化数据#   get_anchors_and_decode训练时:返回归一化grid, feats, box_xy, box_wh#   其中:#   grid        (13,13,3,2) 网格坐标#   raw_pred    (m,13,13,3,85) 尚未处理的预测结果#   pred_xy     (m,13,13,3,2) 解码后的中心坐标#   pred_wh     (m,13,13,3,2) 解码后的宽高坐标# -----------------------------------------------------------#grid, raw_pred, pred_xy, pred_wh = get_anchors_and_decode(yolo_outputs[l],anchors[anchors_mask[l]], num_classes, input_shape, calc_loss=True)# -----------------------------------------------------------##   预测框pred_box,返回归一化数据#   (m,13,13,3,4)# -----------------------------------------------------------#pred_box = K.concatenate([pred_xy, pred_wh])# -----------------------------------------------------------##   找到负样本群组,第一步是创建一个数组,[]# -----------------------------------------------------------#ignore_mask = tf.TensorArray(K.dtype(y_true[0]), size=1, dynamic_size=True)# 此层特征图是否存在物体,将张量转化为布尔类型# object_mask_bool.shape=(?, 13, 13, 3, 1), dtype=bool# object_mask_bool.shape=(?, 26, 26, 3, 1), dtype=bool# object_mask_bool.shape=(?, 52, 52, 3, 1), dtype=boolobject_mask_bool = K.cast(object_mask, 'bool')# -----------------------------------------------------------##   对每一张图片计算ignore_mask# -----------------------------------------------------------#def loop_body(b, ignore_mask):# -----------------------------------------------------------##   b为图片数量维度,取出n个真实框:n,4;前四维度为x,y,w,h;object_mask_bool存储是否存在物体#   1-D 例程#   tensor = [0, 1, 2, 3]#   mask = np.array([True, False, True, False])#   boolean_mask(tensor, mask)  # [0, 2]#   true_box代表存在物体的真实框#   object_mask_bool[b, ..., 0].shape=(13, 13, 3), dtype=bool#   object_mask_bool[b, ..., 0].shape=(26, 26, 3), dtype=bool#   object_mask_bool[b, ..., 0].shape=(52, 52, 3), dtype=bool# -----------------------------------------------------------#true_box = tf.boolean_mask(y_true[l][b, ..., 0:4], object_mask_bool[b, ..., 0])# -----------------------------------------------------------##   计算预测框与真实框的iou的交并比例,数据都是归一化的数据#   pred_box    13,13,3,4 预测框的坐标#   true_box    n,4 真实框的坐标#   iou         shape=(?, ?, 3, ?) 预测框和真实框的iou,其中3代表3个特征层预测,每个特征层有3个先验框# -----------------------------------------------------------#iou = box_iou(pred_box[b], true_box)# -----------------------------------------------------------##   best_iou    shape=(?, ?, 3) 每个特征点与真实框的最大重合程度,其中3代表每一特征层3个预测框# -----------------------------------------------------------#best_iou = K.max(iou, axis=-1)# -----------------------------------------------------------##   判断预测框和真实框的最大iou小于ignore_thresh#   则认为该预测框没有与之对应的真实框#   该操作的目的是:#   忽略预测结果与真实框非常对应特征点,因为这些框已经比较准了#   不适合当作负样本,所以忽略掉。#   k.cast(x,dtype) 转化张量为dtype类型#   tf.TensorArray.write(pos,val)通过序号索引b向数组tf.TensorArray写入值val#   ignore_mask 存储0.0或1.0两类数据# -----------------------------------------------------------#ignore_mask = ignore_mask.write(b, K.cast(best_iou<ignore_thresh, K.dtype(true_box)))return b+1, ignore_mask# -----------------------------------------------------------##   在这个地方进行一个循环、循环是对每一张图片进行的.返回值_, ignore_mask就是loop_body的返回值#   tf.while_loop(cond,  body, loop_vars) 条件cond为真循环执行body,loop_vars为body传入参数#   可以这样理解:#   loop_vars = []#   while cond(loop_vars):#    loop_vars = body(loop_vars)#   即loop_vars参数先传入cond 判断条件是否成立,成立之后,把 loop_vars参数传入body 执行操作, 然后返回 操作后的 loop_vars 参数,#   即loop_vars参数已被更新,再把更新后的参数传入cond, 依次循环,直到不满足条件。##   b初始值为0,m是图片数量,将[0, ignore_mask]传递给(lambda b, *args: b < m),条件为真时,再将[0, ignore_mask]传递给loop_body#   计算b和ignore_mask并更新它们,再次循环判断(lambda b, *args: b < m)是否为真,决定是否继续执行下去# -----------------------------------------------------------#_, ignore_mask = tf.while_loop(lambda b, *args: b < m, loop_body, [0, ignore_mask])# -----------------------------------------------------------##   ignore_mask用于提取出作为负样本的特征点#   (m,13,13,3)# -----------------------------------------------------------#ignore_mask = ignore_mask.stack()  #shape=(?, ?, ?, 3), dtype=float32#  k.expand_dims(x, axis=-1)  在最后轴扩充一维度ignore_mask = K.expand_dims(ignore_mask, -1) #shape=(?, ?, ?, 3, 1), dtype=float32)# -----------------------------------------------------------##   y_true[l][..., 2:3]和y_true[l][..., 3:4]#   表示真实框的宽高,二者均在0-1之间#   真实框越大,比重越小,小框的比重更大。#   box_loss_scale定义为2-真实框w*h# -----------------------------------------------------------#box_loss_scale = 2 - y_true[l][..., 2:3] * y_true[l][..., 3:4]if ciou_flag: #如果使用ciou计算交并比例# -----------------------------------------------------------##   计算Ciou loss#   k.sum(x, axis=None, keepdims=False)  沿某轴合并张量求和# -----------------------------------------------------------#raw_true_box    = y_true[l][..., 0:4] #真实框x,y,w,h,都是归一化数据ciou            = box_ciou(pred_box, raw_true_box)ciou_loss       = object_mask * (1 - ciou)location_loss   = K.sum(ciou_loss)else:# -----------------------------------------------------------##   将真实框进行编码,使其格式与预测的相同,后面用于计算loss#   k.log(x) 求x对数# -----------------------------------------------------------#raw_true_xy     = y_true[l][..., :2] * grid_shapes[l][::-1] - grid #转化为相对网格数# 转化为raw_pred数据规格,pred_wh是tf.exp(x)* anchors_tensor/input_shape[::-1]  x=feats[..., 2:4] 作为真数,真正x需要求对数# y_true[l][..., 2:4]*input_shape[::-1] / anchors_tensor 转化为归一化数据作为真数,需要求对数raw_true_wh     = K.log(y_true[l][..., 2:4] / anchors[anchors_mask[l]] * input_shape[::-1])# -----------------------------------------------------------##   object_mask如果真实存在目标则保存其wh值#   switch接口,就是一个if/else条件判断语句#   k.switch(condition, then_expression, else_expression) 根据标量值在两个操作之间切换#   k.zeros_like 初始化全部为0# -----------------------------------------------------------#raw_true_wh     = K.switch(object_mask, raw_true_wh, K.zeros_like(raw_true_wh))# -----------------------------------------------------------##   利用binary_crossentropy计算中心点偏移情况,效果更好#   k.binary_crossentropy(target, output, from_logits=False) 输出张量和目标张量之间的二进制交叉熵# -----------------------------------------------------------#xy_loss         = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[..., 0:2], from_logits=True)# -----------------------------------------------------------##   wh_loss用于计算宽高损失#   k.square(x) 求x平方# -----------------------------------------------------------#wh_loss         = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh - raw_pred[..., 2:4])location_loss   = (K.sum(xy_loss) + K.sum(wh_loss)) * 0.1# ------------------------------------------------------------------------------##   如果该位置本来有框,那么计算1与置信度的交叉熵#   如果该位置本来没有框,那么计算0与置信度的交叉熵#   在这其中会忽略一部分样本,这些被忽略的样本满足条件best_iou<ignore_thresh#   该操作的目的是:#   忽略预测结果与真实框非常对应特征点,因为这些框已经比较准了#   不适合当作负样本,所以忽略掉。# ------------------------------------------------------------------------------#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_maskclass_loss      = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[..., 5:], from_logits=True)# -----------------------------------------------------------##   计算正样本数量,负样本数量#   tf.maximum(x, y, name=None) 求x,y中最大者# -----------------------------------------------------------#num_pos         = tf.maximum(K.sum(K.cast(object_mask, tf.float32)), 1)num_neg         = tf.maximum(K.sum(K.cast((1 - object_mask) * ignore_mask, tf.float32)), 1)# -----------------------------------------------------------##   将所有损失求和# -----------------------------------------------------------#location_loss   = location_loss * box_ratio / num_pos #位置损失*box比率/正样本数confidence_loss = K.sum(confidence_loss) * balance[l] * obj_ratio / (num_pos + num_neg) #物体置信度*平衡系数*物体比率/总样本数class_loss      = K.sum(class_loss) * cls_ratio / num_pos / num_classes #分类损失*分类比率/正样本数/分类总数loss            += location_loss + confidence_loss + class_lossif print_loss:loss = tf.Print(loss, [loss, location_loss, confidence_loss, class_loss, tf.shape(ignore_mask)],summarize=100, message='loss: ')return lossdef get_lr_scheduler(lr_decay_type, lr, min_lr, total_iters, warmup_iters_ratio = 0.05, warmup_lr_ratio = 0.1,no_aug_iter_ratio = 0.05, step_num = 10):def yolox_warm_cos_lr(lr, min_lr, total_iters, warmup_total_iters, warmup_lr_start, no_aug_iter, iters):#如果迭代次数iters小于warmup_total_itersif iters <= warmup_total_iters:#调整学习率lrlr = (lr - warmup_lr_start) * pow(iters / float(warmup_total_iters), 2) + warmup_lr_startelif iters >= total_iters - no_aug_iter:#如果迭代次数iters大于总次数total_iters-no_aug_iter,取最小学习率lr = min_lrelse:#其他情况,调整学习率lrlr = min_lr + 0.5 * (lr - min_lr) * (1.0+ math.cos(math.pi* (iters - warmup_total_iters)/ (total_iters - warmup_total_iters - no_aug_iter)))return lr#根据迭代次数调整学习率def step_lr(lr, decay_rate, step_size, iters):if step_size < 1:raise ValueError("step_size must above 1.")n       = iters // step_sizeout_lr  = lr * decay_rate ** nreturn out_lrif lr_decay_type == "cos":warmup_total_iters  = min(max(warmup_iters_ratio * total_iters, 1), 3)warmup_lr_start     = max(warmup_lr_ratio * lr, 1e-6)no_aug_iter         = min(max(no_aug_iter_ratio * total_iters, 1), 15)func = partial(yolox_warm_cos_lr, lr, min_lr, total_iters, warmup_total_iters, warmup_lr_start, no_aug_iter)else:decay_rate  = (min_lr / lr) ** (1 / (step_num - 1))step_size   = total_iters / step_numfunc = partial(step_lr, lr, decay_rate, step_size)return func

代码解释部分

方法box_iou第122行
intersect_mins = K.maximum(b1_mins, b2_mins)
intersect_maxes = K.minimum(b1_maxes, b2_maxes)
intersect_wh = K.maximum(intersect_maxes - intersect_mins, 0.)
intersect_area = intersect_wh[…, 0] * intersect_wh[…, 1]
当两个预测框存在重叠时,使用box_iou计算交并比是正确的,如果两个预测框不存在重叠时,使用上述代码计算交并比是错误的,如下图:
在这里插入图片描述

方法box_ciou第62行
在这里插入图片描述

Box_ciou在box_iou基础上减去两个调整因子,一个是两Box中心距D2与外接矩形对角线D1的平方比。
在这里插入图片描述

二是将两个Box矩形宽高比例因素作为调整因子,两个矩形宽高比例差距越大,需要调整交并比例的数值就越大。
在这里插入图片描述

参考文档:https://zhuanlan.zhihu.com/p/648882134
在这里插入图片描述

方法yolo_loss第135行
Yolo算法损失函数是使用Lambda封装了方法yolo_loss作为网络的最后一层。由于需要自定义损失,故此必须将网络预测值和真实值y_true作为参数传递到方法yolo_loss。
在这里插入图片描述

参数args通过Lambda封装函数传入,在文件yolo_model.py的get_train_model()方法。
在这里插入图片描述

参数([*model_body.output, *y_true])将model_body.output与y_true封装为List列表作为参数传递到yolo_loss的args。model_body.output是网络预测输出,y_true是真实值替位符形式参数,其真实值是通过
train.py中的model.fit_generator()方法传入的。
调用过程:
参考keras.engine.training.py 第1951行 fit_generator --> 第2228行 self.train_on_batch -->第1883行self.train_function(ins) 将x,y连接
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

方法yolo_loss第181行
grid_shapes = [K.cast(K.shape(yolo_outputs[l])[1:3], K.dtype(y_true[0])) for l in range(num_layers)]
循环遍历每个层,将yolo_outputs中第1,2个元素(w,h)转化为y_true类型,生成三个列表List,分别是[13,13],[26,26],[52,52]。
例程:
yolo_outputs0 = np.random.normal(0, 10, 13 * 13 * 3 * 85)
yolo_outputs1 = np.random.normal(0, 10, 26 * 26 * 3 * 85)
yolo_outputs2 = np.random.normal(0, 10, 52 * 52 * 3 * 85)
yolo_outputs0 = yolo_outputs0.reshape(13, 13, 3, 85)
yolo_outputs1 = yolo_outputs1.reshape(26, 26, 3, 85)
yolo_outputs2 = yolo_outputs2.reshape(52, 52, 3, 85)
yolo_outputs = [yolo_outputs0, yolo_outputs1, yolo_outputs2]
yolo_outputs = np.array(yolo_outputs)
y_true = np.arange(0, 1, 1)
y_true = K.cast(y_true, tf.float32)
num_layers = 3
grid_shapes = [K.cast(K.shape(yolo_outputs[l])[1:3], K.dtype(y_true)) for l in range(num_layers)]
print(‘grid_shapes=’, grid_shapes)
with tf.Session() as sess:
print(‘yolo_outputs尺寸=’, K.shape(yolo_outputs[0]).eval() )
grid_shapes= [<tf.Tensor ‘Cast_2:0’ shape=(2,) dtype=float32>, <tf.Tensor ‘Cast_3:0’ shape=(2,) dtype=float32>, <tf.Tensor ‘Cast_4:0’ shape=(2,) dtype=float32>]
yolo_outputs尺寸= [13 13 3 85]

方法yolo_loss第224行

grid, raw_pred, pred_xy, pred_wh = get_anchors_and_decode(yolo_outputs[l],
anchors[anchors_mask[l]], num_classes, input_shape, calc_loss=True)
根据Yolo网络预测值返回归一化grid,feats,box_xy,box_wh。

方法yolo_loss第235行
ignore_mask = tf.TensorArray(K.dtype(y_true[0]), size=1, dynamic_size=True)

定义TensorArray数组
例程
a = tf.TensorArray(tf.float32, size=2, dynamic_size=True)
a = a.write(0, [0, 1]) # 这里的write需要赋值给对方.
a = a.write(1, [1, 0])
a = a.write(2, [1, 1])

read_value = a.read(0) # 读取某个索引下的值=[0. 1.]
stack_value = a.stack() #[[0. 1.] [1. 0.] [1. 1.]]
concat_value = a.concat() #[0. 1. 1. 0. 1. 1.]
gather_value = a.gather([1, 2]) # gather是look up的意思. [[1. 0.] [1. 1.]]

方法yolo_loss第255行
true_box = tf.boolean_mask(y_true[l][b, …, 0:4], object_mask_bool[b, …, 0])
object_mask_bool[b, …, 0]中…用法

  1. 省略号
    Ellipsis就是省略号(…),省略号(…)就是Ellipsis。而Ellipsis是ellipsis类的唯一实例(singleton object)
    print(type(…)) # output: <class ‘ellipsis’>
    print(Ellipsis == …) # True
    print(…) # Ellipsis

  2. 类型提示
    关于Python中的类型提示(type hints)详见【Python】作为动态语言,Python中的“类型声明”有什么用?。省略号(…)在类型提示中经常被使用
    from typing import Callable, Tuple

print(Callable[…, int]) # 输入参数随意,返回值为int
print(Tuple[int, …]) # int组成的元组

3.函数内部,相当于pass
def m1(): pass

def m2(): …

4.索引切片
nd = np.arange(13133*3)
nd = np.reshape(nd,(1, 13,13, 3, -1))
t = tf.convert_to_tensor(nd)
print(‘t===’, t)
print(‘t[0,…,0]=', t[0, …, 0]) 取最后一维第1个元素,返回shape=(13, 13, 3)
print('t[0,…,0]
=’, t[0,…,0:1]) 取最后一维第1个元素,返回shape=(13, 13, 3, 1)
print(‘t[0,…,0]===’, t[0, …, 0:3]) 取最后一维第1,2,3个元素,返回shape=(13, 13, 3, 1)

方法yolo_loss第288行
_, ignore_mask = tf.while_loop(lambda b, *args: b < m, loop_body, [0, ignore_mask])
b初始值为0,m是图片数量,将[0, ignore_mask]传递给(lambda b, *args: b < m),条件为真时,再将[0, ignore_mask]传递给loop_body,计算b和ignore_mask并更新它们,再次循环判断(lambda b, *args: b < m)是否为真,决定是否继续执行下去。

def loop_body(b, ignore_mask):
# -----------------------------------------------------------#
# b为图片数量维度,取出n个真实框:n,4;前四维度为x,y,w,h;object_mask_bool存储是否存在物体
# 1-D 例程
# tensor = [0, 1, 2, 3]
# mask = np.array([True, False, True, False])
# boolean_mask(tensor, mask) # [0, 2]
# true_box代表存在物体的真实框
# -----------------------------------------------------------#
true_box = tf.boolean_mask(y_true[l][b, …, 0:4], object_mask_bool[b, …, 0])
# -----------------------------------------------------------#
# 计算预测框与真实框的iou的交并比例,数据都是归一化的数据
# pred_box 13,13,3,4 预测框的坐标
# true_box n,4 真实框的坐标
# iou shape=(?, ?, 3, ?) 预测框和真实框的iou,其中3代表3个特征层预测,每个特征层有3个先验框
# -----------------------------------------------------------#
iou = box_iou(pred_box[b], true_box)
# -----------------------------------------------------------#
# best_iou shape=(?, ?, 3) 每个特征点与真实框的最大重合程度,其中3代表每一特征层3个预测框
# -----------------------------------------------------------#
best_iou = K.max(iou, axis=-1)
# -----------------------------------------------------------#
# 判断预测框和真实框的最大iou小于ignore_thresh
# 则认为该预测框没有与之对应的真实框
# 该操作的目的是:
# 忽略预测结果与真实框非常对应特征点,因为这些框已经比较准了
# 不适合当作负样本,所以忽略掉。
# k.cast(x,dtype) 转化张量为dtype类型
# tf.TensorArray.write(pos,val)通过序号索引b向数组tf.TensorArray写入值val
# ignore_mask 存储0.0或1.0两类数据
# -----------------------------------------------------------#
ignore_mask = ignore_mask.write(b, K.cast(best_iou<ignore_thresh, K.dtype(true_box)))
return b+1, ignore_mask
_, ignore_mask = tf.while_loop(lambda b, *args: b < m, loop_body, [0, ignore_mask])
M是图片数量,b从0循环递增到m,遍历每幅图片,每个特征层3个先验框,当交并比小于ignore_thresh时,忽略此先验框,故此ignore_mask最大维度(?,?,?,3),其中数值为0.0或1.0。

方法yolo_loss第295行
ignore_mask = K.expand_dims(ignore_mask, -1) #shape=(?, ?, ?, 3, 1), dtype=float32)
例程
list = [[1,2],[3,4]]
nd = np.array(list)
print(‘nd=’, nd.shape)
nd1 = np.expand_dims(nd, axis=0)
print(‘nd1=’, nd1.shape)
nd2 = np.expand_dims(nd, axis=-1)
print(‘nd2=’, nd2.shape)
nd3 = np.expand_dims(nd, axis=-2)
print(‘nd3=’, nd3.shape)
nd= (2, 2)
nd1= (1, 2, 2)
nd2= (2, 2, 1)
nd3= (2, 1, 2)

方法yolo_loss第310行
当参数ciou_flag为True时执行此分支。
raw_true_box = y_true[l][…, 0:4] #真实框x,y,w,h,都是归一化数据
ciou = box_ciou(pred_box, raw_true_box)
ciou_loss = object_mask * (1 - ciou)
location_loss = K.sum(ciou_loss)
张量object_mask尺寸为(?,13,13,3,1)、 (?,26,26,3,1)、(?,52,52,3,1)其中3代表先验框,最后一维数据为1时代表此先验框存在物体,为0时代表不存在物体。object_mask * (1 - ciou)含义为存在物体的预测框交并比损失,以此作为Box预测的位置损失。

方法yolo_loss第317行
raw_true_xy = y_true[l][…, :2] * grid_shapes[l][::-1] - grid #转化为相对网格数
当参数ciou_flag为False时执行此分支。
标签数据y_true是通过dataloader.py计算获取的,都是归一化的相对数据,因此需要再次减去grid的网格数量。
在这里插入图片描述
方法yolo_loss第320行

raw_true_wh = K.log(y_true[l][…, 2:4] / anchors[anchors_mask[l]] * input_shape[::-1])
标签数据y_true是通过dataloader.py计算获取的,都是归一化的相对数据。
在这里插入图片描述
数值y_true[l][…, 2:4] / anchors[anchors_mask[l]] * input_shape[::-1]作为真数求其对数作为标签数据。
在这里插入图片描述
预测数据raw_pred来自于utils_bbox.py的方法get_anchors_and_decode()
在这里插入图片描述
y_true[l][…, 2:4]与raw_pred做差值运算,数据算法必须统一口径。

方法yolo_loss第349行
在这里插入图片描述
置信度损失包括两部分,一是预测框中存在物体的置信度,二是预测框中不存在物体的置信度。
交叉熵损失函数
在这里插入图片描述
预测框中存在物体的置信度(预测为无物体):
object_mask * K.binary_crossentropy(object_mask, raw_pred[…, 4:5], from_logits=True)
比如: object_mask = (…,1…),
真实框存在物体,如果预测没有物体,预测值raw_pred[…, 4:5] = (…,0…),预测错误
L = 1log(0) = ∞,说明预测错误,损失为∞;
真实框存在物体,如果预测有物体,如果预测值raw_pred[…, 4:5] = (…,1…),预测正确
L = 1
log(1) = 0,说明预测正确,损失为0

预测框中不存在物体的置信度
(1 - object_mask) * K.binary_crossentropy(object_mask, raw_pred[…, 4:5], from_logits=True) * ignore_mask
比如: (1- object_mask) = (…,1…),
真实框不存在物体,如果预测没有物体,如果预测值raw_pred[…, 4:5] = (…,0…),ignore_mask = (…,0…)预测正确
L = 1log(1-0) * 0 = 0,说明预测正确,损失为0;
真实框不存在物体,如果预测有物体,如果预测值raw_pred[…, 4:5] = (…,1…),ignore_mask = (…,1…)预测错误
L = 1
log(1-1) * 1 = ∞,说明预测错误,损失为∞

方法yolo_loss第352行
class_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[…, 5:], from_logits=True)
交叉熵损失函数
在这里插入图片描述
当预测物体分类正确时:
比如: true_class_probs = (…,1…), raw_pred[…, 5:] = (…,1…),
L = log(1) = 0,说明预测正确,损失为0;
当预测物体分类错误时:
比如: true_class_probs = (…,1…), raw_pred[…, 5:] = (…,0…),
L = log(0) = ∞,说明预测错误,损失为∞

方法yolo_loss第358,359行
num_pos = tf.maximum(K.sum(K.cast(object_mask, tf.float32)), 1)
num_neg = tf.maximum(K.sum(K.cast((1 - object_mask) * ignore_mask, tf.float32)), 1)
用于统计正样本数量,就是真实框中包含物体的总数量,负样本数量是统计真实框中不包含物体的数量。

方法yolo_loss第363行
location_loss = location_loss * box_ratio / num_pos #位置损失box比率/正样本数
confidence_loss = K.sum(confidence_loss) * balance[l] * obj_ratio / (num_pos + num_neg) #物体置信度
平衡系数物体比率/总样本数
class_loss = K.sum(class_loss) * cls_ratio / num_pos / num_classes #分类损失
分类比率/正样本数/分类总数
计算位置损失时除以正样本数量,计算置信度损失时除以总样本数,计算物体分类损失时除以正样本数量。

方法yolo_loss第408行
if lr_decay_type == “cos”:
warmup_total_iters = min(max(warmup_iters_ratio * total_iters, 1), 3)
warmup_lr_start = max(warmup_lr_ratio * lr, 1e-6)
no_aug_iter = min(max(no_aug_iter_ratio * total_iters, 1), 15)
func = partial(yolox_warm_cos_lr, lr, min_lr, total_iters, warmup_total_iters, warmup_lr_start, no_aug_iter)
else:
decay_rate = (min_lr / lr) ** (1 / (step_num - 1))
step_size = total_iters / step_num
func = partial(step_lr, lr, decay_rate, step_size)
学习率动态调整方法,partial作用是将函数部分封装。
例程:

from functools import partial
def add(a, b):return a + b
add_two = partial(add, 2)
print(add_two(3))  # 输出:5
print(add_two(4))  # 输出:6from functools import partial
def hello(name, greet="Hello"):return f"{greet}, {name}!"
hi_greet = partial(hello, greet="Hi")
print(hi_greet("Tom"))  # 输出:Hi, Tom!
print(hi_greet("Jack"))  # 输出:Hi, Jack!def repeat_function(func, times):for _ in range(times):func()
from functools import partialdef log_msg(message):print(message)# 使用partial定义新函数
log_function = partial(log_msg, "Python partial fuction...")
repeat_function(log_function, 2)

train.py中调用学习率方法get_lr_scheduler()
在这里插入图片描述
传入参数分别为:lr_decay_type,Init_lr_fit,Min_lr_fit,UnFreeze_Epoch,
当lr_decay_type=’cos‘时分别对应
Init_lr_fit <------> lr
Min_lr <------> min_lr
UnFreeze_Epoch <------> total_iters
当lr_decay_type=’step‘时分别对应
Init_lr_fit <------> lr

http://www.xdnf.cn/news/1082395.html

相关文章:

  • Ubuntu:Mysql服务器
  • 08_容器化与微服务:构建弹性架构
  • 【Linux】自旋锁和读写锁
  • (LeetCode 面试经典 150 题) 14. 最长公共前缀 (字符串)
  • JVM与JMM
  • 全素山药开发指南:从防痒处理到高可用食谱架构
  • 虚拟机网络编译器还原默认设置后VMnet8和VMnet1消失了
  • 2025最新软件测试面试八股文
  • WPF学习笔记(24)命令与ICommand
  • 【Oracle专栏】分区表增加分区
  • 【机器学习深度学习】模型参数量、微调效率和硬件资源的平衡点
  • Linux:多线程---深入互斥浅谈同步
  • vue中添加原生右键菜单
  • LucidShape 2024.09 最新
  • FreeCAD傻瓜教程-拉簧拉力弹簧的画法及草图的附着位置设定和Part工作台中形体构建器的妙用
  • Flutter 使用http库获取网络数据的方法(一)
  • 初识Linux:Linux开发工具gcc/g++和gdb以及Makefile的使用
  • App爬虫工具篇-appium配置
  • 【STM32实践篇】:GPIO 详解
  • 2025使用VM虚拟机安装配置Macos苹果系统下Flutter开发环境保姆级教程--上篇
  • 九、K8s污点和容忍
  • web前端面试-- MVC、MVP、MVVM 架构模式对比
  • 递归与循环
  • 高频交易服务器篇
  • A/B测试实战:页面微小改动如何带来30%转化率提升?
  • ABC413 : E Reverse 2^i
  • Vue前端项目接收webSocket信息
  • Linux网络配置与故障排除完全指南
  • 介绍electron
  • 【ES6】Latex总结笔记生成器(网页版)