yolo v8

这个系列代码被封装的非常的精致,对二次开发不太友好,虽然也还是可以做些调节

这里写目录标题

  • 模型的导出
  • multi-scale
  • Loss
    • VFL
    • DFL
      • 问题1
      • 问题2
      • 问题1 solution
      • 问题2 solution
      • Finally
  • 其它
    • yolo v8 里的哪些结构让它比yolo v5 更好
    • C3 --> c2f
    • decoupled head 只是为了做anchor free对吗
    • 3x3 的 conv 要优于 1x1 的conv 的原因
    • yolo v8中的正负匹配和yolo v5 中正负匹配不同在哪里
  • Distillation

模型的导出

有三种方式试过,都可以导出onnx的模型
1. 用yolov8
源码来自:ultralytics\yolo\engine\exporter.py
(不固定尺寸)

yolo export model=path/to/best.pt format=onnx dynamic=True

2. 用yolov5 里 export.py
但是attempt_load_weights这一步,要用yolo v8

3. 直接用 torch

class Demo(nn.Module):
    def __init__(self, model=None):
        super(Demo, self).__init__()
        self.model = YOUR_PROCESS(model, 0, 255, False)

    def forward(self, img):
        return self.model(img)[0]

from ultralytics.nn.tasks import attempt_load_weights
model = attempt_load_weights(weights, device=0, inplace=True, fuse=True)
model = Demo(model)
model.to(device).eval() 
#......(过程省略)  
torch.onnx.export(self.model, img, output_path, verbose=False, opset_version=11, input_names=['images'],
                          output_names=['output'],
                          dynamic_axes={'images': {0: 'batch', 2: 'height', 3: 'width'}})  
  • 这里的Demo和YOUR_PROCESS都需要基于 nn.Module,在YOUR_PROCESS用于包含一些模型额外的处理。
    • YOUR_PROCESS 中的内容,如果是用于前处理,记得不要进行梯度计算,并对运算过程和整个层做梯度忽略。如下:
with torch.no_grad():
    # 在这个代码块中执行的操作不会被记录用于自动求导
    output = model(input)
self.conv_xx.eval()
# 对于self.conv_xx层以及与其相关的层,将启用评估模式的行为
output = self.conv_xx(input)
  • attempt_load_weights这一步,要用yolo v8的

multi-scale

def preprocess_batch(self, batch, imgsz_train, gs):
        """
        Allows custom preprocessing model inputs and ground truths depending on task type.
        """
        sz = random.randrange(int(self.args.imgsz * 0.5), int(self.args.imgsz * 1.5) + self.gs) // self.gs * self.gs  # size
        sf = sz / max(batch['img'].shape[2:])  # scale factor
        if sf != 1:
            ns = [math.ceil(x * sf / self.gs) * self.gs for x in batch['img'].shape[2:]]  # new shape (stretched to gs-multiple)
            batch['img'] =  batch['img'].to(self.device, non_blocking=True).float() / 255
            batch['img'] = nn.functional.interpolate(batch['img'], size=ns, mode='bilinear', align_corners=False)
        return batch

Loss

Yolo V8 只有两个 loss, 因为是anchor-free 了,所以不需要objective loss, 直接看分类和预测出来的框的两个内容。

  • Yolo v5 使用 CIOU 去算的loss, 而 Yolo v8 加入了更符合anchor-free的 loss 。所以这一步,用了两个loss一起帮助优化IOU。CIOU loss + DFL 来自Generalized focal loss: Learning qualified and distributed bounding boxes for dense object detection这篇论文(DFL论文)

    • VFL 在 yolo v8中也implement了,但是没有用上,这个loss 也是针对 anchor-free 的, 来自arifocalNet: An IoU-aware Dense Object Detector这篇论文(VFL论文)。
  • 对分类loss, yolo v5 使用Focal loss(由BCE为基础), Yolo v8 沿用 BCEWithLogitsLoss

# cls loss
# loss[1] = self.varifocal_loss(pred_scores, target_scores, target_labels) / target_scores_sum  # VFL way
 loss[1] = self.bce(pred_scores, target_scores.to(dtype)).sum() / target_scores_sum  # BCE

 # bbox loss
 if fg_mask.sum():
     loss[0], loss[2] = self.bbox_loss(pred_distri, pred_bboxes, anchor_points, target_bboxes, target_scores,
                                       target_scores_sum, fg_mask)

 loss[0] *= self.hyp.box  # box gain
 loss[1] *= self.hyp.cls  # cls gain
 loss[2] *= self.hyp.dfl  # dfl gain

 return loss.sum() * batch_size, loss.detach()  # loss(box, cls, dfl)

VFL

class VarifocalLoss(nn.Module):
    """Varifocal loss by Zhang et al. https://arxiv.org/abs/2008.13367."""

    def __init__(self):
        """Initialize the VarifocalLoss class."""
        super().__init__()

    def forward(self, pred_score, gt_score, label, alpha=0.75, gamma=2.0):
        """Computes varfocal loss."""
        weight = alpha * pred_score.sigmoid().pow(gamma) * (1 - label) + gt_score * label
        with torch.cuda.amp.autocast(enabled=False):
            loss = (F.binary_cross_entropy_with_logits(pred_score.float(), gt_score.float(), reduction='none') *
                    weight).sum()
        return loss

DFL

(感谢大白话 Generalized Focal Loss)
DFL 来自于GFL (Generalised focal loss)
GFL 主要解决了两个大的问题:

  1. classification score 和 IoU/centerness score 训练测试不一致
  2. bbox regression 采用的表示不够灵活,没有办法建模复杂场景下的uncertainty
class BboxLoss(nn.Module):

    def __init__(self, reg_max, use_dfl=False):
        """Initialize the BboxLoss module with regularization maximum and DFL settings."""
        super().__init__()
        self.reg_max = reg_max
        self.use_dfl = use_dfl

    def forward(self, pred_dist, pred_bboxes, anchor_points, target_bboxes, target_scores, target_scores_sum, fg_mask):
        """IoU loss."""
        weight = torch.masked_select(target_scores.sum(-1), fg_mask).unsqueeze(-1)
        iou = bbox_iou(pred_bboxes[fg_mask], target_bboxes[fg_mask], xywh=False, CIoU=True)
        loss_iou = ((1.0 - iou) * weight).sum() / target_scores_sum

        # DFL loss
        if self.use_dfl:
            target_ltrb = bbox2dist(anchor_points, target_bboxes, self.reg_max)
            loss_dfl = self._df_loss(pred_dist[fg_mask].view(-1, self.reg_max + 1), target_ltrb[fg_mask]) * weight
            loss_dfl = loss_dfl.sum() / target_scores_sum
        else:
            loss_dfl = torch.tensor(0.0).to(pred_dist.device)

        return loss_iou, loss_dfl

    @staticmethod
    def _df_loss(pred_dist, target):
        """Return sum of left and right DFL losses."""
        # Distribution Focal Loss (DFL) proposed in Generalized Focal Loss https://ieeexplore.ieee.org/document/9792391
        tl = target.long()  # target left
        tr = tl + 1  # target right
        wl = tr - target  # weight left
        wr = 1 - wl  # weight right
        return (F.cross_entropy(pred_dist, tl.view(-1), reduction='none').view(tl.shape) * wl +
                F.cross_entropy(pred_dist, tr.view(-1), reduction='none').view(tl.shape) * wr).mean(-1, keepdim=True)

问题1

不一致有两方面:
方面1 分类,objective 和 IOU 都是各自训练自己的这部分,比如Fcos(论文里提到). 查看了Fcos 的loss 计算,看到和YOLO的方式类似。
yolo v8_第1张图片
yolo v8_第2张图片
以上的loss 计算,在GFL一文中,作者不认可,他认为这个不够End-to-End

方面2 归功于focal loss,分类的计算,能帮助平衡样本类别imbalance的情况。但是IOU的计算这里,没有考虑到样本imbalance的情况。如果将不公平的IOU分数乘上还算公平的分类分数,那么可能导致这个结果有水分(因为我们希望IOU和分类都够好的,排到前面,作为正样本)。
yolo v8_第3张图片

问题2

yolo v8_第4张图片
作者认为对于训练中IOU形成的Dirac delta distribution (fully connect layer 去做) 或者预先做Gaussian分布的假设不足以用于general purpose 的场景。所以作者提出的新distribution, 可以让分布有形状上的明显特征。
y = ∫ [ − ∞ t o + ∞ ] δ ( x − y ) ∗ x d x y = ∫[−∞ to +∞] δ(x − y) * x dx y=[to+]δ(xy)xdx
δ ( x − y ) δ(x − y) δ(xy): Dirac delta distribution, 理想情况是当 x=y 时最高概率,其它概率为0. 针对这篇论文,这里变成了 P ( x ) P(x) P(x)
x x x: input
y y y: 已知truth bbox regressed label (因为box的相关坐标在continue space)

针对于这篇论文,为了让公式符合convolution layer ,y这个公式应是discrete。并且 P ( x ) P(x) P(x) 就是 softmax.

确定为锐利区域的是紫色箭头所指,模棱两可为平滑区域的为红色箭头所指。从图上看,当遇到模棱两可的情况时,predict 的结果离 ground truth 有点多。

问题1 solution

针对方面1的问题,在QFL中用了classification-IoU joint representation(作为NMS score,而不像之前的做法是把两者相乘)。针对方面2,将Focal loss 运用进了 loss 公式。

对于一个目标检测任务,一个样本的联合表示可以是一个四维向量 [c, IoU, x, y]

问题2 solution

第二个问题使用general 分布解决,虽然数据也会有正负样本imbalance问题,但我们做的是目标检测,只有是正样本的时候,我们才在意它的IOU,因此作者决定只考虑正样本的情况,所以DFL仅用了cross entropy。
它之所以也叫focal,应该是因为它通过增加两个y labels( y i y_i yi , y i + 1 y_{i+1} yi+1)的结果,来快速定位到正确的label y. y i < = y < = y i + 1 y_i <= y <= y_{i+1} yi<=y<=yi+1

DFL 中的 S i , S i + 1 S_i, S_{i+1} Si,Si+1 能保证 y ^ \hat{y} y^(估计的y) close to y (真实的y):
y ^ = ∑ j = 0 n P ( y j ) y j = S i y i + S i + 1 y i + 1 \hat{y} = \sum ^n_{j=0} P(y_j)y_j = S_iy_i + S_{i+1}y_{i+1} y^=j=0nP(yj)yj=Siyi+Si+1yi+1
y ^ = ( y i + 1 − y ) / ( y i + 1 − y i ) ∗ y i + ( y − y i ) / ( y i + 1 − y i ) ∗ y i + 1 \hat{y} = (y_{i+1} - y)/(y_{i+1} - y_i)*y_i + (y - y_i)/(y_{i+1} - y_i)*y_{i+1} y^=(yi+1y)/(yi+1yi)yi+(yyi)/(yi+1yi)yi+1
y ^ = y \hat{y} = y y^=y (把 y i y_i yi = y, y i + 1 = y y_{i+1} = y yi+1=y分别代入上面公式可以验证)

Finally

总的来说,作者靠QFL & DFL 解决了以上所有问题。但是一直强调的GFL呢?
在论文中,作者将QFL 与 DFL 做了 unified, GFL 就融合QFL 与 DFL的思想。

其它

感谢 (YOLOV8改进对比 vs v5 X)

yolo v8 里的哪些结构让它比yolo v5 更好

yolo v8 architecture
yolo v5 architecture

  1. CSP(C3) 换成了 c2f
  2. 在 Backbone 中将第一个 6x6 Conv 替换为 3x3 Conv
  3. 删除两个 Conv(在YOLOv5配置中的No.10和No.14)
  4. 在 Bottleneck 中将第一个 1x1 Conv 替换为 3x3 Conv
  5. 同 yolo v6 一样,有更多的conv 在 head 提取特征
  6. Anchor based 变成 anchor free, 在loss 处,只有 box,cls. 而没有obj
  7. Loss 计算方面采用了 TaskAlignedAssigner 正样本分配策略,并引入了 Distribution Focal Loss
  8. 训练的数据增强部分引入了 YOLOX 中的最后 10 epoch 关闭 Mosiac 增强的操作,可以有效地提升精度

C3 --> c2f

torch.split()的作用是把一个tensor拆分为多个tensor,相当于是concat的逆过程

decoupled head 只是为了做anchor free对吗

hmmm… 不完全是
decouple head 最早来自于 yoloX(anchor_free)
yolo v8_第5张图片
yolo v8_第6张图片

3x3 的 conv 要优于 1x1 的conv 的原因

yolo v8中的正负匹配和yolo v5 中正负匹配不同在哪里

Distillation

DAMO-YOLO 用了KD, yolo v6 用了 self-distillation.

https://www.youtube.com/watch?v=MvM9J1lj1a8

https://openaccess.thecvf.com/content_ICCV_2019/papers/Zhang_Be_Your_Own_Teacher_Improve_the_Performance_of_Convolutional_Neural_ICCV_2019_paper.pdf

https://crossminds.ai/video/generalized-focal-loss-learning-qualified-and-distributed-bounding-boxes-for-dense-object-detection-606fdcaef43a7f2f827bf6f1/

你可能感兴趣的:(YOLO,深度学习,计算机视觉)