PyTorch フレームワークを使用した NNCF による量子化対応トレーニング#

この Jupyter ノートブックは、ローカルへのインストール後にのみ起動できます。

GitHub

このノートブックは、PyTorch での ImageNet トレーニングに基づいています。

のノートブックの目的は、ニューラル・ネットワーク圧縮フレームワーク NNCF 8 ビット量子化を使用して、OpenVINO ツールキットによる推論用に PyTorch モデルを最適化する方法を示すことです。最適化プロセスには次の手順が含まれます:

  • オリジナルの FP32 モデルを INT8 に変換します。

  • 微調整により精度を向上させます。

  • 最適化されたオリジナルのモデルを OpenVINO IR にエクスポートします

  • モデルのパフォーマンスを測定および比較します。

さらに高度な使用法については、これらのを参照してください。

このチュートリアルでは、ResNet-18 モデルと Tiny ImageNet-200 データセットを使用します。ResNet-18 は、最も少ないレイヤー (18) を含む ResNet モデルのバージョンです。Tiny ImageNet-200 は、より小さな画像を含むより大きな ImageNet データセットのサブセットです。データセットはノートブックにダウンロードされます。より小さいモデルとデータセットを使用すると、トレーニングとダウンロード時間が短縮されます。他の ResNet モデルを確認するには、PyTorch ハブにアクセスしてください。

: このノートブックには、量子化のため PyTorch カスタム操作をコンパイルする C++ コンパイラーが必要です。Windows* の場合は、C++ サポートを備えた Visual Studio* をインストールすることを推奨します。手順については、こちらをご覧ください。macOS* では、xcode-select --install コマンドで、C++ を含む開発者ツールをインストールします。Linux* では、ディストリビューションのパッケージ・マネージャーを使用して gcc をインストールできます。

目次:

%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu "openvino>=2024.0.0" "torch" "torchvision" "tqdm" 
%pip install -q "nncf>=2.9.0"
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.

インポートと設定#

Windows では、必要な C++ ディレクトリーをシステム PATH に追加します。

NNCF とすべての補助パッケージを Python コードからインポートします。モデルの名前と、ネットワークに使用される画像の幅と高さを設定します。また、モデルの PyTorch および OpenVINO IR バージョンが保存されるパスを定義します。

: チュートリアルを簡略化するために、エラーレベル未満のすべての NNCF ログメッセージ (情報および警告) が無効になっています。運用環境で使用する場合は、set_log_level(logging.ERROR) を削除してログを有効にすることを推奨します。

import time 
import warnings # エクスポート・モデルの警告を無効にします 
import zipfile 
from pathlib import Path 

import torch 

import torch.nn as nn 
import torch.nn.parallel 
import torch.optim 
import torch.utils.data 
import torch.utils.data.distributed 
import torchvision.datasets as datasets 
import torchvision.models as models 
import torchvision.transforms as transforms 

import openvino as ov 
from torch.jit import TracerWarning 

# `notebook_utils` モジュールを取得 
import requests 

r = requests.get( 

url="https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py", 
) 

open("notebook_utils.py", "w").write(r.text) 
from notebook_utils import download_file 

torch.manual_seed(0) 
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 
print(f"Using {device} device") 

MODEL_DIR = Path("model") 
OUTPUT_DIR = Path("output") 
DATA_DIR = Path("data") 
BASE_MODEL_NAME = "resnet18" 
image_size = 64 

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

# PyTorch および OpenVINO IR モデルが保存されるパス 
fp32_pth_path = Path(MODEL_DIR / (BASE_MODEL_NAME + "_fp32")).with_suffix(".pth") 
fp32_ir_path = fp32_pth_path.with_suffix(".xml") 
int8_ir_path = Path(MODEL_DIR / (BASE_MODEL_NAME + "_int8")).with_suffix(".xml") 

# FP32 モデルを最初からトレーニングすることは可能ですが、遅くなる可能性があります。したがって、事前トレーニングされた重みはデフォルトでダウンロードされます 
pretrained_on_tiny_imagenet = True 
fp32_pth_url = 
"https://storage.openvinotoolkit.org/repositories/nncf/openvino_notebook_ckpts/302_resnet18_fp32_v1.pth" 
download_file(fp32_pth_url, directory=MODEL_DIR, filename=fp32_pth_path.name)
Using cpu device
model/resnet18_fp32.pth: 0%|          | 0.00/43.1M [00:00<?, ?B/s]
PosixPath('/opt/home/k8sworker/ci-ai/cibuilds/ov-notebook/OVNotebookOps-727/.workspace/scm/ov-notebook/notebooks/pytorch-quantization-aware-training/model/resnet18_fp32.pth')

Tiny ImageNet データセットをダウンロード

  • 形状 3x64x64 の 100k の画像

  • 200 種類のクラス: ヘビ、クモ、ネコ、トラック、バッタ、カモメなど。

def download_tiny_imagenet_200( 
    data_dir: Path, 
    url="http://cs231n.stanford.edu/tiny-imagenet-200.zip", 
    tarname="tiny-imagenet-200.zip", 
): 
    archive_path = data_dir / tarname 
    download_file(url, directory=data_dir, filename=tarname) 
    zip_ref = zipfile.ZipFile(archive_path, "r") 
    zip_ref.extractall(path=data_dir) 
    zip_ref.close() 

def prepare_tiny_imagenet_200(dataset_dir: Path): 
    # フォーマット検証セットは、トレインセットがフォーマットされるのと同じ方法で設定 
    val_data_dir = dataset_dir / "val" 
    val_annotations_file = val_data_dir / "val_annotations.txt" 
    with open(val_annotations_file, "r") as f: 
        val_annotation_data = map(lambda line: line.split("\t")[:2], f.readlines()) 
    val_images_dir = val_data_dir / "images" 
    for image_filename, image_label in val_annotation_data: 
        from_image_filepath = val_images_dir / image_filename 
        to_image_dir = val_data_dir / image_label 
        if not to_image_dir.exists(): 
            to_image_dir.mkdir() 
        to_image_filepath = to_image_dir / image_filename 
        from_image_filepath.rename(to_image_filepath) 
    val_annotations_file.unlink() 
    val_images_dir.rmdir() 

DATASET_DIR = DATA_DIR / "tiny-imagenet-200" 
if not DATASET_DIR.exists(): 
    download_tiny_imagenet_200(DATA_DIR) 
    prepare_tiny_imagenet_200(DATASET_DIR) 
    print(f"Successfully downloaded and prepared dataset at: {DATASET_DIR}")
data/tiny-imagenet-200.zip: 0%|          | 0.00/237M [00:00<?, ?B/s]
Successfully downloaded and prepared dataset at: data/tiny-imagenet-200

浮動小数点モデルの事前トレーニング#

モデル圧縮に NNCF を使用する場合は、事前トレーニングされたモデルとトレーニング・パイプラインがすでに使用されていることを前提としています。

このチュートリアルでは、考えられるトレーニング・パイプラインの 1 つを示します。ImageNet の 1000 クラスで事前トレーニングされた ResNet-18 モデルが、Tiny-ImageNet の 200 クラスで微調整されます。

その後、トレーニング関数と検証関数は量子化対応トレーニングにそのまま再利用されます。

トレーニング関数#

def train(train_loader, model, criterion, optimizer, epoch): 
    batch_time = AverageMeter("Time", ":3.3f") 
    losses = AverageMeter("Loss", ":2.3f") 
    top1 = AverageMeter("Acc@1", ":2.2f") 
    top5 = AverageMeter("Acc@5", ":2.2f") 
    progress = ProgressMeter( 
        len(train_loader), 
        [batch_time, losses, top1, top5], 
        prefix="Epoch:[{}]".format(epoch), 
    ) 

    # トレーニング・モードに切り替え 
    model.train() 

    end = time.time() 
    for i, (images, target) in enumerate(train_loader): 
        images = images.to(device) 
        target = target.to(device) 

        # 出力を計算 
        output = model(images) 
        loss = criterion(output, target) 

        # 精度を測定し損失を記録 
        acc1, acc5 = accuracy(output, target, topk=(1, 5)) 
        losses.update(loss.item(), images.size(0)) 
        top1.update(acc1[0], images.size(0)) 
        top5.update(acc5[0], images.size(0)) 

        # 勾配を計算し opt ステップを実行 
        optimizer.zero_grad() 
        loss.backward() 
        optimizer.step() 

        # 経過時間を測定 
        batch_time.update(time.time() - end) 
        end = time.time() 

        print_frequency = 50 
        if i % print_frequency == 0: 
            progress.display(i)

検証関数#

def validate(val_loader, model, criterion): 
    batch_time = AverageMeter("Time", ":3.3f") 
    losses = AverageMeter("Loss", ":2.3f") 
    top1 = AverageMeter("Acc@1", ":2.2f") 
    top5 = AverageMeter("Acc@5", ":2.2f") 
    progress = ProgressMeter(len(val_loader), [batch_time, losses, top1, top5], prefix="Test: ") 

    # 評価モードに切り替え 
    model.eval() 

    with torch.no_grad(): 
        end = time.time() 
        for i, (images, target) in enumerate(val_loader): 
            images = images.to(device) 
            target = target.to(device) 

            # 出力を計算 
            output = model(images) 
            loss = criterion(output, target) 

            # 精度を測定し損失を記録 
            acc1, acc5 = accuracy(output, target, topk=(1, 5)) 
            losses.update(loss.item(), images.size(0)) 
            top1.update(acc1[0], images.size(0)) 
            top5.update(acc5[0], images.size(0)) 

            # 経過時間を測定 
            batch_time.update(time.time() - end) 
            end = time.time() 

            print_frequency = 10 
            if i % print_frequency == 0: 
                progress.display(i) 

        print(" * Acc@1 {top1.avg:.3f} Acc@5 {top5.avg:.3f}".format(top1=top1, top5=top5)) 
    return top1.avg

ヘルパー#

class AverageMeter(object): 
    """Computes and stores the average and current value""" 

    def __init__(self, name, fmt=":f"): 
        self.name = name 
        self.fmt = fmt 
        self.reset() 

    def reset(self): 
        self.val = 0 
        self.avg = 0 
        self.sum = 0 
        self.count = 0 

    def update(self, val, n=1): 
        self.val = val 
        self.sum += val * n 
        self.count += n 
        self.avg = self.sum / self.count 

    def __str__(self): 
        fmtstr = "{name} {val" + self.fmt + "} ({avg" + self.fmt + "})" 
        return fmtstr.format(**self.__dict__) 

class ProgressMeter(object): 
    def __init__(self, num_batches, meters, prefix=""): 
        self.batch_fmtstr = self._get_batch_fmtstr(num_batches) 
        self.meters = meters 
        self.prefix = prefix 

    def display(self, batch): 
        entries = [self.prefix + self.batch_fmtstr.format(batch)] 
        entries += [str(meter) for meter in self.meters] 
        print("\t".join(entries)) 

    def _get_batch_fmtstr(self, num_batches): 
        num_digits = len(str(num_batches // 1)) 
        fmt = "{:"+ str(num_digits) + "d}" 
        return "[" + fmt + "/" + fmt.format(num_batches) + "]" 

def accuracy(output, target, topk=(1,)): 
    """Computes the accuracy over the k top predictions for the specified values of k""" 
    with torch.no_grad(): 
        maxk = max(topk) 
        batch_size = target.size(0) 

        _, pred = output.topk(maxk, 1, True, True) 
        pred = pred.t() 
        correct = pred.eq(target.view(1, -1).expand_as(pred)) 

        res = [] 
        for k in topk: 
            correct_k = correct[:k].reshape(-1).float().sum(0, keepdim=True) 
            res.append(correct_k.mul_(100.0 / batch_size)) 
        return res

事前トレーニングされた FP32 モデルを取得#

事前トレーニングされた浮動小数点モデルは量子化に必要な前提条件です。以下のコードでゼロからチューニングすることで取得できます。ただし、これにはかなりの時間がかかります。このコードはすでに実行されており、4 エポック後に十分な重みを取得できました (簡単にするために、最高の精度が得られるまでチューニングは行われていません)。デフォルトでは、このノートブックはトレーニングを開始せずに重みをロードするだけです。ImageNet で事前トレーニングされたモデル上で自身のモデルをトレーニングするには、このノートブックの上部にある [インポートと設定] セクションで pretrained_on_tiny_imagenet = False を設定します。

num_classes = 200 # 200 は Tiny ImageNet 用、デフォルトは ImageNet 用の 1000 です 
init_lr = 1e-4 
batch_size = 128 
epochs = 4 

model = models.resnet18(pretrained=not pretrained_on_tiny_imagenet) 
# Tiny ImageNet のクラス数の最後の FC レイヤーを更新 
model.fc = nn.Linear(in_features=512, out_features=num_classes, bias=True) 
model.to(device) 

# データ読み込みコード 
train_dir = DATASET_DIR / "train" 
val_dir = DATASET_DIR / "val" 
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) 

train_dataset = datasets.ImageFolder( 
    train_dir, 
    transforms.Compose( 
        [ 
            transforms.Resize(image_size), 
            transforms.RandomHorizontalFlip(), 
            transforms.ToTensor(), 
            normalize, 
        ] 
    ), 
) 
val_dataset = datasets.ImageFolder( 
    val_dir, 
    transforms.Compose( 
        [ 
            transforms.Resize(image_size), 
            transforms.ToTensor(), 
            normalize, 
        ] 
    ), 
) 

train_loader = torch.utils.data.DataLoader( 
    train_dataset, 
    batch_size=batch_size, 
    shuffle=True, 
    num_workers=0, 
    pin_memory=True, 
    sampler=None, 
) 

val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=0, pin_memory=True) 

# 損失関数 (基準) とオプティマイザーを定義 
criterion = nn.CrossEntropyLoss().to(device) 
optimizer = torch.optim.Adam(model.parameters(), lr=init_lr)
/opt/home/k8sworker/ci-ai/cibuilds/ov-notebook/OVNotebookOps-727/.workspace/scm/ov-notebook/.venv/lib/python3.8/site-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and may be removed in the future, please use 'weights' instead. 
  warnings.warn( 
/opt/home/k8sworker/ci-ai/cibuilds/ov-notebook/OVNotebookOps-727/.workspace/scm/ov-notebook/.venv/lib/python3.8/site-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or None for 'weights' are deprecated since 0.13 and may be removed in the future.The current behavior is equivalent to passing weights=None. 
  warnings.warn(msg)
if pretrained_on_tiny_imagenet:
    # 
    # ** 警告: `torch.load` 機能は Python の pickling モジュールを使用します。 
    # このモジュールは、unpickle 中に任意のコードを実行するために使用できます。 
    # 信頼できるデータのみをロードします。 
    # 
    checkpoint = torch.load(str(fp32_pth_path), map_location="cpu") 
    model.load_state_dict(checkpoint["state_dict"], strict=True) 
    acc1_fp32 = checkpoint["acc1"] 
else: 
    best_acc1 = 0 
    # トレーニング・ループ 
    for epoch in range(0, epochs):
        # 単一のトレーニング・エポックを実行 
        train(train_loader, model, criterion, optimizer, epoch) 

        # 検証セットで評価 
        acc1 = validate(val_loader, model, criterion) 

        is_best = acc1 > best_acc1 
        best_acc1 = max(acc1, best_acc1) 

        if is_best: 
            checkpoint = {"state_dict": model.state_dict(), "acc1": acc1} 
            torch.save(checkpoint, fp32_pth_path) 
    acc1_fp32 = best_acc1 

print(f"Accuracy of FP32 model: {acc1_fp32:.3f}")
Accuracy of FP32 model: 55.520

FP32 モデルを OpenVINO™ 中間表現にエクスポートし、INT8 モデルと比較してベンチマークします。

dummy_input = torch.randn(1, 3, image_size, image_size).to(device) 

ov_model = ov.convert_model(model, example_input=dummy_input, input=[1, 3, image_size, image_size]) 
ov.save_model(ov_model, fp32_ir_path, compress_to_fp16=False) 
print(f"FP32 model was exported to {fp32_ir_path}.")
['x'] 
FP32 model was exported to model/resnet18_fp32.xml.

量子化の作成と初期#

NNCF は、通常のトレーニング・パイプラインに統合することで、圧縮を意識したトレーニングを可能にします。このフレームワークは、元のトレーニング・コードへの変更が最小限になるように設計されています。量子化に必要なのは 2 つの変更のみです。

  1. バッチサイズが 1 の量子化データローダーを作成し、それを nncf.Dataset でラップして、量子化中のモデルに適合するよう入力データを準備する変換関数を指定します。ここでは、ペア (入力テンソルとラベル) から入力テンソルを選択します。

import nncf 

def transform_fn(data_item): 
    return data_item[0] 

# バッチ数が 1 を超えるデータローダーとして、 
# バッチサイズが 1 の別のデータローダーを作成するのはまだサポートされていません。 
quantization_loader = torch.utils.data.DataLoader(val_dataset, batch_size=1, shuffle=False, num_workers=0, pin_memory=True) 

quantization_dataset = nncf.Dataset(quantization_loader, transform_fn)
INFO:nncf:NNCF initialized successfully.Supported frameworks detected: torch, tensorflow, onnx, openvino
  1. 最適化されたモデルを取得するには、nncf.quantize を実行します。

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

quantized_model = nncf.quantize(model, quantization_dataset)
2024-07-13 01:48:22.717129: 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 01:48:22.753125: 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 01:48:23.281403: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
Output()
INFO:nncf:Compiling and loading torch extension: quantized_functions_cpu... INFO:nncf:Finished loading torch extension: quantized_functions_cpu
Output()

量子化の初期化後、検証セットで新しいモデルを評価します。ここで実証されているような単純なケースでは、精度は浮動小数点 FP32 モデルの精度とそれほど変わらないはずです。

acc1 = validate(val_loader, quantized_model, criterion) 
print(f"Accuracy of initialized INT8 model: {acc1:.3f}")
Test: [ 0/79] Time 0.223 (0.223) Loss 1.005 (1.005) Acc@1 78.91 (78.91) Acc@5 88.28 (88.28) 
Test: [10/79] Time 0.172 (0.176) Loss 1.992 (1.625) Acc@1 44.53 (60.37) Acc@5 79.69 (83.66) 
Test: [20/79] Time 0.179 (0.173) Loss 1.814 (1.705) Acc@1 60.94 (58.04) Acc@5 80.47 (82.66) 
Test: [30/79] Time 0.168 (0.173) Loss 2.287 (1.795) Acc@1 50.78 (56.48) Acc@5 68.75 (80.97) 
Test: [40/79] Time 0.171 (0.173) Loss 1.615 (1.832) Acc@1 60.94 (55.43) Acc@5 82.81 (80.43) 
Test: [50/79] Time 0.172 (0.173) Loss 1.952 (1.833) Acc@1 57.03 (55.51) Acc@5 75.00 (80.16) 
Test: [60/79] Time 0.171 (0.173) Loss 1.794 (1.856) Acc@1 57.03 (55.16) Acc@5 84.38 (79.84) 
Test: [70/79] Time 0.171 (0.173) Loss 2.371 (1.889) Acc@1 46.88 (54.68) Acc@5 74.22 (79.14)
 * Acc@1 55.040 Acc@5 79.730 
Accuracy of initialized INT8 model: 55.040

圧縮モデルを微調整#

このステップでは、量子化モデルの精度をさらに向上させるため定期的な微調整プロセスが適用されます。通常、元のモデルのトレーニングの最後に使用されるのと同じ学習率を小さくして、数エポックの調整が必要になります。トレーニング・パイプラインにその他の変更は必要ありません。以下に簡単な例を示します。

compression_lr = init_lr / 10 
optimizer = torch.optim.Adam(quantized_model.parameters(), lr=compression_lr) 

# NNCF を使用して 1 エポックをトレーニング 
train(train_loader, quantized_model, criterion, optimizer, epoch=0) 

# 量子化を考慮したトレーニング (QAT の場合) 後の検証セットで評価 
acc1_int8 = validate(val_loader, quantized_model, criterion) 

print(f"Accuracy of tuned INT8 model: {acc1_int8:.3f}") 
print(f"Accuracy drop of tuned INT8 model over pre-trained FP32 model: {acc1_fp32 - acc1_int8:.3f}")
Epoch:[0][ 0/782] Time 0.433 (0.433) Loss 1.029 (1.029) Acc@1 75.00 (75.00) Acc@5 90.62 (90.62) 
Epoch:[0][ 50/782] Time 0.380 (0.380) Loss 0.672 (0.823) Acc@1 87.50 (79.81) Acc@5 94.53 (93.84) 
Epoch:[0][100/782] Time 0.376 (0.379) Loss 0.661 (0.799) Acc@1 85.94 (80.31) Acc@5 98.44 (94.41) 
Epoch:[0][150/782] Time 0.368 (0.377) Loss 0.632 (0.797) Acc@1 85.94 (80.50) Acc@5 94.53 (94.24) 
Epoch:[0][200/782] Time 0.380 (0.377) Loss 0.742 (0.790) Acc@1 81.25 (80.69) Acc@5 94.53 (94.31) 
Epoch:[0][250/782] Time 0.380 (0.377) Loss 0.815 (0.785) Acc@1 81.25 (80.80) Acc@5 93.75 (94.34) 
Epoch:[0][300/782] Time 0.365 (0.376) Loss 0.878 (0.781) Acc@1 76.56 (80.87) Acc@5 92.19 (94.37) 
Epoch:[0][350/782] Time 0.372 (0.376) Loss 0.746 (0.774) Acc@1 82.03 (81.03) Acc@5 93.75 (94.44) 
Epoch:[0][400/782] Time 0.378 (0.376) Loss 0.766 (0.772) Acc@1 79.69 (81.12) Acc@5 96.88 (94.42) 
Epoch:[0][450/782] Time 0.379 (0.376) Loss 0.865 (0.768) Acc@1 77.34 (81.28) Acc@5 93.75 (94.48) 
Epoch:[0][500/782] Time 0.372 (0.376) Loss 0.526 (0.765) Acc@1 89.06 (81.33) Acc@5 97.66 (94.53) 
Epoch:[0][550/782] Time 0.369 (0.376) Loss 0.826 (0.762) Acc@1 79.69 (81.39) Acc@5 92.19 (94.55) 
Epoch:[0][600/782] Time 0.367 (0.376) Loss 0.644 (0.761) Acc@1 85.94 (81.45) Acc@5 95.31 (94.55) 
Epoch:[0][650/782] Time 0.371 (0.376) Loss 0.585 (0.757) Acc@1 81.25 (81.57) Acc@5 98.44 (94.59) 
Epoch:[0][700/782] Time 0.370 (0.376) Loss 0.578 (0.755) Acc@1 86.72 (81.65) Acc@5 96.88 (94.60) 
Epoch:[0][750/782] Time 0.381 (0.376) Loss 0.783 (0.753) Acc@1 79.69 (81.69) Acc@5 95.31 (94.63) 
Test: [ 0/79] Time 0.150 (0.150) Loss 1.063 (1.063) Acc@1 74.22 (74.22) Acc@5 87.50 (87.50) 
Test: [10/79] Time 0.150 (0.149) Loss 1.785 (1.514) Acc@1 50.78 (63.21) Acc@5 81.25 (84.38) 
Test: [20/79] Time 0.150 (0.150) Loss 1.582 (1.588) Acc@1 64.84 (61.09) Acc@5 82.03 (84.04) 
Test: [30/79] Time 0.148 (0.150) Loss 2.103 (1.691) Acc@1 55.47 (59.30) Acc@5 71.09 (82.41) 
Test: [40/79] Time 0.149 (0.150) Loss 1.597 (1.745) Acc@1 64.06 (57.89) Acc@5 83.59 (81.48) 
Test: [50/79] Time 0.148 (0.150) Loss 1.895 (1.751) Acc@1 53.91 (57.74) Acc@5 77.34 (81.20) 
Test: [60/79] Time 0.151 (0.150) Loss 1.566 (1.783) Acc@1 65.62 (57.18) Acc@5 84.38 (80.75) 
Test: [70/79] Time 0.151 (0.150) Loss 2.457 (1.811) Acc@1 45.31 (56.65) Acc@5 73.44 (80.27)
 * Acc@1 57.080 Acc@5 80.940 
Accuracy of tuned INT8 model: 57.080 
Accuracy drop of tuned INT8 model over pre-trained FP32 model: -1.560

INT8 モデルを OpenVINO IR にエクスポート#

if not int8_ir_path.exists(): 
    warnings.filterwarnings("ignore", category=TracerWarning) 
    warnings.filterwarnings("ignore", category=UserWarning) 
    # INT8 モデルを OpenVINO™IR にエクスポート 
    ov_model = ov.convert_model(quantized_model, example_input=dummy_input, input=[1, 3, image_size, image_size]) 
    ov.save_model(ov_model, int8_ir_path) 
    print(f"INT8 model exported to {int8_ir_path}.")
WARNING:tensorflow:Please fix your imports.Module tensorflow.python.training.tracking.base has been moved to tensorflow.python.trackable.base.The old module will be deleted in version 2.11.
['x'] 
INT8 model exported to model/resnet18_int8.xml.

推論時間の計算によるベンチマーク・モデルのパフォーマンス#

最後に、ベンチマーク・ツールを使用して、FP32 モデルと INT8 モデルの推論パフォーマンスを測定します。 - OpenVINO の推論パフォーマンス測定ツール。デフォルトでは、ベンチマーク・ツールは CPU 上の非同期モードで推論を 60 秒間実行します。推論速度をレイテンシー (画像あたりのミリ秒) およびスループット (1 秒あたりのフレーム数) の値として返します。

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

import ipywidgets as widgets 

# OpenVINO ランタイムを初期化 
core = ov.Core() 
device = widgets.Dropdown( 
    options=core.available_devices, 
    value="CPU", 
    description="Device:", disabled=False, 
) 

device
Dropdown(description='Device:', options=('CPU',), value='CPU')
def parse_benchmark_output(benchmark_output): 
    parsed_output = [line for line in benchmark_output if "FPS" in line] 
    print(*parsed_output, sep="\n") 

print("Benchmark FP32 model (IR)") 
benchmark_output = ! benchmark_app -m $fp32_ir_path -d $device.value -api async -t 15 
parse_benchmark_output(benchmark_output) 

print("Benchmark INT8 model (IR)") 
benchmark_output = ! benchmark_app -m $int8_ir_path -d $device.value -api async -t 15 
parse_benchmark_output(benchmark_output)
Benchmark FP32 model (IR) 
[ INFO ] Throughput: 2933.90 FPS 
Benchmark INT8 model (IR) 
[ INFO ] Throughput: 11862.93 FPS

参考としてデバイス情報を表示します。

core.get_property(device.value, "FULL_DEVICE_NAME")
'Intel(R) Core(TM) i9-10920X CPU @ 3.50GHz'