inisis / OnnxSlim

A Toolkit to Help Optimize Onnx Model
MIT License
71 stars 8 forks source link

[DOC] ultralytics 8.2.47~8.2.101 版本训练出来的yolov8.pt模型转ncnn出现的问题 #26

Open ZXB6 opened 3 weeks ago

ZXB6 commented 3 weeks ago

前置准备https://github.com/feigechuanshu/ncnn-android-yolov8/issues/8

ultralytics\nn\modules\block.py ultralytics\nn\modules\head.py

1.需要修改,ultralytics\nn\modules\block.py class C2f(nn.Module): 中的:

class C2f(nn.Module):
    """CSP Bottleneck with 2 convolutions."""
    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        self.c = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)
        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # optional act=FReLU(c2)
        self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))

    def forward(self, x):
        x = self.cv1(x)
        x = [x, x[:, self.c:, ...]]
        x.extend(m(x[-1]) for m in self.m)
        x.pop(1)
        return self.cv2(torch.cat(x, 1))

2.需要修改,ultralytics\nn\modules\head.py class Detect(nn.Module): 中的:

    def forward(self, x):
        """Concatenates and returns predicted bounding boxes and class probabilities."""
        shape = x[0].shape  # BCHW
        for i in range(self.nl):
            x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
        if self.training:
            return x
        elif self.dynamic or self.shape != shape:
            self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
            self.shape = shape
        pred = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2).permute(0, 2, 1)
        return pred

1.使用8.2.87

model.export(format="ncnn",
                #task="segment",
                #opset=12,
                dynamic = True,
                simplify=True)

image

2.使用8.2.87

model.export(format="onnx",
                #task="segment",
                opset=12,
                dynamic = False,
                simplify=True)

再通过onnx2ncnn 结构会多出BinaryOp,推理320会出现N个框 ,640又不准 6f655aedee1d807f016bfa5b423250f

3.使用pnnx yolov8.pt无法直接转

ae4111f26db8b165aac896c4d719ec0 需要先转成torchscript 再用pnnx,但是最终推理还是存在问题,同2

model.export(format="torchscript",imgsz=640,
                       #task="segment",
                       opset=12,
                       simplify=True)

df18a8d85f17f4172b77ae36fd143da 参考Tencent/ncnn/issues/5666

@fanxiangyu399 :经过同一个pt模型测试, 使用yolov8自带的ncnn一键导出了模型,用example的yolov8.cpp去推理速度300-500毫秒之间 网络上那些需要修改head和block所导出的模型再去用旧代码推理发现只要15-30毫秒...

还有个问题是推理的时候如果target_size如果由640改为320就会导致检测结果多至4000多...以前旧版的改了就没问题反而速度会变快

Q1 ANSWER:

@wzyforgit :第一个问题是因为官方版的把模型后处理给塞进模型里面了,他们图方便就把所有的框都解码出来了。原来那些速度快的版本在解码每个框之前会读取和计算这个框的得分,分数太低就不会去进一步解码。然后因为解码操作里面有矩阵乘法、softmax之类很慢的操作,所以用官方版的反而速度会很慢,虽然开发起来很方便=。= 解决方法就是学隔壁yolov5的example,去找到解码每个框之前的节点,在那个位置将数据给extract出来(ncnn也不会继续计算后续的节点),随后自己写先判断得分后进行解码的后处理,这样速度应该会快很多

Q2 ANSWER:

@whyb :如果target_size如果由640改为320就会导致检测结果多至4000多... 的原因是使用ultralytics官方的model.export()函数,即便您使用了 dynamic=True 参数来导出ncnn格式的模型,仍然导出来的是固定640x640 shape,我理解这是他们的bug。 如果您确实有其他图像尺寸的识别需求的话,可能尝试下先用ultralytics官方的model.export()导出torchscript,然后再使用nihui实现的pnnx工具(https://github.com/pnnx/pnnx )将其转换成 dynamic input shape 的ncnn模型。 估计只有这种方案才能达到您的需求,祝您好运~

4.使用8.2.0及以下版本转ncnn出现以下问题

63b66a68fbff1ec630e61b1b9fdb126
ZXB6 commented 3 weeks ago

解决方案:

因为8.2.0以下的loss.py没有将DFLoss单独引出来,只需要在8.2.0以下导入8.2.87等版本的即可,其他的参照之前的改法。 1727351084824 1727351108990

class DFLoss(nn.Module):
    """Criterion class for computing DFL losses during training."""

    def __init__(self, reg_max=16) -> None:
        """Initialize the DFL module."""
        super().__init__()
        self.reg_max = reg_max

    def __call__(self, 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
        """
        target = target.clamp_(0, self.reg_max - 1 - 0.01)
        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)

导出的模型和之前的一样,切推理没问题,成功解决👋 image