ncnn icon indicating copy to clipboard operation
ncnn copied to clipboard

使用yolo训练后再用pnnx转模型出错

Open libofei2004 opened this issue 6 months ago • 13 comments

我使用官网的yolov8n.pt模型训练我自己的目标,单一一类目标。 yolo task=detect mode=train model=yolov8n.pt data=shot.yaml epochs=100 imgsz=640 训练后用pnnx的步骤进行转换,到再次导出这一步时 python3 -c 'import best_pnnx; best_pnnx.export_torchscript()' 报错: Traceback (most recent call last): File "", line 1, in File "/mnt/d/项目/识别/yolo/best_pnnx.py", line 477, in export_torchscript mod = torch.jit.trace(net, v_0) File "/home/libofei/.local/lib/python3.10/site-packages/torch/jit/_trace.py", line 1002, in trace traced_func = _trace_impl( File "/home/libofei/.local/lib/python3.10/site-packages/torch/jit/_trace.py", line 698, in _trace_impl return trace_module( File "/home/libofei/.local/lib/python3.10/site-packages/torch/jit/_trace.py", line 1278, in trace_module module._c._create_method_from_trace( File "/home/libofei/.local/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1736, in _wrapped_call_impl return self._call_impl(*args, **kwargs) File "/home/libofei/.local/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1747, in _call_impl return forward_call(*args, **kwargs) File "/home/libofei/.local/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1726, in _slow_forward result = self.forward(*input, **kwargs) File "/mnt/d/项目/识别/yolo/best_pnnx.py", line 445, in forward v_165 = v_142.view(1, 144, -1).transpose(1, 2) RuntimeError: shape '[1, 144, -1]' is invalid for input of size 416000 请问这是怎么回事?

libofei2004 avatar Aug 27 '25 05:08 libofei2004

你的报错是因为模型输出的 shape 跟 ncnn 的要求不一致,尤其是在你只训练了单一类别时。ncnn 的 YoloDetectionOutput 层要求每个 anchor 的输出通道数必须是 4(bbox坐标)+1(置信度)+类别数(num_class),也就是单类别时每个 anchor 6 个通道。如果 view 操作用的是 144(比如 view(1, 144, -1)),但实际输出 size 不匹配,就会报错 详情见源码

解决方法是:你需要手动检查并修改 pnnx 导出的 best_pnnx.py 脚本,确保 view/transpose 操作的参数和你的类别数一致。例如,单类别时,应该用 view(1, 6 * anchor数, -1) 或类似的方式,具体 anchor 数和输出 shape要和你的模型结构对应。修改后再重新导出 torchscript,再用 pnnx 转换为 ncnn 格式。

详细流程和 shape 检查建议可以参考 官方 issue 讨论。如果还是不对,可以用 ncnn 推理一张图片,打印输出 shape,确保通道数和 grid 数都和预期一致。

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

dosubot[bot] avatar Aug 27 '25 05:08 dosubot[bot]

@dosubot 我的模型是官方下载的yolov8n.pt,以它为基础进行的训练,那么anchor数应该是多少?yolov8n原本的识别类别数是80,我只训练了一个类别,是否训练后的best.pt类别数就变成了1?

libofei2004 avatar Aug 27 '25 10:08 libofei2004

yolov8n.pt(Ultralytics官方模型)是 anchor-free 设计,ncnn 侧的 anchor 数(num_box)默认就是 1,不需要你手动设置。原始模型的类别数是80,但你只训练了一个类别,训练后的 best.pt 的类别数就变成了1,输出 shape 也会随之变化。ncnn 的 YoloDetectionOutput 层会根据你的类别数自动适配输出 shape,比如单类别时每个 grid cell 输出通道数就是 6(4+1+1),不用担心 anchor 数的问题,直接按单类别模型导出和转换即可。参考

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

dosubot[bot] avatar Aug 27 '25 10:08 dosubot[bot]

@dosu 我的问题就是不知道best_pnnx.py 脚本应该怎么改,看你的解释似乎应该是修改 view(1, 6 * anchor数, -1),如果 anchor 数是1,就应该是view(1, 6 , -1)吗?把view(1, 144, -1)改成view(1, 6 , -1)?

libofei2004 avatar Aug 27 '25 15:08 libofei2004

是的,你理解得完全正确:对于你这种单类别、anchor-free 的 YOLOv8n,view(1, 6, -1) 就是对的。原本 view(1, 144, -1) 是针对多类别(比如80类)和多 anchor 的情况,单类别时每个 grid cell 只需要 6 个通道(4 bbox + 1 obj + 1 class),anchor 数为 1。

你只需要把 best_pnnx.py 里相关的 view(1, 144, -1) 改成 view(1, 6, -1),然后重新导出 torchscript,再用 pnnx 转换即可。只要你的类别数和 anchor 数没变,其他地方不用动。如果有多个 detection head,记得每个 head 的 view 都要检查一下,确保都是 6 通道。

详细原理和官方说明可以参考 这里

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

dosubot[bot] avatar Aug 27 '25 15:08 dosubot[bot]

@dosu 我修改了之后导出还是报错: Traceback (most recent call last): File "", line 1, in File "/mnt/d/项目/识别/yolo/best_pnnx.py", line 477, in export_torchscript mod = torch.jit.trace(net, v_0) File "/home/libofei/.local/lib/python3.10/site-packages/torch/jit/_trace.py", line 1002, in trace traced_func = _trace_impl( File "/home/libofei/.local/lib/python3.10/site-packages/torch/jit/_trace.py", line 698, in _trace_impl return trace_module( File "/home/libofei/.local/lib/python3.10/site-packages/torch/jit/_trace.py", line 1278, in trace_module module._c._create_method_from_trace( File "/home/libofei/.local/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1736, in _wrapped_call_impl return self._call_impl(*args, **kwargs) File "/home/libofei/.local/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1747, in _call_impl return forward_call(*args, **kwargs) File "/home/libofei/.local/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1726, in _slow_forward result = self.forward(*input, **kwargs) File "/mnt/d/项目/识别/yolo/best_pnnx.py", line 445, in forward v_165 = v_142.view(1, 6, -1).transpose(1, 2) RuntimeError: shape '[1, 6, -1]' is invalid for input of size 416000

libofei2004 avatar Aug 27 '25 15:08 libofei2004

你遇到的 shape 报错,说明 view(1, 6, -1) 并不符合 v_142 的实际 shape。建议你在 best_pnnx.py 的 view 操作前加一句:

print(v_142.shape)

运行后会输出 v_142 的真实 shape,比如 (N,),(C, H, W) 或其他。你需要根据实际 shape 和总元素数(比如 416000),重新计算 view 的参数,确保 1 × 6 × X = 416000 能整除。比如 416000 ÷ 6 = 69333.33,说明 6 不是正确的通道数,类别数、anchor数或 head 拼接方式可能和你预期不符。

如果 v_142 是多个 head 拼接的结果,建议也分别 print 每个 head 的 shape,确认每个 head 的输出和拼接逻辑。只有实际 shape 和 view 参数完全匹配,才能避免 RuntimeError。

这种调试方式是 PyTorch 和 numpy 通用的标准做法,非常安全有效,能快速定位 shape 问题。

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

dosubot[bot] avatar Aug 27 '25 15:08 dosubot[bot]

@dosu 目前print(v_142.shape)打出的是{torch.Size([1, 65, 80, 80])},这样应该怎么算通道数?

libofei2004 avatar Aug 27 '25 15:08 libofei2004

你打印出来的 torch.Size([1, 65, 80, 80]),这里的 65 就是“通道数”。对于 YOLOv8n 单类别模型,这 65 通常表示每个 grid cell 有 13 个预测,每个预测有 5 或 6 个属性(4 个 bbox + 1 个 obj + 1 个 class),即 13×5 或 13×6。你当前的输出总元素数 1×65×80×80=416000,所以 view 操作应该用 view(1, 65, -1),而不是 view(1, 6, -1),否则会 shape 不匹配报错。

但 ncnn 的 YoloDetectionOutput 层要求输入 shape 的通道数必须是 num_box × (4+1+num_class),单类别 anchor-free 时应为 6。如果你的模型输出是 65 通道,说明 head 结构和 ncnn 期望不一致,需要进一步处理,比如拆分/拼接 head 或调整导出脚本,确保最终输出 shape 满足 ncnn 要求。详细原理和类似问题可以参考 相关 issue 讨论

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

dosubot[bot] avatar Aug 27 '25 16:08 dosubot[bot]

@dosu 那问题来了,我既然是用官方yolov8n.pt的预训练模型进行的训练,且训练只有一个类型,我的yaml文件是:

path: /mnt/d/项目/识别/yolo/train # 指向你的 val2017 文件夹 train: images/train # 训练集图片的相对路径(相对于 path) val: images/val # 验证集图片的相对路径(相对于 path) #test: images/test # 测试集路径(可选)

类别数量

nc: 1

COCO 80 类名称

names: ['shot']

为什么最后通道数是65,这是否说明训练中并没有将其他类型都去掉?

libofei2004 avatar Aug 27 '25 16:08 libofei2004

你的模型输出通道数是 65,并不是因为训练没去掉其他类别,而是 YOLOv8 的检测头结构决定的。即使你只训练了一个类别(nc:1),模型的 head 还是会保留原始的“每个 grid cell 预测 13 个框,每个框有 5 或 6 个属性(4 bbox + 1 obj + 1 class)”,所以 13×5 或 13×6=65。这个结构和类别数无关,类别数只影响每个预测的属性数量,不影响预测数量。

所以,65 通道只是模型结构的结果,和类别有没有去掉没关系,实际上你的模型已经只输出一个类别了。ncnn 的 YoloDetectionOutput 层要求输入 shape 必须是 num_box × (4+1+num_class),单类别时应该是 6 通道,但 YOLOv8 导出的模型会有 65 通道,需要你在转换或后处理时适配 shape,否则会报错。详细原理和类似问题可以参考 相关 issue 讨论

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

dosubot[bot] avatar Aug 27 '25 16:08 dosubot[bot]

@dosu 80类的是144,,1类的是65,正好差79,所以view(1, 65, -1)这个应该是正确的,而不是view(1, 6, -1),模型确实只剩一个类型了

libofei2004 avatar Aug 28 '25 04:08 libofei2004

你的理解完全正确:view(1, 65, -1) 正好对应你单类别 YOLOv8n 模型的输出 shape,这 65 是由检测头结构决定的(每个 grid cell 13 个预测 × 每个预测 5 或 6 个属性),和类别数变化有关,但和是否“去掉”其他类别无关,模型确实只剩一个类型了 参考

不过要注意,ncnn 的 YoloDetectionOutput 层要求输入通道数必须是 num_box × (4+1+num_class),单类别 anchor-free 时应为 6。如果直接用 65 通道输出,ncnn 会报 shape 不匹配错误,需要你在转换或后处理时适配 shape,否则无法正常推理 详细原理

如果需要 ncnn 兼容,建议参考官方流程,手动调整 pnnx 导出的 Python 脚本,确保最终输出 shape 满足 ncnn 要求。如果有需要,可以帮你查找相关脚本或转换细节。

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

dosubot[bot] avatar Aug 28 '25 04:08 dosubot[bot]