Практическая работа №1. Программная реализация вывода глубоких нейронных сетей для классификации изображений средствами нескольких фреймворков

1. Цели и задачи работы

Цель работы -- освоить методику анализа производительности вывода глубоких нейросетевых моделей с использованием некоторого набора фреймворков на примере решения задачи классификации изображений.

Достижение указанной цели предполагает решение следующих задач:

  1. Формулировка задачи классификации изображений.
  2. Выбор открытой модели для решения поставленной задачи. Изучение ее архитектуры.
  3. Изучение программного интерфейса двух-трех фреймворков для вывода открытых нейросетевых моделей (PyTorch, TensorFlow, TensorFlow Lite, OpenVINO toolkit, другие). Выбор фреймворков зависит от формата модели, наличия необходимых конвертеров и возможности функционирования этих фреймворков на тестовой аппаратной платформе. Например, на RISC-V в настоящее время запускаются TensorFlow Lite и ncnn, также имеется неофициальный порт PyTorch, OpenVINO toolkit можно собрать из исходников (в ближайшее время будет оптимизирован под RISC-V).
  4. Программная реализация вывода выбранной модели с использованием изученных фреймворков.
  5. Анализ качества решения задачи.
  6. Анализ производительности вывода и определение оптимальных параметров запуска вывода на выбранной аппаратуре (размер пачки входных данных, количество потоков, другие).

Выполнение перечисленных задач демонстрируется на примере глубокой модели DenseNet-121, фреймворков OpenVINO toolkit и TensorFlow Lite, программный интерфейс которых изучен в ходе лекции 3 "Обзор инструментов глубокого обучения" настоящего курса. Процедура сбора и анализа результатов качества и производительности вывода моделей демонстрируется на примере x86-архитектуры.

2. Постановка задачи классификации изображений

Задача классификации изображений состоит в том, чтобы поставить в соответствие изображению класс объектов, содержащихся на этом изображении. Формализуем постановку задачи.

Исходное изображение, как правило, представляется набором интенсивностей пикселей

$$I=(I_{ij}^j)_{0 \le i < w, 0 \le j < h, 0 \le k < 3},$$

где $w$ и $h$ -- ширина и высота изображения, $k$ -- количество каналов, а $I_{ij}^j$ -- значение интенсивности пикселя с пространственными координатами $(i, j)$ по каналу $k$.

Предполагается, что определено множество наблюдаемых классов объектов на изображении:

$$C=\{0, 1,...,𝑁−1\}.$$

Множество идентификаторов классов однозначно соответствует множеству названий классов. Таким образом, задача классификации изображений состоит в том, чтобы каждому изображению поставить в соответствие класс, которому оно принадлежит:

$$\phi : I \overrightarrow{} C.$$

3. Нейросетевая модель DenseNet-121

Модель DenseNet-121 реализуется посредством формирования последовательности "плотных" блоков (dense block). Каждый "плотный" блок содержит набор сверточных слоев. Вход каждого следующего слоя -- конкатенация карт признаков, построенных на предыдущих слоях.

dense_block

Общая структура моделей DenseNet имеет следующий вид:

densenet

Отметим, что слои между двумя смежными плотными блоками называются переходными слоями, они в отличие от плотных блоков изменяют пространственные размеры карты признаков.

Структура "плотных" блоков сети DenseNet-121:

  1. Dense Block 1: 6 x [Conv 1x1, Conv 3x3]
  2. Dense Block 2: 12 x [Conv 1x1, Conv 3x3]
  3. Dense Block 3: 24 x [Conv 1x1, Conv 3x3]
  4. Dense Block 4: 16 x [Conv 1x1, Conv 3x3]

Подробнее архитектура DenseNet-121 разобрана по ссылке.

4. Подготовка модели

4.1. Загрузка модели

Далее в работе используется обученная модель DenseNet-121, опубликованная в OpenVINO - Open Model Zoo Repository (OMZ). Для загрузки модели можно использовать инструмент Model Downloader в составе пакета openvino-dev. Ниже приведены команды для установки данного пакета и загрузки модели DenseNet-121 в формате фреймворка TensorFlow.

conda create -n openvino_converter python=3.10
conda activate openvino_converter
pip install --upgrade pip
pip install openvino-dev
pip install openvino-dev[tensorflow2]
cd <work_dir>
omz_downloader --name densenet-121-tf 
conda deactivate

В результате выполнения указанной последовательности команд в директории <work_dir> создается вложенная директория public/densenet-121-tf, в которой находится файл модели densenet-121.savedmodel в формате исходного фреймворка TensorFlow.

4.2. Конвертация модели в формат OpenVINO toolkit

Чтобы сконвертировать модель из формата TensorFlow во внутреннее представление OpenVINO toolkit, достаточно воспользоваться инструментом Model Converter в составе пакета openvino-dev так, как показано ниже. Отметим, что при выполнении команды конвертации директория <work_dir> должна быть установлена в качестве рабочей.

cd <work_dir>
conda activate openvino_converter
omz_converter --name densenet-121-tf
conda deactivate

В результате выполнения приведенной последовательности команд в <work_dir>/public/densenet-121-tf создаются две вложенные директории.

  1. FP32. Cодержит модель, сконвертированную во внутреннее представление OpenVINO toolkit с форматом весов fp32.
  2. FP16. Cодержит модель, сконвертированную во внутреннее представление OpenVINO toolkit с форматом весов fp16.

4.3. Конвертация модели в формат TensorFlow Lite

Чтобы сконвертировать выбранную модель в формат какого-либо другого фреймворка, можно воспользоваться конвертерами моделей из репозитория системы бенчмаркинга Deep Learning Inference Benchmark. Далее используется tf2tflite-конвертер. Ниже приведена последовательность команд для установки необходимого окружения и запуска указанного конвертера моделей из формата библиотеки TensorFlow в TensorFlow Lite.

cd <work_dir>
conda create -n tflite_converter python=3.9
conda activate tflite_converter
pip install tensorflow==2.14.0
pip install tf-keras==2.15.0
pip install onnx-tf==1.10.0
pip install tensorflow-probability==0.22.0

git clone https://github.com/itlab-vision/dl-benchmark
cd dl-benchmark/src/model_converters/tf2tflite

python tflite_converter.py \
     --model-path <work_dir>/public/densenet-121-tf/densenet-121.savedmodel \
     --source-framework tf
conda deactivate

Примечание: для запуска конвертера требуется версия пакета numpy не ниже 1.23.5.

В результате успешного выполнения приведенной последовательности команд в директории <work_dir>/public/densenet-121-tf сформируется файл densenet-121.tflite.

5. Подготовка виртуальной среды для запуска экспериментов

Для проведения экспериментов необходимо подготовить виртуальную среду с установленными пакетами opencv для загрузки и предварительной обработки изображений, openvino-dev для вывода с использованием OpenVINO toolkit (далее используется Sync API), tensorflow или tflite-runtime для вывода средствами TensorFlow Lite. Ниже приведена соответствующая последовательность команд.

conda create -n 01_practice_env python==3.9
conda activate 01_practice_env
conda install jupyter
conda install opencv
pip install tensorflow
pip install openvino-dev
pip install matplotlib
conda deactivate

Чтобы созданная виртуальная среда 01_practice_env была доступна для последующего запуска в ней вывода из Jupiter Notebook, достаточно в ней выполнить команду, приведенную ниже.

conda activate 01_practice_env
python -m ipykernel install --user --name 01_practice_env \
                            --display-name "Python (01_practice_env)"
conda deactivate

Примечание:

  1. Вложенная директория 01_Practice_envs содержит перечень установленных в каждой виртуальной среде пакетов с указанием их версий (результат выполнения команды conda list в каждой среде): 01_practice_env.list соответствует среде с установленным пакетом tflite-runtime, 01_practice_env_tf.list - с пакетом tensorflow.

  2. Импорт пакета tflite может привести к ошибке следующего вида: ImportError: /lib64/libm.so.6: versionGLIBC_2.27' not found`. Существует два варианта решения указанной проблемы.

    • Понижение версии tflite-runtime. Перечень доступных версий пакета можно найти здесь. Использование данного решения может отрицательно сказаться на производительности вывода.
    • Обновление библиотеки glibc, установленной в системе. Для этого необходимо выполнить следующие команды.

      sudo apt-get update
      sudo apt-get install libc6
      
  3. Установка пакетов может быть выполнена либо в терминале с помощью описанных выше команд, либо в текущем Jupiter Notebook с использованием следующей последовательности команд.

    import sys
    !conda install --yes --prefix {sys.prefix} opencv
    !{sys.executable} -m pip install openvino-dev
    !{sys.executable} -m pip install tensorflow
    !{sys.executable} -m pip install matplotlib
    

6. Программная реализация вспомогательных функций

6.1. Функции для вычисления показателей производительности

In [1]:
# функция удаления заведомо некорректных времен
def delete_incorrect_time(times, min_correct_time=0.0):
    valid_times = []
    for i in range(len(times)):
        if times[i] >= min_correct_time:
            valid_times.append(times[i])
    return valid_times

# функция удаления времен, выходящих для пределы трех стандартных
# среднеквадратических отклонений (удаление выбросов)
def three_sigma_rule(times):
    average_time = np.mean(times)
    sigm = np.std(times)
    upper_bound = average_time + (3 * sigm)
    lower_bound = average_time - (3 * sigm)
    valid_times = []
    for i in range(len(times)):
        if lower_bound <= times[i] <= upper_bound:
            valid_times.append(times[i])
    return valid_times

# функция вычисления латентности - медианы набора корректных времен
def calculate_latency(times):
    latency = np.median(times)
    return latency

# функция вычисления метрики FPS - отношение произведения количества итераций
# и размера пачки данных к общему времени вывода
def calculate_fps(iter_num, batch_size, total_time):
    return iter_num * batch_size / total_time

# общая функция вычисления показателей производительности
def calculate_performance_metrics(times, batch_size, min_correct_time=0.0):
    valid_times = delete_incorrect_time(times, min_correct_time)
    valid_times = three_sigma_rule(valid_times)
    latency = calculate_latency(valid_times)
    fps = calculate_fps(len(valid_times), batch_size, sum(valid_times))
    return latency, fps

6.2. Функции обработки выхода классификационной сети

In [2]:
# функция загрузки меток классов
def load_labels_map(labels_file_name):
    with open(labels_file_name, 'r') as f:
        labels_map = [line.strip() for line in f]
    return labels_map    

# функция печати topk-классов и соответствующих достоверностей
def process_output(files, output_data, labels_file_name, topk=5):
    labels_map = load_labels_map(labels_file_name)
    for i in range(len(output_data)):
        file_name = files[i]
        probs_ = np.squeeze(output_data[i])
        top_ind = np.argsort(probs_)[-topk:][::-1]
        print(f'{file_name}')
        for id_ in top_ind:
            det_label = labels_map[id_] if labels_map else '#{0}'.format(id_)
            print('\t{}\t{:.7f}\t{}'.format(id_, probs_[id_], det_label))

6.3. Чтение и предварительная обработка данных

Для чтения и подготовки изображений используется функционал библиотеки компьютерного зрения OpenCV:

  1. cv2.imread(...) -- функция, обеспечивающая загрузку изображения.
  2. cv2.resize(...) -- функция, обеспечивающая масштабирование изображения в соответствии с размерами входного тензора нейронной сети.

В качестве данных для проверки корректности программной реализации вывода и последующего анализа производительности предлагается использовать набор изображений из валидационной части набора данных ImageNet, которая является открытой. Она содержит 50 000 изображений естественного мира, принадлежащих 1 000 классам. Далее принимается, что избранные изображения находятся в директории ../data. Примечание: по указанной ссылке скачивается архив с полным набором данных (~150 ГБ данных), чтобы загрузить только валидационную выборку можно воспользоваться неофициальными ресурсами, которые выдаются поисковой системой (например, можно использовать эту ссылку).

In [3]:
import cv2
import os
import numpy as np
from matplotlib import pyplot as plt

# размер пачки данных
batch_size = 2
# размеры входного тензора нейронной сети
input_shape = [batch_size, 224, 224, 3]
# директория, содержащая данные для проверки корректности и анализа
# производительности вывода (должна содержать не менее, чем batch_size
# изображений)
images_dir = '../data'


def prepare_input(images_dir, input_image_resolution, mean=(0, 0, 0),
                  scale=(1, 1, 1), bgr_to_rgb=False, show=False):
    files = [f for f in os.listdir(images_dir) if os.path.isfile(os.path.join(images_dir, f))]
    print(f'Number of available images: {len(files)}')
    images = []
    for file in files:
        image = cv2.imread(os.path.join(images_dir, file), cv2.IMREAD_COLOR)
        if bgr_to_rgb == True:
            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        if show == True:
            plt.imshow(image)
            plt.show()
        image = cv2.resize(image, input_image_resolution)
        image = (image - mean) / scale;
        images.append(image)
    return files, images

7. Программная реализация вывода с использованием OpenVINO toolkit (Sync API)

7.1. Загрузка пакетов

In [4]:
import openvino as ov

7.2. Загрузка и предварительная обработка данных

In [5]:
print(f'Input shape {input_shape}')
files, images = prepare_input(images_dir, input_shape[1:3])
Input shape [2, 224, 224, 3]
Number of available images: 64

7.3. Создание и настройка окружения OpenVINO Runtime Core

In [6]:
device = 'CPU'


# Функция создания и настройки окружения
def create_core(device):
    # создание объекта окружения
    core = ov.Core()
    # проверка, что заданное устройство входит в число доступных устройств для запуска
    available_devices = core.available_devices
    if device not in available_devices:        
        print(f'Unsupported device \'{device}\'. Available devices: {available_devices}')
    print(f'{core}')
    return core


core = create_core(device)
<Core: available plugins[CPU]>

7.4. Загрузка и компиляция модели

In [7]:
# пути до файлов модели DenseNet-121
model_xml = '../public/densenet-121-tf/FP32/densenet-121-tf.xml'
model_bin = '../public/densenet-121-tf/FP32/densenet-121-tf.bin'


def prepare_model(core, model_xml, model_bin, input_shape):
    # чтение модели
    model = core.read_model(model=model_xml, weights=model_bin)
    # изменение размера входа в соответствии с размером пачки
    model.reshape(input_shape)
    # компиляция модели
    compiled_model = core.compile_model(model, device)
    return model, compiled_model


model, compiled_model = prepare_model(core, model_xml, model_bin, input_shape)
print(f'Model: {model}')
print(f'Compiled model: {compiled_model}')
Model: <Model: 'TensorFlow_Frontend_IR'
inputs[
<ConstOutput: names[input_1] shape[2,224,224,3] type: f32>
]
outputs[
<ConstOutput: names[predictions] shape[2,1000] type: f32>
]>
Compiled model: <CompiledModel:
inputs[
<ConstOutput: names[input_1] shape[2,224,224,3] type: f32>
]
outputs[
<ConstOutput: names[predictions] shape[2,1000] type: f32>
]>

7.5. Создание запроса на вывод

In [8]:
request = compiled_model.create_infer_request()
print(request)
<InferRequest:
inputs[
<ConstOutput: names[input_1] shape[2,224,224,3] type: f32>
]
outputs[
<ConstOutput: names[predictions] shape[2,1000] type: f32>
]>

7.6. Запуск вывода и обработка результатов

In [9]:
# количество повторений вывода для заданной пачки данных
iter_num = 3
# файл меток классов
labels_file_name = '../labels/image_net_synset.txt'
# параметр метрики top-k для оценки качества работы модели
topk = 5


# функция многократного запуска вывода для фиксированного размера пачки
# и сбор результатов работы сети и времени работы каждого запроса на вывод
def inference_openvino_sync(iter_num, batch_size, files, images, request):
    times = []
    results = dict()
    istart = 0
    ifinish = batch_size
    for iteration in range(iter_num):
        input_tensor = ov.Tensor(np.array(images[istart:ifinish], dtype=np.float32))
        request.set_input_tensor(input_tensor)
        request.infer()
        times.append(request.latency / 1000)
        results[iteration] = (files[istart:ifinish], request.get_output_tensor().data.copy())
        
        istart = ifinish
        ifinish = (istart + batch_size) % (len(images) + 1)
        if istart > ifinish:
            istart = 0
            ifinish = batch_size
        
    return results, times

# функция запуска теста и вычисления показателей производительности вывода
def inference_openvino_perf(iter_num, batch_size, files, images, request):
    results, times = inference_openvino_sync(iter_num, batch_size, files, images, request)
    latency, fps = calculate_performance_metrics(times, batch_size)
    return results, latency, fps


results, latency, fps = inference_openvino_perf(iter_num, batch_size, files, images, request)
print('Performance metrics:')
print(f'\tLatency: {latency:.3f} s\n\tFPS:     {fps:.2f} fps')

print('Results:')
for iteration in range(len(results)):
    process_output(results[iteration][0], results[iteration][1], labels_file_name, topk)
Performance metrics:
	Latency: 0.026 s
	FPS:     65.31 fps
Results:
ILSVRC2012_val_00000023.JPEG
	948	0.9939481	Granny Smith
	951	0.0029236	lemon
	950	0.0014409	orange
	954	0.0006842	banana
	952	0.0001481	fig
ILSVRC2012_val_00000247.JPEG
	13	0.9960331	junco, snowbird
	10	0.0016737	brambling, Fringilla montifringilla
	14	0.0014138	indigo bunting, indigo finch, indigo bird, Passerina cyanea
	19	0.0004691	chickadee
	18	0.0000661	magpie
ILSVRC2012_val_00018592.JPEG
	625	0.9841743	lifeboat
	540	0.0021340	drilling platform, offshore rig
	628	0.0018786	liner, ocean liner
	913	0.0017149	wreck
	724	0.0016311	pirate, pirate ship
ILSVRC2012_val_00019000.JPEG
	752	0.9872140	racket, racquet
	852	0.0084676	tennis ball
	429	0.0032796	baseball
	981	0.0004642	ballplayer, baseball player
	890	0.0002705	volleyball
ILSVRC2012_val_00019001.JPEG
	532	0.9815797	dining table, board
	762	0.0055746	restaurant, eating house, eating place, eatery
	765	0.0032740	rocking chair, rocker
	559	0.0011973	folding chair
	495	0.0011608	china cabinet, china closet
ILSVRC2012_val_00019002.JPEG
	218	0.8885472	Welsh springer spaniel
	156	0.0818036	Blenheim spaniel
	217	0.0074692	English springer, English springer spaniel
	220	0.0061150	Sussex spaniel
	157	0.0058036	papillon

7.7. Удаление рабочих объектов

In [10]:
del model
del compiled_model
del core

8. Программная реализация вывода с использованием TensorFlow Lite

8.1. Загрузка пакетов

In [11]:
try:
    import tflite_runtime.interpreter as tflite
except ModuleNotFoundError:
    import tensorflow.lite as tflite
2024-07-13 09:26:37.956415: I external/local_xla/xla/tsl/cuda/cudart_stub.cc:32] Could not find cuda drivers on your machine, GPU will not be used.
2024-07-13 09:26:37.962526: I external/local_xla/xla/tsl/cuda/cudart_stub.cc:32] Could not find cuda drivers on your machine, GPU will not be used.
2024-07-13 09:26:37.977354: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:485] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered
2024-07-13 09:26:38.003483: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:8454] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered
2024-07-13 09:26:38.011028: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1452] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered
2024-07-13 09:26:38.032006: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: AVX2 AVX512F FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
2024-07-13 09:26:39.146827: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT

8.2. Загрузка и предварительная обработка данных

In [12]:
print(f'Input shape {input_shape}')
files, images = prepare_input(images_dir, input_shape[1:3], 
                              (123.68,116.78,103.94), (58.395,57.12,57.375),
                              True)
Input shape [2, 224, 224, 3]
Number of available images: 64

8.3. Загрузка модели

In [13]:
# путь до файла модели в формате TensorFlow Lite
model_tflite = '../public/densenet-121-tf/densenet-121.tflite'


# функция печати информации о входах модели
def print_input_details(model):
    input_details = model.get_input_details()
    for idx in range(len(input_details)):
        print(f'\tInput {idx}: {input_details[idx]}')


# загрузка модели
model_interpreter = tflite.Interpreter(model_path=model_tflite)
print('Input details for the model (before input reshape):')
print_input_details(model_interpreter)

# изменение размеров входного тензора (тестовые модели имеют только один
# вход - input_details[0])
input_details = model_interpreter.get_input_details()
model_interpreter.resize_tensor_input(input_details[0]['index'], input_shape)
print('Input details for the model (after input reshape):')
print_input_details(model_interpreter)

# выделение памяти для хранения тензоров
model_interpreter.allocate_tensors()
Input details for the model (before input reshape):
	Input 0: {'name': 'input_1', 'index': 0, 'shape': array([  1, 224, 224,   3], dtype=int32), 'shape_signature': array([ -1, 224, 224,   3], dtype=int32), 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0), 'quantization_parameters': {'scales': array([], dtype=float32), 'zero_points': array([], dtype=int32), 'quantized_dimension': 0}, 'sparsity_parameters': {}}
Input details for the model (after input reshape):
	Input 0: {'name': 'input_1', 'index': 0, 'shape': array([  2, 224, 224,   3], dtype=int32), 'shape_signature': array([ -1, 224, 224,   3], dtype=int32), 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0), 'quantization_parameters': {'scales': array([], dtype=float32), 'zero_points': array([], dtype=int32), 'quantized_dimension': 0}, 'sparsity_parameters': {}}
INFO: Created TensorFlow Lite XNNPACK delegate for CPU.

8.4. Установка входных тензоров и запуск вывода

In [14]:
from time import time

# количество повторений вывода для заданной пачки данных
iter_num = 3
# файл меток классов
labels_file_name = '../labels/image_net_synset.txt'
# параметр метрики top-k для оценки качества работы модели
topk = 5


# функция многократного запуска вывода для фиксированного размера пачки
# и сбор результатов работы сети и времени работы каждого запроса на вывод
def inference_tflite(iter_num, batch_size, files, images, model):
    results = dict()
    times = []
    istart = 0
    ifinish = batch_size
    for iteration in range(iter_num):
        model.set_tensor(input_details[0]['index'], np.array(images[istart:ifinish], dtype=np.float32))
        ts = time()
        model.invoke()
        tf = time()
        output_details = model.get_output_details()
        results[iteration] = (files[istart:ifinish], 
                              model.get_tensor(output_details[0]['index']).copy())
        
        istart = ifinish
        ifinish = (istart + batch_size) % (len(images) + 1)
        if istart > ifinish:
            istart = 0
            ifinish = batch_size
        times.append(tf - ts)
    return results, times

# функция запуска теста и вычисления показателей производительности вывода
def inference_tflite_perf(iter_num, batch_size, files, images, model_interpreter):
    results, times = inference_tflite(iter_num, batch_size, files, images, model_interpreter)
    latency, fps = calculate_performance_metrics(times, batch_size)
    return results, latency, fps


results, latency, fps = inference_tflite_perf(iter_num, batch_size, files, images,
                                              model_interpreter)
print('Performance metrics:')
print(f'\tLatency: {latency:.3f} s\n\tFPS:     {fps:.2f} fps')

print('Results:')
for iteration in range(len(results)):
    process_output(results[iteration][0], results[iteration][1], labels_file_name, topk)
Performance metrics:
	Latency: 0.228 s
	FPS:     8.87 fps
Results:
ILSVRC2012_val_00000023.JPEG
	948	0.9939482	Granny Smith
	951	0.0029236	lemon
	950	0.0014409	orange
	954	0.0006843	banana
	952	0.0001481	fig
ILSVRC2012_val_00000247.JPEG
	13	0.9960352	junco, snowbird
	10	0.0016737	brambling, Fringilla montifringilla
	14	0.0014138	indigo bunting, indigo finch, indigo bird, Passerina cyanea
	19	0.0004691	chickadee
	18	0.0000661	magpie
ILSVRC2012_val_00018592.JPEG
	625	0.9841738	lifeboat
	540	0.0021340	drilling platform, offshore rig
	628	0.0018787	liner, ocean liner
	913	0.0017149	wreck
	724	0.0016311	pirate, pirate ship
ILSVRC2012_val_00019000.JPEG
	752	0.9872142	racket, racquet
	852	0.0084676	tennis ball
	429	0.0032796	baseball
	981	0.0004642	ballplayer, baseball player
	890	0.0002705	volleyball
ILSVRC2012_val_00019001.JPEG
	532	0.9815797	dining table, board
	762	0.0055746	restaurant, eating house, eating place, eatery
	765	0.0032740	rocking chair, rocker
	559	0.0011973	folding chair
	495	0.0011608	china cabinet, china closet
ILSVRC2012_val_00019002.JPEG
	218	0.8885481	Welsh springer spaniel
	156	0.0818037	Blenheim spaniel
	217	0.0074692	English springer, English springer spaniel
	220	0.0061150	Sussex spaniel
	157	0.0058036	papillon

9. Анализ качества решения задачи классификации

Инструменты для сбора показателей качества. Для анализа качества решения задачи классификации с использованием моделей, загруженных из OpenVINO - Open Model Zoo Repository (OMZ), а также некоторых других моделей, которые обучены средствами поддерживаемых фреймворков, можно воспользоваться инструментом Accuracy Checker в составе репозитория OMZ. Указанный инструмент позволяет запускать модели в разных форматах с использованием разных фреймворков глубокого обучения на широко известных наборах данных и собирать общепринятные показатели качества решения классических задач компьютерного зрения (computer vision), обработки естественного языка (natural language processing) и аудиоанализа.

Для упрощения массовых запусков инструмента Accuracy Checker для набора моделей предлагается использовать обертку, разработанную в рамках проекта Deep Learning Inference Benchmark (DLI), которая доступна по ссылке.

Набор данных. Измерение качества предполагается выполнять на валидационной части набора данных ImageNet, которая является открытой. Она содержит 50 000 изображений естественного мира, принадлежащих 1 000 классам. Примечание: по указанной ссылке скачивается архив с полным набором данных (~150 ГБ данных), чтобы загрузить только валидационную выборку можно воспользоваться неофициальными ресурсами, которые выдаются поисковой системой (например, можно использовать эту ссылку).

Показатели качества. Для анализа качества решения задачи классификации изображений используются общепринятые метрики точности top-1 и top-5. Точность top-k (top-k accuracy) -- отношение числа правильно проклассифицированных изображений к общему их количеству. На выходе классификационной нейронной сети имеется вектор достоверности принадлежности изображения каждому из допустимых классов. В случае top-1 изображение считается проклассифицированным правильно, если искомому классу соответствует максимальное значение достоверности, а в случае top-5 -- если искомому классу соответствует одно из пяти наибольших значений достоверностей.

Последовательность подготовки окружения и сбора показателей качества. Чтобы собрать значения показателей качества классификации с использованием описанных инструментов, необходимо выполнить следующую последовательность действий.

  1. Загрузить репозиторий OMZ и установить инструмент Accuracy Checker. Ниже приведена соответствующая последовательность команд. Подробнее установка описана здесь.

    git clone https://github.com/openvinotoolkit/open_model_zoo
    cd ./open_model_zoo/tools/accuracy_checker
    python3 -m pip install .
    python3 -m pip install .[extra]
    

    После установки выполнение команды accuracy_check -h должно приводить к выводу справочной информации о параметрах запуска инструмента.

  2. Загрузить репозиторий DLI, содержащий обертку компонента Accuracy Checker, которая далее используется для упрощения запуска Accuracy Checker при наличии набора моделей.

    git clone https://github.com/itlab-vision/dl-benchmark
    

    Примечание: данный шаг можно пропустить, если система DLI была использована для конвертации модели из формата TensorFlow в TensorFlow Lite.

  3. Загрузить валидационную выборку набора данных ImageNet, состоящую из 50 000 изображений, для которой опубликованы результаты качества классификации. Полный набор данных ImageNet доступен по ссылке. Для последующего запуска Accuracy Checker необходима директория с изображениями ILSVRC2012_img_val и файл val.txt, содержащий разметку этих изображений в формате <image_name> <class_id>, где <image_name> -- название файла, <class_id> -- идентификатор категории, которой принадлежит изображение. Далее для определенности будем считать, что они находятся в директории с названием <dataset_dir>.

  4. Сформировать конфигурационный файл accuracy_checker_conf.xml для анализа качества работы модели на загруженном наборе данных.

    OpenVINO toolkit. Ниже приведена шаблонная структура конфигурационного файла для вывода средствами OpenVINO toolkit (файл accuracy_checker_config_template_openvino.xml дублирует эту структуру). Для использования этой конфигурации достаточно вставить корректное значение пути work_dir. Тег <Directory> должен содержать путь до файлов модели в формате OpenVINO toolkit, а тег <Config> -- полный путь до конфигурационного файла модели densenet-121-tf.yml в загруженном репозитории OMZ. Примечание: указанные в xml-файле пути должны быть абсолютными.

    <?xml version="1.0" encoding="utf-8"?>
    <Tests>
        <Test>
            <Model>
                <Task>classification</Task>
                <Name>densenet-121-tf</Name>
                <Precision>FP32</Precision>
                <SourceFramework>TensorFlow</SourceFramework>
                <Directory>work_dir/public/densenet-121-tf/FP32</Directory>
            </Model>
            <Parameters>
                <InferenceFramework>OpenVINO DLDT</InferenceFramework>
                <Device>CPU</Device>
                <Config>work_dir/open_model_zoo/tools/accuracy_checker/configs/densenet-121-tf.yml</Config>
            </Parameters>
        </Test>
    </Tests>
    

    TensorFlow Lite. Шаблонная структура конфигурационного файла для вывода средствами TensorFlow Lite приведена ниже (файл accuracy_checker_config_template_tflite.xml дублирует эту структуру). Она отличается от ранее показанной только значениями тегов <Directory>, <InferenceFramework> и <Config>. Тег <Directory> содержит путь до файла модели в формате .tflite, куда была сконвертирована исходная модель, <InferenceFramework> -- название фреймворка для вывода, <Config> -- полное название файла с описанием конфигурации модели (файл densenet-121-tflite.yml, содержащий корректный набор параметров, можно найти в директории с файлами конфигурации 01_Practice_configs к данной практической работе).

    <?xml version="1.0" encoding="utf-8"?>
    <Tests>
        <Test>
            <Model>
                <Task>classification</Task>
                <Name>densenet-121-tf</Name>
                <Precision>FP32</Precision>
                <SourceFramework>TensorFlow</SourceFramework>
                <Directory>work_dir/public/densenet-121-tf</Directory>
            </Model>
            <Parameters>
                <InferenceFramework>TensorFlow_Lite</InferenceFramework>
                <Device>CPU</Device>
                <Config>work_dir/open_model_zoo/tools/accuracy_checker/configs/densenet-121-tflite.yml</Config>
            </Parameters>
        </Test>
    </Tests>
    

    Примечание: сбор показателей качества можно выполнить за один запуск обертки Accuracy Checker, если объединить конфигурационные файлы в единый xml-файл, содержащий два блока <Test>.

  5. Перейти в директорию с оберткой компонента Accuracy Checker и запустить ее с использованием командной строки, приведенной ниже. Передаваемые параметры:

    • -r - выходной файл с результатами точности классификации,
    • - сформированный конфигурационный файл,
    • -s - директория, содержащая загруженный набор данных,
    • -d - файл с определением параметров наборов данных,
    • --executor_type - окружение для исполнения (host_machine означает прямой запуск в окружении, установленном на устройстве, docker - запуск в докер- контейнере).

    Подробнее параметры командной строки описаны по ссылке.

    cd <work_dir>/dl-benchmark/src/accuracy_checker
    
    python3 accuracy_checker.py \
     -r <work_dir>/accuracy_results.csv \
     -c <work_dir>/accuracy_checker_config.xml \
     -s <dataset_dir> \
     -d <work_dir>/open_model_zoo/tools/accuracy_checker/dataset_definitions.yml \
     --executor_type host_machine
    

    OpenVINO toolkit. В результате выполнения приведенной команды для конфигурации, предполагающей вывод средствами OpenVINO toolkit, будет выведена информация по структуре, аналогичная приведенной ниже с точностью до названий директорий.

    Processing info:
    model: densenet-121-tf
    launcher: openvino
    device: CPU
    dataset: imagenet_1000_classes
    OpenCV version: 4.5.4
    Annotation conversion for imagenet_1000_classes dataset has been started
    Parameters to be used for conversion:
    converter: imagenet
    annotation_file: /home/kustikova_v/linuxshare/datasets/val.txt
    Annotation conversion for imagenet_1000_classes dataset has been finished
    Converted annotation for imagenet_1000_classes dataset will be saved to imagenet1000.pickle
    IE version: 2024.2.0-15519-5c0f38f83f6-releases/2024/2
    Loaded CPU plugin version:
        CPU - openvino_intel_cpu_plugin: 2024.2.2024.2.0-15519-5c0f38f83f6-releases/2024/2
    Found model /home/kustikova_v/tensor-compiler-course/public/densenet-121-tf/FP32/densenet-121-tf.xml
    Found weights /home/kustikova_v/tensor-compiler-course/public/densenet-121-tf/FP32/densenet-121-tf.bin
    Input info:
            Node name: input_1
            Tensor names: input_1
            precision: f32
            shape: (1, 224, 224, 3)
    
    Output info
            Node name: predictions
            Tensor names: predictions
            precision: f32
            shape: (1, 1000)
    
    50000 objects processed in 1188.619 seconds
    accuracy@top1: 74.46% [FAILED:  abs error = 0.002 | relative error = 2.686e-05]
    accuracy@top5: 92.13% [FAILED:  abs error = 0.002 | relative error = 2.171e-05]
    [ INFO ] Process returncode = 0
    [ INFO ] Saving test result in file
    
    [ INFO ] Accuracy tests completed
    

    TensorFlow Lite. В результате выполнения приведенной команды для конфигурации, предполагающей вывод средствами TensorFlow Lite, будет выведена информация, аналогичная приведенной ниже также с точностью до названий директорий.

    [ INFO ] Create result table with name: /home/kustikova_v/tensor-compiler-course/01_Practice/accuracy_results.csv
    [ INFO ] Start 1 accuracy tests
    
    [ INFO ] Start accuracy check for 1 test: densenet-121-tf
    [ INFO ] Command line is : accuracy_check -c /home/kustikova_v/tensor-compiler-course/open_model_zoo/tools/accuracy_checker/configs/densenet-121-tflite.yml -m /home/kustikova_v/tensor-compiler-course/public/densenet-121-tf/ -s /home/kustikova_v/linuxshare/datasets/ -td CPU --csv_result /common/home/kustikova_v/tensor-compiler-course/dl-benchmark/src/accuracy_checker/result.csv -tf tf_lite -d /home/kustikova_v/tensor-compiler-course/open_model_zoo/tools/accuracy_checker/dataset_definitions.yml
    [ INFO ] CSV file /common/home/kustikova_v/tensor-compiler-course/dl-benchmark/src/accuracy_checker/result.csv exists, remove...
    Processing info:
    model: densenet-121-tf
    launcher: tf_lite
    device: CPU
    dataset: imagenet_1000_classes
    OpenCV version: 4.5.4
    Annotation for imagenet_1000_classes dataset will be loaded from imagenet1000.pickle
    Loaded dataset info:
            Dataset name: imagenet_1000_classes
            Accuracy Checker version 0.10.6
            Dataset size 50000
            Conversion parameters:
                    converter: imagenet
                    annotation_file: PATH/val.txt
    2024-06-29 12:57:14.727103: I tensorflow/tsl/cuda/cudart_stub.cc:28] Could not find cuda drivers on your machine, GPU will not be used.
    2024-06-29 12:57:14.782913: E tensorflow/compiler/xla/stream_executor/cuda/cuda_dnn.cc:9342] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered
    2024-06-29 12:57:14.782968: E tensorflow/compiler/xla/stream_executor/cuda/cuda_fft.cc:609] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered
    2024-06-29 12:57:14.783017: E tensorflow/compiler/xla/stream_executor/cuda/cuda_blas.cc:1518] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered
    2024-06-29 12:57:14.796212: 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 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
    2024-06-29 12:57:15.828945: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
    12:57:16 accuracy_checker WARNING: /home/kustikova_v/.conda/envs/01_practice_env/lib/python3.9/site-packages/accuracy_checker/config/config_validator.py:164: UserWarning: launcher.tf_lite specifies unknown options: ['_list_processed_image_infos']
      warnings.warn(message)
    
    INFO: Created TensorFlow Lite XNNPACK delegate for CPU.
    50000 objects processed in 4315.193 seconds
    accuracy@top1: 74.46% [FAILED:  abs error = 0.002 | relative error = 2.686e-05]
    accuracy@top5: 92.13% [FAILED:  abs error = 0.002 | relative error = 2.171e-05]
    [ INFO ] Process returncode = 0
    [ INFO ] Saving test result in file
    
    [ INFO ] Accuracy tests completed
    

    Отметим, что в стандартном выводе в строках accuracy@top1 и accuracy@top5 приведены посчитанные значения метрик top-1 и top-5 в процентах, которые округлены до второго знака после запятой. Также в этих строках указаны абсолютная и относительная разница по сравнению с референсными значения. Более точные значения логируются в промежуточный файл results.csv в директории <work_dir>/dl-benchmark/src/accuracy_checker.

    Наряду с этим, в результате выполнения команды будет создан файл <work_dir>/accuracy_results.csv с результирующими показателями качества классификации (точность - 2 знака после запятой).

    Примеры файлов всех генерируемых файлов приложены к настоящей практической работе.

  6. Cравнить полученные в файле <work_dir>/accuracy_results.csv результаты точности классификации и значения метрик top-1 и top-5 с опубликованными в работах авторов модели. Референсные значения точностей можно найти в файле <work_dir>/open_model_zoo/tools/accuracy_checker/configs/densenet-121-tf.yml. Для визуализации результатов можно воспользоваться построением гистограмм средствами пакета matplotlib.

In [15]:
import matplotlib.pyplot as plt
import numpy as np

ref_top1 = 74.460
ref_top5 = 92.130

openvino_computed_top1 = 74.458
openvino_computed_top5 = 92.128
tflite_computed_top1 = 74.458
tflite_computed_top5 = 92.128


def draw_hist_topK(ref_top1, ref_top5, computed_top1, computed_top5, model_format):
    metrics = ("top-1", "top-5")
    values = {
        'Computed values':  (computed_top1, computed_top5),
        'Reference values': (ref_top1, ref_top5),
    }

    x = np.arange(len(metrics))  # the label locations
    width = 0.3  # the width of the bars
    multiplier = 0

    fig, ax = plt.subplots(layout='constrained')
    fig.set_size_inches(5, 2.2)
    yax = ax.axes.get_yaxis()
    yax = yax.set_visible(False)

    for attribute, measurement in values.items():
        offset = width * multiplier
        rects = ax.bar(x + offset, measurement, width, label=attribute)
        ax.bar_label(rects, padding=2)
        multiplier += 1

    ax.set_title(f'top-1 and top-5 accuracies for DenseNet-121 ({model_format}), %')
    ax.set_xticks(x + width / 2, metrics)
    ax.legend(loc='upper left', ncols=2)
    ax.set_ylim(0, 140)

    plt.show()

draw_hist_topK(ref_top1, ref_top5, openvino_computed_top1, openvino_computed_top5,
               'OpenVINO IR')
draw_hist_topK(ref_top1, ref_top5, tflite_computed_top1, tflite_computed_top5,
               'TensorFlow Lite')

Из построенных гистограмм можно видеть, что исходная модель, сконвертированная в форматы OpenVINO toolkit и TensorFlow Lite, демонстрирует одинаковые показатели точности top-1 и top-5. Полученные результаты соответствуют референсным значениям.

10. Анализ производительности вывода и определение оптимальных параметров запуска вывода

10.1. Параметры тестовой инфраструктуры

Для автоматического определения параметров тестовой инфраструктуры воспользуемся возможностями пакетов platform и openvino.runtime по аналогии, как это сделано в системе бенчмаркинга вывода DLI (ссылка на ресурс).

In [16]:
import platform
import os
import subprocess
from collections import OrderedDict
from sys import version as python_formatted_version

try:
    from openvino.runtime import Core
    _ov_core_supported = True
except ImportError:
    _ov_core_supported = False

    
def get_cpu_name():
    cpuname = 'Undefined'

    if _ov_core_supported:
        core = Core()
        if 'CPU' in core.available_devices:
            cpuname = core.get_property('CPU', 'FULL_DEVICE_NAME')
        del core

    return cpuname.strip()

def get_gpu_name():
    gpuname = 'Undefined'

    if _ov_core_supported:
        core = Core()
        if 'GPU' in core.available_devices:
            gpuname = core.get_property('GPU', 'FULL_DEVICE_NAME')
        del core

    return gpuname

def get_ram_size(ostype):
    ramsize = 'Undefined'
    if ostype == 'Windows':
        command = ['wmic', 'OS', 'get', 'TotalVisibleMemorySize', '/Value']
        p = subprocess.Popen(command, universal_newlines=True, shell=True,
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        text = p.stdout.read()
        p.wait()
        text = text.split('=')
        ramsize = text[1].strip() + ' KB'
    elif ostype == 'Linux':
        command = ['cat', '/proc/meminfo']
        all_info = subprocess.check_output(command).strip().decode()
        for line in all_info.split('\n'):
            if 'MemTotal' in line:
                return line.split(':')[1].strip()
    return ramsize

def get_system_characteristics():
    ostype = platform.system()
    characteristics = OrderedDict()
    characteristics.update({'CPU': get_cpu_name()})
    characteristics.update({'Number of CPUs': os.cpu_count()})
    characteristics.update({'CPU family': platform.processor()})
    characteristics.update({'GPU': get_gpu_name()})
    characteristics.update({'RAM size': get_ram_size(ostype)})
    characteristics.update({'OS family': platform.system()})
    characteristics.update({'OS version': platform.platform()})
    characteristics.update({'Python version': python_formatted_version})

    return characteristics


hardware_dict = get_system_characteristics()
for key, value in hardware_dict.items():
    print(f'{key:16}: {value}')
CPU             : Intel(R) Xeon(R) Silver 4310T CPU @ 2.30GHz
Number of CPUs  : 8
CPU family      : x86_64
GPU             : Undefined
RAM size        : 65807804 kB
OS family       : Linux
OS version      : Linux-3.10.0-1160.83.1.el7.x86_64-x86_64-with-glibc2.17
Python version  : 3.9.19 (main, May  6 2024, 19:43:03) 
[GCC 11.2.0]

10.2. Параметры для перебора

In [17]:
# размеры входных пачек данных
batch_sizes = [1, 2, 4, 8, 16]
# количество потоков
num_threads = [1, 2, 4, 8]
# количество итераций для определения показателей латентности и FPS
iter_num = 50
# устройство для запуска вывода
device = 'CPU'

10.3. Определение оптимальных параметров запуска вывода с использованием OpenVINO toolkit

Разработчики OpenVINO toolkit утверждают, что на аппаратных решениях компании Intel инструмент выбирает оптимальные параметры запуска вывода (количество потоков и другие) автоматически. Поэтому единственный параметр, который необходимо подобрать, -- размер входной пачки данных, обрабатываемых за один проход.

In [18]:
import openvino as ov


# функция запуска тестов производительности для разных размеров входных пачек данных
def openvino_perf_tests(model_xml, model_bin, batch_sizes, iter_num, files, images):
    core = create_core(device)

    # словарь "размер пачки" - "латентность"
    openvino_latency = dict()
    # словарь "размер пачки" - "FPS"
    openvino_fps = dict()
    # цикл по размерам пачек данных
    for batch_size in batch_sizes:
        input_shape = [batch_size, 224, 224, 3]
        # подготовка модели (загрузка и масштабирование входа)
        model, compiled_model = prepare_model(core, model_xml, model_bin, input_shape)
        # создание запроса на вывод
        request = compiled_model.create_infer_request()
        # запуск теста производительности для фиксированного размера пачки данных
        _, latency, fps = inference_openvino_perf(iter_num, batch_size, files, images,
                                                  request)
        # сохранение результатов производительности в словари
        openvino_latency[batch_size] = latency
        openvino_fps[batch_size] = fps
        # вывод показателей производительности
        print(f'batch_size = {batch_size:2}: latency = {latency:.3f} s, '
              f'FPS = {fps:3.3f} frames/s')
    return openvino_latency, openvino_fps


files, images = prepare_input(images_dir, input_shape[1:3])
openvino_latency, openvino_fps = openvino_perf_tests(model_xml, model_bin, batch_sizes,
                                                     iter_num, files, images)
Number of available images: 64
<Core: available plugins[CPU]>
batch_size =  1: latency = 0.011 s, FPS = 92.647 frames/s
batch_size =  2: latency = 0.018 s, FPS = 112.951 frames/s
batch_size =  4: latency = 0.034 s, FPS = 117.357 frames/s
batch_size =  8: latency = 0.068 s, FPS = 120.019 frames/s
batch_size = 16: latency = 0.132 s, FPS = 120.013 frames/s

Для наглядности полученных результатов производительности построим графики зависимости латентности и FPS от размера пачки данных.

In [19]:
fig, axs = plt.subplots(figsize=(7, 4.5)) 
x = list(openvino_latency.keys())
y = list(openvino_latency.values())
axs.plot(x, y, marker="o", color='teal')
plt.grid(True)
for i, j in zip(x, y):
    axs.annotate(f'{j:.3f}', xy=(i,j), ha='left', va='top', fontsize=12)
axs.set_xlabel('Batch size', fontsize=12, fontweight="bold")
axs.set_ylabel('Latency, s', fontsize=12, fontweight="bold")
axs.set_xlim(0, 18)
axs.set_title('Latency (OpenVINO toolkit)', fontsize=12, fontweight="bold")
plt.show()

Вывод из графика зависимости латентности от размера пачки данных: реализация вывода хорошо масштабируется, с увеличением пачки вдвое латентность увеличивается примерно в 2 раза.

In [20]:
fig, axs = plt.subplots() 
x = list(openvino_fps.keys())
y = list(openvino_fps.values())
axs.plot(x, y, marker="o", color='teal')
plt.grid(True)
for i, j in zip(x, y):
    axs.annotate(f'{j:.2f}', xy=(i,j), ha='right', va='bottom', fontsize=12)
axs.set_xlabel('Batch size', fontsize=12, fontweight="bold")
axs.set_ylabel('FPS', fontsize=12, fontweight="bold")
axs.set_xlim(-1, 17)
axs.set_ylim(90, 130)
axs.set_title('FPS (OpenVINO toolkit)', fontsize=12, fontweight="bold")
plt.show()

Выводы из графика зависимости FPS от размера пачки:

  1. Вывод работает в режиме реального времени независимо от размера пачки данных.
  2. Лучшая производительность модели DenseNet-121 (~120 fps) при выводе средствами OpenVINO toolkit наблюдается на размере пачки данных в 8 и 16 изображений.

10.4. Определение оптимальных параметров запуска вывода с использованием TensorFlow Lite

TensorFlow Lite в отличие от OpenVINO toolkit не обеспечивает автоматический подбор параметров запуска вывода. Поэтому для данного инструмента необходимо сравнивать производительность вывода при разных размерах пачки данных и разном количестве потоков, исполняемых параллельно, и подбирать оптимальные значения параметров.

In [21]:
# функция запуска тестов производительности для разных размеров входных пачек данных
# при разном количестве потоков
def tflite_perf_tests(model_tflite, batch_sizes, num_threads, iter_num, files, images):
    # словарь "количество потоков" - словарь "размер пачки" - "латентность"
    tflite_latency = dict()
    # словарь "количество потоков" - словарь "размер пачки" - "FPS"
    tflite_fps = dict()
    # цикл по количеству потоков
    for nthreads in num_threads:
        print(f'Number of threads: {nthreads}')
        # словарь "размер пачки" - "латентность"
        tflite_nt_latency = dict()
        # словарь "размер пачки" - "FPS"
        tflite_nt_fps = dict()
        # цикл по размерам пачек данных
        for batch_size in batch_sizes:
            input_shape = [batch_size, 224, 224, 3]
            # подготовка модели (загрузка и масштабирование входа)
            model_interpreter = tflite.Interpreter(model_path=model_tflite,
                                                   num_threads=nthreads)
            input_details = model_interpreter.get_input_details()
            model_interpreter.resize_tensor_input(input_details[0]['index'], input_shape)
            model_interpreter.allocate_tensors()
            # запуск теста производительности для фиксированного размера пачки данных
            _, latency, fps = inference_tflite_perf(iter_num, batch_size, files, images,
                                                    model_interpreter)
            # сохранение результатов производительности в словари
            tflite_nt_latency[batch_size] = latency
            tflite_nt_fps[batch_size] = fps
            # вывод показателей производительности
            print(f'\tbatch_size = {batch_size:2}: latency = {latency:.3f} s, '
                  f'FPS = {fps:.3f} frames/s')
        tflite_latency[nthreads] = tflite_nt_latency
        tflite_fps[nthreads] = tflite_nt_fps
    return tflite_latency, tflite_fps


files, images = prepare_input(images_dir, input_shape[1:3], 
                              (123.68,116.78,103.94), (58.395,57.12,57.375),
                              True)
tflite_latency, tflite_fps = tflite_perf_tests(model_tflite, batch_sizes, num_threads,
                                               iter_num, files, images)
Number of available images: 64
Number of threads: 1
	batch_size =  1: latency = 0.073 s, FPS = 13.764 frames/s
	batch_size =  2: latency = 0.147 s, FPS = 13.593 frames/s
	batch_size =  4: latency = 0.300 s, FPS = 13.327 frames/s
	batch_size =  8: latency = 0.616 s, FPS = 12.977 frames/s
	batch_size = 16: latency = 1.260 s, FPS = 12.699 frames/s
Number of threads: 2
	batch_size =  1: latency = 0.038 s, FPS = 24.596 frames/s
	batch_size =  2: latency = 0.077 s, FPS = 26.058 frames/s
	batch_size =  4: latency = 0.155 s, FPS = 25.708 frames/s
	batch_size =  8: latency = 0.320 s, FPS = 24.957 frames/s
	batch_size = 16: latency = 0.657 s, FPS = 24.301 frames/s
Number of threads: 4
	batch_size =  1: latency = 0.022 s, FPS = 44.849 frames/s
	batch_size =  2: latency = 0.040 s, FPS = 49.817 frames/s
	batch_size =  4: latency = 0.081 s, FPS = 49.662 frames/s
	batch_size =  8: latency = 0.166 s, FPS = 47.810 frames/s
	batch_size = 16: latency = 0.341 s, FPS = 46.780 frames/s
Number of threads: 8
	batch_size =  1: latency = 0.014 s, FPS = 69.652 frames/s
	batch_size =  2: latency = 0.026 s, FPS = 77.655 frames/s
	batch_size =  4: latency = 0.049 s, FPS = 80.867 frames/s
	batch_size =  8: latency = 0.100 s, FPS = 79.495 frames/s
	batch_size = 16: latency = 0.206 s, FPS = 77.092 frames/s

По аналогии с OpenVINO toolkit для наглядности построим графики зависимости латентности и FPS от размера пачки данных при разных значениях числа потоков.

In [22]:
colors = ['teal', 'skyblue', 'steelblue', 'maroon']
markers = ['o', 'd', 's', 'v']
linestyles = ['solid', 'dotted', 'dashed', 'dashdot']
labels = ['nthreads=1', 'nthreads=2', 'nthreads=4', 'nthreads=8']

fig, axs = plt.subplots()
plt.grid(True)
line_idx = 0
for nthreads, bs_latency in tflite_latency.items():
    x = list(bs_latency.keys())
    y = list(bs_latency.values())
    axs.plot(x, y, marker=markers[line_idx], color=colors[line_idx],
             linestyle=linestyles[line_idx], label=labels[line_idx])
    line_idx += 1
    for i, j in zip(x, y):
        if (i != 1) and (i != 2) and (i != 4):
            axs.annotate(f'{j:.2f}', xy=(i,j), ha='right', va='bottom', fontsize=12)
axs.set_xlabel('Batch size', fontsize=12, fontweight="bold")
axs.set_ylabel('Latency, s', fontsize=12, fontweight="bold")
axs.set_title('Latency (TensorFlow Lite)', fontsize=12, fontweight="bold")
axs.set_xlim(0, 17)
axs.set_ylim(0, 1.5)
plt.legend(loc='upper left', ncols=1, fontsize=11)
plt.show()

Вывод из графика зависимости латентности от пачки данных при разном числе потоков: лучшие показатели латентности независимо от размера пачки достигаются на максимальном количестве доступных физических ядер.

In [23]:
colors = ['teal', 'skyblue', 'steelblue', 'maroon']
markers = ['o', 'd', 's', 'v']
linestyles = ['solid', 'dotted', 'dashed', 'dashdot']
labels = ['nthreads=1', 'nthreads=2', 'nthreads=4', 'nthreads=8']

fig, axs = plt.subplots()
plt.grid(True)
line_idx = 0
for nthreads, bs_fps in tflite_fps.items():
    x = list(bs_fps.keys())
    y = list(bs_fps.values())
    axs.plot(x, y, marker=markers[line_idx], color=colors[line_idx],
             linestyle=linestyles[line_idx], label=labels[line_idx])
    line_idx += 1
    for i, j in zip(x, y):
        if i != 1:
            if nthreads == 1:
                axs.annotate(f'{j:.2f}', xy=(i,j-0.2), ha='left', va='top', fontsize=12)
            else:
                axs.annotate(f'{j:.2f}', xy=(i,j+0.2), ha='left', va='bottom', fontsize=12)
axs.set_xlabel('Batch size', fontsize=12, fontweight="bold")
axs.set_ylabel('FPS', fontsize=12, fontweight="bold")
axs.set_title('FPS (TensorFlow Lite)', fontsize=12, fontweight="bold")
axs.set_xlim(0, 18)
axs.set_ylim(5, 110)
plt.legend(loc='upper left', ncols=2, fontsize=11)
plt.show()

Вывод из графика зависимости FPS от размера пачки данных при разном числе потоков: лучшие показатель FPS (~80.87) достигается на пачке в 4 изображения при количестве потоков, равном числу физических ядер.

10.5. Сравнение лучших показателей производительности

In [24]:
import operator

# определение лучшего показателя FPS для OpenVINO toolkit
max_item = max(openvino_fps.items(), key=operator.itemgetter(1))
openvino_maxarg = max_item[0]
openvino_maxfps = max_item[1]
print('OpenVINO maximum FPS\n'
      f'\tBatch size:        {openvino_maxarg}\n'
       '\tNumber of threads: default\n'
      f'\tFPS:               {openvino_maxfps:.3f}')

# определение лучшего показателя FPS для TensorFlow Lite
tflite_maxs = {key: max(val.items(), key=operator.itemgetter(1)) 
               for key, val in tflite_fps.items()}
max_item = max(tflite_maxs.items(), key=operator.itemgetter(1))
tflite_maxarg = max_item[0]
tflite_maxbs = max_item[1][0]
tflite_maxfps = max_item[1][1]
print('TensorFlow Lite maximum FPS\n'
      f'\tBatch size:        {tflite_maxbs}\n'
      f'\tNumber of threads: {tflite_maxarg}\n'
      f'\tFPS:               {tflite_maxfps:.3f}')


print('\nFPS ratio: {:.2f}'.format(openvino_maxfps / tflite_maxfps))
OpenVINO maximum FPS
	Batch size:        8
	Number of threads: default
	FPS:               120.019
TensorFlow Lite maximum FPS
	Batch size:        4
	Number of threads: 8
	FPS:               80.867

FPS ratio: 1.48

Вывод из полученных результатов: OpenVINO toolkit при определенном наборе параметров позволяет достичь числа обрабатываемых изображений за секунду (FPS) в ~1.48 раза больше по сравнению с TensorFlow Lite на данной тестовой инфраструктуре.