Лекция 3. Пример вывода обученной нейронной сети с использованием TensorFlow Lite¶

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

In [1]:
import tensorflow.lite as tflite
import cv2
import os
import numpy as np
from matplotlib import pyplot as plt

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

Далее в качестве тестовых моделей используются DenseNet-121 и MobileNetV2. Для загрузки указанных моделей предлагается использовать инструмент Model Downloader в составе OpenVINO toolkit - Open Model Zoo. Предполагается, что тестовые модели загружаются в директорию ./trained_models.

conda create -n openvino_converter python=3.10
conda activate openvino_converter

pip install openvino-dev
pip install openvino-dev[tensorflow2]

mkdir trained_models
cd trained_models/

omz_downloader --name densenet-121-tf 
omz_converter --name densenet-121-tf

omz_downloader --name mobilenet-v2-1.4-224
omz_converter --name mobilenet-v2-1.4-224
conda deactivate

Вложенная директория public/<model_name> (<model_name> - название модели) создается автоматически инструментом Model Downloader после загрузки соответствующей модели.

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

# клонирование репозитория, содержащего конвертер
git clone https://github.com/itlab-vision/dl-benchmark
# переход в директорию с конвертером
cd dl-benchmark/src/model_converters/tf2tflite

# создание витуальной среды и установка необходимых пакетов в этой среде
conda create -n tflite_converter python=3.9 -y
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

# запуск конвертера (${MODELS_PATH} - путь до директории, куда были загружены модели)
python tflite_converter.py \
     --model ${MODELS_PATH}/trained_models/public/densenet-121-tf/densenet-121.savedmodel \
     --source-framework tf
python tflite_converter.py \
     --model ${MODELS_PATH}/trained_models/public/mobilenet-v2-1.4-224/mobilenet_v2_1.4_224_frozen.pb \
     --source-framework tf --input-names input

conda deactivate
In [5]:
# Путь до модели DenseNet-121
model_path = './trained_models/public/densenet-121-tf/densenet-121.tflite'
# Путь до модели MobileNet-v2
# model_path = './trained_models/public/mobilenet-v2-1.4-224/mobilenet_v2_1.4_224_frozen.tflite'

# Размер пачки данных, обрабатываемых за проход
batch_size = 2
# Размер обрабатываемого входного тензора
input_shape = [batch_size, 224, 224, 3]


# Функция печати информации о входах модели
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 = tflite.Interpreter(model_path=model_path)
print('Input details for the model (before input reshape):')
print_input_details(model)

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

# Выделение памяти для хранения тензоров
model.allocate_tensors()
Input details for the model (before input reshape):
	Input 0: {'name': 'input_1', 'index': 0, 'shape': array([  1, 224, 224,   3]), 'shape_signature': array([ -1, 224, 224,   3]), '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]), 'shape_signature': array([ -1, 224, 224,   3]), 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0), 'quantization_parameters': {'scales': array([], dtype=float32), 'zero_points': array([], dtype=int32), 'quantized_dimension': 0}, 'sparsity_parameters': {}}

3. Загрузка данных¶

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

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

Наряду с этим, для каждого загруженного изображения выполняется вычитание средней интенсивности mean, посчитанной по тренировочной выборке, и деление на среднеквадратическое отклонение scale с использованием перегруженных операций вычитания и деления для объектов класса Mat.

In [6]:
images_dir = '.\data'

# Параметры mean и scale для модели DenseNet-121
mean = [123.68, 116.78, 103.94]
scale = [58.395, 57.12, 57.375]
# Параметры mean и scale для модели MobileNet-v2
# mean = [127.5, 127.5, 127.5]
# scale = [127.5, 127.5, 127.5]


# Функция загрузки и предварительной обработки изображений
def prepare_input(images_dir, input_image_resolution):
    files = [f for f in os.listdir(images_dir) if os.path.isfile(os.path.join(images_dir, f))]
    print(f'List of available images: {files}')
    images = []
    for file in files:
        print(f'{file}')
        image = cv2.imread(os.path.join(images_dir, file), cv2.IMREAD_COLOR)
        plt.imshow(image)
        plt.show()
        print(f'\tShape of the image {file} before resize: {image.shape}')
        image = cv2.resize(image, input_image_resolution)
        print(f'\tShape of the image {file} after resize: {image.shape}')
        image = (image - mean) / scale
        images.append(image)
    return files, images


files, images = prepare_input(images_dir, input_shape[1:3])
List of available images: ['ILSVRC2012_val_00000023.JPEG', 'ILSVRC2012_val_00000247.JPEG', 'ILSVRC2012_val_00018592.JPEG']
ILSVRC2012_val_00000023.JPEG
No description has been provided for this image
	Shape of the image ILSVRC2012_val_00000023.JPEG before resize: (510, 709, 3)
	Shape of the image ILSVRC2012_val_00000023.JPEG after resize: (224, 224, 3)
ILSVRC2012_val_00000247.JPEG
No description has been provided for this image
	Shape of the image ILSVRC2012_val_00000247.JPEG before resize: (500, 500, 3)
	Shape of the image ILSVRC2012_val_00000247.JPEG after resize: (224, 224, 3)
ILSVRC2012_val_00018592.JPEG
No description has been provided for this image
	Shape of the image ILSVRC2012_val_00018592.JPEG before resize: (500, 333, 3)
	Shape of the image ILSVRC2012_val_00018592.JPEG after resize: (224, 224, 3)

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

При установке входных тензоров используются первые batch_size изображений из набора загруженных изображений.

In [7]:
model.set_tensor(input_details[0]['index'], np.array(images[0:batch_size], dtype=np.float32))
model.invoke()

5. Обработка результатов вывода¶

В качестве тестовых нейронных сетей используются модели, обеспечивающие решение задачи классификации с большим числом категорий на наборе данных ImageNet. На выходе этих сетей формируется вектор достоверностей принадлежности входного изоображения каждому из допустимых 1000 классов, представленных в наборе ImageNet (перечень классов доступен в файле ./labels/image_net_synset.txt). Отметим, что некоторые модели строят на выходе вектор из 1001 элемента, поскольку учитывается класс фона (перечень классов доступен в файле ./labels/image_net_synset_first_class_base.txt).

Обработка выходного вектора достоверностей состоит в выборе topk классов, для которых получена максимальная достоверность, и вывод названий этих классов.

In [8]:
# Файл меток для модели DenseNet-121
labels_file_name = './labels/image_net_synset.txt'
# Файл меток для модели MobileNet-v2
# labels_file_name = './labels/image_net_synset_first_class_base.txt'

topk = 5


# функция загрузки меток классов
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))


# Получение выходов модели
output_details = model.get_output_details()
print('Output details:')
for idx in range(len(output_details)):
    print(f'\tOutput {idx}: {output_details[idx]}')
# Получение тензора с первого выхода
output_data = model.get_tensor(output_details[0]['index'])
# Обработка полученного тензора
process_output(files, output_data, labels_file_name, topk)
Output details:
	Output 0: {'name': 'Identity', 'index': 623, 'shape': array([   2, 1000]), 'shape_signature': array([  -1, 1000]), 'dtype': <class 'numpy.float32'>, 'quantization': (0.0, 0), 'quantization_parameters': {'scales': array([], dtype=float32), 'zero_points': array([], dtype=int32), 'quantized_dimension': 0}, 'sparsity_parameters': {}}
ILSVRC2012_val_00000023.JPEG
	948	0.9525880	Granny Smith
	950	0.0132317	orange
	951	0.0123400	lemon
	954	0.0028143	banana
	719	0.0020238	piggy bank, penny bank
ILSVRC2012_val_00000247.JPEG
	13	0.9847551	junco, snowbird
	19	0.0068679	chickadee
	10	0.0034510	brambling, Fringilla montifringilla
	20	0.0015685	water ouzel, dipper
	14	0.0012343	indigo bunting, indigo finch, indigo bird, Passerina cyanea