Вышла версия 0.5 нашей библиотеки глубокого обучения KotlinDL.

Этот выпуск посвящен новому API для удобного и простого в использовании развертывания моделей ONNX на Android. Мы доработали препроцессинг DSL, осуществили поддержку ONNX Runtime Execution Providers и многое другое. Вот краткий обзор того, что ждет вас в этом релизе:

  1. Поддержка Android

  2. Препроцессинг DSL

  3. Вывод при аппаратном ускорении

  4. Вывод пользовательских моделей

GitHub - Kotlin/kotlindl: High-level Deep Learning Framework written in Kotlin and inspired by Keras [Kotlin/kotlindl: Фреймворк глубокого обучения высокого уровня, написанный на Kotlin и инспирированный Keras]

GitHub - Kotlin/kotlindl-app-sample: This repo demonstrates how to use KotlinDL for neural network inference on Android devices. [Kotlin/kotlindl-app-sample: в этом репозитории показано, как использовать KotlinDL для логического вывода нейронной сети на устройствах Android]

Поддержка Android

В KotlinDL 0.3 мы включили поддержку ONNX. ONNX — это форма представления моделей глубокого обучения с открытым исходным кодом, гибкой и расширяемой спецификацией, которая поддерживается множеством различных фреймворков. Она была разработана, чтобы быть быстрой, переносимой и совместимой с существующими инструментами, такими как TensorFlow или PyTorch. Благодаря данному релизу KotlinDL вы теперь можете запускать модели ONNX на устройствах Android, используя лаконичный Kotlin API!

Для запуска KotlinDL на Android наиболее удобным способом является загрузка модели через ModelHub. Вы можете легко инстанцировать модели, включенные в наш ModelHub, через API ONNXModels, и тогда сможете выполнить вывод на Bitmap или ImageProxy.

Примеры использования, поддерживаемые Android ONNX ModelHub, включают:

  • Обнаружение объектов

  • Классификация изображений

  • Определение позы

  • Обнаружение лица

  • Выравнивание лица

Модели из Android ONNX ModelHub, используемые в примере приложения
Модели из Android ONNX ModelHub, используемые в примере приложения

KotlinDL ONNX готов к использованию с API ImageAnalysis. Это позволяет, например, напрямую выводить модели, переданные через ModelHub, на объект ImageProxy.

class KotlinDLAnalyzer(
	context: Context,
	private val uiUpdateCallBack: (List<detectedobject>) -> Unit
) : ImageAnalysis.Analyzer {
	val hub = ONNXModelHub(context)
	val model = ONNXModels.ObjectDetection.EfficientDetLite0.pretrainedModel(hub)
	override fun analyze(image: ImageProxy) {
    	val detections = model.detectObjects(image, topK=5)
    	uiUpdateCallBack(detections)
	}
}

Вывод модели EfficientDetLite0 непосредственно на вход ImageProxy. Посмотрите демонстрационный пример здесь.

Обратите внимание, что ориентация полученного изображения с камеры будет скорректирована автоматически.

Препроцессинг DSL

При работе с изображениями часто необходимо выполнить определенные этапы предварительной обработки (препроцессинга), прежде чем передать их в модель. KotlinDL предоставляет удобную DSL для предварительной обработки, позволяющую легко применять различные преобразования к входному изображению. DSL основывается на концепции пайплайна, где каждое преобразование - это этап. Каждый этап описывается входным и выходным типом данных. Если выходной тип одного этапа совпадает с входным типом другого, их можно объединить в пайплайн. В этом случае проверка типов происходит во время компиляции.

Такой подход позволяет реализовать различные преобразования для BufferedImage на десктопе и Bitmap на Android, используя при этом одну DSL.

// Preprocessing pipeline for an Android bitmap       	// Preprocessing pipeline for a BufferedImage
val preprocessing = pipeline<bitmap>()                 	val preprocessing = pipeline<bufferedimage>()
	.resize {                                               	.resize {
    	outputHeight = 224                                      	outputHeight = 224
    	outputWidth = 224                                       	outputWidth = 224
	}                                                       	}
	.rotate { degrees = 90f }                               	.rotate { degrees = 90f }
	.toFloatArray { layout = TensorLayout.NCHW }            	.toFloatArray {}
	.transpose { axes = intArrayOf(2, 0, 1) }

Предварительная обработка Bitmap в сравнении с препроцессингом BufferedImage (параллельное сравнение бок о бок)

Обратите внимание, что DSL не ограничивается предварительной обработкой изображений. Вы можете использовать ее для реализации любого пайплайна предварительной обработки ваших данных.

Мы выполнили имплементацию следующего набора операций для Android Bitmap:

  • Изменение размера

  • Поворот

  • Кадрирование

  • Изменение масштаба

  • Нормализация

  • ConvertToFloatArray

Операция ConvertToFloatArray поддерживает две популярные схемы низкоуровневого представления тензора: ChannelFirst (TensorLayout.NCHW) и ChannelsLast (TensorLayout.NHWC).

Вывод при аппаратном ускорении

В KotlinDL 0.5 появилась возможность запускать модели на оптимизированном аппаратном обеспечении с помощью фреймворка ONNX Runtime Execution Providers (EP). Этот интерфейс обеспечивает гибкость при развертывании моделей ONNX в различных средах в облаке и на периферии и оптимизирует выполнение, используя вычислительные возможности платформы.

В настоящее время KotlinDL поддерживает следующие EP:

  • CPU (по умолчанию)

  • CUDA (для устройств с GPU и поддержкой CUDA)

  • NNAPI (для устройств Android с API 27+).

NNAPI — это фреймворк, который позволяет выполнять выводы на устройствах Android с использованием аппаратного ускорения. С помощью NNAPI ресурсоемкие вычисления могут выполняться в 9 раз быстрее, чем при использовании CPU на том же устройстве. Однако важно отметить, что NNAPI не может ускорить все модели. NNAPI поддерживает только подмножество операторов. Если модель содержит неподдерживаемые операторы, то рантайм снова переходит на использование CPU. Поэтому вы можете не получить никакого прироста производительности, и даже столкнуться с ее снижением из-за передачи данных между CPU и акселератором. ONNX runtime предоставляет инструмент для проверки того, может ли NNAPI ускорить вывод модели.

Одним из вариантов определения провайдеров выполнения (execution providers) для вывода является использование функции initializeWith.

val model = modelHub.loadModel(ONNXModels.CV.MobilenetV1())
model.initializeWith(NNAPI())

Загрузка и инициализация модели с помощью NNAPI execution provider

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

Вывод пользовательских моделей

KotlinDL ONNX ModelHub — это отличный способ начать работу с KotlinDL на Android. Однако если у вас есть собственная модель ONNX, вы можете легко использовать ее в KotlinDL.

Любая модель ONNX может быть загружена и выведена с помощью низкоуровневого API.

val modelBytes = resources.openRawResource(R.raw.model).readBytes()
val model = OnnxInferenceModel(modelBytes)
val preprocessing = pipeline<bitmap>()
	.resize {
    	outputHeight = 224
    	outputWidth = 224
	}
	.toFloatArray { layout = TensorLayout.NCHW }
	.call(InputType.TORCH.preprocessing(channelsLast = false))
val (tensor, shape) = preprocessing.apply(bitmap)
val logits = model.predictSoftly(tensor)
val labelId = logits.argmax()

Загрузка и вывод пользовательской модели с помощью API OnnxInferenceModel. Посмотрите демонстрацию здесь.

Как мы видим, можно инстанцировать OnnxInferenceModel из байтового представления файла модели. Файл модели может храниться в ресурсах приложения или быть получен по сети.

Дополнительные детали

Изменения в препроцессинге DSL

Начиная с версии 0.5, в KotlinDL появился новый синтаксис при описании пайплайнов для препроцессинга (preprocessing).

val preprocessing: Preprocessing = preprocess {       	val preprocessing = pipeline<bufferedimage>()
	transformImage {                                       	.resize {
    	resize {                                               	outputHeight = 224
        	outputHeight = 224                                 	outputWidth = 224
        	outputWidth = 224                              	}
        	interpolation = InterpolationType.BILINEAR         	.onResult { ImageIO.write(it, "jpg", File("image.jpg")) }
        	save {                                         	.convert { colorMode = ColorMode.BGR }
            	dirLocation = "image.jpg"                  	.toFloatArray { }
        	}                                              	.call(ResNet50().preprocessor)
    	}
    	convert { colorMode = ColorMode.BGR }
	}
	transformTensor {
    	sharpen {
        	modelTypePreprocessing = TFModels.CV.ResNet50()
    	}
	}
}

Старая DSL против новой (параллельное сравнение)

До версии 0.5 у DSL Preprocessing было несколько ограничений. DSL описывала пайплайн предварительной обработки с фиксированной структурой, а именно этап обработки BufferedImage (transformImage) и последующий этап обработки тензорного представления (transformTensor). В версии 0.5 мы изменили этот подход. Отныне Preprocessing DSL позволяет выстраивать пайплайн из произвольного набора операций.

Операция save больше не поддерживается, но вы можете использовать onResult в качестве альтернативы. Эта операция позволяет применить лямбду к результату предыдущего преобразования. Данный метод может быть полезен в процессе отладки.

Еще одной особенностью Preprocessing DSL в KotlinDL 0.5 является возможность повторного использования отдельных фрагментов пайплайна в разных местах с помощью функции call . Для многих моделей требуются входные данные с однотипной предварительной обработкой, и функция call позволяет сократить дублирование кода в таких сценариях.

Удобные функции для вывода с провайдерами выполнения

KotlinDL предоставляет удобные функции расширения для вывода моделей ONNX с использованием различных провайдеров исполнения:

  • inferUsing

  • inferAndCloseUsing .

Эти функции явно объявляют в своей области видимости EP (execution providers), которые будут использоваться для вывода. Хотя они имеют одну и ту же цель — явную инициализацию модели с заданными провайдерами исполнения, но ведут себя несколько по-разному. inferAndCloseUsing имеет семантику scope-функции use в Kotlin, что означает, что она закрывает модель в конце блока; в то время как inferUsing предназначена для многократного использования и имеет семантику scope-функции run в Kotlin.

Некоторые примеры вы можете найти здесь.

Реализация пользовательских операций препроцессинга

Если вам необходимо реализовать пользовательскую операцию препроцессирования, вы можете сделать это, воспользовавшись интерфейсом Operation (Операция) и соответствующими функциями расширения.

Посмотрите этот пример реализации пользовательских операций.

Новый API для парсинга композитного вывода

Большинство моделей в KotlinDL имеют один тензор на выходе. Однако некоторые модели имеют несколько выходов.

Например, модель SSD имеет три выходных тензора. В KotlinDL 0.5 мы вводим новый API для парсинга композитных выходов и добавляем кучу функций удобства по обработке OrtSession.Result, например:

  • getFloatArrayWithShape

  • get2DFloatArray

  • getByteArrayWithShape

  • getShortArray

Это позволяет писать более явный и читабельный код и уменьшает количество непроверенных преобразований.

Например, этот код используется для получения вывода модели SSD.


Сегодня вечером пройдет открытый урок «Простой чат с помощью Firebase и Kotlin».

На вебинаре научимся создавать firebase аккаунт. Разберемся, как работает Realtime Database, как обрабатываются запросы, транзакции и реализуем прослушивание данных. Подключим client-приложение к проекту и реализуем архитектуру простого чата.

В результате вебинара получим:

  • Базовое умение работы с Firebase,

  • Знание как работает realtime база данных,

  • Полностью написанный на Kotlin онлайн чат.

Записаться можно на странице онлайн-курса "Kotlin Backend Developer. Professional".

Комментарии (0)