Tensorflow2.0—SSD网络原理及代码解析(五)- 损失函数的计算
前面写了三篇关于SSD代码的讲解,还有最后一个关键代码—损失函数的计算,废话不多说,直接上干货~
这行代码是进行损失计算函数的调用。
损失函数被包装成一个MultiboxLoss类,最后一个compute_loss方法用于调用计算。
首先,先搞清楚y_true, y_pred分别是个啥shape的,其实二者的shape都是(2,8732,33)。2表示的是batch_size,8732表示的是每张图片的锚点框,33表示每个训练图片进行encode之后的结果。
# 分类的loss
# batch_size,8732,21 -> batch_size,8732
# --------------------------------------------- #
conf_loss = self._softmax_loss(y_true[:, :, 4:-8],
y_pred[:, :, 4:-8])
先计算所有真实框(其实就是锚点框基于真实框进行encode之后)与预测框的分类的loss。这里用的是softmax。
def _softmax_loss(self, y_true, y_pred):
y_pred = tf.maximum(y_pred, 1e-7)
softmax_loss = -tf.reduce_sum(y_true * tf.math.log(y_pred),
axis=-1)
return softmax_loss
输入的shape为(2,8732,21),输出的shape为(2,8732)。
然后,计算所有真实框(其实就是锚点框基于真实框进行encode之后)与预测框的坐标的loss,这里用的是l1平滑损失函数。
什么是L_1损失函数呢???
https://blog.csdn.net/weixin_41940752/article/details/93159710
代码实现:
def _l1_smooth_loss(self, y_true, y_pred):
abs_loss = tf.abs(y_true - y_pred) # y_1 = |y_t - y_p|
sq_loss = 0.5 * (y_true - y_pred)**2 #y_2 = 0.5 * (y_t - y_p)^2
l1_loss = tf.where(tf.less(abs_loss, 1.0), sq_loss, abs_loss - 0.5)
return tf.reduce_sum(l1_loss, -1)
输入的shape为(2,8732,4),输出的shape为(2,8732)。
接下来,计算所有正样本的先验框的loss:
# 获取所有的正标签的loss
# --------------------------------------------- #
pos_loc_loss = tf.reduce_sum(loc_loss * y_true[:, :, -8],
axis=1)
pos_conf_loss = tf.reduce_sum(conf_loss * y_true[:, :, -8],
axis=1)
loc_loss和y_true[:, :, -8]的shape为(2,8732),按照最后一维进行先相乘,然后相加,最后得到了shape为(2,)的pos_loc_loss 和pos_conf_loss 。
现在,损失就是一个:负样本的conf的loss。
# --------------------------------------------- #
# 每一张图的正样本的个数
# batch_size,
# --------------------------------------------- #
num_pos = tf.reduce_sum(y_true[:, :, -8], axis=-1) #计算每个批次中每个图正样本的个数
# --------------------------------------------- #
# 每一张图的负样本的个数
# batch_size,
# --------------------------------------------- #
num_neg = tf.minimum(self.neg_pos_ratio * num_pos,
num_boxes - num_pos)
# 找到了哪些值是大于0的
pos_num_neg_mask = tf.greater(num_neg, 0)
# --------------------------------------------- #
# 如果有些图,它的正样本数量为0,
# 默认负样本为100
# --------------------------------------------- #
has_min = tf.cast(tf.reduce_any(pos_num_neg_mask),tf.float32)
num_neg = tf.concat(axis=0, values=[num_neg, [(1 - has_min) * self.negatives_for_hard]])
以上这么一大堆代码,其实任务很简单,就是在找负样本,如果有正样本,那么就取3倍的负样本。如果没有正样本,那么就筛选出100个负样本。
# --------------------------------------------- #
num_neg_batch = tf.reduce_sum(tf.boolean_mask(num_neg, tf.greater(num_neg, 0)))
num_neg_batch = tf.cast(num_neg_batch,tf.int32) #一个批次中所有的负样本的个数
# --------------------------------------------- #
# 对预测结果进行判断,如果该先验框没有包含物体
# 那么它的不属于背景的预测概率过大的话
# 就是难分类样本
# --------------------------------------------- #
confs_start = 4 + self.background_label_id + 1
confs_end = confs_start + self.num_classes - 1
# --------------------------------------------- #
# batch_size,8732
# 把不是背景的概率求和,求和后的概率越大
# 代表越难分类。
# --------------------------------------------- #
max_confs = tf.reduce_sum(y_pred[:, :, confs_start:confs_end], axis=2)
# --------------------------------------------------- #
# 只有没有包含物体的先验框才得到保留
# 我们在整个batch里面选取最难分类的num_neg_batch个
# 先验框作为负样本。
# --------------------------------------------------- #
max_confs = tf.reshape(max_confs * (1 - y_true[:, :, -8]), [-1])
_, indices = tf.nn.top_k(max_confs, k=num_neg_batch)
neg_conf_loss = tf.gather(tf.reshape(conf_loss, [-1]), indices)
这一步比较难理解,我个人理解为:先找到每个批次中所有负样本的数量,然后计算所有预测框的不是背景的概率进行求和,求和后的概率越大,代表越难分类。(我认为可以这么理解:除了背景的概率,其他概率相加越大,说明这个预测框就是属于那种越难分类的,比如说一只狗,预测出它为猫为0.3,狗为0.4,老虎为0.35,像这种的就很难区别出到底是哪个动物。)然后,我们在整个batch里面选取最难分类的num_neg_batch个先验框作为负样本。
最后,将三个损失进行相加,并归一化。
# 进行归一化
num_pos = tf.where(tf.not_equal(num_pos, 0), num_pos, tf.ones_like(num_pos))
total_loss = tf.reduce_sum(pos_conf_loss) + tf.reduce_sum(neg_conf_loss) + tf.reduce_sum(self.alpha * pos_loc_loss)
total_loss /= tf.reduce_sum(num_pos)
return total_loss