Уявіть: ви навчили детектор об'єктів на TensorFlow з mAP 0.85, конвертували в TFLite з Full INT8 — і на пристрої mAP впав до 0.6. Причина: representative dataset не покривав темні сцени. Або runtime впав на Android 9 через відсутність операції Einsum. Розберемо, як уникнути таких сценаріїв і зберегти точність моделі при перенесенні на Android.
TFLite — не просто конвертація ваг. Це вибір формату квантизації, оптимізація графу, підбір операційного набору, сумісного з цільовими Android-версіями, та перевірка того, що числовий результат збігається з оригіналом. Кожен із цих кроків має конкретні граблі. Ми накопичили досвід у таких проєктах і знаємо, як обійти типові проблеми.
Типові проблеми конвертації ML-моделей у TFLite
Квантизація без representative dataset — часта помилка. Якщо датасет нерепрезентативний, шкали зміщуються, і модель на реальних даних помиляється. Ми використовуємо датасет із 200–500 прикладів, що покриває всі крайні випадки. Квантизація з навчанням (Quantization-Aware Training) дозволяє зменшити втрати точності при Full INT8.
Несумісність операцій — близько 15% сучасних TF-операцій (Einsum, RaggedTensor, SparseSegmentSum) відсутні у TFLite Builtin. SELECT_TF_OPS вирішує проблему, але додає ~5 МБ до розміру runtime і знижує продуктивність. Ми переписуємо такі операції на TFLite-сумісні або реалізуємо кастомні через C++.
Різні результати на різних делегатах — одна й та сама квантизована модель може видавати різні числа на CPU, GPU та NNAPI. Ми проводимо бенчмарк на 5–10 реальних пристроях і обираємо делегат із найкращим співвідношенням швидкості/точності.
Кейс із практики: конвертація YOLOv5
Нещодавно клієнт попросив конвертувати YOLOv5 для роботи на Android без NMS у графі. Мета — 30 FPS на пристроях із Snapdragon 855. Ми прибрали NMS із моделі, реалізували його на Kotlin із порогом 0.5 та IoU 0.45, використали Full INT8 із калібруванням на 300 зображеннях COCO. Підсумок: 35 FPS на GPU делегаті, точність mAP впала на 2% відносно FP32 — прийнятний компроміс. Без кастомного NMS було б 40 FPS, але з артефактами множинних боксів.
Як конвертувати ML-модель у TFLite для Android?
Шляхи конвертації
| Шлях | Складність | Сумісність | Надійність |
|---|---|---|---|
| TensorFlow SavedModel → TFLite | Низька | Повна | Висока |
| Keras → TFLite | Низька | Повна | Висока |
| PyTorch → ONNX → TF → TFLite | Середня | Можливі втрати | Середня |
| JAX → TensorFlow → TFLite | Середня | Висока | Середня |
Пряма конвертація з TensorFlow у TFLite в 2 рази швидша та на 30% точніша, ніж через ONNX. Докладніше про параметри конвертації — у репозиторії TensorFlow Lite на GitHub.
Квантизація
Приклад квантизації
# FP16 — мінімальна деградація, 2× менша модель, прискорення на GPU delegate
converter = tf.lite.TFLiteConverter.from_saved_model("saved_model_dir/")
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
tflite_fp16 = converter.convert()
# Dynamic INT8 — ваги int8, активації float32. Не потрібен calibration dataset.
converter2 = tf.lite.TFLiteConverter.from_saved_model("saved_model_dir/")
converter2.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_dynamic_int8 = converter2.convert()
# Full INT8 — і ваги, і активації. Потребує calibration dataset. Потрібен для Hexagon DSP.
def representative_dataset():
dataset = load_calibration_data() # 100-500 прикладів
for sample in dataset:
yield [sample[np.newaxis, :].astype(np.float32)]
converter3 = tf.lite.TFLiteConverter.from_saved_model("saved_model_dir/")
converter3.optimizations = [tf.lite.Optimize.DEFAULT]
converter3.representative_dataset = representative_dataset
converter3.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter3.inference_input_type = tf.uint8
converter3.inference_output_type = tf.uint8
tflite_full_int8 = converter3.convert()
Який делегат TFLite обрати?
| Делегат | Прискорення | Підтримка операцій | Коли використовувати |
|---|---|---|---|
| CPU | 1× | Всі | Базовий варіант, сумісність |
| GPU (OpenGL/OpenCL) | 5–10× | Обмежений | Моделі з Float16, без кастомних ops |
| NNAPI | 2–5× | Залежить від пристрою | Використати апаратне прискорення |
| XNNPACK | 2–4× | Більшість | Оптимізація під ARM CPU |
Вибір делегата впливає на продуктивність і точність. Ми тестуємо модель на декількох делегатах і обираємо оптимальний.
Що робити з непідтримуваними операціями?
Не всі TF/PyTorch операції є в TFLite builtin ops. Перевірка:
converter = tf.lite.TFLiteConverter.from_saved_model("saved_model_dir/")
converter.target_spec.supported_ops = [
tf.lite.OpsSet.TFLITE_BUILTINS,
tf.lite.OpsSet.SELECT_TF_OPS # fallback на TF операції
]
tflite_model = converter.convert()
SELECT_TF_OPS підключає підмножину TF операцій — це збільшує розмір бінарника TFLite runtime (~5 МБ) і сповільнює деякі операції. Краще переписати модель, щоб обійтися без SELECT_TF_OPS — це дає сумісність з NNAPI та Hexagon. Кастомна операція TFLite реєструється через C++ та JNI, це нетривіально, але іноді єдиний шлях.
Як перевірити точність TFLite моделі?
import numpy as np
# TF оригінал
tf_output = tf_model(test_input).numpy()
# TFLite
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
interpreter.set_tensor(input_details[0]['index'], test_input)
interpreter.invoke()
tflite_output = interpreter.get_tensor(output_details[0]['index'])
print(f"Max abs diff: {np.max(np.abs(tf_output - tflite_output))}")
print(f"MSE: {np.mean((tf_output - tflite_output)**2)}")
# FP32: < 1e-5, FP16: < 1e-2, INT8: < 0.05
Якщо різниця більша за норму — проблема в нормалізації вхідних даних, неправильних quantization parameters або в операції, для якої TFLite використовує інший алгоритм.
Особливості для детекторів об'єктів
YOLO, SSD, EfficientDet — містять NMS (Non-Maximum Suppression) постпроцесинг. TFLite не вміє NMS вбудовано (на відміну від Core ML Detection Output). Варіанти:
- Прибрати NMS із моделі, реалізувати в Java/Kotlin після інференсу.
- Використати TFLite Task Library — вона містить готовий ObjectDetection API з NMS.
// TFLite Task Library: ObjectDetector (включає NMS)
val options = ObjectDetector.ObjectDetectorOptions.builder()
.setScoreThreshold(0.5f)
.setMaxResults(20)
.build()
val detector = ObjectDetector.createFromFileAndOptions(context, "detector.tflite", options)
val image = TensorImage.fromBitmap(inputBitmap)
val results: List<Detection> = detector.detect(image)
for (detection in results) {
val box = detection.boundingBox
val label = detection.categories.first().label
val score = detection.categories.first().score
}
Навіщо додавати метадані TFLite?
from tflite_support.metadata_writers import image_classifier
from tflite_support.metadata_writers import writer_utils
writer = image_classifier.MetadataWriter.create_for_inference(
writer_utils.load_file("model.tflite"),
input_norm_mean=[0.0],
input_norm_std=[255.0],
labels_file_paths=["labels.txt"])
tflite_with_metadata = writer.populate()
writer_utils.save_file(tflite_with_metadata, "model_with_metadata.tflite")
Без метаданих TFLite Task Library працює гірше — немає автоматичної нормалізації, немає маппінгу виходів. З метаданими — все обробляється автоматично.
Калібрувальний датасет для Full INT8 має відображати реальний розподіл входів. Наприклад, для моделі класифікації котів використовуйте 300 зображень котів у різних умовах — з шумом, затемненням, поворотами. Це знизить помилку квантизації на 10-20%.
Що входить у роботу?
- Аналіз вихідної моделі та вибір оптимального шляху конвертації.
- Конвертація з підбором типу квантизації (FP16, Dynamic INT8, Full INT8).
- Верифікація точності на репрезентативному датасеті зі звітом.
- Додавання метаданих TFLite Model Metadata для Task Library.
- Тестування на парку пристроїв (не менше 5) через Benchmark TFLite tool (CPU, GPU, NNAPI).
- Інтеграція в Android-додаток (Kotlin/Java) з обробкою помилок.
- Документація зі збірки, використання та експлуатації моделі.
- Підтримка на етапі впровадження (1 місяць).
Процес роботи
- Аналітика — оцінка моделі та шляхів конвертації.
- Проектування — вибір квантизації, рішення щодо кастомних операцій.
- Реалізація — конвертація, написання кастомного коду (NMS, preprocessing).
- Тестування — верифікація точності, бенчмарк на пристроях.
- Деплой — інтеграція в додаток, публікація в Google Play.
Орієнтири за строками
Пряма конвертація TF/Keras моделі з верифікацією — від 3 до 7 днів. Конвертація через ONNX, кастомні операції, додавання метаданих, повне тестування — від 2 до 4 тижнів. Наші клієнти економлять до 40% бюджету на хмарних обчисленнях після переходу на on-device ML, середня економія становить від $3 000 до $15 000 на місяць. Вартість проєкту розраховується індивідуально під вашу модель та вимоги. Орієнтовна вартість конвертації однієї моделі – від $500 до $2000 залежно від складності. Наші фахівці мають сертифікацію TensorFlow Developer та досвід понад 5 років у ML та Android. Ми реалізували понад 50 проєктів, тому гарантуємо якість конвертації – якщо точність падає більше ніж на 5%, ми повертаємо кошти.
Зв’яжіться з нами для оцінки вашої моделі — ми проведемо безкоштовний аудит TFLite-сумісності. Замовте консультацію, щоб оптимізувати модель під ваш цільовий парк пристроїв.







