• 【RKNN】YOLO V5中pytorch2onnx,pytorch和onnx模型输出不一致,精度降低


    yolo v5训练的模型,转onnx,再转rknn后,测试发现:

    1. rknn模型,量化与非量化,相较于pytorch模型,测试精度都有降低
    2. 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 !!!'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    对其中的输出结果进行了打印,将差异性比较明显的地方进行了标记,如下所示:

    在这里插入图片描述
    也可以直接使用我下面这个版本,在转完onnx后,进行评测,转好的onnx和pt文件之间的差异性。如下:

    参考pytorch官方:(OPTIONAL) EXPORTING A MODEL FROM PYTORCH TO ONNX AND RUNNING IT USING ONNX RUNTIME

    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110

    测试1:图像是一个全0的数组,一致性检查如下:

    Mismatched elements: 76 / 112896 (0.0673%)
    Max absolute difference:  0.00053406
    Max relative difference:      2.2101
    
    output: [[[     3.1054       3.965      8.9553 ...  6.8545e-07     0.36458     0.53113]
      [     9.0205      2.5498       13.39 ...  6.2585e-07     0.18449     0.70698]
      [     20.786      2.2233      13.489 ...  2.3842e-06    0.033101     0.95657]
      ...
      [     419.42      493.04      106.14 ...  8.4937e-06     0.24135     0.60916]
      [     485.68      500.22      46.923 ...  1.1176e-05     0.33573     0.48875]
      [     488.37      503.87      68.881 ...  5.9605e-08  0.00030029     0.99639]]] (1, 16128, 7)
    pytorch_result: [[[     3.1054       3.965      8.9553 ...  7.0523e-07     0.36458     0.53113]
      [     9.0205      2.5498       13.39 ...  6.0181e-07     0.18449     0.70698]
      [     20.786      2.2233      13.489 ...  2.4172e-06    0.033101     0.95657]
      ...
      [     419.42      493.04      106.14 ...  8.5151e-06     0.24135     0.60916]
      [     485.68      500.22      46.923 ...  1.1174e-05     0.33573     0.48875]
      [     488.37      503.87      68.881 ...  9.3094e-08   0.0003003     0.99639]]] (1, 16128, 7)
    cosine_dis: 0.04229331
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    测试2:图像是加载的本地图像,一致性检查如下:

    Mismatched elements: 158 / 112896 (0.14%)
    Max absolute difference:   0.0016251
    Max relative difference:      1.2584
    
    output: [[[     3.0569      2.4338      10.758 ...  2.0862e-07     0.16333     0.78551]
      [     11.028      2.0251      13.407 ...  3.5763e-07    0.090503     0.88087]
      [     19.447      1.8957      13.431 ...  6.8545e-07    0.047358     0.95029]
      ...
      [     418.66       487.8      80.157 ...  1.4573e-05     0.65453     0.23448]
      [     472.99      491.78      79.313 ...  1.3232e-05     0.79356     0.15061]
      [     496.41      488.49      44.447 ...  2.6256e-05     0.89966     0.08772]]] (1, 16128, 7)
    pytorch_result: [[[     3.0569      2.4338      10.758 ...  2.5371e-07     0.16333     0.78551]
      [     11.028      2.0251      13.407 ...  3.3069e-07    0.090503     0.88087]
      [     19.447      1.8957      13.431 ...  6.6051e-07    0.047358     0.95029]
      ...
      [     418.66       487.8      80.157 ...  1.4618e-05     0.65453     0.23448]
      [     472.99      491.78      79.313 ...  1.3215e-05     0.79356     0.15061]
      [     496.41      488.49      44.447 ...  2.6262e-05     0.89966     0.08772]]] (1, 16128, 7)
    cosine_dis: 0.04071107
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    发现,输出结果中,差异的数据点还是挺多的,那么就说明在模型中,有些部分的参数是有差异的,这才导致相同的输入,在最后的输出结果中存在差异。

    但是在一定的误差内,结果是一致的。比如我验证了小数点后3位,都是一样的,但是到第4位的时候,就开始出现了差异性。

    那么,如何降低,甚至没有这种差异,该怎么办呢?不知道你们有没有这方面的知识储备或经验,欢迎评论区给出指导,感谢。

    二、新的pytorch转onnx:torch.onnx.dynamo_export

    在参考pytorch官方,关于torch.onnx.export的模型转换,相关文档中:(OPTIONAL) EXPORTING A MODEL FROM PYTORCH TO ONNX AND RUNNING IT USING ONNX RUNTIME

    1
    上述案例,是pytorch官方给出评测pytorch和onnx转出模型,在相同输入的情况下,输出结果一致性对比的评测代码。对比这里:

    testing.assert_allclose(actual, desired, rtol=1e-07, atol=0, equal_nan=True, err_msg='', verbose=True)
    
    • 1

    其中:

    • rtol:相对tolerance(容忍度,公差,容许偏差)
    • atol:绝对tolerance
    • 要求 actualdesired 值的差别不超过 atol + rtol * abs(desired),否则弹出错误提示

    可以看出,这是在误差允许的范围内,进行的评测。只要满足一定的误差要求,还是满足的。并且在本测试案例中,也确实通过了上述设定值的误差要求。

    但是,峰回路转,有个提示,如下:

    2
    于是,就转到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")
    
    • 1
    • 2
    • 3

    2.2、转出失败

    在这里插入图片描述

    给出失败的的提示:torch.onnx.OnnxExporterError,转出onnx模型失败,产生了一个SARIF的文件。然后介绍了什么是SARIF文件,可以通过VS Code SARIF,也可以 SARIF web查看。最后说吧这个错误,报告给pytorchGitHubissue地方。

    产生了一个名为: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"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    这更像是一个运行环境收集的一个记录文件。在我对全网进行搜索时候,发现了类似的报错提示,但并没有解决办法。不知道是不是因为这个函数还在内测阶段,并没有很好的适配。

    如果你也遇到了同样的问题,欢迎给评论,指导问题出在了哪里?如何解决这个问题。感谢

    三、总结

    原本想着验证最终转rknn的模型,与原始pytorch模型是否一致的问题,最后发现在转onnx阶段,这种差异性就已经存在了。并且发现rknn的测试结果,与onnx模型的测试结果更加的贴近。无论是量化后的rknn,还是未量化的,均存在这个问题。

    同时发现,量化后的rknn模型,在config阶段改变量化的方式,确实会提升模型的性能,且几乎接近于未量化的模型版本。

    原本以为采用pytorch新的转出onnx的模型函数,可以解决这个问题。但是,发现还是内测版本,不知道问题是出在了哪里,还需要大神帮助,暂时未跑通。

    最后,如果你也遇到了同样的问题,欢迎给评论,指导问题出在了哪里?如何解决这个问题。感谢

  • 相关阅读:
    MX25U25673GZ4I40(存储器)XC6SLX16-2FTG256C(FPGA)概述
    保卫你的API:深入了解接口限流
    C++-openssl-aes-cbc-pkcs5
    python实现调和反距离空间插值法AIDW
    Android 8.1 persisten 应用通过安装方式更新升级
    Day05-docker-compose与私有仓库
    小黑子—spring:第二章 注解开发
    MySQL(17):触发器
    antv-G6知识图谱安装--使用(实例)--连接线修改成动态,并添加跟随线移动的光圈,设置分支跟踪定位功能
    【黄啊码】MySQL入门—5、掌握这些数据筛选技能比你学python还有用-2
  • 原文地址:https://blog.csdn.net/wsLJQian/article/details/133783202