天天看點

openvino使用(一)轉換并量化(INT8)分類網絡模型0. 前言1. 在pycharm中使用openvino環境2. 将ONNX模型轉為IR格式3. 使用benchmark腳本進行性能測試4. 使用INT8推理5. 使用python接口調用生成的IR模型

0. 前言

在上一篇博文中,我們講述了如何在ubuntu18.04下安裝了openvino工具。那麼本文就在之前安裝配置好的環境中來簡單使用下openvino工具。之前有做過使用pytorch搭建resnet網絡的教程,有興趣的可以看下Resnet網絡結構詳解與模型的搭建,本示例就以将pytorch訓練的Resnet34為例進行講解,具體流程如下:

  • 将Pytorch模型轉為ONNX格式(這個不講,直接參考Pytorch官網的教程)
  • 将ONNX格式轉為openvino的IR格式(float32)
  • 将IR模型(float32)量化成(int8)
  • 使用openvino的推理引擎調用量化後的IR模型進行推理預測

1. 在pycharm中使用openvino環境

如果你不用pycharm,而是直接通過終端調用python腳本,那麼可直接跳過該步驟。

安裝配置好openvino後,打開終端時系統會自動載入openvino的相關資源(因為我們在安裝openvino時将載入資源的指令寫入到目前使用者的

~/.bashrc

檔案中了,是以在終端中進入python環境導入openvino包是正常的),但在pycharm中(直接通過圖示啟動的)導入openvino包時,會報錯找不到openvino(因為這裡的pycharm沒有載入目前使用者

~/.bashrc

中的環境)。解決這個問題的方法有很多,我這裡提供一個笨方法。

由于直接使用圖示啟動pycharm時不會載入目前使用者

~/.bashrc

中的環境,但如果打開終端(此時已載入openvino的環境了),通過終端啟動pycharm就能夠正常使用openvino包了。

如果不知道pycharm的啟動檔案在哪,可以通過以下指令查找:

sudo find / -name pycharm.sh
           

找到後進入

pycharm.sh

所在檔案夾,執行該啟動腳本:

./pycharm.sh
           

這樣就可以在pycharm中調試含有openvino包的代碼了。

2. 将ONNX模型轉為IR格式

将onnx模型轉成IR格式(建議轉FP32,FP16除了模型小一點,沒任何提速),這裡使用的ONNX模型時自己通過pytorch搭建的resnet34并轉成ONNX格式:

首先進入

<INSTALL_DIR>/deployment_tools/model_optimizer

檔案夾:

cd ~/intel/openvino/deployment_tools/model_optimizer
           

然後使用mo.py檔案進行轉換,如果不将預處理方法寫入網絡,可使用以下指令:

python mo.py --input_model ~/my_project/resnet34.onnx --output_dir ~/openvino_samples/ --input_shape [1,3,224,224] --data_type FP32 
           

下面是轉換過程中,終端列印的資訊:

openvino使用(一)轉換并量化(INT8)分類網絡模型0. 前言1. 在pycharm中使用openvino環境2. 将ONNX模型轉為IR格式3. 使用benchmark腳本進行性能測試4. 使用INT8推理5. 使用python接口調用生成的IR模型

如果要将預處理方法(這裡僅指減mean,除以std的預處理)寫入網絡,可使用以下指令:

其中

--input_model

為需要轉換的onnx檔案路徑,

--input_shape

為指定輸入圖像的shape,

--data_type

為轉換後的資料類型,

--mean_values

為圖像預處理過程中減去的mean,

--scale_values

為圖像預處理過程中除以的std,其他參數可參考官方文檔。

3. 使用benchmark腳本進行性能測試

首先進入

<INSTALL_DIR>/deployment_tools/tools/benchmark_tool

檔案夾:

cd ~/intel/openvino/deployment_tools/tools/benchmark_tool
           

然後安裝下需要使用的python環境:

pip install -r requirements.txt
           

接着使用benchmark_app.py腳本進行測試:

python ./benchmark_app.py -m ~/openvino_samples/resnet34.xml -d CPU -api async -i ~/openvino_samples/tulip.jpg -progress true -b 1
           

這裡有些參數需要注意下,

-m

是指定前面轉換的IR檔案的xml檔案路徑,

-d

是指定裝置類型,

-API

是推理方式,同步或異步

sync, async

-i

是指定輸入圖像的路徑,

-progress

是否顯示進度條,

-b

指定輸入的batch_size,還有很多參數可以通過以下指令查詢:

python ./benchmark_app.py -h

最後在終端會輸出如下類似内容:

采用異步推理的輸出,采用異步的方式能夠充分使用硬體資源,跑測試時8核的CPU,8個核全部跑滿。通過以下輸出可以看出,

Latency

較高,但

Count

也高。

[Step 10/11] Measuring performance (Start inference asyncronously, 4 inference requests using 4 streams for CPU, limits: 60000 ms duration)
Progress: |................................| 100%

[Step 11/11] Dumping statistics report
Count:      3092 iterations
Duration:   60072.90 ms
Latency:    76.20 ms
Throughput: 51.47 FPS
           

采用同步推理的輸出,采用同步的方式能夠以最快的速度去推理,跑測試時8核的CPU,隻有4個核全部跑滿。通過以下輸出可以看出,

Latency

較低,但

Count

也低。

[Step 10/11] Measuring performance (Start inference syncronously, limits: 60000 ms duration)
Progress: |................................| 100%

[Step 11/11] Dumping statistics report
Count:      2640 iterations
Duration:   60003.31 ms
Latency:    21.21 ms
Throughput: 47.15 FPS
           

4. 使用INT8推理

在将模型部署至生産環境時,一般為了加速模型的推理速度,常常會将模型變量類型從float32轉成int8(加速模型推理,但精度會有一定下降)。對于量化模型至int8,openvino的官方文檔中給了兩種量化方法。一種是快速簡單的

fast DefaultQuantization

,一種是更加精确的

precise AccuracyAwareQuantization

方法。

4.1 安裝Post-Training Optimization工具

在使用openvino的量化工具前需要先配置下環境。

  • ~/intel/openvino/deployment_tools/open_model_zoo/tools/accuracy_checker

    目錄下執行以下指令:
  • ~/intel/openvino/deployment_tools/tools/post_training_optimization_toolkit

    目錄下執行以下指令:

配置好環境後,在終端中就可以直接使用

pot

指令(在後面量化過程中會使用到):

4.2 Annotation Converters标注轉換

在轉換之前,我們需要準備一下資料(在量化過程中會使用到)。對于自己的資料集首先轉換成openvino支援的資料格式,這裡使用imagenet資料集格式(公開常用的資料集格式openvino基本都支援),對于分類任務需要

annotation.txt

檔案以及

labels.txt

共兩個檔案(後面測試發現不需要labels.txt檔案也行)。

這裡提供一個腳本,利用自己的資料集生成

annotation.txt

以及

labels.txt

檔案,下面是已花分類資料集為例(五類花),資料的擺放目錄如下:

├── dataset:: 存放資料集的根目錄
│     ├── daisy:         該檔案夾下存放類别為daisy的所有圖檔
│     ├── dandelion:     該檔案夾下存放類别為dandelion的所有圖檔
│     ├── roses:         該檔案夾下存放類别為roses的所有圖檔
│     ├── sunflowers:    該檔案夾下存放類别為sunflowers的所有圖檔
│     └── tulips :       該檔案夾下存放類别為tulips的所有圖檔
           

然後使用我提供的python腳本來生成所需檔案:

import os
import glob

image_dir = "/your_dataset_dir"
assert os.path.exists(image_dir), "image dir does not exist..."

img_list = glob.glob(os.path.join(image_dir, "*", "*.jpg"))
assert len(img_list) > 0, "No images(.jpg) were found in image dir..."

classes_info = os.listdir(image_dir)
classes_info.sort()
classes_dict = {}

# create label file
with open("my_labels.txt", "w") as lw:
    # 注意,沒有背景時,index要從0開始
    for index, c in enumerate(classes_info, start=0):
        txt = "{}:{}".format(index, c)
        if index != len(classes_info):
            txt += "\n"
        lw.write(txt)
        classes_dict.update({c: str(index)})
print("create my_labels.txt successful...")

# create annotation file
with open("my_annotation.txt", "w") as aw:
    for img in img_list:
        img_classes = classes_dict[img.split("/")[-2]]
        txt = "{} {}".format(img, img_classes)
        if index != len(img_list):
            txt += "\n"
        aw.write(txt)
print("create my_annotation.txt successful...")
           

接着使用

convert_annotation

工具将自己标注的資料集(之前轉成imagenet格式)進行轉換,其實隻會在

AccuracyAwareQuantization

方法中使用到

imagenet.pickle

檔案:

convert_annotation imagenet --annotation_file ./my_annotation.txt --labels_file ./my_labels.txt --has_background False -o new_annotations -a imagenet.pickle -m imagenet.json
           

下面是官方給的參數解釋:

openvino使用(一)轉換并量化(INT8)分類網絡模型0. 前言1. 在pycharm中使用openvino環境2. 将ONNX模型轉為IR格式3. 使用benchmark腳本進行性能測試4. 使用INT8推理5. 使用python接口調用生成的IR模型

4.3 使用

DefaultQuantization

方法

在準備好上述

annotation.txt

檔案後,接下來配置使用

fast DefaultQuantization

方法中所需的

json

檔案。在

~/intel/openvino/deployment_tools/tools/post_training_optimization_toolkit/config

檔案夾中有一些官方提供的模闆。這裡我們參考

default_quantization_template.json

模闆,并進行修改。

{
    /* Model parameters */

    "model": {
        "model_name": "resnet34", // Model name
        "model": "/home/wz/openvinotest/resnet34.xml", // Path to model (.xml format)
        "weights": "/home/wz/openvinotest/resnet34.bin" // Path to weights (.bin format)
    },

    /* Parameters of the engine used for model inference */

    "engine": {
        "config": "resnet34.yaml" // Path to Accuracy Checker config
    },

    /* Optimization hyperparameters */

    "compression": {
        "target_device": "CPU", // Target device, the specificity of which will be taken
                                // into account during optimization
        "algorithms": [
            {
                "name": "DefaultQuantization", // Optimization algorithm name
                "params": {
                    "preset": "performance", // Preset [performance, mixed, accuracy] which control the quantization
                                             // mode (symmetric, mixed (weights symmetric and activations asymmetric)
                                             // and fully asymmetric respectively)

                    "stat_subset_size": 300  // Size of subset to calculate activations statistics that can be used
                                             // for quantization parameters calculation
                }
            }
        ]
    }
}
           

在這個模闆中,我們需要配置的參數有

model_name

(自定義量化模型的名稱),

model

(之前轉換好的

*.xml

檔案),

weights

(之前轉換好的

*.bin

檔案)以及engine中的

config

(這裡指向的是個yaml檔案,下面會解釋)。其他的參數基本按預設來就行了,具體每個參數官方都有解釋。

接下來配置剛剛上面提到的yaml檔案(engine->config),在

~/intel/openvino/deployment_tools/tools/post_training_optimization_toolkit/configs/examples/accuracy_checker

檔案夾中也有官方給的一些模闆,下面是我自己根據模闆改的一個

yaml

models:
  - name: resnet34

    launchers:
      - framework: dlsdk
        device: CPU
        adapter: classification

    datasets:
      - name: classification_dataset
        data_source: /home/wz/openvinotest/data_set/flower_data
        annotation_conversion:
          converter: imagenet  
          annotation_file: /home/wz/openvinotest/my_annotation.txt

        reader: opencv_imread  # default setting

        preprocessing:
          - type: resize
            size: 256
            aspect_ratio_scale: greater
          - type: crop
            size: 224
          - type: bgr_to_rgb  # bgr format in opencv 
          - type: normalization
            # you may specify precomputed statistics manually or use precomputed values, such as ImageNet as well
            mean: (123.675, 116.28, 103.53)
            std: (58.395, 57.12, 57.375)
           

其中

name

data_source

(感覺這個參數沒有用,因為資料的路徑都記錄在annotation.txt檔案中,但又不可缺)以及

annotation_file

(就是上面生成的

annotation.txt

檔案)參數是需要我們配置的。

配置好相關檔案後,使用pot工具調用我們上面配置好的json檔案進行量化:

pot -c ./default_quantization_resnet34.json
           

4.4 使用

AccuracyAwareQuantization

方法

這裡先貼張官方的介紹。我這裡簡單談下我自己的看法(英語比較渣,有錯誤還請指教)。

  • 首先

    AccuracyAwareQuantization

    方法會執行前面說到的

    DefaultQuantization

    方法去量化整個模型。
  • 接着将量化前和量化後的模型在我們提供的驗證集上進行比較,找出預測差異較大的樣本。
  • 如果精度下降程度超過我們的要求,将模型設定成混合精度的模式。
  • 對所有層進行排序,排序的依據是根據量化前後導緻精度下降了多少,将量化前後對精度影響較大的層排在前面。
  • 将量化前後對精度影響最大的層還原回原始精度(這裡我是使用float32轉的,是以還原回float32),接着重新計算在所有驗證集上的精度。
  • 如果精度下降的程度達到我們的要求,就停止,否則接着對層排序,接着以上步驟。
    openvino使用(一)轉換并量化(INT8)分類網絡模型0. 前言1. 在pycharm中使用openvino環境2. 将ONNX模型轉為IR格式3. 使用benchmark腳本進行性能測試4. 使用INT8推理5. 使用python接口調用生成的IR模型
    首先配置下

    AccuracyAwareQuantization

    方法所需的

    json

    檔案。在

    ~/intel/openvino/deployment_tools/tools/post_training_optimization_toolkit/config

    檔案夾中有一些官方提供的模闆。這裡我們參考

    accuracy_aware_quantization_template.json

    模闆,并進行修改。
/* This configuration file is the fastest way to get started with the accuracy aware
quantization algorithm. It contains only mandatory options with commonly used
values. All other options can be considered as an advanced mode and requires
deep knowledge of the quantization process. An overall description of all possible
parameters can be found in the accuracy_aware_quantization_spec.json */

{
    /* Model parameters */

    "model": {
        "model_name": "resnet34a", // Model name
        "model": "/home/wz/openvinotest/resnet34p.xml", // Path to model (.xml format)
        "weights": "/home/wz/openvinotest/resnet34p.bin" // Path to weights (.bin format)
    },

    /* Parameters of the engine used for model inference */

    "engine": {
        "config": "resnet34.yaml" // Path to Accuracy Checker config
    },

    /* Optimization hyperparameters */

    "compression": {
        "target_device": "CPU", // Target device, the specificity of which will be taken
                                // into account during optimization
        "algorithms": [
            {
                "name": "AccuracyAwareQuantization", // Optimization algorithm name
                "params": {
                    "preset": "performance", // Preset [performance, mixed, accuracy] which control the quantization
                                             // mode (symmetric, mixed (weights symmetric and activations asymmetric)
                                             // and fully asymmetric respectively)

                    "stat_subset_size": 300, // Size of subset to calculate activations statistics that can be used
                                             // for quantization parameters calculation

                    "maximal_drop": 0.01 // Maximum accuracy drop which has to be achieved after the quantization
                }
            }
        ]
    }
}
           

下面是在配置

json

檔案中指向的yaml檔案,其中的annotation是我們之前轉換的

*.pickle

檔案:

models:
  - name: resnet34

    launchers:
      - framework: dlsdk
        device: CPU
        adapter: classification

    datasets:
      - name: classification_dataset
        data_source: /home/wz/data_set/flower_data
        
        annotation: /home/wz/openvinotest/imagenet.pickle

        reader: opencv_imread  # default setting

        preprocessing:
          - type: resize
            size: 256
            aspect_ratio_scale: greater
          - type: crop
            size: 224
          - type: bgr_to_rgb  # bgr format in opencv 
          - type: normalization
            # you may specify precomputed statistics manually or use precomputed values, such as ImageNet as well
            mean: (123.675, 116.28, 103.53)
            std: (58.395, 57.12, 57.375)

        metrics:
          - name: [email protected]
            type: accuracy
            top_k: 1

          - name: [email protected]
            type: accuracy
            top_k: 5
           

配置好相關檔案後,使用pot工具調用我們上面配置好的json檔案進行量化:

pot -c ./accuracy_aware_quantization_resnet34.json
           

下圖是在量化過程中終端的輸出,可以看到首先使用

DefaultQuantization

方法去量化,量化後acuuracy下降的很少,是以就直接停止了,沒有去使用

accuracy_aware_quantization

去進一步調整。(由于使用的模型比較簡單,是以量化過程比較順利)

openvino使用(一)轉換并量化(INT8)分類網絡模型0. 前言1. 在pycharm中使用openvino環境2. 将ONNX模型轉為IR格式3. 使用benchmark腳本進行性能測試4. 使用INT8推理5. 使用python接口調用生成的IR模型

4.5 benchmark測試(int8)

首先看下在float32下跑的benchmark名額:

[Step 10/11] Measuring performance (Start inference asyncronously, 4 inference requests using 4 streams for CPU, limits: 60000 ms duration)
Progress: |................................| 100%

[Step 11/11] Dumping statistics report
Count:      2932 iterations
Duration:   60094.71 ms
Latency:    78.73 ms
Throughput: 48.79 FPS
           

在看下量化後(int8)的benchmark名額:

[Step 10/11] Measuring performance (Start inference asyncronously, 4 inference requests using 4 streams for CPU, limits: 60000 ms duration)
Progress: |................................| 100%

[Step 11/11] Dumping statistics report
Count:      5820 iterations
Duration:   60036.01 ms
Latency:    40.42 ms
Throughput: 96.94 FPS

           

5. 使用python接口調用生成的IR模型

下面給出了一個自己根據demo改的基礎調用IR模型方法,(這裡以前面轉換的resnet34分類模型為例)需要自己指定

model_xml_path

model_bin_path

image_path

以及

class_json_path

,其中

image_path

是存放圖像的根目錄,程式會自動尋找該目錄下的所有

*.jpg

檔案,并進行推理。

class_json_path

是對應分類任務的index與label的json檔案,格式如下:

{
    "0": "daisy",
    "1": "dandelion",
    "2": "roses",
    "3": "sunflowers",
    "4": "tulips"
}
           

下面是我提供的python測試代碼:

import sys
import cv2
import os
import glob
import json
import numpy as np
import logging as log
from openvino.inference_engine import IECore


def main():
    device = "CPU"
    model_xml_path = "./resnet34.xml"
    model_bin_path = "./resnet34.bin"
    image_path = "./"
    class_json_path = './class_indices.json'

    # set log format
    log.basicConfig(format="[ %(levelname)s ] %(message)s", level=log.INFO, stream=sys.stdout)

    assert os.path.exists(model_xml_path), ".xml file does not exist..."
    assert os.path.exists(model_bin_path), ".bin file does not exist..."

    # search *.jpg files
    image_list = glob.glob(os.path.join(image_path, "*.jpg"))
    assert len(image_list) > 0, "no image(.jpg) be found..."

    # load class label
    assert os.path.exists(class_json_path), "class_json_path does not exist..."
    json_file = open(class_json_path, 'r')
    class_indict = json.load(json_file)

    # inference engine
    ie = IECore()

    # read IR
    net = ie.read_network(model=model_xml_path, weights=model_bin_path)
    # load model
    exec_net = ie.load_network(network=net, device_name=device)

    # check supported layers for device
    if device == "CPU":
        supported_layers = ie.query_network(net, "CPU")
        not_supported_layers = [l for l in net.layers.keys() if l not in supported_layers]
        if len(not_supported_layers) > 0:
            log.error("device {} not support layers:\n {}".format(device,
                                                                  ",".join(not_supported_layers)))
            log.error("Please try to specify cpu extensions library path in sample's command line parameters using -l "
                      "or --cpu_extension command line argument")
            sys.exit(1)

    # get input and output name
    input_blob = next(iter(net.input_info))
    output_blob = next(iter(net.outputs))

    # set batch size
    batch_size = 1
    net.batch_size = batch_size

    # read and pre-process input images
    n, c, h, w = net.input_info[input_blob].input_data.shape
    # images = np.ndarray(shape=(n, c, h, w))
    # inference every image
    for i in range(len(image_list)):
        image = cv2.imread(image_list[i])
        if image.shape[:-1] != (h, w):
            image = cv2.resize(image, (w, h))
        # bgr(opencv default format) -> rgb
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        # pre-process
        image = image / 255.
        image = (image - [0.485, 0.456, 0.406]) / [0.229, 0.224, 0.225]
        # change data from HWC to CHW
        image = image.transpose((2, 0, 1))
        # add batch dimension
        image = np.expand_dims(image, axis=0)

        # start sync inference
        res = exec_net.infer(inputs={input_blob: image})
        prediction = np.squeeze(res[output_blob])
        # print(prediction)

        # np softmax process
        prediction -= np.max(prediction, keepdims=True)  # 為了穩定地計算softmax機率, 一般會減掉最大元素
        prediction = np.exp(prediction) / np.sum(np.exp(prediction), keepdims=True)
        class_index = np.argmax(prediction, axis=0)
        print("prediction: '{}'\nclass:{}  probability:{}\n".format(image_list[i],
                                                                    class_indict[str(class_index)],
                                                                    np.around(prediction[class_index]), 2))


if __name__ == '__main__':
    main()
           

繼續閱讀