Представьте: вы обучили детектор объектов на 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 примеров, покрывающий все крайние случаи.
Несовместимость операций — около 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 | Средняя | Высокая | Средняя |
Прямой путь из TF даёт минимальные расхождения. Путь через 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. Кастомная операция через 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 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 в месяц. Стоимость проекта рассчитывается индивидуально под вашу модель и требования.
Свяжитесь с нами для оценки вашей модели — мы проведём бесплатный аудит TFLite-совместимости. Закажите консультацию, чтобы оптимизировать модель под ваш целевой парк устройств.







