目录
代码下载链接:https://download.csdn.net/download/weixin_40723264/88011324
一、概述
(1)
通过YOLOv8-训练流程-正负样本分配的介绍,我们可以知道,经过预处理与筛选的过程得到最终的训练数据:
a. 网络输出值:pred_scores[bx8400xcls_num]、pred_bboxes[bx8400x4]
b. 训练标签值:
target_scores[bx8400xcls_num](one-hot类型),在计算损失时与预测结果pred_scores[bx8400xcls_num],计算交叉熵损失,该损失是对每个类别计算BCE Loss,因为类别预测采用的sigmoid分类器。
target_bboxes[bx8400x4](target_bboxes需要缩放到特征图尺度,即除以相应的stride),在计算损失时,分别与预测的pred_bboxes计算Ciou Loss, 同时与pred_regs(预测的anchors中心点到各边的距离)计算回归DFL Loss。
c. 训练mask值:fg_mask [bx8400],对8400个anchor进行正负样本标记,计算损失过程中通过fg_mask筛选正负样本。
二、损失函数
(1) 类别分类损失
在yolov8中,类别损失最终采用的是交叉熵损失,该方法是我们非常熟知的,不再赘述。
代码如下:
self.bce = nn.BCEWithLogitsLoss(reduction='none')
loss[1] = self.bce(pred_scores,target_scores).sum()/target_scores_sum
其中预测pred_scores: b x 8400 x cls_num; target_scores: b x 8400 x cls_num, 相当于对于每个box,其cls_num个分类都视为二分类,并进行交叉熵运算。
(2) 边框回归损失
边框回归,采用的是DFL Loss + CIOU Loss
target_bboxes /= self.stride_scales
loss[0],loss[2] = self.bbox_loss(pred_regs,pred_bboxes,self.anc_points,target_bboxes,
target_scores,target_scores_sum,fg_mask)
DFL loss:
weight = torch.masked_select(target_scores.sum(-1), fg_mask).unsqueeze(-1)
# DFL loss
if self.use_dfl:
target_ltrb = self.bbox2reg(anchor_points, target_bboxes, self.reg_max)
loss_dfl = self._df_loss(pred_regs[fg_mask].view(-1, self.reg_max + 1), target_ltrb[fg_mask]) * weight
--------------------原理分析---------------------
提问:采用的是交叉熵运算,为什么是交叉熵运算呢?
因为DFL是以概率的方式对预测box进行回归,如设置超参数reg_max = 16 ,这此时网络该分支的输出通道为64=4*reg_max;
在此之前,会预先设置16个固定的参考值A: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]其每个位置与reg_max的每个位置是相互对应的。
对这reg_max个数,通过softmax函数进行离散化处理,视为一个16分类,得到相对于每个固定值的概率,既然是概率,那么在计算损失的时候就多采用交叉熵损失。
----------------------------------------------------
首先将正样本标签target_bboxes[bx8400x4](其是相对于特征图大小的xmin,ymin,xmax,ymax),将其转换到左上右下四个边,距离中心点距离,并保证便宜量不能超过reg_max=15(这相当于是16个类别的标签,说明距离中心点的距离不能超过15)。
def bbox2reg(self,anchor_points, target_bboxes,reg_max):
"""Transform bbox(xyxy) to dist(ltrb)."""
x1y1,x2y2 = target_bboxes[...,:2],target_bboxes[...,2:]
return torch.cat((anchor_points - x1y1, x2y2 - anchor_points), -1).clamp(0, reg_max - 0.01) # dist (lt, rb)
然后,将网络预测值pred_regs[b,8400,4*16],筛选出正样本部分pred_regs'[K,64],转换为网络输出的分布形式 [Kx4,16],
同时target_bboxes筛选出正样本部分targets_bboxes' [K,4](每个坐标位置提供一个标签0~15,然后与预测结果)进行DFL计算。
注意!!
上面获得的target_bboxes'其坐标值一般不会落在具体的网格角点上,但是标签又需要为整数,以ximin的预测为例,其真实的值 y = 8.3(对应公式中的y), 那么它左侧整数为8(对应公式中的yi),右侧整数为9(对应公式中的yi+1), 但是具体用哪个整数来计算又不好说,那就都用!
但是这两个整数,距离真实值的距离又是不同的,所以就给8一个大一点的权重0.7(9-8.3=0.7,对应于公式中的yi+1-y),给9一个小一点的权重0.3(8.3-8=0.3,对应于公式中的y-yi).
def _df_loss(pred_dist, target):
"""Return sum of left and right DFL losses."""
# target向下取整,作为目标的左侧整数 K x 4(取值范围0-15)
tl = target.long() # target left (包括中心点距离左边和上边的距离)
# tl加上整数1,作为目标的右侧整数 K x 4(取值范围0-15)
tr = tl + 1 # target right (包括中心点距离右边和下边的距离)
# 分别将偏移值作为权重
# 如真实坐标值为8.3,那么它距离8近给一个大一点的权重0.7,距离9远,给一个小一点的权重0.3
wl = tr - target # weight left Kx4
wr = target - tl # weight right Kx4
# 左右目标分别拉直 tl -> K x 4 -> 4K, pred_dist->4K x 16
l_loss = F.cross_entropy(pred_dist, tl.view(-1), reduction='none')
# 左右目标分别拉直 tr -> K x 4 -> 4K
r_loss = F.cross_entropy(pred_dist, tr.view(-1), reduction='none')
loss = l_loss.view(tl.shape) * wl + r_loss.view(tl.shape) * wr
loss = loss.mean(-1,keepdim=True)
return loss
首先pred_dist ,其shape为4K x 16, 其中16是经过softmax处理后得到的离散分布值,而tl和tr经过处理后为 Kx2,这里就相当于在做softmax处理的16个分类,tr和tr是标签(理论上是0-15之间的值),而pred_dist是16个各自的预测值,经过训练,使预测坐标的结果逐渐向正确的区域逼近。
Ciou Loss:
概括:边框回归,reg_max个分布值经softmax离散分布,通过巧妙利用交叉熵损失将预测位置迅速聚焦到目标位置附近,然后经过加权求和得到四个预测坐标值,再用Ciou Loss进一步精确坐标位置。
其中,weights [Kx1],表示batch中共有K个正样本,故从加权后的target_scores[bx8400xcls_num]通过fg_mask[bx8400],筛选出正样本的K个位置,作为权重因子。
然后计算根据CIOU的计算公式计算Ciou值,并得到ciou loss.