Используйте собственную модель TensorFlow Lite на Android.

Если ваше приложение использует пользовательские модели TensorFlow Lite , вы можете использовать Firebase ML для развертывания своих моделей. Развертывая модели с помощью Firebase, вы можете уменьшить первоначальный размер загрузки вашего приложения и обновить модели машинного обучения вашего приложения, не выпуская новую версию вашего приложения. А с помощью Remote Config и A/B-тестирования вы можете динамически предоставлять разные модели разным группам пользователей.

Модели TensorFlow Lite

Модели TensorFlow Lite — это модели машинного обучения, оптимизированные для работы на мобильных устройствах. Чтобы получить модель TensorFlow Lite:

Прежде чем вы начнете

  1. Если вы еще этого не сделали, добавьте Firebase в свой проект Android .
  2. В файле Gradle вашего модуля (на уровне приложения) (обычно <project>/<app-module>/build.gradle.kts или <project>/<app-module>/build.gradle ) добавьте зависимость для Firebase ML. библиотека загрузки моделей для Android. Мы рекомендуем использовать Firebase Android BoM для управления версиями библиотеки.

    Кроме того, в рамках настройки загрузчика моделей Firebase ML вам необходимо добавить в свое приложение TensorFlow Lite SDK.

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:32.8.0"))
    
        // Add the dependency for the Firebase ML model downloader library
        // When using the BoM, you don't specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-ml-modeldownloader")
    // Also add the dependency for the TensorFlow Lite library and specify its version implementation("org.tensorflow:tensorflow-lite:2.3.0")
    }

    Используя Firebase Android BoM , ваше приложение всегда будет использовать совместимые версии библиотек Firebase Android.

    (Альтернатива) Добавить зависимости библиотеки Firebase без использования BoM

    Если вы решите не использовать спецификацию Firebase, вы должны указать каждую версию библиотеки Firebase в ее строке зависимости.

    Обратите внимание: если вы используете в своем приложении несколько библиотек Firebase, мы настоятельно рекомендуем использовать BoM для управления версиями библиотек, что гарантирует совместимость всех версий.

    dependencies {
        // Add the dependency for the Firebase ML model downloader library
        // When NOT using the BoM, you must specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-ml-modeldownloader:24.2.3")
    // Also add the dependency for the TensorFlow Lite library and specify its version implementation("org.tensorflow:tensorflow-lite:2.3.0")
    }
    Ищете библиотечный модуль, специфичный для Kotlin? Начиная с октября 2023 года (Firebase BoM 32.5.0) от основного модуля библиотеки могут зависеть как разработчики Kotlin, так и Java (подробнее см. FAQ по этой инициативе ).
  3. В манифесте вашего приложения укажите, что требуется разрешение ИНТЕРНЕТА:
    <uses-permission android:name="android.permission.INTERNET" />

1. Разверните свою модель

Развертывайте свои собственные модели TensorFlow с помощью консоли Firebase или Firebase Admin Python и Node.js SDK. См. раздел Развертывание пользовательских моделей и управление ими .

После добавления пользовательской модели в проект Firebase вы можете ссылаться на нее в своих приложениях, используя указанное вами имя. В любой момент вы можете развернуть новую модель TensorFlow Lite и загрузить новую модель на устройства пользователей, вызвав getModel() (см. ниже).

2. Загрузите модель на устройство и инициализируйте интерпретатор TensorFlow Lite.

Чтобы использовать модель TensorFlow Lite в своем приложении, сначала используйте Firebase ML SDK, чтобы загрузить последнюю версию модели на устройство. Затем создайте экземпляр интерпретатора TensorFlow Lite с моделью.

Чтобы начать загрузку модели, вызовите метод getModel() загрузчика модели, указав имя, которое вы присвоили модели при ее загрузке, хотите ли вы всегда загружать самую последнюю модель и условия, при которых вы хотите разрешить загрузку.

Вы можете выбрать один из трех вариантов загрузки:

Тип загрузки Описание
ЛОКАЛ_МОДЕЛЬ Получите локальную модель с устройства. Если доступной локальной модели нет, это ведет себя как LATEST_MODEL . Используйте этот тип загрузки, если вы не заинтересованы в проверке обновлений модели. Например, вы используете Remote Config для получения названий моделей и всегда загружаете модели под новыми именами (рекомендуется).
LOCAL_MODEL_UPDATE_IN_BACKGROUND Получите локальную модель с устройства и начните обновлять модель в фоновом режиме. Если доступной локальной модели нет, это ведет себя как LATEST_MODEL .
ПОСЛЕДНЯЯ МОДЕЛЬ Приобретите последнюю модель. Если локальная модель является последней версией, возвращается локальная модель. В противном случае загрузите последнюю модель. Такое поведение будет блокироваться до тех пор, пока не будет загружена последняя версия (не рекомендуется). Используйте это поведение только в тех случаях, когда вам явно нужна последняя версия.

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

Kotlin+KTX

val conditions = CustomModelDownloadConditions.Builder()
        .requireWifi()  // Also possible: .requireCharging() and .requireDeviceIdle()
        .build()
FirebaseModelDownloader.getInstance()
        .getModel("your_model", DownloadType.LOCAL_MODEL_UPDATE_IN_BACKGROUND,
            conditions)
        .addOnSuccessListener { model: CustomModel? ->
            // Download complete. Depending on your app, you could enable the ML
            // feature, or switch from the local model to the remote model, etc.

            // The CustomModel object contains the local path of the model file,
            // which you can use to instantiate a TensorFlow Lite interpreter.
            val modelFile = model?.file
            if (modelFile != null) {
                interpreter = Interpreter(modelFile)
            }
        }

Java

CustomModelDownloadConditions conditions = new CustomModelDownloadConditions.Builder()
    .requireWifi()  // Also possible: .requireCharging() and .requireDeviceIdle()
    .build();
FirebaseModelDownloader.getInstance()
    .getModel("your_model", DownloadType.LOCAL_MODEL_UPDATE_IN_BACKGROUND, conditions)
    .addOnSuccessListener(new OnSuccessListener<CustomModel>() {
      @Override
      public void onSuccess(CustomModel model) {
        // Download complete. Depending on your app, you could enable the ML
        // feature, or switch from the local model to the remote model, etc.

        // The CustomModel object contains the local path of the model file,
        // which you can use to instantiate a TensorFlow Lite interpreter.
        File modelFile = model.getFile();
        if (modelFile != null) {
            interpreter = new Interpreter(modelFile);
        }
      }
    });

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

3. Выполните вывод по входным данным.

Получите входные и выходные формы вашей модели

Интерпретатор модели TensorFlow Lite принимает на входе и выдает на выходе один или несколько многомерных массивов. Эти массивы содержат значения byte , int , long или float . Прежде чем вы сможете передавать данные в модель или использовать ее результат, вы должны знать количество и размеры («форму») массивов, которые использует ваша модель.

Если вы построили модель самостоятельно или формат ввода и вывода модели задокументирован, возможно, эта информация у вас уже есть. Если вы не знаете форму и тип входных и выходных данных вашей модели, вы можете использовать интерпретатор TensorFlow Lite для проверки вашей модели. Например:

Питон

import tensorflow as tf

interpreter = tf.lite.Interpreter(model_path="your_model.tflite")
interpreter.allocate_tensors()

# Print input shape and type
inputs = interpreter.get_input_details()
print('{} input(s):'.format(len(inputs)))
for i in range(0, len(inputs)):
    print('{} {}'.format(inputs[i]['shape'], inputs[i]['dtype']))

# Print output shape and type
outputs = interpreter.get_output_details()
print('\n{} output(s):'.format(len(outputs)))
for i in range(0, len(outputs)):
    print('{} {}'.format(outputs[i]['shape'], outputs[i]['dtype']))

Пример вывода:

1 input(s):
[  1 224 224   3] <class 'numpy.float32'>

1 output(s):
[1 1000] <class 'numpy.float32'>

Запустите интерпретатор

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

Например, если у вас есть модель классификации изображений с входной формой значений с плавающей запятой [1 224 224 3] , вы можете сгенерировать входной ByteBuffer из объекта Bitmap , как показано в следующем примере:

Kotlin+KTX

val bitmap = Bitmap.createScaledBitmap(yourInputImage, 224, 224, true)
val input = ByteBuffer.allocateDirect(224*224*3*4).order(ByteOrder.nativeOrder())
for (y in 0 until 224) {
    for (x in 0 until 224) {
        val px = bitmap.getPixel(x, y)

        // Get channel values from the pixel value.
        val r = Color.red(px)
        val g = Color.green(px)
        val b = Color.blue(px)

        // Normalize channel values to [-1.0, 1.0]. This requirement depends on the model.
        // For example, some models might require values to be normalized to the range
        // [0.0, 1.0] instead.
        val rf = (r - 127) / 255f
        val gf = (g - 127) / 255f
        val bf = (b - 127) / 255f

        input.putFloat(rf)
        input.putFloat(gf)
        input.putFloat(bf)
    }
}

Java

Bitmap bitmap = Bitmap.createScaledBitmap(yourInputImage, 224, 224, true);
ByteBuffer input = ByteBuffer.allocateDirect(224 * 224 * 3 * 4).order(ByteOrder.nativeOrder());
for (int y = 0; y < 224; y++) {
    for (int x = 0; x < 224; x++) {
        int px = bitmap.getPixel(x, y);

        // Get channel values from the pixel value.
        int r = Color.red(px);
        int g = Color.green(px);
        int b = Color.blue(px);

        // Normalize channel values to [-1.0, 1.0]. This requirement depends
        // on the model. For example, some models might require values to be
        // normalized to the range [0.0, 1.0] instead.
        float rf = (r - 127) / 255.0f;
        float gf = (g - 127) / 255.0f;
        float bf = (b - 127) / 255.0f;

        input.putFloat(rf);
        input.putFloat(gf);
        input.putFloat(bf);
    }
}

Затем выделите ByteBuffer достаточно большой, чтобы содержать выходные данные модели, и передайте входной и выходной буфер методу run() интерпретатора TensorFlow Lite. Например, для выходной формы [1 1000] значений с плавающей запятой:

Kotlin+KTX

val bufferSize = 1000 * java.lang.Float.SIZE / java.lang.Byte.SIZE
val modelOutput = ByteBuffer.allocateDirect(bufferSize).order(ByteOrder.nativeOrder())
interpreter?.run(input, modelOutput)

Java

int bufferSize = 1000 * java.lang.Float.SIZE / java.lang.Byte.SIZE;
ByteBuffer modelOutput = ByteBuffer.allocateDirect(bufferSize).order(ByteOrder.nativeOrder());
interpreter.run(input, modelOutput);

Как вы используете выходные данные, зависит от используемой модели.

Например, если вы выполняете классификацию, в качестве следующего шага вы можете сопоставить индексы результата с метками, которые они представляют:

Kotlin+KTX

modelOutput.rewind()
val probabilities = modelOutput.asFloatBuffer()
try {
    val reader = BufferedReader(
            InputStreamReader(assets.open("custom_labels.txt")))
    for (i in probabilities.capacity()) {
        val label: String = reader.readLine()
        val probability = probabilities.get(i)
        println("$label: $probability")
    }
} catch (e: IOException) {
    // File not found?
}

Java

modelOutput.rewind();
FloatBuffer probabilities = modelOutput.asFloatBuffer();
try {
    BufferedReader reader = new BufferedReader(
            new InputStreamReader(getAssets().open("custom_labels.txt")));
    for (int i = 0; i < probabilities.capacity(); i++) {
        String label = reader.readLine();
        float probability = probabilities.get(i);
        Log.i(TAG, String.format("%s: %1.4f", label, probability));
    }
} catch (IOException e) {
    // File not found?
}

Приложение: Безопасность модели

Независимо от того, как вы делаете свои модели TensorFlow Lite доступными для Firebase ML, Firebase ML сохраняет их в стандартном сериализованном формате protobuf в локальном хранилище.

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

На уровне Android API 21 (Lollipop) и более поздних версиях модель загружается в каталог, исключенный из автоматического резервного копирования .

На уровне API Android 20 и более ранних версия модель загружается в каталог с именем com.google.firebase.ml.custom.models во внутреннем хранилище приложения. Если вы включили резервное копирование файлов с помощью BackupAgent , вы можете исключить этот каталог.