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>=2023.1.0" "torch" "torchvision"
%pip install -q "nncf>=2.6.0"
DEPRECATION: pytorch-lightning 1.6.5 has a non-standard dependency specifier torch>=1.8.*. pip 24.1 will enforce this behaviour change. A possible replacement is to upgrade to a newer version of pytorch-lightning or contact the author to suggest that they release a version with a conforming dependency specifiers. Discussion can be found at https://github.com/pypa/pip/issues/12063
Note: you may need to restart the kernel to use updated packages.
DEPRECATION: pytorch-lightning 1.6.5 has a non-standard dependency specifier torch>=1.8.*. pip 24.1 will enforce this behaviour change. A possible replacement is to upgrade to a newer version of pytorch-lightning or contact the author to suggest that they release a version with a conforming dependency specifiers. Discussion can be found at https://github.com/pypa/pip/issues/12063
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) を削除してログを有効にすることを推奨します。

# On Windows, add the directory that contains cl.exe to the PATH to enable PyTorch to find the
# required C++ tools. This code assumes that Visual Studio 2019 is installed in the default
# directory. If you have a different C++ compiler, add the correct path to os.environ["PATH"]
# directly. Note that the C++ Redistributable is not enough to run this notebook.

# Adding the path to os.environ["LIB"] is not always required - it depends on the system configuration

import sys

if sys.platform == "win32":
    import distutils.command.build_ext
    import os
    from pathlib import Path

    VS_INSTALL_DIR = r"C:/Program Files (x86)/Microsoft Visual Studio"
    cl_paths = sorted(list(Path(VS_INSTALL_DIR).glob("**/Hostx86/x64/cl.exe")))
    if len(cl_paths) == 0:
        raise ValueError(
            "Cannot find Visual Studio. This notebook requires a C++ compiler. If you installed "
            "a C++ compiler, please add the directory that contains cl.exe to `os.environ['PATH']`."
        )
    else:
        # If multiple versions of MSVC are installed, get the most recent one.
        cl_path = cl_paths[-1]
        vs_dir = str(cl_path.parent)
        os.environ["PATH"] += f"{os.pathsep}{vs_dir}"
        # The code for finding the library dirs is from:
        # https://stackoverflow.com/questions/47423246/get-pythons-lib-path
        d = distutils.core.Distribution()
        b = distutils.command.build_ext.build_ext(d)
        b.finalize_options()
        os.environ["LIB"] = os.pathsep.join(b.library_dirs)
        print(f"Added {vs_dir} to PATH")
import sys
import time
import warnings  # To disable warnings on export model
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

sys.path.append("../utils")
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)

# Paths where PyTorch and OpenVINO IR models will be stored.
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")

# It is possible to train FP32 model from scratch, but it might be slow. Therefore, the pre-trained weights are downloaded by default.
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-609/.workspace/scm/ov-notebook/notebooks/302-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):
    # Format validation set the same way as train set is formatted.
    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)
    )

    # Switch to train mode.
    model.train()

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

        # Compute output.
        output = model(images)
        loss = criterion(output, target)

        # Measure accuracy and record loss.
        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))

        # Compute gradient and do opt step.
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # Measure elapsed time.
        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: ")

    # Switch to evaluate mode.
    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)

            # Compute output.
            output = model(images)
            loss = criterion(output, target)

            # Measure accuracy and record loss.
            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))

            # Measure elapsed time.
            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 is for Tiny ImageNet, default is 1000 for ImageNet
init_lr = 1e-4
batch_size = 128
epochs = 4

model = models.resnet18(pretrained=not pretrained_on_tiny_imagenet)
# Update the last FC layer for Tiny ImageNet number of classes.
model.fc = nn.Linear(in_features=512, out_features=num_classes, bias=True)
model.to(device)

# Data loading code.
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
)

# Define loss function (criterion) and optimizer.
criterion = nn.CrossEntropyLoss().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=init_lr)
/opt/home/k8sworker/ci-ai/cibuilds/ov-notebook/OVNotebookOps-609/.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-609/.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:
    #
    # ** WARNING: The `torch.load` functionality uses Python's pickling module that
    # may be used to perform arbitrary code execution during unpickling. Only load data that you
    # trust.
    #
    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
    # Training loop.
    for epoch in range(0, epochs):
        # Run a single training epoch.
        train(train_loader, model, criterion, optimizer, epoch)

        # Evaluate on validation set.
        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}.")
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]
    
    # Creating separate dataloader with batch size = 1
    # as dataloaders with batches > 1 is not supported yet.
    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
    
  2. 最適化されたモデルを取得するには、nncf.quantize を実行します。

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

    quantized_model = nncf.quantize(model, quantization_dataset)
    
    2024-02-10 01:14:34.683062: 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-02-10 01:14:34.719921: 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-02-10 01:14:35.252473: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
    
    WARNING:nncf:NNCF provides best results with torch==2.1.2, while current torch version is 2.2.0+cpu. If you encounter issues, consider switching to torch==2.1.2
    
    Output()
    
    INFO:nncf:Compiling and loading torch extension: quantized_functions_cpu...
    
    INFO:nncf:Finished loading torch extension: quantized_functions_cpu
    
    Output()
    
  3. 量子化の初期化後、検証セットで新しいモデルを評価します。ここで実証されているような単純なケースでは、精度は浮動小数点 FP32 モデルの精度とそれほど変わらないはずです。

    acc1 = validate(val_loader, quantized_model, criterion)
    print(f"Accuracy of initialized INT8 model: {acc1:.3f}")
    
    Test: [ 0/79]       Time 0.186 (0.186)      Loss 1.005 (1.005)      Acc@1 78.91 (78.91)     Acc@5 88.28 (88.28)
    
    Test: [10/79]       Time 0.157 (0.154)      Loss 1.992 (1.625)      Acc@1 44.53 (60.37)     Acc@5 79.69 (83.66)
    
    Test: [20/79]       Time 0.143 (0.159)      Loss 1.814 (1.705)      Acc@1 60.94 (58.04)     Acc@5 80.47 (82.66)
    
    Test: [30/79]       Time 0.145 (0.155)      Loss 2.287 (1.795)      Acc@1 50.78 (56.48)     Acc@5 68.75 (80.97)
    
    Test: [40/79]       Time 0.182 (0.153)      Loss 1.615 (1.832)      Acc@1 60.94 (55.43)     Acc@5 82.81 (80.43)
    
    Test: [50/79]       Time 0.146 (0.152)      Loss 1.952 (1.833)      Acc@1 57.03 (55.51)     Acc@5 75.00 (80.16)
    
    Test: [60/79]       Time 0.146 (0.151)      Loss 1.794 (1.856)      Acc@1 57.03 (55.16)     Acc@5 84.38 (79.84)
    
    Test: [70/79]       Time 0.147 (0.150)      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)

# Train for one epoch with NNCF.
train(train_loader, quantized_model, criterion, optimizer, epoch=0)

# Evaluate on validation set after Quantization-Aware Training (QAT case).
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.398 (0.398)      Loss 0.917 (0.917)      Acc@1 76.56 (76.56)     Acc@5 93.75 (93.75)
Epoch:[0][ 50/782]  Time 0.365 (0.375)      Loss 0.625 (0.812)      Acc@1 87.50 (80.27)     Acc@5 96.88 (93.92)
Epoch:[0][100/782]  Time 0.363 (0.369)      Loss 0.764 (0.807)      Acc@1 79.69 (80.37)     Acc@5 94.53 (94.17)
Epoch:[0][150/782]  Time 0.368 (0.367)      Loss 0.863 (0.799)      Acc@1 82.81 (80.53)     Acc@5 92.97 (94.25)
Epoch:[0][200/782]  Time 0.366 (0.366)      Loss 0.581 (0.787)      Acc@1 85.16 (80.80)     Acc@5 97.66 (94.34)
Epoch:[0][250/782]  Time 0.362 (0.365)      Loss 0.722 (0.782)      Acc@1 82.81 (80.88)     Acc@5 93.75 (94.42)
Epoch:[0][300/782]  Time 0.361 (0.365)      Loss 0.737 (0.777)      Acc@1 78.91 (81.01)     Acc@5 93.75 (94.41)
Epoch:[0][350/782]  Time 0.384 (0.365)      Loss 0.819 (0.767)      Acc@1 80.47 (81.29)     Acc@5 92.97 (94.53)
Epoch:[0][400/782]  Time 0.363 (0.365)      Loss 0.787 (0.767)      Acc@1 80.47 (81.35)     Acc@5 94.53 (94.53)
Epoch:[0][450/782]  Time 0.361 (0.364)      Loss 0.726 (0.763)      Acc@1 82.03 (81.48)     Acc@5 96.88 (94.55)
Epoch:[0][500/782]  Time 0.361 (0.364)      Loss 0.727 (0.760)      Acc@1 82.03 (81.54)     Acc@5 94.53 (94.58)
Epoch:[0][550/782]  Time 0.359 (0.364)      Loss 0.781 (0.758)      Acc@1 82.81 (81.58)     Acc@5 95.31 (94.59)
Epoch:[0][600/782]  Time 0.363 (0.364)      Loss 0.721 (0.756)      Acc@1 80.47 (81.63)     Acc@5 97.66 (94.61)
Epoch:[0][650/782]  Time 0.361 (0.364)      Loss 0.922 (0.755)      Acc@1 76.56 (81.64)     Acc@5 92.97 (94.63)
Epoch:[0][700/782]  Time 0.360 (0.364)      Loss 0.651 (0.753)      Acc@1 83.59 (81.68)     Acc@5 92.97 (94.63)
Epoch:[0][750/782]  Time 0.362 (0.364)      Loss 0.781 (0.751)      Acc@1 80.47 (81.70)     Acc@5 95.31 (94.66)
Test: [ 0/79]       Time 0.148 (0.148)      Loss 1.092 (1.092)      Acc@1 73.44 (73.44)     Acc@5 86.72 (86.72)
Test: [10/79]       Time 0.147 (0.147)      Loss 1.826 (1.522)      Acc@1 49.22 (62.78)     Acc@5 81.25 (84.23)
Test: [20/79]       Time 0.146 (0.147)      Loss 1.531 (1.594)      Acc@1 64.84 (60.83)     Acc@5 82.03 (83.85)
Test: [30/79]       Time 0.147 (0.147)      Loss 2.059 (1.690)      Acc@1 57.03 (59.22)     Acc@5 71.09 (82.26)
Test: [40/79]       Time 0.141 (0.146)      Loss 1.516 (1.744)      Acc@1 64.06 (57.91)     Acc@5 85.16 (81.46)
Test: [50/79]       Time 0.143 (0.146)      Loss 1.922 (1.750)      Acc@1 53.12 (57.69)     Acc@5 76.56 (81.14)
Test: [60/79]       Time 0.146 (0.146)      Loss 1.594 (1.785)      Acc@1 65.62 (57.17)     Acc@5 84.38 (80.60)
Test: [70/79]       Time 0.147 (0.146)      Loss 2.460 (1.811)      Acc@1 46.09 (56.75)     Acc@5 74.22 (80.08)
 * Acc@1 57.180 Acc@5 80.680
Accuracy of tuned INT8 model: 57.180
Accuracy drop of tuned INT8 model over pre-trained FP32 model: -1.660

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

if not int8_ir_path.exists():
    warnings.filterwarnings("ignore", category=TracerWarning)
    warnings.filterwarnings("ignore", category=UserWarning)
    # Export INT8 model to 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 Omodel 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.
INT8 Omodel exported to model/resnet18_int8.xml.

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

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

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

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 CPU -api async -t 15
parse_benchmark_output(benchmark_output)

print('Benchmark INT8 model (IR)')
benchmark_output = ! benchmark_app -m $int8_ir_path -d CPU -api async -t 15
parse_benchmark_output(benchmark_output)
Benchmark FP32 model (IR)
[ INFO ] Throughput:   2907.25 FPS
Benchmark INT8 model (IR)
[ INFO ] Throughput:   11767.47 FPS

参考として CPU 情報を表示します。

ie = ov.Core()
ie.get_property("CPU", "FULL_DEVICE_NAME")
'Intel(R) Core(TM) i9-10920X CPU @ 3.50GHz'