Fork版本项目地址:SSD

一、损失函数介绍

SSD损失函数分为两个部分:对应搜索框的位置loss(loc)和类别置信度loss(conf)。(搜索框指网络生成的网格)

详细的说明如下:

i指代搜索框序号,j指代真实框序号,p指代类别序号,p=0表示背景,

中取1表示此时第i个搜索框和第j个类别框IOU大于阈值,此时真实框中对象类别为p。

cip表示第i个搜索框对应类别p的预测概率。

二、分类损失函数

有了上图的分析,我们可以看具体实现了,首先我们看Lconf部分的计算,其分为最大化第一个累加符号和最大化第二个累加符号两个部分(这牵扯到另一个问题:Pos框和Neg框的选择,这一点我们在下面分析代码中会也提及,注意两者都是对搜索框进行的讨论),我们将分别讨论两部分的实现逻辑,根据代码来看,首先确定正样本框的掩码:

        dtype = logits.dtype
pmask = gscores > match_threshold # (全部搜索框数目, 21),类别搜索框和真实框IOU大于阈值
fpmask = tf.cast(pmask, dtype) # 浮点型前景掩码(前景假定为含有对象的IOU足够的搜索框标号)
n_positives = tf.reduce_sum(fpmask) # 前景总数

也就是说只要IOU到达阈值就认为这个搜索框是正样本(fpmask标记),注意,即使是第0类也可以(不过一般来说是不会有真实框框住背景并进行标注的), 然后看负样本,

        no_classes = tf.cast(pmask, tf.int32)
predictions = slim.softmax(logits) # 此时每一行的21个数转化为概率
nmask = tf.logical_and(tf.logical_not(pmask),
gscores > -0.5) # IOU达不到阈值的类别搜索框位置记1
fnmask = tf.cast(nmask, dtype)
nvalues = tf.where(nmask,
predictions[:, 0], # 框内无物体标记为背景预测概率
1. - fnmask) # 框内有物体位置标记为1
   nvalues_flat = tf.reshape(nvalues, [-1])

此时的负样本(fnmask标记)同样的为{0,1},且和正样本互补,但是这样会导致负样本过多,所以建立nvalue用于筛选负样本,nvalue中fnmask为1的位置记为对应搜索框的第0类(背景)预测概率,否则记为1(fpmask标记位置),

        # 在nmask中剔除n_neg个最不可能背景点(对应的class0概率最低)
max_neg_entries = tf.cast(tf.reduce_sum(fnmask), tf.int32)
# 3 × 前景掩码数量 + batch_size
n_neg = tf.cast(negative_ratio * n_positives, tf.int32) + batch_size
n_neg = tf.minimum(n_neg, max_neg_entries)
val, idxes = tf.nn.top_k(-nvalues_flat, k=n_neg) # 最不可能为背景的n_neg个点
max_hard_pred = -val[-1]
# Final negative mask.
nmask = tf.logical_and(nmask, nvalues < max_hard_pred) # 不是前景,又最不像背景的n_neg个点
fnmask = tf.cast(nmask, dtype)

在进一步处理中,我们希望负样本不要超过正样本数目的3倍,确保能够收敛(具体推导不明),由于知道这些负样本都属于背景(和真实框IOU不足),所以理论上其class 0预测值越大越好,我们取class 0预测值最小的3倍正样本数目的负样本,最大化其class 0预测值,达到最小化损失函数的目的。筛选后的负样本(fnmask标记)为原负样本中class 0预测值最小的目标数目的点。

        # Add cross-entropy loss.
with tf.name_scope('cross_entropy_pos'):
loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,
labels=gclasses) # 0-20
loss = tf.div(tf.reduce_sum(loss * fpmask), batch_size, name='value')
tf.losses.add_loss(loss) with tf.name_scope('cross_entropy_neg'):
loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,
labels=no_classes) # {0,1}
loss = tf.div(tf.reduce_sum(loss * fnmask), batch_size, name='value')
tf.losses.add_loss(loss)

对应两部分的损失函数计算。上面的公式中第一部分比第二部分多了个x,实际上是为了确定cp中p的取值,而第二部分不需要了,因为p恒为0。

no_classes为标签,只要保证fnmask中标记点(负样本)对应位置为0即可。对应的gclasses其实只要pnmask为1位置有真实分类标签即可,之所以额外划分出no_classes是因为gclasses在迭代生成时有可能给得分(IOU)不足够高的搜索框标注上类别信息,而在本函数一开始,我们就使用得分(IOU)对搜索框进行了二次筛选(在gclasses、gscores、glocalisations生成过程中会对IOU进行一次筛选),fpmask可能会将一些一次筛选中标记了gclasses(且不为0)的搜索框剔除,这对正样本没有影响(正样本位置一定是gclasses标记位置的子集),但是会影响负样本(新的认定为背景的搜索框在gclasses上有标记类别,同时也是说其gscore分数不够二次筛选的标准),所以需要为负样本标注新的类别标签。

三、定位损失函数

定位损失函数形式简单一点,

        # Add localization loss: smooth L1, L2, ...
with tf.name_scope('localization'):
# Weights Tensor: positive mask + random negative.
weights = tf.expand_dims(alpha * fpmask, axis=-1)
loss = custom_layers.abs_smooth(localisations - glocalisations)
loss = tf.div(tf.reduce_sum(loss * weights), batch_size, name='value')
tf.losses.add_loss(loss)

调用函数如下:

def abs_smooth(x):
"""Smoothed absolute function. Useful to compute an L1 smooth error. Define as:
x^2 / 2 if abs(x) < 1
abs(x) - 0.5 if abs(x) > 1
We use here a differentiable definition using min(x) and abs(x). Clearly
not optimal, but good enough for our purpose!
"""
absx = tf.abs(x)
minx = tf.minimum(absx, 1)
r = 0.5 * ((absx - 1) * minx + absx)
return r

四、损失函数全览

def ssd_losses(logits, localisations,  # 预测类别,位置
gclasses, glocalisations, gscores, # ground truth类别,位置,得分
match_threshold=0.5,
negative_ratio=3.,
alpha=1.,
label_smoothing=0.,
device='/cpu:0',
scope=None): with tf.name_scope(scope, 'ssd_losses'): # 提取类别数和batch_size
lshape = tfe.get_shape(logits[0], 5) # tensor_shape函数可以取代
num_classes = lshape[-1]
batch_size = lshape[0] # Flatten out all vectors!
flogits = []
fgclasses = []
fgscores = []
flocalisations = []
fglocalisations = []
for i in range(len(logits)): # 按照ssd特征层循环
flogits.append(tf.reshape(logits[i], [-1, num_classes]))
fgclasses.append(tf.reshape(gclasses[i], [-1]))
fgscores.append(tf.reshape(gscores[i], [-1]))
flocalisations.append(tf.reshape(localisations[i], [-1, 4]))
fglocalisations.append(tf.reshape(glocalisations[i], [-1, 4]))
# And concat the crap!
logits = tf.concat(flogits, axis=0) # 全部的搜索框,对应的21类别的输出
gclasses = tf.concat(fgclasses, axis=0) # 全部的搜索框,真实的类别数字
gscores = tf.concat(fgscores, axis=0) # 全部的搜索框,和真实框的IOU
localisations = tf.concat(flocalisations, axis=0)
glocalisations = tf.concat(fglocalisations, axis=0) """[<tf.Tensor 'ssd_losses/concat:0' shape=(279424, 21) dtype=float32>,
<tf.Tensor 'ssd_losses/concat_1:0' shape=(279424,) dtype=int64>,
<tf.Tensor 'ssd_losses/concat_2:0' shape=(279424,) dtype=float32>,
<tf.Tensor 'ssd_losses/concat_3:0' shape=(279424, 4) dtype=float32>,
<tf.Tensor 'ssd_losses/concat_4:0' shape=(279424, 4) dtype=float32>]
""" dtype = logits.dtype
pmask = gscores > match_threshold # (全部搜索框数目, 21),类别搜索框和真实框IOU大于阈值
fpmask = tf.cast(pmask, dtype) # 浮点型前景掩码(前景假定为含有对象的IOU足够的搜索框标号)
n_positives = tf.reduce_sum(fpmask) # 前景总数 # Hard negative mining...
no_classes = tf.cast(pmask, tf.int32)
predictions = slim.softmax(logits) # 此时每一行的21个数转化为概率
nmask = tf.logical_and(tf.logical_not(pmask),
gscores > -0.5) # IOU达不到阈值的类别搜索框位置记1
fnmask = tf.cast(nmask, dtype)
nvalues = tf.where(nmask,
predictions[:, 0], # 框内无物体标记为背景预测概率
1. - fnmask) # 框内有物体位置标记为1
nvalues_flat = tf.reshape(nvalues, [-1]) # Number of negative entries to select.
# 在nmask中剔除n_neg个最不可能背景点(对应的class0概率最低)
max_neg_entries = tf.cast(tf.reduce_sum(fnmask), tf.int32)
# 3 × 前景掩码数量 + batch_size
n_neg = tf.cast(negative_ratio * n_positives, tf.int32) + batch_size
n_neg = tf.minimum(n_neg, max_neg_entries)
val, idxes = tf.nn.top_k(-nvalues_flat, k=n_neg) # 最不可能为背景的n_neg个点
max_hard_pred = -val[-1]
# Final negative mask.
nmask = tf.logical_and(nmask, nvalues < max_hard_pred) # 不是前景,又最不像背景的n_neg个点
fnmask = tf.cast(nmask, dtype) # Add cross-entropy loss.
with tf.name_scope('cross_entropy_pos'):
loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,
labels=gclasses) # 0-20
loss = tf.div(tf.reduce_sum(loss * fpmask), batch_size, name='value')
tf.losses.add_loss(loss) with tf.name_scope('cross_entropy_neg'):
loss = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,
labels=no_classes) # {0,1}
loss = tf.div(tf.reduce_sum(loss * fnmask), batch_size, name='value')
tf.losses.add_loss(loss) # Add localization loss: smooth L1, L2, ...
with tf.name_scope('localization'):
# Weights Tensor: positive mask + random negative.
weights = tf.expand_dims(alpha * fpmask, axis=-1)
loss = custom_layers.abs_smooth(localisations - glocalisations)
loss = tf.div(tf.reduce_sum(loss * weights), batch_size, name='value')
tf.losses.add_loss(loss)

『TensorFlow』SSD源码学习_其七:损失函数的更多相关文章

  1. 『TensorFlow』SSD源码学习_其一:论文及开源项目文档介绍

    一.论文介绍 读论文系列:Object Detection ECCV2016 SSD 一句话概括:SSD就是关于类别的多尺度RPN网络 基本思路: 基础网络后接多层feature map 多层feat ...

  2. 『TensorFlow』SSD源码学习_其五:TFR数据读取&数据预处理

    Fork版本项目地址:SSD 一.TFR数据读取 创建slim.dataset.Dataset对象 在train_ssd_network.py获取数据操作如下,首先需要slim.dataset.Dat ...

  3. 『TensorFlow』SSD源码学习_其四:数据介绍及TFR文件生成

    Fork版本项目地址:SSD 一.数据格式介绍 数据文件夹命名为VOC2012,内部有5个子文件夹,如下, 我们的检测任务中使用JPEGImages文件夹和Annotations文件夹. JPEGIm ...

  4. 『TensorFlow』SSD源码学习_其二:基于VGG的SSD网络前向架构

    Fork版本项目地址:SSD 参考自集智专栏 一.SSD基础 在分类器基础之上想要识别物体,实质就是 用分类器扫描整张图像,定位特征位置 .这里的关键就是用什么算法扫描,比如可以将图片分成若干网格,用 ...

  5. 『TensorFlow』SSD源码学习_其八:网络训练

    Fork版本项目地址:SSD 作者使用了分布式训练的写法,这使得训练部分代码异常臃肿,我给出了部分注释.我对于多机分布式并不很熟,而且不是重点,所以不过多介绍,简单的给出一点训练中作者的优化手段,包含 ...

  6. 『TensorFlow』SSD源码学习_其六:标签整理

    Fork版本项目地址:SSD 一.输入标签生成 在数据预处理之后,图片.类别.真实框格式较为原始,不能够直接作为损失函数的输入标签(ssd向前网络只需要图像就行,这里的处理主要需要满足loss的计算) ...

  7. 『TensorFlow』SSD源码学习_其三:锚框生成

    Fork版本项目地址:SSD 上一节中我们定义了vgg_300的网络结构,实际使用中还需要匹配SSD另一关键组件:被选取特征层的搜索网格.在项目中,vgg_300网络和网格生成都被统一进一个class ...

  8. nginx源码学习_源码结构

    nginx的优秀除了体现在程序结构以及代码风格上,nginx的源码组织也同样简洁明了,目录结构层次结构清晰,值得我们去学习.nginx的源码目录与nginx的模块化以及功能的划分是紧密结合,这也使得我 ...

  9. 『TensorFlow』读书笔记_TFRecord学习

    一.程序介绍 1.包导入 # Author : Hellcat # Time : 17-12-29 import os import numpy as np np.set_printoptions(t ...

随机推荐

  1. iptables 分析(三)

    原文:http://blog.chinaunix.net/uid-24207747-id-2622902.html find_target查到目标并加载成功,返回一个xtables_target型对象 ...

  2. zabbix报错排错大全

    zabbix报错 https://www.cnblogs.com/losbyday/category/876878.html作者总结的很全棒 1.在启动zabbix-agent 时系统日志输出 PID ...

  3. 为什么分布式数据库中不使用uuid作为主键?

    分布式数据库当然也有主键的需求,但是为什么不直接使用uuid作为主键呢?作为曾经被这个问题困惑过的人,试着回答一下 1. UUID生成速率低下 Java的UUID依赖于SecureRandom.nex ...

  4. docker单机网络类型

    docker单机网络类型概述 Docker 安装时会自动在 host 上创建三种网络  分别为 bridge    host   none .   可用 docker network ls 命令查看 ...

  5. 赵丽颖固然漂亮,可这份Hadoop核心教程也不差呀

    阿里巴巴采用了 15 个节点组成的 Hadoop 集群,用于处理从数据库中导出的商业数据的排序和组合. Ebay 使用了 32 个节点组成的集群,包括 8 532 个计算核心以及 5.3 PB 的存储 ...

  6. TP连接数据库报错:SQLSTATE[HY000] [2002] No such file or directory

     连接数据库报错:“SQLSTATE[HY000] [2002] No such file or directory”. 出现这个问题的原因是PDO无法找到mysql.sock或者mysqld.soc ...

  7. Streaming从Spark2X迁移到Spark1.5 summary

    配置文件的加载是一个难点,在local模式下非常容易,但是submit后一直报找不到文件,后来采用将properties文件放在加载类同一个package下,打包到同一个jar中解决. import ...

  8. 《linux就该这么学》开课,linux之路新开始

    今天开课第一天,虽然不会有实实在在的干货知识,只要是了解一下linux和认证.所以我也简单说一下我的linux之路 linux我是无意接触到的,因为工作,我接触的服务器较多,但是都是linux系统,记 ...

  9. HDU - 6440 Dream 2018中国大学生程序设计竞赛 - 网络选拔赛

    给定的\(p\)是素数,要求给定一个加法运算表和乘法运算表,使\((m+n)^p = m^p +n^p(0 \leq m,n < p)\). 因为给定的p是素数,根据费马小定理得 \((m+n) ...

  10. 玩转spring boot——负载均衡与session共享

     前言 当项目上线后,如果要修复bug或扩充功能,都需要重启tomcat服务.此时,正在使用应用的用户们就需要等待服务器的重启,而这就会造成不好的用户体验.还有,当仅仅只有一台tomcat服务时,如果 ...