记一次ONNX问题排查(关联mmcls, mmdeploy)
起因是在opencls中,自定义的mobilenetv2的config文件,训练两个标签,数据量合计1000左右,转换到onnx模型后, b标签的精度严重降低。
特别感谢@hanrui1sensetime在我问题排查中提供的帮助
情况描述
自定义的mobilenetv2的config文件,训练两个标签,数据量合计1000左右,转换到onnx模型后, b标签的精度严重降低。
-
使用训练生成的
best_accuracy_top-1_xxx.pth
, 对val数据集进行测试,命令为python tools/test.py configs\mobilenet_v2\mobilenet-v2_custom.py work_dirs\mobilenet-v2_custom\best_accuracy_top-1_xxx.pth --out data\my_dataset\result.json
, 截取一部分的输出结果为:[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
val中a,b 标签对半,看上去是正常的。
-
不论使用
tools/deployment/pytorch2onnx.py
,还是使用mmdeploy
生成的onnx文件,对val中b标签数据的准确率为31.2%. -
mmcls
的生成命令:python tools/deployment/pytorch2onnx.py \ configs\mobilenet_v2\mobilenet-v2_custom.py \ --checkpoint path/to/pth/best_accuracy_top-1_xxx.pth \ --output-file path/to/onnx/output.onnx
-
mmdeploy
中的命令片段如下:python ./tools/deploy.py \ configs/mmcls/classification_onnxruntime_dynamic.py \ path/to/mmclassification/mobilenet-v2_custom.py \ path/to/pth/best_accuracy_top-1_xxx.pth \ path/to/img/val/b/a.jpg \ --test-img path/to/img/val/b/b.jpg \ --work-dir path/to/output \ --device cpu \ --log-level INFO \ --show \ --dump-info
-
pip list | grep "mmcv\|mmcls\|^torch"
命令的输出:-
mmcls 0.23.1 d:\projects\github\mmclassification
-
mmcv-full 1.5.3
-
torch 1.8.2+cu111
-
torchaudio 0.8.2
-
torchvision 0.9.2+cu111
-
-
自己的配置文件:
_base_ = [ '../_base_/models/mobilenet_v2_1x.py', '../_base_/schedules/imagenet_bs256_epochstep.py', '../_base_/default_runtime.py' ] dataset_type = 'CustomDataset' classes = ['a', 'b'] img_norm_cfg = dict( mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True) train_pipeline = [ dict(type='LoadImageFromFile'), dict(type='RandomResizedCrop', size=224, backend='pillow'), dict(type='RandomFlip', flip_prob=0.5, direction='horizontal'), dict(type='Normalize', **img_norm_cfg), dict(type='ImageToTensor', keys=['img']), dict(type='ToTensor', keys=['gt_label']), dict(type='Collect', keys=['img', 'gt_label']) ] test_pipeline = [ dict(type='LoadImageFromFile'), dict(type='Resize', size=(256, -1), backend='pillow'), dict(type='CenterCrop', crop_size=224), dict(type='Normalize', **img_norm_cfg), dict(type='ImageToTensor', keys=['img']), dict(type='Collect', keys=['img']) ] data = dict( samples_per_gpu=64, workers_per_gpu=1, train=dict( type = dataset_type, data_prefix = 'data/my_dataset/train', classes = classes, pipeline=train_pipeline ), val=dict( type = dataset_type, data_prefix = 'data/my_dataset/val', classes = classes, pipeline=test_pipeline ), test=dict( type = dataset_type, data_prefix = 'data/my_dataset/test', classes = classes, pipeline=test_pipeline ) ) evaluation = dict(interval=1, save_best='auto', metric='accuracy', metric_options={'topk': (1, )}) checkpoint_config = dict(interval=10) runner = dict(type='EpochBasedRunner', max_epochs=300)
想到的可能
- 导出到pth时精度降低:测试无这个问题
- pth到onnx时哪里不对:一开始我推测是这样,可能是模型哪里bug了,或者说导出工具没支持到算子,后来用了其他两个官方支持的模型,也出现了精度问题。总不能全部模型都出问题吧。
后续的交流
之后@hanrui1sensetime提醒了我,可以用官方模型,官方数据集来进行测试,确定是哪里的问题。因为按理来说,官方config是经过测试的。
于是我去测了。导出后的onnx依然测不准。于是我带着demo代码去提了issue。
import numpy as np
import onnxruntime as ort
from PIL import Image
session= ort.InferenceSession("path/to/end2end.onnx")
path = "path/to/img"
image = Image.open(path).resize((224,224))
image_data = np.array(image).transpose(2, 0, 1)
input_name = session.get_inputs()[0].name
label_name = session.get_outputs()[0].name
pred_onx = session.run([label_name], {input_name: [img_data]})[0]
print(pred_onx)
print(pred_onx.argmax())
然后回复是没有归一化...config那边做的预处理,这边也要再做一次。
下面是修复后的代码:
import numpy as np
import onnxruntime as ort
from PIL import Image
session= ort.InferenceSession("path/to/end2end.onnx")
path = "path/to/img"
image = Image.open(path).resize((224,224))
image_data = np.array(image).transpose(2, 0, 1).astype(np.float64)
mean=np.array([123.675, 116.28, 103.53])
image_data[0,:,:] -= mean[0]
image_data[1,:,:] -= mean[1]
image_data[2,:,:] -= mean[2]
std=np.array([58.395, 57.12, 57.375])
image_data[0,:,:] /= std[0]
image_data[1,:,:] /= std[1]
image_data[2,:,:] /= std[2]
input_name = session.get_inputs()[0].name
label_name = session.get_outputs()[0].name
pred_onx = session.run([label_name], {input_name: [image_data]})[0]
print(pred_onx)
print(pred_onx.argmax())
所以一定要多翻翻文档。但是这个好像也没说,也可能是我菜了。
C++部署,问题再现
本以为Python踩过坑了,在Cpp上会轻松很多,没想到C++也一样是重重困难。
首先是Cmake加依赖,demo用到的是onnxruntime和opencv。opencv还好,之前有踩过坑,所以很轻松。而onnxruntime给的预编译是不带cmake的,就需要手动加include和lib,又去翻了一遍cmake教程属于是。
当然,上面的困难还不算大困难,之后遇到了onnx的各种东西怎么调用,几经周转参考了这篇文章。就基本弄明白调用了。
c++大致流程是创建env等一大堆东西,在run的时候都丢进去
但是又遇到了之前的精度问题。这次当然是已经归一化了。于是考虑别的原因。
直到我打印输入数据的大小,比图像大了一倍!
然后就想起了我对代码的改动:input里,别人用array,我用的vector,array当然要预先声明长度,vector不需要啊!那我提前申请了一倍大小的vector,当然最终结果是2倍大小了。
笑死,光速改了代码,精度正常。
To be continued...