在深度学习模型部署时,从pytorch转换onnx的过程中,踩了一些坑。本文总结了这些踩坑记录,希望可以帮助其他人。
在上一次分享的时候,我们已经撰写了pth转onnx的过程及一些部署过程,今天我们继续分享整个部署过程遇到的坑及解决办法!
(点击上方图片就可以进入《模型部署:pytorch转onnx踩坑实录(上)》)
onnxruntime支持3维池化和3维卷积
在上次讲到opencv不支持3维池化,那么onnxruntime是否支持呢?接着编写了一个程序探索onnxruntime对3维池化的支持情况,代码和运行结果如下,可以看到程序报错了。
查看nn.MaxPool3d的说明文档,截图如下,可以看到它的输入和输出是5维张量,于是修改上面的代码,把输入调整到5维张量。
代码和运行结果如下,可以看到这时候onnxruntime库能正常读取onnx文件,并且它的输出结果跟pytorch的输出结果相等。
继续实验,把三维池化改作三维卷积,代码和运行结果如下,可以看到平均差异在小数点后11位,可以忽略不计。
在上次分享中讲到过opencv不支持3维池化,那时候的输入张量是4维的,如果把输入张量改成5维的,那么opencv是否就能进行3维池化计算呢?
为此,编写代码,验证这个想法。代码和运行结果如下,可以看到在cv2.dnn.blobFromImage这行代码出错了。
查看cv2.dnn.blobFromImage这个函数的说明文档,截图如下,可以看到它的输入image是4维的,这说明它不支持5维的输入。
经过这一系列的程序实验论证,可以看出onnxruntime库对onnx模型支持的更好。如果深度学习模型有3维池化或3维卷积层,那么在转换到onnx文件后,使用onnxruntime部署深度学习是一个不错的选择。并且,我看到一篇文章里面通过一系列实验,最后得出结论是onnxruntime库的推理速度最快。
onnx动态分辨率输入
不过我在做pytorch导出onnx文件时,还发现了一个问题。
在torch.export函数里有一个输入参数dynamic_axes,它表示动态的轴,即可变的维度。假如一个神经网络输入是动态分辨率的,那么需要定义dynamic_axes = {'input': {2: 'height', 3: 'width'}, 'output': {2: 'height', 3: 'width'}},接下来我编写一个程序来验证,代码和运行结果的截图如下:
可以看到,在生成onnx文件后,使用onnxruntime库读取,对输入blob的高增加10个像素单位,在run这一步出错了。使用opencv读取onnx文件,代码和运行结果的截图如下,可以看到依然出错了。
通过这个程序实验,让人怀疑torch.export函数的输入参数dynamic_axes是否真的支持动态分辨率输入的。
此外,DBNet的官方代码里提供了转换到onnx模型文件,于是我依然编写了一套使用opencv部署DBNet文字检测的程序,依然是包含C++和Python两个版本的代码。官方代码的模型是在ICDAR场景文本检测数据集上训练的,考虑到车牌里也含有文字,我把文章开头展示的汽车图片作为输入,程序检测结果如下,可以看到依然能检测到车牌的4个角点,只是不够准确。如果想要获得准确的角点定位,可以在车牌数据集上训练DBNet。
使用opencv部署DBNet文字检测的程序发布在github上,程序依然是包含c++和python两种版本的实现,地址是:
https://github.com/hpc203/dbnet-opencv-cpp-python
后来,在github上传了一套程序,使用BiSeNet做人脸面部解析,里面包含了分别基于pytorch, opencv, onnxruntime三种库的程序实现,并且比较了在调用三种库的输入和输出的差异,结果发现调用 pytorch框架的输出和调用opencv和onnxruntime的输出都不同,而opencv和onnxruntime的输出相同(平均差异在小数点后10位),这又是一个pytorch转onnx时的一个坑。这套程序的地址是:
https://github.com/hpc203/Face-Parsing-pytorch-opencv-onnxruntime