Конвертація ML-моделі в TensorFlow Lite формат для Android
TFLite — це не просто конвертація ваг. Це вибір формату квантизації, оптимізація графу, вибір набору операцій, сумісного з цільовими версіями Android, та перевірка того, що числовий результат відповідає оригіналу. Кожен крок має конкретні грабли.
Шляхи конвертації
З TensorFlow SavedModel — прямий шлях. З PyTorch — через ONNX проміжний формат. З JAX — через TensorFlow експорт.
# Шлях 1: TF SavedModel → TFLite (найнадійніший)
converter = tf.lite.TFLiteConverter.from_saved_model("saved_model_dir/")
tflite_model = converter.convert()
# Шлях 2: Keras модель → TFLite
converter = tf.lite.TFLiteConverter.from_keras_model(keras_model)
tflite_model = converter.convert()
# Шлях 3: PyTorch → ONNX → TF → TFLite
import subprocess
subprocess.run(["python", "-m", "tf2onnx.convert",
"--onnx", "model.onnx",
"--output", "model_tf",
"--opset", "17"])
converter = tf.lite.TFLiteConverter.from_saved_model("model_tf/")
Шлях через ONNX вносить додаткові потенційні несумісності — використовуйте тільки коли прямий шлях недоступний.
Квантизація при конвертації
# FP16 — мінімальна деградація, модель у 2 рази менша, GPU delegate прискорення
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
tflite_fp16 = converter.convert()
# Dynamic INT8 — ваги int8, активації float32. Не потрібен калібрувальний набір.
converter2 = tf.lite.TFLiteConverter.from_saved_model("saved_model_dir/")
converter2.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_dynamic_int8 = converter2.convert()
# Full INT8 — обидві ваги та активації. Вимагає калібрувального набору. Потрібен для 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 # або tf.int8
converter3.inference_output_type = tf.uint8
tflite_full_int8 = converter3.convert()
Full INT8 з inference_input_type = tf.uint8 — вхідні дані передаються як uint8 (0–255), без нормалізації до float32 в Java/Kotlin. Видаляє крок попередньої обробки, але вимагає обережної синхронізації з параметрами квантизації моделі.
Непідтримувані операції
Не всі TF/PyTorch операції існують у TFLite builtin ops. Перевірка:
# Які операції в моделі не підтримуються TFLite
converter.target_spec.supported_ops = [
tf.lite.OpsSet.TFLITE_BUILTINS,
tf.lite.OpsSet.SELECT_TF_OPS # fallback на TF операції
]
SELECT_TF_OPS включає підмножину TF операцій — збільшує розмір TFLite runtime (~5 МБ) та сповільнює деякі операції. Краще переписати модель, уникаючи SELECT_TF_OPS — дозволяє NNAPI та Hexagon сумісність.
Користувацька операція через C++:
// Реєстрація користувацької операції
static TfLiteRegistration* GetMyCustomOpRegistration() {
static TfLiteRegistration reg = {
nullptr, nullptr,
[](TfLiteContext* ctx, TfLiteNode* node) -> TfLiteStatus {
// Реалізація інференсу
return kTfLiteOk;
},
nullptr, "MyCustomOp", 1
};
return ®
}
// У коді Android NDK:
interpreter.AddCustomOp("MyCustomOp", GetMyCustomOpRegistration, 1);
Kotlin через JNI. Нетривіально, але іноді єдиний шлях.
Верифікація числової точності
# Порівняйте PyTorch/TF та 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
Якщо різниця перевищує норму — проблема в нормалізації входу, неправильних параметрах квантизації або операції, яка використовує інший алгоритм у 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 // RectF
val label = detection.categories.first().label
val score = detection.categories.first().score
}
Task Library підтримує тільки специфічні модельні сигнатури — модель повинна відповідати форматі TFLite Model Metadata.
Model Metadata: важливо для Interpreter + Task Library
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 працює гірше — без автоматичної нормалізації, без маппінгу виходів. З метаданими — все обраховується автоматично.
Бенчмарк на пристроях
# ADB: запустіть TFLite Benchmark Tool безпосередньо на пристрої
adb push model.tflite /data/local/tmp/
adb shell /data/local/tmp/benchmark_model \
--graph=/data/local/tmp/model.tflite \
--use_gpu=true \
--num_threads=4 \
--num_runs=50
# Вихід: середня затримка, min/max, час прогрівання
TFLite Benchmark Tool — офіційний Google інструмент, дає чесні цифри без JVM overhead та Android UI. Використовуйте для порівняння делегатів (GPU vs NNAPI vs CPU).
Процес
Вибір шляху конвертації → конвертація з потрібним рівнем квантизації → верифікація точності → додання метаданих → тестування на парку пристроїв через Benchmark Tool → інтеграція в Android додаток.
Орієнтири за часом
Прямої конвертації TF/Keras моделі з верифікацією — 3–7 днів. Конвертація через ONNX, користувацькі операції, додання метаданих, повне тестування — 2–4 тижні.







