标题问题1、
出现该问题说明你并未连接rk1808或者未给出rk1808的ID;如果你是在rknn模拟rk1808环境下进行测试,则需要将代码中的相应语句进行修改:
ret = rknn.init_runtime() #模拟rk1808环境下进行测试
ret = rknn.init_runtime('rk1808',device_id='1808') #连接rk1808进行测试
问题2、rknn中不支持pytorch+GPU,因此需要转化成CPU模型
问题3、
出现以上问题,需检查yolov5的版本,yolov5 5.*版本的输出有三个,而yolov5 6.*版本的输出有4个。
问题4、
出现该问题的原因是numpy版本太高,改成1.16.3版本或者降低到适合的版本。
pip install --upgrade numpy==1.16.3
问题5、
出现该问题的原因是onnxruntime与CUDA版本不匹配,进入给出的网址选择合适的onnxruntime版本。
参考:rknn模型
自己训练了一个只有2种类别的yolov8模型之后,部署到瑞芯微RKNN。
踩坑一:类别的变化
之前用COCO数据集训练的.pt模型转rknn后,output0的shape为8400 x 176.
而把自定义数据集训练的模型转rknn后,output0的shape为8400 x 98.
为什么会不一样?一度以为模型或代码有问题,拿图片测了下发现模型没问题,能检测出来。
分析一下8400x176,其中8400是proposal的个数,176是box相关的64+80个类别+32的mask coefficient.
现在类别变成了2,自然要变成64+2+32=98.
所以,如果后处理中用到类别数=80,画图中的类别标签用了80个的,这里需要修改一下。
踩坑二:模型转化(.pt 转 .rknn)
用转化后的rknn模型检测,只能检测出一种类别,另一种检测不出来。
于是从后处理出发找原因,
现输入图片中有两种物体,每种一个,理论上应该检测出2个物体。
先看NMS之前检测了多少个物体。
std::vector<int> picked;
nms_sorted_bboxes(proposals, picked); //picked里面保存的是proposals的下标
int count = picked.size();
这里NMS之前 picked.size() = 1, 说明NMS之前就只检测出一个,已经不对了。
回到NMS之前的处理,
这里选择较大概率的类别作为label,保存label, score和box坐标。
int label = -1;
float score = -FLT_MAX;
//找到最大score和对应的label
for (int k = 0; k < num_class; k++) {
float confidence = deqnt_affine_to_f32(score_ptr[k], zp,
scale); //反量化,int转float
if (confidence > score) {
label = k;
score = confidence;
}
}
float box_prob = sigmoid(score);
if (box_prob >= thres) {
//忽略一段处理过程
obj.rect.x = x0;
obj.rect.y = y0;
obj.rect.width = x1 - x0;
obj.rect.height = y1 - y0;
obj.label = label;
obj.prob = box_prob;
}
于是追踪label = 0和label = 1的检测概率。
用同一图片测试,很神奇的是.pt模型(转rknn之前)测试中,label=0和label=1的目标 检测概率都>0.9,
而转rknn之后label=1的目标 检测概率只有0.5,label=0的目标 检测概率小得出奇。
观察到score作softmax运算前,label=0的目标 反量化的检测概率小于-7.
认为这是反量化出现了问题,不过反量化用的是从rknn模型中读出的量化参数scales和zp。
for (int i = 0; i < io_num.n_output; ++i) {
output_attrs[i].index = i;
if(rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr)) < 0) {
LOGE("rknn_query output_attrs[%d] fail!\n", i);
return;
}
// set out_scales/out_zps for post_process
out_scales.push_back(output_attrs[i].scale);
out_zps.push_back(output_attrs[i].zp);
}
如果量化参数有问题,那就是转rknn模型的过程有问题。
找到转rknn模型的代码。
OUT_DIR = "rknn_models"
RKNN_MODEL_PATH = '{}/{}.rknn'.format(OUT_DIR,exp)
if NEED_BUILD_MODEL:
DATASET = './dataset.txt'
rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform="rkXXXX")
# Load model
print('--> Loading model')
ret = rknn.load_onnx(MODEL_PATH)
if ret != 0:
print('load model failed!')
exit(ret)
print('done')
# Build model
print('--> Building model')
ret = rknn.build(do_quantization=True, dataset=DATASET)
if ret != 0:
print('build model failed.')
exit(ret)
print('done')
# Export rknn model
if not os.path.exists(OUT_DIR):
os.mkdir(OUT_DIR)
print('--> Export RKNN model: {}'.format(RKNN_MODEL_PATH))
ret = rknn.export_rknn(RKNN_MODEL_PATH)
if ret != 0:
print('Export rknn model failed.')
exit(ret)
print('done')
else:
ret = rknn.load_rknn(RKNN_MODEL_PATH)
rknn.release()
注意到rknn模型的转换用到了dataset.txt,它指定的是原版COCO训练下的yolov8的图片,而现在的自定义数据集中没有这样的图片。
换成现在自定义数据集中的图片,再次转rknn模型。
然后观察scales和zp这两个量化值,发现跟之前不一样了。
再次检测,成功检测出2个目标,且检测概率>0.9,和.pt模型差不多。
总结:
转rknn模型时用的图片可能需要用现有数据集中的图片,不要用不相关的图片。
【RK3588 第四篇】YOLO V5中pytorch2onnx,pytorch和onnx模型输出不一致,精度降低
在yolo v5训练的模型,转onnx,再转rknn后,测试发现:
rknn模型,量化与非量化,相较于pytorch模型,测试精度都有降低
onnx模型,相较于pytorch模型,测试精度也有降低,且与rknn模型的精度更接近
于是,根据这种测试情况,rknn模型的上游,就是onnx。onnx这里发现不对劲,肯定是这步就出现了问题。于是就查pytorch转onnx阶段,就存在转化的精度降低了。
一、pytorch转onnx:torch.onnx.export
yolo v5 export.py: def export_onnx()中,添加下面代码,检查转储的onnx模型,与pytorch模型的输出结果是否一致。代码如下:
torch.onnx.export(
model.cpu() if dynamic else model, # --dynamic only compatible with cpu
im.cpu() if dynamic else im,
f,
verbose=False,
opset_version=opset,
export_params=True, # 将训练好的权重保存到模型文件中
do_constant_folding=True, # 执行常数折叠进行优化
input_names=['images'],
output_names=output_names,
dynamic_axes={
"image": {0: "batch_size"}, # variable length axes
"output": {0: "batch_size"},
}
)
# Checks
model_onnx = onnx.load(f) # load onnx model
onnx.checker.check_model(model_onnx) # check onnx model
import onnxruntime
import numpy as np
print('onnxruntime run start', f)
sess = onnxruntime.InferenceSession('best.onnx')
print('sess run start')
output = sess.run(['output0'], {'images': im.detach().numpy()})[0]
print('pytorch model inference start')
pytorch_result = model(im)[0].detach().numpy()
print(' allclose start')
print('output:', output)
print('pytorch_result:', pytorch_result)
assert np.allclose(output, pytorch_result), 'the output is different between pytorch and onnx !!!'
对其中的输出结果进行了打印,将差异性比较明显的地方进行了标记,如下所示:
也可以直接使用我下面这个版本,在转完onnx后
import os
import platform
import sys
import warnings
from pathlib import Path
import torch
FILE = Path(__file__).resolve()
ROOT = FILE.parents[0] # YOLOv5 root directory
if str(ROOT) not in sys.path:
sys.path.append(str(ROOT)) # add ROOT to PATH
if platform.system() != 'Windows':
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
from models.experimental import attempt_load
from models.yolo import ClassificationModel, Detect, DetectionModel, SegmentationModel
from utils.dataloaders import LoadImages
from utils.general import (LOGGER, Profile, check_dataset, check_img_size, check_requirements, check_version,
check_yaml, colorstr, file_size, get_default_args, print_args, url2file, yaml_save)
from utils.torch_utils import select_device, smart_inference_mode
import numpy as np
def cosine_distance(arr1, arr2):
# flatten the arrays to shape (16128, 7)
arr1_flat = arr1.reshape(-1, 7)
arr2_flat = arr2.reshape(-1, 7)
# calculate the cosine distance
cosine_distance = np.dot(arr1_flat.T, arr2_flat) / (np.linalg.norm(arr1_flat) * np.linalg.norm(arr2_flat))
return cosine_distance.mean()
def check_onnx(model, im):
import onnxruntime
import numpy as np
print('onnxruntime run start')
sess = onnxruntime.InferenceSession('best.onnx')
print('sess run start')
output = sess.run(['output0'], {'images': im.detach().numpy()})[0]
print('pytorch model inference start')
with torch.no_grad():
pytorch_result = model(im)[0].detach().numpy()
print(' allclose start')
print('output:', output, output.shape)
print('pytorch_result:', pytorch_result, pytorch_result.shape)
cosine_dis = cosine_distance(output, pytorch_result)
print('cosine_dis:', cosine_dis)
# 判断小数点后几位(4),是否相等,不相等就报错
# np.testing.assert_almost_equal(pytorch_result, output, decimal=4)
# compare ONNX Runtime and PyTorch results
np.testing.assert_allclose(pytorch_result, output, rtol=1e-03, atol=1e-05)
# assert np.allclose(output, pytorch_result), 'the output is different between pytorch and onnx !!!'
import cv2
from utils.augmentations import letterbox
def preprocess(img, device):
img = cv2.resize(img, (512, 512))
img = img.transpose((2, 0, 1))[::-1]
img = np.ascontiguousarray(img)
img = torch.from_numpy(img).to(device)
img = img.float()
img /= 255
if len(img.shape) == 3:
img = img[None]
return img
def main(
weights=ROOT / 'weights/best.pt', # weights path
imgsz=(512, 512), # image (height, width)
batch_size=1, # batch size
device='cpu', # cuda device, i.e. 0 or 0,1,2,3 or cpu
inplace=False, # set YOLOv5 Detect() inplace=True
dynamic=False, # ONNX/TF/TensorRT: dynamic axes
):
# Load PyTorch model
device = select_device(device)
model = attempt_load(weights, device=device, inplace=True, fuse=True) # load FP32 model
# Checks
imgsz *= 2 if len(imgsz) == 1 else 1 # expand
# Input
gs = int(max(model.stride)) # grid size (max stride)
imgsz = [check_img_size(x, gs) for x in imgsz] # verify img_size are gs-multiples
im = torch.zeros(batch_size, 3, *imgsz).to(device) # image size(1,3,320,192) BCHW iDetection
# im = cv2.imread(r'F:\tmp\yolov5_multiDR\data\0000005_20200929_M_063Y16640.jpeg')
# im = preprocess(im, device)
print(im.shape)
# Update model
model.eval()
for k, m in model.named_modules():
if isinstance(m, Detect):
m.inplace = inplace
m.dynamic = dynamic
m.export = True
warnings.filterwarnings(action='ignore', category=torch.jit.TracerWarning) # suppress TracerWarning
check_onnx(model, im)
if __name__ == "__main__":
main()
这更像是一个运行环境收集的一个记录文件。在我对全网进行搜索时候,发现了类似的报错提示,但并没有解决办法。不知道是不是因为这个函数还在内测阶段,并没有很好的适配。
二、新的pytorch转onnx:torch.onnx.dynamo_export
在参考pytorch官方,关于torch.onnx.export的模型转换,相关文档中:(OPTIONAL) EXPORTING A MODEL FROM PYTORCH TO ONNX AND RUNNING IT USING ONNX RUNTIME
上述案例,是pytorch官方给出评测pytorch和onnx转出模型,在相同输入的情况下,输出结果一致性对比的评测代码。对比这里:
testing.assert_allclose(actual, desired, rtol=1e-07, atol=0, equal_nan=True, err_msg='', verbose=True)
其中:
rtol:相对tolerance(容忍度,公差,容许偏差)
atol:绝对tolerance
要求 actual 的 desired 值的差别不超过 atol + rtol * abs(desired),否则弹出错误提示。
于是,就转到torch.onnx.dynamo_export链接,点击这里直达:[EXPORT A PYTORCH MODEL TO ONNX]
同样的流程,导出模型,然后进行一致性评价,发现官方竟然没有采用允许误差的评测,而是下面这样:
致,这是一个大好消息。至此,开始验证
2.1、验证结果
与此同时,发现yolo v5更新到了v7.0.0的版本,于是就想着把yolo 进行升级,同时将pytorch版本也更新到最新的2.1.0,这样就可以采用torch.onnx.dynamo_export 进行转onnx模型的操作尝试了。
当一起就绪后,采用下面的代码转出onnx模型的时候,却出现了错误提示。
export_output = torch.onnx.dynamo_export(model.cpu() if dynamic else model,
im.cpu() if dynamic else im)
export_output.save("my_image_classifier.onnx")
2.2、转出失败
给出失败的的提示:torch.onnx.OnnxExporterError,转出onnx模型失败,产生了一个SARIF的文件。然后介绍了什么是SARIF文件,可以通过VS Code SARIF,也可以 SARIF web查看。最后说吧这个错误,报告给pytorch的GitHub的issue地方。
产生了一个名为:report_dynamo_export.sarif是文件,打开文件,记录的信息如下:
{
"runs":[
{
"tool":{
"driver":{
"name":"torch.onnx.dynamo_export",
"contents":[
"localizedData",
"nonLocalizedData"
],
"language":"en-US",
"rules":[],
"version":"2.1.0+cu118"
}
},
"language":"en-US",
"newlineSequences":[
"\r\n",
"\n"
],
"results":[]
}
],
"version":"2.1.0",
"schemaUri":"https://docs.oasis-open.org/sarif/sarif/v2.1.0/cs01/schemas/sarif-schema-2.1.0.json"
}
三、总结
原本想着验证最终转rknn的模型,与原始pytorch模型是否一致的问题,最后发现在转onnx阶段,这种差异性就已经存在了。并且发现rknn的测试结果,与onnx模型的测试结果更加的贴近。无论是量化后的rknn,还是未量化的,均存在这个问题。
同时发现,量化后的rknn模型,在config阶段改变量化的方式,确实会提升模型的性能,且几乎接近于未量化的模型版本。