最近看vad相关的库发现了一个使用神经网络进行检测的仓库,试了一下感觉效果还可以,刚好这个库提供了onnx,于是想把onnx转成ncnn的模型试试看,因为是第一次做模型转换,遇到了很多问题,整理了一下对应的解决方案以供参考
相关仓库
转换的onnx模型在该仓库的files的文件里面,也可以使用torhchub下载,仓库里面也有说明
ncnn提供了编译好的二进制文件,但我是用源码编译的二进制文件,建议自己编译下debug版本的工具和库,遇到一些意想不到的问题方便调试,onnx2ncnn的源码也在仓库里面
简化onnx算子的库,提供了onnxsim
onnx转换相关工具,里面有一个onnxconverter_common.onnx2py的文件可以互相转换onnx和python文件,使用pip安装即可
pip install onnxconverter_common
编译ncnn需要用到的库
编译ncnn[可选]
开始我并不打算编译ncnn,但是因为转换过程中遇到了很多问题的错误提示信息比较模糊,只能编译一个debug版本来进行调试
ncnn的编译问题不大,但是因为ncnn提供的onnx2ncnn的工具使用protobuf,不幸的是我在安装protobuf出了问题,于是我又编译了下protobuf
- protobuf编译
以下是编译相关命令
cmake . -B build -DCMAKE_INSTALL_PREFIX:PATH=install -Dprotobuf_BUILD_SHARED_LIBS=ON
cmake --build build --target install
- 修改ncnn的CMakeLists.txt
参考下面的文档
- ncnn编译
cmake . -B build
ncnn我是用visual studio打开来运行并调试的,所以只生成了工程文件
onnx转ncnn
- 删除If算子
原始的onnx包含If很多算子,直接使用onnxsim没有办法删除,我尝试了一些修改onnx的工具,但是感觉效果并不是很好,最后是使用下面的命令
python -m onnxconverter_common.onnx2py silero_vad.onnx model.py
把onnx转换成python文件,然后修python代码再转换成onnx,幸运的是我删除了一个If算子后使用onnxsim后就把其余的If都简化了
- 删除Slice算子
转换完的onnx使用onnx2ncnn仍然会报下面的错误
Unsupported slice step !
查了相关的文档,很多都是转yolov5的模型遇到的问题,具体可以看一下github.com/Tencent/ncn… 里面的讨论
因为不知道这里问题出在哪,我查看了onnx2ncnn.cpp里面打印错误的位置,发现是onnx里面的Slice算子的step的值在onnx2ncnn里面不支持,虽然并不清楚是ncnn本身的算子不支持还是onnx2ncnn的工具没有支持
我看用netron看一下onnx的模型结构,里面有两个Slice的step为-1导致的,-1作用是对输入的数据进行翻转,我尝试删除了对应的Slice后测试了下对最终的结果影响不大,所以就没有再尝试别的方案
加载ncnn
通过上面的操作我已经可以把onnx转换成对应的ncnn模型,正当我尝试加载ncnn模型的时候,又遇到了新的问题
- load_param 报find_blob_index_by_name 144_splitncnn_-1
ncnn的仓库里面有关于这个错误的一些讨论,总结下来就是ncnn的param文件里面的有算子的输入层找不到,我查看了param的模型文件确实出现了很多splitncnn_后面为负数的层名字,推测还是onnx2ncnn的处理有问题,于是我又调试下代码同时也阅读了下代码的逻辑
onnx2ncnn在转换Slice层会替换成Split和Crop两个算子,Split把输入数据分成多个部分,而Crop分割Split的输出,正常来说Split应该输入应该是Crop的输出,而实际上Crop并没有用Split的输出而是用了MemoryData
找到了问题的位置后面就是调试onnx2ncnn的源码,最终发现问题是Squeeze和Unsqueeze这两个算子处理有问题导致的,在onnx2ncnn.cpp后添加
else if (op == "Squeeze")
{
if (node.input_size() == 2) {
node_reference[node.input(1)] -= 1;
}
}
else if (op == "Unsqueeze")
{
if (node.input_size() == 2) {
node_reference[node.input(1)] -= 1;
}
}
重新编译onnx2ncnn,再次转换后加载模型就成功了
运行ncnn对齐结果
虽然模型转换成功,但是运行的结果却完全不对,这种问题只能一层一层和onnx的模型去对结果
- onnx获取中间层结果是通过在onnx对应的python代码里添加output层获取输出
- ncnn则是使用extract来获取
- Conv卷积结果不正确
这个问题应该也是ncnn2onnx转换的问题,我把ncnn对应的param里面Convolution1D替换成ConvolutionDepthWise1D后就正常了
- BinaryOp里Pow结果不正确
这个可以参考下面的ncnn里面的isuue
总结下是因为Pow的实现在部分平台上有问题,导致输入为0时结果不正确,如果只是平方操作,比较简单的方案是把Pow换成Mul就可以
BinaryOp Pow_89 2 1 188 188 193 0=2
用上面两个输入层相乘替换下面的Pow即可
BinaryOp Pow_89 1 1 188 193 0=6 1=1 2=2.000000e+00
总结
本来以为onnx转ncnn应该是一件比较容易的事情,实际操作后才发现还是有很多问题,实际上onnx转ncnn的中途我还尝试过pnnx的方案,不过没有成功.虽然花费了很长的时间,但还是学习到了许多自己之前不了解的内容,受限于本人的水平上面的办法可能不是最好的,也欢迎指正