• 基于OpenVINO 2022.1 POT API实现YOLOv5模型INT8量化


    作者:孙霞克  英特尔AI框架软件工程师

    1、概述

    Ultralytics YOLOv5作为最流行的目标检测网络之一,因为其良好的工程化和文档支持,深受广大AI开发者的喜爱,也广泛地应用于工业界实践中。我们在之前的文章《基于OpenVINOTM 2022.1实现YOLOv5推理程序》及 《使用OpenVINOTM预处理API进一步提升YOLOv5推理性能》中详细介绍了:

    • YOLOv5框架的安装以及如何导出YOLOv5 ONNX模型
    • OpenVINOTM 2022.1的安装以及如何编写YOLOv5模型的推理程序
    • 使用OpenVINOTM 2022.1的预处理API提升YOLOv5模型的推理计算性能。

    在此基础上,本文将重点介绍如何使用OpenVINOTM 2022.1 Post-training Optimization Tool(POT)API对YOLOv5 OpenVINO FP32模型进行INT8量化,实现模型文件压缩,从而进一步提高模型推理性能。此外,我们提供了FP32和INT8模型精度计算方法,介绍了OpenVINO 性能测试工具Benchmark App的使用方法,并展示了基于OpenVINO backend的YOLOv5 INT8模型目标检测demo。

    本文完整代码请参考OpenVINO notebook: 220-yolov5-accuracy-check-and-quantization

    2、什么是POT工具

    POT通过在已训练好的模型上应用量化算法,将模型的权重和激活函数从FP32/FP16的值域映射到INT8的值域中,从而实现模型压缩,以降低模型推理所需的计算资源和内存带宽,进一步提高模型的推理性能。不同于Quantization-aware Training (QAT)方法, POT在不需要对原模型进行fine tuning的情况下进行量化,也能得到精度较好的INT8模型,因此广泛地被应用于工业界的量化实践中。

    Fig.1展示了OpenVINO POT优化的主要流程, 我们可以看到使用POT工具需要以下几个要素:

    • 一个能在CPU上运行推理程序的OpenVINO FP32/FP16 IR (Intermediate Representation)模型
    • 有代表性场景的数据作为标定数据集(比如300张带标注的图片)
    • 精度校验所需的验证数据集及评价精度的Metric

     Fig.1 OpenVINO POT优化的主要流程

    2.1 POT两种量化算法

    POT提供了两种量化算法: Default Quantization和Accuracy-aware Quantization,其中

    • Default Quantization (DQ)提供了一种快速的量化方法,量化后的模型在大多数情况下能够提供较好的精度,适合作为模型INT8量化的baseline。
    • Accuracy-aware Quantization (AAQ)是一种基于Default Quantization上的迭代量化算法,以DQ量化后的模型作为baseline,若INT8模型精度达到预期精度范围,则停止迭代,反之,量化算法会分析模型各层对精度的影响,并将对精度影响最大的层回退到FP32精度,然后重新评估模型精度,重复以上流程,直至模型达到预期精度范围。

    2.2 POT两种调用方式

    POT提供了以下两种调用方式:POT命令行方式和POT API方式,其中

    • POT命令行方式:通过命令行运行相应配置文件来调用OpenVINO Accuracy Checker Tool预定义的DataLoader, Metric, Adapter, Pre/Postprocessing等模块,这种方式适用于OpenVINO Open Model Zoo支持模型或类似模型的INT8量化。
    • POT API方式:提供了POT 量化流水线通用化的接口,包括DataLoader和Metric等基类,用户可以通过继承DataLoader来定义客制化的数据集加载及预处理模块,通过继承Metric来定义客制化的后处理和精度计算的模块。这种方式更加灵活,可以适用不同客制化模型的量化需求。Fig.2展示了基于POT API进行INT8量化的通用流程。

    因为YOLOv5模型的前后处理模块包括letterbox,Non-maximum Suppression(NMS)与OpenVINO Accuracy Checker Tool预定义的前后处理模块不完全一致,因此我们采用基于POT API调用方式,通过集成客制化DataLoader和Metric到量化流水线,来实现YOLOv5的模型INT8量化。

    3、基于POT API对YOLOv5模型进行量化

       3.1 配置YOLOv5和OpenVINO开发环境

    首先,下载YOLOv5源码,安装YOLOv5和OpenVINO的python依赖。

    git clone https://github.com/ultralytics/yolov5.git -b v6.1

    cd yolov5 && pip install -r requirements.txt && pip install \ openvino==2022.1.0 openvino-dev==2022.1.0

    然后,通过YOLOv5提供的export.py将预训练的Pytorch模型转换为OpenVINO FP32 IR模型,Fig.3展示了模型转换的输出结果。

    python export.py --weights yolov5m/yolov5m.pt --imgsz 640 \

    --batch-size 1 --include openvino

     Fig.3 YOLOv5 Pytorch模型转换为OpenVINO FP32 IR模型输出结果

    接下来,我们按以下步骤来实现基于POT API的量化流水线:

    • 创建YOLOv5 DataLoader Class:定义数据和annotation加载和预处理
    • 创建COCOMetric Class:定义模型后处理及精度计算方法
    • 设置量化算法及相关参数,定义并运行量化流水线

    3.2 创建YOLOv5DataLoader Class

    首先,我们通过继承POT DataLoader基类来定义客制化的YOLOv5Dataloader子类。我们节选了部分代码如下,其中_init_dataloader(self)函数调用YOLOv5自定义的create_dataloader()函数读取数据集并通过letterbox改变输入图片尺寸。此外,每次调用函数__getitem__(self, item)会读取index为item的输入图片和对应的annotation,并对图片做归一化处理,最后返回item,annotation和预处理后的图片。

    1. class YOLOv5DataLoader(DataLoader):
    2. """ Inherit from DataLoader function and implement for YOLOv5.
    3. """
    4. def _init_dataloader(self):
    5. dataloader = create_dataloader(self._data_source['val'],
    6. imgsz=self._imgsz, batch_size=self._batch_size,
    7. stride=self._stride, single_cls=self._single_cls,
    8. pad=self._pad, rect=self._rect, workers=self._workers)[0]
    9. return dataloader
    10. def __getitem__(self, item):
    11. try:
    12. batch_data = next(self._data_iter)
    13. except StopIteration:
    14. self._data_iter = iter(self._data_loader)
    15. batch_data = next(self._data_iter)
    16. im, target, path, shape = batch_data
    17. im = im.float()
    18. im /= 255
    19. nb, _, height, width = im.shape
    20. img = im.cpu().detach().numpy()
    21. target = target.cpu().detach().numpy()
    22. annotation = dict()
    23. annotation['image_path'] = path
    24. annotation['target'] = target
    25. annotation['batch_size'] = nb
    26. annotation['shape'] = shape
    27. annotation['width'] = width
    28. annotation['height'] = height
    29. annotation['img'] = img
    30. return (item, annotation), img

     3.3 创建COCOMetric Class 

        接下来我们通过继承POT Metric基类创建COCOMetric子类, 该类集成了YOLOv5原生的后处理NMS函数和计算精度的方法,可以用来计算基于COCO数据集Mean Average Precision(mAP)精度,包括AP@0.5和AP@0.5:0.95。其中update(self, output, target)函数有output和target两个输入,分别是模型推理结果的原始输出和输入图片对应的annotation。模型原始输出经过YOLOv5 NMS后处理后,会与annotation一起计算得到该输入图片的目标检测精度统计数据。最后,_process_stats(self, stats)以所有图片的精度统计数据stats为输入,计算得到包括AP@0.5和AP@0.5:0.95的模型精度。

    1. class COCOMetric(Metric):
    2. """ Inherit from DataLoader function and implement for YOLOv5.
    3. """
    4. def _process_stats(self, stats):
    5. mp, mr, map50, map = 0.0, 0.0, 0.0, 0.0
    6. stats = [np.concatenate(x, 0) for x in zip(*stats)]
    7. if len(stats) and stats[0].any():
    8. tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats, plot=False, save_dir=None, names=self._class_names)
    9. ap50, ap = ap[:, 0], ap.mean(1)
    10. mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
    11. np.bincount(stats[3].astype(np.int64), minlength=self._nc)
    12. else:
    13. torch.zeros(1)
    14. return mp, mr, map50, map
    15. def update(self, output, target):
    16. """ Calculates and updates metric value
    17. Contains postprocessing part from Ultralytics YOLOv5 project
    18. :param output: model output
    19. :param target: annotations
    20. """
    21. annotation = target[0]["target"]
    22. width = target[0]["width"]
    23. height = target[0]["height"]
    24. shapes = target[0]["shape"]
    25. paths = target[0]["image_path"]
    26. im = target[0]["img"]
    27. iouv = torch.linspace(0.5, 0.95, 10).to(self._device) # iou vector for mAP@0.5:0.95
    28. niou = iouv.numel()
    29. seen = 0
    30. stats = []
    31. # NMS
    32. annotation = torch.Tensor(annotation)
    33. annotation[:, 2:] *= torch.Tensor([width, height, width, height]).to(self._device) # to pixels
    34. lb = []
    35. out = output[0]
    36. out = torch.Tensor(out).to(self._device)
    37. out = non_max_suppression(out, self._conf_thres, self._iou_thres, labels=lb,
    38. multi_label=True, agnostic=self._single_cls)
    39. # Metrics
    40. for si, pred in enumerate(out):
    41. labels = annotation[annotation[:, 0] == si, 1:]
    42. nl = len(labels)
    43. tcls = labels[:, 0].tolist() if nl else [] # target class
    44. _, shape = Path(paths[si]), shapes[si][0]
    45. seen += 1
    46. if len(pred) == 0:
    47. if nl:
    48. stats.append((torch.zeros(0, niou, dtype=torch.bool), torch.Tensor(), torch.Tensor(), tcls))
    49. continue
    50. # Predictions
    51. if self._single_cls:
    52. pred[:, 5] = 0
    53. predn = pred.clone()
    54. scale_coords(im[si].shape[1:], predn[:, :4], shape, shapes[si][1]) # native-space pred
    55. # Evaluate
    56. if nl:
    57. tbox = xywh2xyxy(labels[:, 1:5]) # target boxes
    58. scale_coords(im[si].shape[1:], tbox, shape, shapes[si][1]) # native-space labels
    59. labelsn = torch.cat((labels[:, 0:1], tbox), 1) # native-space labels
    60. correct = process_batch(predn, labelsn, iouv)
    61. else:
    62. correct = torch.zeros(pred.shape[0], niou, dtype=torch.bool)
    63. stats.append((correct.cpu(), pred[:, 4].cpu(), pred[:, 5].cpu(), tcls))
    64. self._stats.append((correct.cpu(), pred[:, 4].cpu(), pred[:, 5].cpu(), tcls))
    65. self._last_stats = stats

     3.4 设置POT的量化参数

    创建好YOLOv5DataLoader和COCOMetric类之后,我们可以用get_config()设置POT量化流水线中model,engine,dataset,metric和algorithms的参数。以下我们节选了algorithms部分的config,这里我们选择“DefaultQuantization”量化算法来快速获得最佳性能的模型。此外,针对采用non-ReLU activation的YOLOv5模型,我们通过设置"preset": "mixed"来对模型weights进行对称量化,并对模型activation进行非对称量化,从而更好地保证量化后模型的精度。

    1. def get_config():
    2. """ Set the configuration of the model, engine,
    3. dataset, metric and quantization algorithm.
    4. """
    5. algorithms = [
    6. {
    7. "name": "DefaultQuantization", # or AccuracyAwareQuantization
    8. "params": {
    9. "target_device": "CPU",
    10. "preset": "mixed",
    11. "stat_subset_size": 300
    12. }
    13. }
    14. ]
    15. config["algorithms"] = algorithms
    16. return config

     3.5 定义并运行量化流水线

      接下来,我们以下面的代码逐步演示如何定义和运行量化流水线,其中包括模型加载、量化所需的DataLoader,Metric,Engine和Pipeline初始化、FP32模型精度校验、INT8模型量化、INT8模型精度校验等步骤。最终,量化生成的OpenVINO INT8 IR模型会被保存在本地。

    1. """ Download dataset and set config
    2. """
    3. config = get_config()
    4. init_logger(level='INFO')
    5. logger = get_logger(__name__)
    6. save_dir = increment_path(Path("./yolov5/yolov5m/yolov5m_openvino_model/"), exist_ok=True) # increment run
    7. save_dir.mkdir(parents=True, exist_ok=True) # make dir
    8. # Step 1: Load the model.
    9. model = load_model(config["model"])
    10. # Step 2: Initialize the data loader.
    11. data_loader = YOLOv5DataLoader(config["dataset"])
    12. # Step 3 (Optional. Required for AccuracyAwareQuantization): Initialize the metric.
    13. metric = COCOMetric(config["metric"])
    14. # Step 4: Initialize the engine for metric calculation and statistics collection.
    15. engine = IEEngine(config=config["engine"], data_loader=data_loader, metric=metric)
    16. # Step 5: Create a pipeline of compression algorithms.
    17. pipeline = create_pipeline(config["algorithms"], engine)
    18. metric_results = None
    19. # Check the FP32 model accuracy.
    20. metric_results_fp32 = pipeline.evaluate(model)
    21. logger.info("FP32 model metric_results: {}".format(metric_results_fp32))
    22. # Step 6: Execute the pipeline to calculate Min-Max value
    23. compressed_model = pipeline.run(model)
    24. # Step 7 (Optional): Compress model weights to quantized precision
    25. # in order to reduce the size of final .bin file.
    26. compress_model_weights(compressed_model)
    27. # Step 8: Save the compressed model to the desired path.
    28. optimized_save_dir = Path(save_dir).joinpath("optimized")
    29. save_model(compressed_model, Path(Path.cwd()).joinpath(optimized_save_dir), config["model"]["model_name"])
    30. # Step 9 (Optional): Evaluate the compressed model. Print the results.
    31. metric_results_i8 = pipeline.evaluate(compressed_model)
    32. logger.info("Save quantized model in {}".format(optimized_save_dir))
    33. logger.info("Quantized INT8 model metric_results: {}".format(metric_results_i8))

    3.6 YOLOv5m FP32和INT8模型精度比较

        Fig.4展示了YOLOv5m FP32及INT8模型精度的比较,我们可以从图中看到,相对FP32模型,通过“DefaultQuantization”算法量化后的INT8模型的精度下降控制在1%以内,对于目标识别的网络,这种程度的精度下降一般是可以接受的,如果用户对INT8模型有更高的精度要求,建议可以尝试采用“AccuracyAwareQuantization”的算法对INT8模型做进一步迭代优化。

    Fig.4 YOLOv5m FP32及INT8模型精度AP@0.5, AP@0.5:0.95比较

     3.7 OpenVINO性能测试工具Benchmark App介绍

    OpenVINO提供了性能测试工具Benchmark App,方便开发者快速测试OpenVINO模型在不同的硬件平台上的性能。我们以下面的例子,简单介绍benchmark app的使用方法和相关参数,更多内容请参考Benchmark App官方文档

    1. benchmark_app -m \
    2. ./yolov5/yolov5m/yolov5m_openvino_model/optimized/yolov5m.xml \
    3. -i ./yolov5/data/images/bus.jpg -d CPU -hint throughput

    benchmark_app提供了Python和C++的两种版本,我们使用通过openvino-dev安装的Python版本的benchmark_app来进行性能测试,涉及到的参数包括:

    • -m: 指定OpenVINO模型文件.xml的路径。这里我们设置为量化后的YOLOv5 INT8模型路径。
    • -i: 指定性能测试使用的输入数据文件/文件夹路径。这里我们选择bus.jpg图片作为输入,若-i缺省,benchmark_app会自动生成与模型输入尺寸相应的随机数据作为输入。
    • -d: 指定性能测试的目标硬件。这里我们选择CPU作为测试硬件进行推理,用户可以选择其他OpenVINO支持的目标硬件。若-d缺省, CPU会被默认选择为目标硬件。
    • -hint: 指定性能测试的优先策略,以自动选择底层性能优化相关参数。这里我们选择throughput模式来提升系统整体吞吐量。如果应用对延迟比较敏感,推荐使用latency模式来减少推理延迟。

    开发者可以参考上述例子,针对自己的硬件平台和使用场景,选择合适的性能测试参数来对YOLOv5 FP32及INT8模型进行性能测试。

     3.8 YOLOv5 INT8模型推理demo

        最后,我们用以下命令行,运行基于OpenVINO backend的YOLOv5m INT8模型推理demo。

    1. cd yolov5 && python detect.py \
    2. --weights ./yolov5m/yolov5m_openvino_model/optimized/yolov5m.xml

        Fig.5展示了推理的输入图片及INT8模型的推理结果,我们可以看到,INT8模型以较高置信度检测到了图片中的所有车和行人bounding box和label。

    Fig.5 基于OpenVINO backend的推理demo的输入图片(左图)及目标检测结果(右图)

    4、小结

        本文基于Ultralytics YOLOv5源码,将预训练的YOLOv5m Pytorch模型转换为OpenVINO FP32 Intermediate Representation (IR)模型。下一步,通过OpenVINO Post-Training Optimization Tool (POT) API来定义客制化DataLoader和Metric,从而复用YOLOv5客制化的前后处理(letterbox,Non-maximum Suppression)及精度计算等模块。采用“DefaultQuantization”的量化算法,定义和运行量化流水线对FP32模型进行INT8量化。此外,通过与FP32模型精度比较,我们发现采用“DefaultQuantization”算法量化的INT8模型已经具有较好的精度(AP@0.5, AP@0.5:0.95精度下降都小于1%)。然后,我们介绍了OpenVINO性能测试工具Benchmark App的使用方法。最后,我们通过基于OpenVINO backend的demo演示了YOLOv5m INT8模型推理的效果。

    5、参考文献

  • 相关阅读:
    JavaScript中的map()和forEach()方法有什么区别?
    MySQL数据库(5)——逻辑处理
    《数据结构学习笔记---第九篇》---循环队列的实现
    FastDFS安装
    手写 Vue2 系列 之 初始渲染
    基于粒子群优化算法的最优潮流(IEEE30节点(Matlab代码实现)
    2022年高教社杯建模国赛N条重要建议(精简版)
    Android在app中实现蓝牙服务Service的案例
    Oracle数据库:oracle启动,oracle客户端工具plsql安装教程和使用方法
    有没有不用加班的程序员 ?
  • 原文地址:https://blog.csdn.net/gc5r8w07u/article/details/126385713