目录
一、概述
预测的内容,包含了物体所在的位置(位置回归损失),是否有物体(物体置信度损失),物体的分类(分类损失)三样内容,因此我们使用这三种数据来计算总体损失。物体所在位置主要使用IOU,而是否有物体以及分类的预测损失使用二分类交叉熵损失。
二、计算
1.位置回归损失
下面的式子就是位置回归损失的计算公式,意思就是对每一个网格(共S^2个网格),的每一个锚框(共B个,其实就是3个),去计算CIOU这样的一个数。同时前面有一个示性函数I,也就是说,只有网格里面有物体的时候,才会计算位置回归损失,没物体的话就不算,就是0。
下面是CIOU的计算细节。其中:
lambda: 损失的系数,可以调整。
d:两个框中心点的距离
c:两个框最小外包框的对角线长度
v:根据实际框宽高的夹角,以及预测框宽高的夹角,二者之差,所得的一个数,也能够表示出两个框的相似度。两个框越相似,夹角之差越小,这个v就越小,如果两个框一模一样,v就是0。
IOU:两个框的交并比
2.物体置信度损失
相当于对“这个网格是否有物体”这件事情进行分类,使用二分类交叉熵损失。注意这里对“有物体的网格”,以及“没有物体的网格”,全部都进行了计算。
其中:
C hat是第i个网格第j个框,实际的物体置信度,通常是1或者0
C是第i个网格第j个框,预测的物体置信度
I依旧是示性函数,B, S^2等也和前面的一致。
3.分类损失
类似于逻辑回归,整体逻辑和上面的物体置信度损失一致,只不过这里是针对于有物体的那些网格,才会有物体分类这一说。
三、代码
这里代码需要和前面文章的目标构建结合起来,因为需要用到标签数据的构建。
class YOLOLoss(nn.Module):
def __init__(self, anchors, num_classes, img_size, label_smooth=0, cuda=True):
super(YOLOLoss, self).__init__()
self.anchors = anchors
self.num_anchors = len(anchors)
self.num_classes = num_classes
self.bbox_attr = 5 + num_classes
self.img_size = img_size
self.feature_length = [img_size[0] // 8, img_size[0] // 16, img_size[0] // 32]
self.label_smooth = label_smooth
self.ignore_thresh = 0.7
# 损失函数用的lambda因子
self.lambda_conf = 1.0
self.lambda_cls = 1.0
self.lambda_loc = 1.0
self.cuda = cuda
def forward(self, input: torch.Tensor, targets=None):
# 一共有多少张图片
bs = input.size(0)
# 特征图的高、宽
in_h = input.size(2)
in_w = input.size(3)
# 计算步长,有2个,分别是h和w的步长stride_h, stride_w,每一个网格的大小
stride_h = self.img_size[0] / in_h
stride_w = self.img_size[1] / in_w
# scaled_anchors, 把anchor换成以网格为单位
scaled_anchors = [(a_w/stride_w, a_h/stride_h) for a_w, a_h in self.anchors]
# 对预测的形状调整成(B, num_anchors, H, W, n_ch)
prediction = input.view(bs, int(self.num_anchors/3), self.bbox_attr, in_h, in_w).permute(0,1,3,4,2).contiguous()
conf = torch.sigmoid(prediction[..., 4])
pred_cls = torch.sigmoid(prediction[..., 5:])
# # build_target1,构造掩码,填充掩码正样本
mask, noobj_mask, t_box, tconf, tcls, box_loss_scale_x, box_loss_scale_y = self.get_target(targets,scaled_anchors,in_w, in_h)
# # build_target2,筛选负样本,并对头部做decode
noobj_mask, pred_boxes_for_ciou = self.get_ignore(prediction,targets,scaled_anchors,in_w, in_h,noobj_mask)
if self.cuda:
mask, noobj_mask = mask.cuda(), noobj_mask.cuda()
box_loss_scale_x, box_loss_scale_y = box_loss_scale_x.cuda(), box_loss_scale_y.cuda()
tconf, tcls = tconf.cuda(), tcls.cuda()
pred_boxes_for_ciou = pred_boxes_for_ciou.cuda()
t_box = t_box.cuda()
# 计算ciou损失
box_loss_scale = 2 - box_loss_scale_x * box_loss_scale_y
ciou = box_ciou(pred_boxes_for_ciou[mask.bool()], t_box[mask.bool()])
loss_ciou = 1-ciou
loss_ciou = loss_ciou * box_loss_scale[mask.bool()]
loss_loc = torch.sum(loss_ciou / bs)
# 计算位置置信度损失
loss_conf = torch.sum(BCELoss(conf, mask)*mask / bs) + \
torch.sum(BCELoss(conf, mask)*noobj_mask / bs)
# 计算分类置信度损失
loss_cls = torch.sum(BCELoss(pred_cls[mask==1], smooth_labels(tcls[mask==1], self.label_smooth, self.num_classes))/bs)
# 计算总损失
loss = loss_conf*self.lambda_conf + loss_cls*self.lambda_cls + loss_loc*self.lambda_loc
return loss, loss_conf.item(), loss_cls.item(), loss_loc.item()