画像分類モデルの量子化#

この Jupyter ノートブックはオンラインで起動でき、ブラウザーのウィンドウで対話型環境を開きます。ローカルにインストールすることもできます。次のオプションのいずれかを選択します:

BinderGoogle ColabGitHub

このチュートリアルでは、NNCF を使用して画像分類モデルに INT8 量子化を適用する方法を示します。Cifar10 データセットでトレーニングされた MobileNet V2 モデルを使用します。このコードは、カスタムモデルやデータセットに拡張できるように設計されています。このチュートリアルでは、NNCF でモデルの量子化を実行するため OpenVINO バックエンドを使用します。PyTorch モデルに量子化を適用する方法に興味がある場合は、こちらチュートリアルを確認してください。

このチュートリアルは次のステップで構成されます:

  • 量子化用のモデルを準備します。

  • データロード機能を定義します。

  • 量子化を行います。

  • 元のモデルと量子化されたモデルの精度を比較します。

  • 元のモデルと量子化されたモデルのパフォーマンスを比較します。

  • 1 枚の画像で結果を比較します。

目次:

import platform 

# 必要なパッケージをインストール 
%pip install -q "openvino>=2023.1.0" "nncf>=2.6.0" torch torchvision tqdm --extra-index-url https://download.pytorch.org/whl/cpu 

if platform.system() != "Windows":
     %pip install -q "matplotlib>=3.4" 
else:
     %pip install -q "matplotlib>=3.4,<3.7"
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
from pathlib import Path 

# データとモデルのディレクトリーを設定 
DATA_DIR = Path("data") 
MODEL_DIR = Path("model") 
model_repo = "pytorch-cifar-models" 

DATA_DIR.mkdir(exist_ok=True) 
MODEL_DIR.mkdir(exist_ok=True)

モデルの準備#

モデルの準備には次の手順があります:

  • PyTorch モデルをダウンロードします

  • モデル変換 Python API を使用してモデルを OpenVINO 中間表現形式 (IR) に変換します

  • 変換されたモデルをディスク上でシリアル化します

import sys if not Path(model_repo).exists():
     !git clone https://github.com/chenyaofo/pytorch-cifar-models.git 

sys.path.append(model_repo)
Cloning into 'pytorch-cifar-models'... 
remote: Enumerating objects: 282, done.[K 
remote: Counting objects: 100% (281/281), done.[K 
remote: Compressing objects: 100% (96/96), done.[K 
remote: Total 282 (delta 135), reused 269 (delta 128), pack-reused 1[K 
Receiving objects: 100% (282/282), 9.22 MiB | 24.58 MiB/s, done. 
Resolving deltas: 100% (135/135), done.
from pytorch_cifar_models import cifar10_mobilenetv2_x1_0 

model = cifar10_mobilenetv2_x1_0(pretrained=True)

OpenVINO は、モデル変換 Python API を使用した OpenVINO 中間表現形式への変換により、PyTorch モデルをサポートします。ov.convert_model は、PyTorch モデル・インスタンスを受け入れ、それを OpenVINO のモデルの openvino.runtime.Model 表現に変換します。オプションで、モデルトレースのヘルパーとして機能する example_input と、モデルを静的形状に変換する input_shape を指定できます。変換されたモデルは、推論のためデバイスにロードする準備ができており、save_model 関数を使用して次回使用するためにディスクに保存できます。モデル変換 Python API の詳細については、このページを参照してください。

import openvino as ov 

model.eval() 

ov_model = ov.convert_model(model, input=[1, 3, 32, 32]) 

ov.save_model(ov_model, MODEL_DIR / "mobilenet_v2.xml")

データセットの準備#

torchvisionCIFAR10 データセットを使用します。トレーニング設定から取得したモデルの前処理

import torch 
from torchvision import transforms 
from torchvision.datasets import CIFAR10 

transform = transforms.Compose( 
    [ 
        transforms.ToTensor(), 
        transforms.Normalize((0.4914, 0.4822, 0.4465), (0.247, 0.243, 0.261)), 
    ] 
) 
dataset = CIFAR10(root=DATA_DIR, train=False, transform=transform, download=True) 
val_loader = torch.utils.data.DataLoader( 
    dataset, 
    batch_size=1, 
    shuffle=False, 
    num_workers=0, 
    pin_memory=True, 
)
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to data/cifar-10-python.tar.gz
100%|██████████| 170498071/170498071 [00:06<00:00, 24813999.85it/s]
Extracting data/cifar-10-python.tar.gz to data

量子化を実行#

NNCF は、精度の低下を最小限に抑えながら、OpenVINO でニューラル・ネットワーク推論を最適化する一連の高度なアルゴリズムを提供します。MobileNetV2 を最適化するため、ポストトレーニング・モード (微調整パイプラインなし) で 8 ビット量子化を使用します。最適化プロセスには次の手順が含まれます:

  1. 量子化用のデータセットを作成します。

  2. nncf.quantize を実行して、最適化されたモデルを取得します。

  3. openvino.save_model 関数を使用して、OpenVINO IR モデルをシリアル化します。

検証データセットの作成#

NNCF は torch.utils.data.DataLoader インターフェイスと互換性があります。量子化を実行するには、量子化中にモデルに適合する入力データを準備する変換関数を使用して nncf.Dataset オブジェクトに渡す必要があります。この場合、ペア (入力テンソルとラベル) から入力テンソルを選択し、PyTorch テンソルを numpy に変換します。

import nncf 

def transform_fn(data_item): 
    image_tensor = data_item[0] 
    return image_tensor.numpy() 

quantization_dataset = nncf.Dataset(val_loader, transform_fn)
INFO:nncf:NNCF initialized successfully.Supported frameworks detected: torch, tensorflow, onnx, openvino

nncf.quantize を実行して最適化されたモデルを取得#

nncf.quantize 関数は、基本的な量子化を実行するためモデルと準備された量子化データセットを受け取ります。必要に応じて、subset_sizepresetignored_scope などの追加パラメーターを指定して、該当する場合は量子化の結果を改善できます。サポートされているパラメーターの詳細については、このページを参照してください

quant_ov_model = nncf.quantize(ov_model, quantization_dataset)
2024-07-13 00:36:24.894428: I tensorflow/core/util/port.cc:110] oneDNN custom operations are on.You may see slightly different numerical results due to floating-point round-off errors from different computation orders.To turn them off, set the environment variable TF_ENABLE_ONEDNN_OPTS=0.
2024-07-13 00:36:24.926464: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.To enable the following instructions: AVX2 AVX512F AVX512_VNNI FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
2024-07-13 00:36:25.567707: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
Output()
Output()

OpenVINO IR モデルをシリアル化#

ov.convert_model と同様に、量子化モデルは ov.Model オブジェクトであり、デバイスにロードする準備ができており、ov.save_model を使用してディスク上でシリアル化できます。

ov.save_model(quant_ov_model, MODEL_DIR / "quantized_mobilenet_v2.xml")

元のモデルと量子化されたモデルの精度を比較#

from tqdm.notebook import tqdm 
import numpy as np 

def test_accuracy(ov_model, data_loader): 
    correct = 0 
    total = 0 
    for batch_imgs, batch_labels in tqdm(data_loader): 
        result = ov_model(batch_imgs)[0] 
        top_label = np.argmax(result) 
        correct += top_label == batch_labels.numpy() 
        total += 1 
    return correct / total

推論デバイスの選択#

OpenVINO を使用して推論を実行するためにドロップダウン・リストからデバイスを選択します

import ipywidgets as widgets 

core = ov.Core() 
device = widgets.Dropdown( 
    options=core.available_devices + ["AUTO"], 
    value="AUTO", 
    description="Device:", 
    disabled=False, 
) 

device
Dropdown(description='Device:', index=1, options=('CPU', 'AUTO'), value='AUTO')
core = ov.Core() 
compiled_model = core.compile_model(ov_model, device.value) 
optimized_compiled_model = core.compile_model(quant_ov_model, device.value) 

orig_accuracy = test_accuracy(compiled_model, val_loader) 
optimized_accuracy = test_accuracy(optimized_compiled_model, val_loader)
0%|          | 0/10000 [00:00<?, ?it/s]
0%|          | 0/10000 [00:00<?, ?it/s]
print(f"Accuracy of the original model: {orig_accuracy[0] * 100 :.2f}%") 
print(f"Accuracy of the optimized model: {optimized_accuracy[0] * 100 :.2f}%")
Accuracy of the original model: 93.61% 
Accuracy of the optimized model: 93.57%

元のモデルと量子化モデルのパフォーマンスを比較#

最後に、ベンチマーク・ツールを使用して、FP32 モデルと INT8 モデルの推論パフォーマンスを測定します。 - OpenVINO の推論パフォーマンス測定ツール。

: より正確なパフォーマンスを得るには、他のアプリケーションを閉じて、ターミナル/コマンドプロンプトで benchmark_app を実行することを推奨します。benchmark_app -m model.xml -d CPU を実行して、CPU で非同期推論のベンチマークを 1 分間実行します。GPU でベンチマークを行うには、CPU を GPU に変更します。benchmark_app --help を実行すると、すべてのコマンドライン・オプションの概要が表示されます。

# 推論 FP16 モデル (OpenVINO IR) 
!benchmark_app -m "model/mobilenet_v2.xml" -d $device.value -api async -t 15
[Step 1/11] Parsing and validating input arguments 
[ INFO ] Parsing input parameters 
[Step 2/11] Loading OpenVINO Runtime 
[ INFO ] OpenVINO: 
[ INFO ] Build .................................2024.2.0-15519-5c0f38f83f6-releases/2024/2 
[ INFO ] 
[ INFO ] Device info: 
[ INFO ] AUTO 
[ INFO ] Build .................................2024.2.0-15519-5c0f38f83f6-releases/2024/2 
[ INFO ] 
[ INFO ] 
[Step 3/11] Setting device configuration 
[ WARNING ] Performance hint was not explicitly specified in command line. Device (AUTO) performance hint will be set to PerformanceMode. THROUGHPUT.
[Step 4/11] Reading model files 
[ INFO ] Loading model files 
[ INFO ] Read model took 9.87 ms 
[ INFO ] Original model I/O parameters: 
[ INFO ] Model inputs: 
[ INFO ]     x (node: x) : f32 / [...] / [1.3,32.32] 
[ INFO ] Model outputs: 
[ INFO ]     x.17 (node: aten::linear/Add) : f32 / [...] / [1,10] 
[Step 5/11] Resizing model to match image sizes and given batch 
[ INFO ] Model batch size: 1 
[Step 6/11] Configuring input of the model 
[ INFO ] Model inputs: 
[ INFO ]     x (node: x) : u8 / [N,C,H,W] / [1,3,32,32] 
[ INFO ] Model outputs: 
[ INFO ]     x.17 (node: aten::linear/Add) : f32 / [...]/ [1,10] 
[Step 7/11] Loading the model to the device 
[ INFO ] Compile model took 210.66 ms 
[Step 8/11] Querying optimal runtime parameters 
[ INFO ] Model: 
[ INFO ]     NETWORK_NAME: 
[ INFO ]     EXECUTION_DEVICES: ['CPU'] 
[ INFO ]     PERFORMANCE_HINT: PerformanceMode. THROUGHPUT 
[ INFO ]     OPTIMAL_NUMBER_OF_INFER_REQUESTS: 12 
[ INFO ]     MULTI_DEVICE_PRIORITIES: CPU 
[ INFO ]     CPU: 
[ INFO ]       AFFINITY: Affinity.CORE 
[ INFO ]       CPU_DENORMALS_OPTIMIZATION: False 
[ INFO ]       CPU_SPARSE_WEIGHTS_DECOMPRESSION_RATE: 1.0 
[ INFO ]       DYNAMIC_QUANTIZATION_GROUP_SIZE: 0 
[ INFO ]       ENABLE_CPU_PINNING: True 
[ INFO ]       ENABLE_HYPER_THREADING: True 
[ INFO ]       EXECUTION_DEVICES: ['CPU'] 
[ INFO ]       EXECUTION_MODE_HINT: ExecutionMode. PERFORMANCE 
[ INFO ]       INFERENCE_NUM_THREADS: 24 
[ INFO ]       INFERENCE_PRECISION_HINT: <Type: 'float32'> 
[ INFO ]       KV_CACHE_PRECISION: <Type: 'float16'> 
[ INFO ]       LOG_LEVEL: Level.NO 
[ INFO ]       MODEL_DISTRIBUTION_POLICY: set() 
[ INFO ]       NETWORK_NAME: Model2 
[ INFO ]       NUM_STREAMS: 12 
[ INFO ]       OPTIMAL_NUMBER_OF_INFER_REQUESTS: 12 
[ INFO ]       PERFORMANCE_HINT: THROUGHPUT 
[ INFO ]       PERFORMANCE_HINT_NUM_REQUESTS: 0 
[ INFO ]       PERF_COUNT: NO 
[ INFO ]       SCHEDULING_CORE_TYPE: SchedulingCoreType. ANY_CORE 
[ INFO ] MODEL_PRIORITY: Priority.MEDIUM 
[ INFO ] LOADED_FROM_CACHE: False 
[ INFO ] PERF_COUNT: False 
[Step 9/11] Creating infer requests and preparing input tensors 
[ WARNING ] No input files were given for input 'x'!. This input will be filled with random values! 
[ INFO ] Fill input 'x' with random values 
[Step 10/11] Measuring performance (Start inference asynchronously, 12 inference requests, limits: 15000 ms duration) 
[ INFO ] Benchmarking in inference only mode (inputs filling are not included in measurement loop).
[ INFO ] First inference took 3.59 ms 
[Step 11/11] Dumping statistics report 
[ INFO ] Execution Devices:['CPU'] 
[ INFO ] Count: 88356 iterations 
[ INFO ] Duration: 15003.01 ms 
[ INFO ] Latency: 
[ INFO ]     Median: 1.85 ms 
[ INFO ]     Average: 1.85 ms 
[ INFO ]     Min: 1.20 ms 
[ INFO ]     Max: 9.01 ms 
[ INFO ] Throughput: 5889.22 FPS
# 推論 INT8 モデル (OpenVINO IR) 
!benchmark_app -m "model/quantized_mobilenet_v2.xml" -d $device.value -api async -t 15
[Step 1/11] Parsing and validating input arguments 
[ INFO ] Parsing input parameters 
[Step 2/11] Loading OpenVINO Runtime 
[ INFO ] OpenVINO: 
[ INFO ] Build .................................2024.2.0-15519-5c0f38f83f6-releases/2024/2 
[ INFO ] 
[ INFO ] Device info: 
[ INFO ] AUTO 
[ INFO ] Build .................................2024.2.0-15519-5c0f38f83f6-releases/2024/2 
[ INFO ] 
[ INFO ] 
[Step 3/11] Setting device configuration 
[ WARNING ] Performance hint was not explicitly specified in command line. Device (AUTO) performance hint will be set to PerformanceMode. THROUGHPUT.
[Step 4/11] Reading model files 
[ INFO ] Loading model files 
[ INFO ] Read model took 14.98 ms 
[ INFO ] Original model I/O parameters: 
[ INFO ] Model inputs: 
[ INFO ]     x (node: x) : f32 / [...]/ [1.3,32.32] 
[ INFO ] Model outputs: 
[ INFO ]     x.17 (node: aten::linear/Add) : f32 / [...]/ [1,10] 
[Step 5/11] Resizing model to match image sizes and given batch 
[ INFO ] Model batch size: 1 
[Step 6/11] Configuring input of the model 
[ INFO ] Model inputs: 
[ INFO ]     x (node: x) : u8 / [N,C,H,W] / [1,3,32,32] 
[ INFO ] Model outputs: 
[ INFO ]     x.17 (node: aten::linear/Add) : f32 / [...]/ [1,10] 
[Step 7/11] Loading the model to the device 
[ INFO ] Compile model took 318.23 ms 
[Step 8/11] Querying optimal runtime parameters 
[ INFO ] Model: 
[ INFO ]     NETWORK_NAME: 
[ INFO ]     EXECUTION_DEVICES: ['CPU'] 
[ INFO ]     PERFORMANCE_HINT: PerformanceMode. THROUGHPUT 
[ INFO ]     OPTIMAL_NUMBER_OF_INFER_REQUESTS: 12 
[ INFO ]     MULTI_DEVICE_PRIORITIES: CPU 
[ INFO ]     CPU: 
[ INFO ]       AFFINITY: Affinity.CORE 
[ INFO ]       CPU_DENORMALS_OPTIMIZATION: False 
[ INFO ]       CPU_SPARSE_WEIGHTS_DECOMPRESSION_RATE: 1.0 
[ INFO ]       DYNAMIC_QUANTIZATION_GROUP_SIZE: 0 
[ INFO ]       ENABLE_CPU_PINNING: True 
[ INFO ]       ENABLE_HYPER_THREADING: True 
[ INFO ]       EXECUTION_DEVICES: ['CPU'] 
[ INFO ]       EXECUTION_MODE_HINT: ExecutionMode. PERFORMANCE 
[ INFO ]       INFERENCE_NUM_THREADS: 24 
[ INFO ]       INFERENCE_PRECISION_HINT: <Type: 'float32'> 
[ INFO ]       KV_CACHE_PRECISION: <Type: 'float16'> 
[ INFO ]       LOG_LEVEL: Level.NO 
[ INFO ]       MODEL_DISTRIBUTION_POLICY: set() 
[ INFO ]       NETWORK_NAME: Model2 
[ INFO ]       NUM_STREAMS: 12 
[ INFO ]       OPTIMAL_NUMBER_OF_INFER_REQUESTS: 12 
[ INFO ]       PERFORMANCE_HINT: THROUGHPUT 
[ INFO ]       PERFORMANCE_HINT_NUM_REQUESTS: 0 
[ INFO ]       PERF_COUNT: NO 
[ INFO ]       SCHEDULING_CORE_TYPE: SchedulingCoreType. ANY_CORE 
[ INFO ] MODEL_PRIORITY: Priority. MEDIUM 
[ INFO ] LOADED_FROM_CACHE: False 
[ INFO ] PERF_COUNT: False 
[Step 9/11] Creating infer requests and preparing input tensors 
[ WARNING ] No input files were given for input 'x'!. This input will be filled with random values! 
[ INFO ] Fill input 'x' with random values 
[Step 10/11] Measuring performance (Start inference asynchronously, 12 inference requests, limits: 15000 ms duration) 
[ INFO ] Benchmarking in inference only mode (inputs filling are not included in measurement loop).
[ INFO ] First inference took 1.88 ms 
[Step 11/11] Dumping statistics report 
[ INFO ] Execution Devices:['CPU'] 
[ INFO ] Count: 166056 iterations 
[ INFO ] Duration: 15001.00 ms 
[ INFO ] Latency: 
[ INFO ]     Median: 1.01 ms 
[ INFO ]     Average: 1.04 ms 
[ INFO ]     Min: 0.73 ms 
[ INFO ]     Max: 7.22 ms 
[ INFO ] Throughput: 11069.66 FPS

4 枚の写真の結果を比較#

# CIFAR10 データセットから可能なすべてのラベルを定義 
labels_names = [ 
    "airplane", 
    "automobile", 
    "bird", 
    "cat", 
    "deer", 
    "dog", 
    "frog", 
    "horse", 
    "ship", 
    "truck", 
] 
all_pictures = [] 
all_labels = [] 

# すべての写真とそのラベルを取得 
for i, batch in enumerate(val_loader): 
    all_pictures.append(batch[0].numpy()) 
    all_labels.append(batch[1].item())
import matplotlib.pyplot as plt 

def plot_pictures(indexes: list, all_pictures=all_pictures, all_labels=all_labels): 
    """Plot 4 pictures. 
    :param indexes: a list of indexes of pictures to be displayed. 
    :param all_batches: batches with pictures.
    """ 
    images, labels = [], [] 
    num_pics = len(indexes) 
    assert num_pics == 4, f"No enough indexes for pictures to be displayed, got {num_pics}" 
    for idx in indexes: 
        assert idx < 10000, "Cannot get such index, there are only 10000" 
        pic = np.rollaxis(all_pictures[idx].squeeze(), 0, 3) 
        images.append(pic) 

        labels.append(labels_names[all_labels[idx]])

    f, axarr = plt.subplots(1, 4) 
    axarr[0].imshow(images[0]) 
    axarr[0].set_title(labels[0]) 

    axarr[1].imshow(images[1]) 
    axarr[1].set_title(labels[1]) 

    axarr[2].imshow(images[2]) 
    axarr[2].set_title(labels[2]) 

    axarr[3].imshow(images[3]) 
    axarr[3].set_title(labels[3])
def infer_on_pictures(model, indexes: list, all_pictures=all_pictures): 
    """Inference model on a few pictures.
    :param net: model on which do inference 
    :param indexes: list of indexes 
    """ 
    output_key = model.output(0) 
    predicted_labels = [] 
    for idx in indexes: 
        assert idx < 10000, "Cannot get such index, there are only 10000" 
        result = model(all_pictures[idx])[output_key] 
        result = labels_names[np.argmax(result[0])] 
        predicted_labels.append(result) 
    return predicted_labels
indexes_to_infer = [7, 12, 15, 20] # プロットするには、4 つのインデックスを指定 

plot_pictures(indexes_to_infer) 

results_float = infer_on_pictures(compiled_model, indexes_to_infer) 
results_quanized = infer_on_pictures(optimized_compiled_model, indexes_to_infer) 

print(f"Labels for picture from float model : {results_float}.") 
print(f"Labels for picture from quantized model : {results_quanized}.")
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). 
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). 
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Labels for picture from float model : ['frog', 'dog', 'ship', 'horse']. 
Labels for picture from quantized model : ['frog', 'dog', 'ship', 'horse'].
../_images/image-classification-quantization-with-output_30_2.png