[rknpu][yolov5]自训练yolov5模型运行于rv1126npu上(一)训练yolov5模型并转换为onnx模型

首先有几个跑通所有流程然后再回来不断调整的坑。其中与这一阶段相关的为:

1)如果用原版yolov5s.pt这种模型构架,最终跑出来,在不使用零拷贝的情况下可能在1126上运行一次640x640会花150ms~200ms。对于产品落地肯定是不能接受的,所以需要对模型作轻量优化。

2)rknn-toolkit疑似不支持pow层,没细看,一一排查,从结果而言有pow层的那几条输出会为零。

3)rknpu官方例程把最后的锚定给截掉了,使用软件写锚定。有一定可能是因为上面2)所以砍掉了锚定。但为了减少cpu占用与运行时间不稳定的可能,(加上不想写锚定的代码)我需要在模型中加回锚定。

4)因为需要量化,锚定部分的输出数量级的以百为单位的,但置信度的范围却是0~1,精度差异过大,如果不作处理最后输出置信度要么0要么1。所以需要在锚定那里作归一化。

对于坑1,想要解决这个问题,要么换其他更轻量的模型如NanoDet,实测416*416NanoDet是能在1126上跑到30~40ms;要么简化yolov5s模型,如项目github.com/EASY-EAI/yolov5换了卷积核,去掉了切片等操作,单纯使用上述项目训练然后导出,跑完整个流程最终的结果在1126上跑416*416也能跑到35ms左右。但是,EASY-EAI的项目也把锚定给截了,对于不想写锚定代码的我肯定不能接受,所以要对export代码进行一些修改。

yolov5的pt文件导出为onnx:

先使用EASY-EAI的yolov5训练特化的模型,然后他的导出给了三个选项(可同时使用)

--rknn_mode(没细看,改了很多东西,提升速度,是主要目的)

--ignore_output_permute(在砍了锚定的基础上把最后一层砍了,以契合rknpu的例程,但我不需要)

--add_image_preprocess_layer(在输入多加一个Transpose层,据称能提升set输入方式的速度,实测好像确实提升了点,不知是不是误差,但他很奇怪,下文细说)

因为我需要锚定,但不理解哪行代码控制锚定,从detect.py分析pt文件因该自带锚定,但EASY的导出文件里没找到砍或添加的部分,那只能把rknn_mode增添的内容添加到yolov5本体的export里。具体操作:

1)把EASY项目的export.py里if opt.rknn_mode != True:的else:里的内容直接拷贝到本体export.py的run函数里。运行一下看看差什么,系统找不到什么。

2)然后把相应的引用补上,例如:import models;相应的文件补上,例如:common_rk_plug_in.py放到models里;相应的函数补上,例如:common.py里添上SPP;反正运行的时候缺什么补什么。

然后说下add_image_preprocess_layer,EASY-EAI说能提升rknn_input_set的速度,但一般都会用零拷贝也就是map系的操作吧?开了这个选项的话他的输入会从1x3x416x416(NCHW)变成1x416x416x3(NHWC),然后在input后面加一层[0, 3, 1, 2]的Transpose。后面的处理没有变动,但rknn-toolkit转模型的时候报错,rknn-toolkit可能只接受NCHW格式输入的模型,就算config开force_builtin_perm=True也没用。我不理解,不清楚是否还有漏掉的地方。

如果无论如何想要这个操作,需要下面这段代码来改转好的onnx:(有种脱裤子放屁的感觉)

import onnx
import numpy as np

onnx_model = onnx.load("best.onnx")
d = onnx_model.graph.input[0].type.tensor_type.shape.dim
d[0].dim_value = 1
d[1].dim_value = 3
d[2].dim_value = 416
d[3].dim_value = 416
graph = onnx_model.graph
node  = graph.node
id = 0

for i in range(len(node)):
    if node[i].op_type == 'Transpose':
        if node[i].input[0]=='images':
            id = i

old_scale_node = node[id]
new_perm = onnx.helper.make_attribute("perm", [0,1,2,3])
del node[id].attribute[0]
node[id].attribute.extend([new_perm])

onnx.checker.check_model(onnx_model)

onnx_model = onnx.shape_inference.infer_shapes(onnx_model)
onnx.checker.check_model(onnx_model)

onnx.save(onnx_model, 'best+.onnx')

修改Pow层和归一化锚定:

上文提到,转rknn后,过pow层输出会为零,从netron可以得知这个pow的参数为2,我们可以把pow层改成mul层,让输入层自己乘自己,主要代码示例如下:

for i in range(len(node)):
    if node[i].op_type == 'Pow':
        if node[i].input[1]=='336':
            id = i

old_scale_node = node[id]
graph.node.remove(old_scale_node)

new_scale_node = onnx.helper.make_node(
    op_type='Mul',
    inputs=['335','335'],
    outputs=['337'],
)
graph.node.insert(id, new_scale_node)
onnx.checker.check_model(onnx_model)

上文同样提到,锚定需要归一化,不然置信度精度会丢失。操作为直接把锚定的最后一个mul层除一个416即可,主要代码示例如下:(338.npy是锚定层之一的参数,直接从netron下载即可。338只是名字/代号,可能不同规格的模型标号是不一样的,根据自己情况而定)

for i in range(len(node)):
    if node[i].op_type == 'Mul':
        if node[i].input[1]=='327':
            id = i

old_scale_node = node[id]
graph.node.remove(old_scale_node)

mul_tensor = onnx.helper.make_tensor('327_',onnx.TensorProto.FLOAT,[1],[8/416])
graph.initializer.append(mul_tensor)
new_scale_node = onnx.helper.make_node(
    op_type='Mul',
    inputs=['326','327_'],
    outputs=['328'],
)
graph.node.insert(id, new_scale_node)
onnx.checker.check_model(onnx_model)



for i in range(len(node)):
    if node[i].op_type == 'Mul':
        if node[i].input[1]=='338':
            id = i

old_scale_node = node[id]
graph.node.remove(old_scale_node)

data=np.load('338.npy')
data=data/416

mul_tensor = onnx.helper.make_tensor('338_',onnx.TensorProto.FLOAT,data.shape,data)
graph.initializer.append(mul_tensor)
new_scale_node = onnx.helper.make_node(
    op_type='Mul',
    inputs=['337','338_'],
    outputs=['339'],
)
graph.node.insert(id, new_scale_node)
onnx.checker.check_model(onnx_model)

上述两段代码需要的头和尾巴:

import onnx
import numpy as np
import torch

onnx_model = onnx.load("best.onnx")
graph = onnx_model.graph
node  = graph.node
id = 0

'''
上述两段代码的重复
'''

onnx_model = onnx.shape_inference.infer_shapes(onnx_model)
onnx.checker.check_model(onnx_model)

onnx.save(onnx_model, 'out.onnx')


作为个人笔记记录,也可作为参考,如有错误,还望指出)

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值