Я работал над проектом по обнаружению болезней растений в рамках своих исследований в области периферийного ИИ, над проектом, где модель должна работать на Android-смартфоне фермера в деревне без интернета. Довольно быстро я столкнулся с проблемой, с которой рано или поздно сталкивается каждый специалист по машинному обучению.
Модель точная, но слишком большая и медленная для реального мира.
Обученная мной модель EfficientNetB0 достигла точности 96,33% на наборе данных PlantVillage с 15 классами. Отличный результат. Но модель весила около 16 МБ в формате float32. Для Android-смартфона среднего класса с нестабильным 4G-соединением и ограниченным объемом памяти это не идеально.
В этой статье я расскажу о том, что я сделал дальше. Я пропустил модель через три конвейера квантизации: FP16, INT8 и экспорт ONNX, измерил изменения точности, размера и задержки, а затем развернул лучший формат в Android-приложении, работающем полностью в автономном режиме.
Главный результат: квантизация INT8 уменьшила размер модели с 16 МБ до 5,25 МБ — снижение на 67% при потере всего 0,62% точности. Это тот компромисс, который делает реальное развертывание жизнеспособным.
Проблема: почему важны 16 МБ?
PlantVillage — это эталонный набор данных. Реальный мир не является эталоном.
В сельскохозяйственном контексте, на который я ориентировался, — мелкие фермеры в районах с нестабильной связью, — важны несколько факторов, которые не отображаются в метрике точности Colab:
- Размер загружаемого приложения. При использовании 2G или тарифицируемых данных каждый МБ стоит денег и времени.
- Время загрузки модели. Более крупные модели требуют больше времени для инициализации при первом запуске приложения.
- Задержка инференса. Более крупные модели означают больше вычислений на каждый прямой проход.
- Требование автономной работы. Модель должна работать без каких-либо обращений к серверу.
Ничего из этого не решается высоким показателем F1. Вам необходимо сжатие модели. Существует несколько подходов: обрезка, дистилляция знаний и квантизация. Я выбрал постобучающую квантизацию (post-training quantization, PTQ), потому что она не требует переобучения и работает непосредственно с обученной сохраненной моделью.
Базовый уровень: EfficientNetB0 в полном режиме FP32
Прежде чем что-либо сжимать, мне нужен был надежный базовый уровень для сжатия. Я обучил EfficientNetB0 с использованием весов ImageNet на подмножестве PlantVillage из 15 классов (болезни перца, картофеля и томатов), используя двухэтапную стратегию, третий этап которой — разработка Android-приложения:
- Этап A (15 эпох): замороженная базовая модель, обучен только классификационный модуль. Скорость обучения 1e-3.
- Этап B (30 эпох): верхние слои базовой модели разморожены, дообучены с шагом 1e-4, затем скорость обучения уменьшается.
Разделение: 70% обучающая выборка — 15% валидационная выборка — 15% тестовая выборка, стратифицированная по классам, фиксированное случайное начальное значение 42 для полной воспроизводимости. Весовые коэффициенты классов компенсировали 21-кратный дисбаланс между картофель/здоровый (152 изображения) и томаты/вирус желтой курчавости листьев (3209 изображений).
Результаты финального тестового набора после этапа А:
- Точность Top-1: 96,33%
- Точность Top-3: 99,77%
- Размер модели: ~16 МБ (формат SavedModel)
Что такое квантование?
Обученная нейронная сеть хранит свои веса в виде 32-битных чисел с плавающей запятой (FP32). Каждый вес занимает 4 байта памяти. Таким образом, модель с 4 миллионами параметров занимает примерно 16 МБ.
Квантование снижает точность этих чисел:
- FP16 (половинная точность): веса хранятся как 16-битные числа с плавающей запятой. 2 байта на вес. Уменьшение размера в 2 раза при минимальном влиянии на точность.
- INT8 (8-битное целое число): веса отображаются на целые числа в диапазоне [-128, 127]. 1 байт на вес. Уменьшение размера в 4 раза, небольшое влияние на точность.
Сложность с INT8 заключается в том, что необходимо указать квантизатору, в какой диапазон значений попадают ваши активации (а не только веса) во время реального инференса. Это называется калибровкой. Вы пропускаете через модель небольшую репрезентативную выборку ваших реальных данных, конвертер записывает диапазоны активаций и использует их для установки параметров квантизации.
В качестве калибровочного набора данных я использовал 15 изображений на класс (всего 225) из обучающего набора. Этого достаточно, чтобы охватить распределение без больших затрат.
Три конвейера преобразования
FP16 — беспроигрышный вариант
FP16 — это сжатие с наименьшим риском. Вы уменьшаете битовую ширину весов вдвое, но остаетесь в формате с плавающей запятой. Современные мобильные графические процессоры обрабатывают FP16 нативно, поэтому вы часто получаете бесплатное повышение скорости.
INT8 — агрессивное сжатие
Именно в INT8 происходит реальная экономия места. Конвертеру необходим репрезентативный набор данных для калибровки диапазонов активации. Я сохранил входные и выходные типы как float32 для упрощения интеграции, внутренние вычисления выполняются в INT8, но приложению не нужно обрабатывать целочисленные тензоры на границе API.
ONNX — кроссплатформенный экспорт
ONNX (Open Neural Network Exchange) — это формат, позволяющий запускать модели с различными средами выполнения, такими как ONNX Runtime, TensorRT, инструменты преобразования CoreML и другие. Он полезен, если вы хотите, чтобы одна и та же модель работала на устройствах iOS, Windows или NVIDIA Jetson без переобучения.
Результаты
Я оценил все четыре варианта модели на одном и том же тестовом наборе (3104 изображения, никогда не использовавшиеся во время обучения или калибровки). Задержка измерялась на серверном процессоре с использованием 4 потоков — реальные показатели для устройств Android могут отличаться.
FP16 и ONNX немного превзошли базовый показатель FP32 (+0,28 п.п.). Это известный эффект, при котором оптимизированная среда выполнения TFLite обрабатывает пограничные предсказания иначе, чем обучающий граф TensorFlow. Это реальная зарегистрированная ошибка, а не погрешность измерения.
Больше всего меня волнует показатель INT8: потеря точности на 0,62 процентных пункта при уменьшении размера на 67%. В литературе обычно отмечается снижение точности на 1–3 процентных пункта при использовании INT8. Значение 0,62 процентных пункта говорит о том, что калибровочный набор данных был репрезентативным, а активации модели были хорошо распределены — здесь помогают слои BatchNorm в EfficientNet.
Разбор по классам: где именно INT8 навредил?
Общая точность показывает одно число. Анализ по классам показывает, какие фермеры получат худший результат. Я провел полный отчет по классификации для модели INT8:
Единственной реальной потерей стала категория «картофель/здоровый», где снижение составило 12,8 пунктов F1. Но посмотрите на столбец поддержки: 23 тестовых изображения. В таком масштабе две или три дополнительные ошибки классификации резко меняют F1. Это проблема нехватки данных, а не проблема квантования. Все остальные классы оставались в пределах 2 pp.
Наиболее интересный результат: класс «томаты/ранняя фитофтора» — самый сложный класс в FP32 с F1=0,921 — оставался полностью стабильным после квантизации INT8. Способность модели обрабатывать самые сложные случаи нисколько не ухудшилась.
Развертывание на Android, полностью оффлайн: третий и последний этап
Модель INT8 TFLite (5,25 МБ) — это то, что используется в приложении для Android.
Приложение выполняет одну задачу: сделать фотографию, выполнить инференс на устройстве, показать результат. Нет сервера. Нет интернета. Нет ключа API. Файл модели находится в папке assets приложения.
В качестве стека используется CameraX для доступа к камере и библиотека TFLite Android для инференса. Я включил делегат NNAPI, который направляет вычисления на нейронный процессор телефона, когда он доступен — на устройствах, таких как процессоры Snapdragon среднего уровня, это дает существенное ускорение по сравнению с генерацией на ЦП.
Чему я на самом деле научился
Качество калибровки INT8 важнее количества калибровочных изображений
Изначально я пытался калибровать всего с 5 изображениями на класс и получил падение точности на 2,1 pp. Увеличение до 15 изображений на класс снизило точность до 0,62 pp. Изображения должны охватывать распределение каждого класса — вариации освещения, разные стадии заболевания, разные фоны. Чем больше разнообразие, тем важнее количество.
FP16 — почти всегда правильный первый шаг
Если вы не уверены, повредит ли квантизация вашей модели, сначала попробуйте FP16. Это практически без потерь (я фактически получил 0,28 pp), но вы получаете двукратное уменьшение размера бесплатно, и это занимает 30 секунд. Переходите к INT8 только в том случае, если вам нужна дополнительная компрессия.
ONNX предназначен для портативности, а не для мобильной производительности
В моих тестах время ONNX Runtime на ЦП совпало с задержкой INT8 TFLite (22,9 мс для каждого). Но ONNX не предоставляет доступ к мобильным NPU так, как это делает делегат NNAPI в TFLite. Для развертывания на Android TFLite — лучший выбор. ONNX полезен, если вы хотите, чтобы одна и та же модель работала на устройстве Jetson или на ноутбуке с Windows в качестве узла туманных вычислений.
Разрыв между обучающей и валидационной выборками во время дообучения — это нормально
Когда я впервые разморозил основную часть модели, точность на валидационной выборке начала сильно колебаться: она подскакивала с 40% до 48%, а затем снова падала. Я запаниковал и решил, что модель сломалась. Но проблема была не в ней — скорость обучения оказалась слишком высокой для дообучения. После снижения learning rate с 1e-4 до 2e-6 всё стабилизировалось. Вывод: fine-tuning гораздо чувствительнее к скорости обучения, чем обучение модели с нуля.
Что дальше: федеративное обучение на уровне туманных вычислений
Этот проект — фаза 1 и 2 более крупной ИИ-системы на периферии сети. Архитектура состоит из трех уровней:
- Периферия (смартфоны): автономный вывод с использованием модели INT8 TFLite.
- Туманные вычисления (локальный центр — Raspberry Pi или полевой ноутбук): агрегирует обновления модели с нескольких телефонов с помощью федеративного обучения. Исходные изображения никогда не покидают устройство.
- Облако (опционально): глобальное версионирование модели и долгосрочная аналитика при наличии подключения.
Уровень федеративного обучения делает этот проект не просто проектом развертывания, а исследовательским. Используя Flower (flwr), каждый телефон может дорабатывать модель на локально полученных изображениях и отправлять агрегатору туманных вычислений только обновления градиента, а не сами изображения. Это обеспечивает конфиденциальность данных фермеров, позволяя при этом модели улучшаться со временем на всех устройствах.
Работа по квантизации, о которой вы только что прочитали, делает федеративное обучение осуществимым на оборудовании с ограниченными ресурсами. Модель размером 16 МБ, которую необходимо передавать и загружать на каждом этапе агрегации, обходится дорого. Модель размером 5,25 МБ с эквивалентной точностью является практичной.
Важные цифры
Вкратце, основные результаты в одном месте:
- Базовая модель FP32: точность 96,33%, 16 МБ
- INT8 TFLite: точность 95,71%, 5,25 МБ, средняя задержка на ЦП 22,9 мс
- Сокращение размера: 67%
- Потеря точности: 0,62 процентных пункта
- Развертывание: полностью автономное приложение для Android, сервер не требуется
Для системы, ориентированной на фермеров в регионах с низкой доступностью связи, этот компромисс абсолютно оправдан. И поскольку модель работает на устройстве, она работает одинаково, независимо от того, находитесь ли вы в поле в Пенджабе или в городе с 5G.
Полный код всего конвейера — обучение, квантизация, приложение для Android — находится на GitHub. Если вы работаете над периферийным ИИ для сельского хозяйства или пытаетесь развернуть модели TFLite на Android, я надеюсь, это сэкономит вам несколько часов отладки, которые я потратил на скрытые ошибки предварительной обработки и колебания скорости обучения.

