frcnn和yolo3损失函数详解

在分享了yolo3的庖丁解牛版后,陆续有一些小伙伴发来信息说,损失函数讲的太简单了 - -,各位大哥,我错了,现在把yolo3的损失函数重新一点一点刨开,同时预先对接下来要分享的faster rcnn中的损失函数部分一起,提前跟大家一点一点解释清楚,以提早发现问题,方便我在重组代码时能把注释写得明明白白的。

 

先上frcnn吧。。。代码参考:https://github.com/ldhsight/keras_frcnn同事有人问我,没人star,你还去学- -

相比大众版的frcnn或者yolo3等代码,他们确实很完善,然而不便于新手上路。正是因为他们的完善和代码的优化,很多地方会导致你看不懂,比如yolo3对nms的写法,掺杂了非常不常用的tensorflow函数,数据处理大篇幅用numpy大法,空间想象力不足的话会抓狂。同时那些代码是面向工程的,很多附加内容与标准写法让你望而却步。因此,这也是为什么先把frcnn代码先进行分享,因为这篇代码,是我选了又选挑出来最平易近人的(虽然他是有问题的)

 

OK,开始吧

1、Faster Rcnn

要想知道损失函数的意义,必定需要详细地了解三个内容:真实数据,神经网络输出,优化目标

(1)第一阶段:

真实数据为:

1)y_rpn_cls 和 y_rpn_regr

其中y_rpn_cls.shape=(1, row, col, 18),这里以tf版为标准。18代表着:前9个值为anchor是否可用于当前区域,后9个值为该区域是前景还是背景

y_rpn_regr.shape=(1, row, col, 72),72表示:前36个值为是前景背景的X4版本,其实就用4个1表示前景,4个0表示背景,就是y_rpn_cls后9个值扩大四个长度

网络输出为:

1)x_class

x_class.shape=(n, row, col, 9)

x_regr.shape=(n, row, col, 36)

损失函数为:

def rpn_loss_cls(num_anchors):
    def rpn_loss_cls_fixed_num(y_true, y_pred):
        if K.image_dim_ordering() == 'tf':
            # 炒鸡神奇炒鸡巧妙的损失函数
            return lambda_rpn_class * K.sum(y_true[:, :, :, :num_anchors] * K.binary_crossentropy(y_pred[:, :, :, :], y_true[:, :, :, num_anchors:])) / K.sum(epsilon + y_true[:, :, :, :num_anchors])
        else:
            return lambda_rpn_class * K.sum(y_true[:, :num_anchors, :, :] * K.binary_crossentropy(y_pred[:, :, :, :], y_true[:, num_anchors:, :, :])) / K.sum(epsilon + y_true[:, :num_anchors, :, :])

    return rpn_loss_cls_fixed_num

def rpn_loss_regr(num_anchors):
    def rpn_loss_regr_fixed_num(y_true, y_pred):
        if K.image_dim_ordering() == 'th':
            x = y_true[:, 4 * num_anchors:, :, :] - y_pred
            x_abs = K.abs(x)
            x_bool = K.less_equal(x_abs, 1.0)
            return lambda_rpn_regr * K.sum(
                y_true[:, :4 * num_anchors, :, :] * (x_bool * (0.5 * x * x) + (1 - x_bool) * (x_abs - 0.5))) / K.sum(epsilon + y_true[:, :4 * num_anchors, :, :])
        else:
            x = y_true[:, :, :, 4 * num_anchors:] - y_pred
            x_abs = K.abs(x)
            x_bool = K.cast(K.less_equal(x_abs, 1.0), tf.float32)

            return lambda_rpn_regr * K.sum(
                y_true[:, :, :, :4 * num_anchors] * (x_bool * (0.5 * x * x) + (1 - x_bool) * (x_abs - 0.5))) / K.sum(epsilon + y_true[:, :, :, :4 * num_anchors])

    return rpn_loss_regr_fixed_num


第一个函数:rpn_loss_cls

简化为:loss = sum(ytrue[:9] * K.bce(ypred, ytrue[9:])) / sum(e + ytrue[:9])

其中

y_true[:9] 表示anchor选用哪个anchor

bce=binary_crossentropy表示二分类的交叉熵损失函数

y_pred是预测值

y_true[9:]表示是前景还是背景

e表示一个极小值,防止分母太小出现异常值,是常数

详细理论见:https://blog.csdn.net/wfei101/article/details/79252021

咱们看看为什么这个式子能做损失函数:

我们把sum先去掉:

    loss = ytrue[...,:9] * K.bce(ypred, ytrue[...,9:]) / e + ytrue[...,:9]

= a * K / (e + a)

= K (e较小,可忽略)

所以这个本质上就是一个简单交叉熵损失函数

但是呢,把a * K / (e + a)这个式子提出来看一下,似乎还是非常有意思的。

如果要想这个式子越小,除开K越小,a/(e+a)也得尽可能的小,这就要求a也得越小,a越小就意味着选用的anchor越少!anchor越少!anchor越少!也就能选出相对准确的anchor索引值

所以呢,这个式子一方面能让前景选择趋于准确,同时限制了anchor的数量

 

第二个函数:rpn_loss_regr

x = ytrue[..., 36:] - ypred

if = x (|x|<= 1.0)

sum(ytrue[...,:36]*(if*(0.5*x^2)+(1-if)*(|x|-0.5)))/sum(e+ ytrue[...,:36])

其中,x为anchor回归真实值与预测值的欧氏距离。

if表达了x的绝对值小于等于1的部分

y_true前36个值表示前景背景,后36个值表示回归值

详细理论见:https://blog.csdn.net/wfei101/article/details/79809332,美曰其名为:smooth_l1_loss

理论公式为:

简化为:

    bc * (if * 0.5x^2 + (1-if) * (|x| - 0.5)) / (e + bc)

=if * 0.5x^2 + (1-if) * (|x| - 0.5)(忽略e的影响)

其中bc表示前36个前景背景值判断

想要这个式子的值越小,x的值得越小越好;

而if和1-if这个因子的加入,给smooth l1的第二部分锦上添花,控制着括号里面结果的平衡

其本质就是欧氏距离的损失函数,但是经过优化、迭代、进化,变为如上的smooth l1

 

以上是第一阶段的损失函数,第一阶段的损失函数一下子囊括了三个方面的优化:

启用的anchor索引值、前景背景的判断值和anchor的回归值

第二阶段的损失函数是异曲同工的,只是分类器的损失函数改为softmax版的交叉熵损失函数,原理是一样的,这里就不再细说了,大家可以看看原创的、公式

def class_loss_regr(num_classes):
    def class_loss_regr_fixed_num(y_true, y_pred):
        x = y_true[:, :, 4 * num_classes:] - y_pred
        x_abs = K.abs(x)
        x_bool = K.cast(K.less_equal(x_abs, 1.0), 'float32')
        return lambda_cls_regr * K.sum(y_true[:, :, :4 * num_classes] * (x_bool * (0.5 * x * x) + (1 - x_bool) * (x_abs - 0.5))) / K.sum(epsilon + y_true[:, :, :4 * num_classes])

    return class_loss_regr_fixed_num


def class_loss_cls(y_true, y_pred):
    return lambda_cls_class * K.mean(categorical_crossentropy(y_true[0, :, :], y_pred[0, :, :]))

 

OK,咱们回看一下yolo3的损失函数

详见:https://github.com/qqwweee/keras-yolo3

真实数据为:

1)y_true

y_true由三个列表组成,分别对应三层不同大小(13, 26, 52)金字塔结构,每一层shape=(batch, row,col,3,5+classnum)

以classnum=80举例:

batch表示批次大小,因为网络输入统一为416x416可以多个图一起,不想早起的frcnn只能一个一个来

row和col为(13, 26, 52)中的一个值

3表示三组anchor,每组anchor又有三个具体anchor值

5表示4个回归值+1个置信度(可以理解为frcnn中的前景概率),classnum表示类别

网络输出为:

1)grid, raw_pred, pred_xy, pred_wh = (Nx13x13x3x85),(Nx13x13x3x2), (Nx13x13x3x2])

其中grid表示(13,26,52)中的一个,以13为例

raw_pred表示整体网络输出值

pred_xy表示物体中心点网路输出值

pred_wh表示anchor长宽

损失函数为:

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)

函数一共分为四部分

(1)计算xy(物体中心坐标)的损失

object_mask就是置信度

box_loss_scale可以理解为2-w*h

raw_true_xy就是真实的xy坐标点了

raw_pred[..., :2]是xy预测坐标点

所以第一个式子想对还挺直观,简化为:  xyloss=bool*(2-areaPred) * bce

bool是置信度

其中bce是xy值的二值交叉熵损失,这个值越小整个损失值越小

bool*(2-areaPred)的值越小,则需要在确保置信度(bool)的情况下,areaPred需要越大

因此,这部分损失主要优化xy的预测值(bce)和置信度(bool)以及wh回归值(areaPred)

(2)计算wh(anchor长宽回归值)的损失

跟(1)式 的差距就在最后一项,所以我们直接简化之:whloss=bool*(2-areaPred) * (whtrue-whpred)^2

在确保置信度(bool)的情况下,areaPred需要越大,wh需要尽可能靠近真实值wh

这部分主要优化置信度(bool)wh回归值(areaPred、whTrue)

(3)计算置信度损失(前背景)损失

简化为:bool * bce +(1-bool) *bce * ignore

其中bool为置信度,bce为预测值和实际置信度的二值交叉熵,ignore表示iou低于一定阈值的但确实存在的物体,相当于frcnn中的中性点位,既不是前景也不是背景,是忽略的,暂时不计的。

在确保置信度(bool)的情况下,预测值需要尽可能靠近真实值,同时没有物体的部分需要尽可能靠近背景真实值,同时乘以相应的需要忽略点位

这部分主要优化置信度,同时缩减了检测的目标量级

(4)计算类别损失

这个不用多说了,直接就是置信度乘上个多分类的交叉熵

这部分优化置信度损失和类别损失

(5)最后,总损失为所有损失之和相加

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

 

OK,最后咱们做下小结:

(1)frcnn的损失函数分阶段,每个阶段各两个损失函数,所有的损失函数优化了一共5个方向,合计其实只有4个方向。

第一阶段主要优化前背景、一次回归值和锚框索引值;第二阶段主要优化前背景、二次回归值和分类值。

(2)yolo3的损失函数分5步,优化了xy、wh、置信度和类别

(3)frcnn分两阶段,速度略低于yolo3;但是进行两次回归,精度略高于yolo3。其损失函数相比yolo3就简单太多了,目的性强;yolo3的损失函数设计得非常妙,才促成了这么6的检测方法的形成

(4)损失函数无外乎两大形式:联合优化(yolo系)和精准优化(rcnn系);优化方向无外乎主要几种:欧氏距离、交叉熵,或者都可以用交叉熵!

对于yolo存疑的看官,可移步我写过的yolo庖丁解牛。如有错误,欢迎指正。

有一起奔跑的小伙伴欢迎主页联系!

你可能感兴趣的:(frcnn和yolo3损失函数详解)