欢迎参加我们将于 2022 年 10 月 18 日举办的 Firebase 峰会(线上线下同时进行),了解 Firebase 如何帮助您加快应用开发速度、满怀信心地发布应用并在之后需要时轻松地扩大应用规模。立即报名

Используйте модель TensorFlow Lite для логического вывода с помощью ML Kit на Android

Вы можете использовать ML Kit для выполнения логических выводов на устройстве с помощью модели TensorFlow Lite .

Для этого API требуется Android SDK уровня 16 (Jelly Bean) или новее.

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

  1. Если вы еще этого не сделали, добавьте Firebase в свой проект Android .
  2. Добавьте зависимости для библиотек ML Kit Android в файл Gradle вашего модуля (на уровне приложения) (обычно app/build.gradle ):
    apply plugin: 'com.android.application'
    apply plugin: 'com.google.gms.google-services'
    
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-model-interpreter:22.0.3'
    }
    
  3. Преобразуйте модель TensorFlow, которую вы хотите использовать, в формат TensorFlow Lite. См. TOCO: оптимизирующий преобразователь TensorFlow Lite .

Разместите или свяжите свою модель

Прежде чем вы сможете использовать модель TensorFlow Lite для вывода в своем приложении, вы должны сделать модель доступной для ML Kit. ML Kit может использовать модели TensorFlow Lite, размещенные удаленно с помощью Firebase, в комплекте с двоичным файлом приложения или и то, и другое.

Разместив модель в Firebase, вы можете обновить модель, не выпуская новую версию приложения, и вы можете использовать удаленную настройку и A/B-тестирование для динамического предоставления различных моделей различным группам пользователей.

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

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

Хост-модели в Firebase

Чтобы разместить модель TensorFlow Lite в Firebase:

  1. В разделе ML Kit консоли Firebase перейдите на вкладку « Пользовательский ».
  2. Нажмите « Добавить пользовательскую модель» (или «Добавить другую модель» ).
  3. Укажите имя, которое будет использоваться для идентификации вашей модели в проекте Firebase, затем загрузите файл модели TensorFlow Lite (обычно заканчивающийся на .tflite или .lite ).
  4. В манифесте вашего приложения объявите, что требуется разрешение INTERNET:
    <uses-permission android:name="android.permission.INTERNET" />
    

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

Объедините модели с приложением

Чтобы связать модель TensorFlow Lite с вашим приложением, скопируйте файл модели (обычно заканчивающийся на .tflite или .lite ) в папку assets/ вашего приложения. (Возможно, вам потребуется сначала создать папку, щелкнув правой кнопкой мыши app/ папку, а затем выбрав « Создать» > «Папка» > «Папка активов» .)

Затем добавьте следующее в файл build.gradle вашего приложения, чтобы убедиться, что Gradle не сжимает модели при сборке приложения:

android {

    // ...

    aaptOptions {
        noCompress "tflite"  // Your model's file extension: "tflite", "lite", etc.
    }
}

Файл модели будет включен в пакет приложения и доступен для ML Kit в качестве необработанного актива.

Загрузите модель

Чтобы использовать модель TensorFlow Lite в своем приложении, сначала настройте ML Kit, указав места, где доступна ваша модель: удаленно с помощью Firebase, в локальном хранилище или в обоих случаях. Если вы укажете и локальную, и удаленную модель, вы можете использовать удаленную модель, если она доступна, и вернуться к локально сохраненной модели, если удаленная модель недоступна.

Настройка модели, размещенной в Firebase

Если вы разместили свою модель в Firebase, создайте объект FirebaseCustomRemoteModel , указав имя, которое вы присвоили модели при ее загрузке:

Java

FirebaseCustomRemoteModel remoteModel =
        new FirebaseCustomRemoteModel.Builder("your_model").build();

Kotlin+KTX

val remoteModel = FirebaseCustomRemoteModel.Builder("your_model").build()

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

Java

FirebaseModelDownloadConditions conditions = new FirebaseModelDownloadConditions.Builder()
        .requireWifi()
        .build();
FirebaseModelManager.getInstance().download(remoteModel, conditions)
        .addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                // Success.
            }
        });

Kotlin+KTX

val conditions = FirebaseModelDownloadConditions.Builder()
    .requireWifi()
    .build()
FirebaseModelManager.getInstance().download(remoteModel, conditions)
    .addOnCompleteListener {
        // Success.
    }

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

Настроить локальную модель

Если вы связали модель со своим приложением, создайте объект FirebaseCustomLocalModel , указав имя файла модели TensorFlow Lite:

Java

FirebaseCustomLocalModel localModel = new FirebaseCustomLocalModel.Builder()
        .setAssetFilePath("your_model.tflite")
        .build();

Kotlin+KTX

val localModel = FirebaseCustomLocalModel.Builder()
    .setAssetFilePath("your_model.tflite")
    .build()

Создайте интерпретатор из вашей модели

После настройки источников модели создайте объект FirebaseModelInterpreter на основе одного из них.

Если у вас есть только локально связанная модель, просто создайте интерпретатор из вашего объекта FirebaseCustomLocalModel :

Java

FirebaseModelInterpreter interpreter;
try {
    FirebaseModelInterpreterOptions options =
            new FirebaseModelInterpreterOptions.Builder(localModel).build();
    interpreter = FirebaseModelInterpreter.getInstance(options);
} catch (FirebaseMLException e) {
    // ...
}

Kotlin+KTX

val options = FirebaseModelInterpreterOptions.Builder(localModel).build()
val interpreter = FirebaseModelInterpreter.getInstance(options)

Если у вас есть удаленно размещенная модель, вам нужно будет убедиться, что она была загружена, прежде чем запускать ее. Вы можете проверить статус задачи загрузки модели, используя метод isModelDownloaded() диспетчера моделей.

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

Java

FirebaseModelManager.getInstance().isModelDownloaded(remoteModel)
        .addOnSuccessListener(new OnSuccessListener<Boolean>() {
            @Override
            public void onSuccess(Boolean isDownloaded) {
                FirebaseModelInterpreterOptions options;
                if (isDownloaded) {
                    options = new FirebaseModelInterpreterOptions.Builder(remoteModel).build();
                } else {
                    options = new FirebaseModelInterpreterOptions.Builder(localModel).build();
                }
                FirebaseModelInterpreter interpreter = FirebaseModelInterpreter.getInstance(options);
                // ...
            }
        });

Kotlin+KTX

FirebaseModelManager.getInstance().isModelDownloaded(remoteModel)
    .addOnSuccessListener { isDownloaded -> 
    val options =
        if (isDownloaded) {
            FirebaseModelInterpreterOptions.Builder(remoteModel).build()
        } else {
            FirebaseModelInterpreterOptions.Builder(localModel).build()
        }
    val interpreter = FirebaseModelInterpreter.getInstance(options)
}

Если у вас есть только удаленно размещенная модель, вам следует отключить функции, связанные с моделью, например затенить или скрыть часть пользовательского интерфейса, пока вы не подтвердите загрузку модели. Вы можете сделать это, подключив прослушиватель к методу download() менеджера модели:

Java

FirebaseModelManager.getInstance().download(remoteModel, conditions)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void v) {
              // Download complete. Depending on your app, you could enable
              // the ML feature, or switch from the local model to the remote
              // model, etc.
            }
        });

Kotlin+KTX

FirebaseModelManager.getInstance().download(remoteModel, conditions)
    .addOnCompleteListener {
        // Download complete. Depending on your app, you could enable the ML
        // feature, or switch from the local model to the remote model, etc.
    }

Задайте вход и выход модели

Затем настройте входные и выходные форматы интерпретатора модели.

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

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

import tensorflow as tf

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

# Print input shape and type
print(interpreter.get_input_details()[0]['shape'])  # Example: [1 224 224 3]
print(interpreter.get_input_details()[0]['dtype'])  # Example: <class 'numpy.float32'>

# Print output shape and type
print(interpreter.get_output_details()[0]['shape'])  # Example: [1 1000]
print(interpreter.get_output_details()[0]['dtype'])  # Example: <class 'numpy.float32'>

После того, как вы определили формат ввода и вывода вашей модели, вы можете настроить интерпретатор модели вашего приложения, создав объект FirebaseModelInputOutputOptions .

Например, модель классификации изображений с плавающей запятой может принимать в качестве входных данных массив значений с float запятой N x 224x224x3, представляющий пакет трехканальных (RGB) изображений размером N 224x224, и создавать на выходе список из 1000 значений с float , каждое из которых представляет вероятность того, что изображение принадлежит к одной из 1000 категорий, предсказываемых моделью.

Для такой модели вы должны настроить ввод и вывод интерпретатора модели, как показано ниже:

Java

FirebaseModelInputOutputOptions inputOutputOptions =
        new FirebaseModelInputOutputOptions.Builder()
                .setInputFormat(0, FirebaseModelDataType.FLOAT32, new int[]{1, 224, 224, 3})
                .setOutputFormat(0, FirebaseModelDataType.FLOAT32, new int[]{1, 5})
                .build();

Kotlin+KTX

val inputOutputOptions = FirebaseModelInputOutputOptions.Builder()
        .setInputFormat(0, FirebaseModelDataType.FLOAT32, intArrayOf(1, 224, 224, 3))
        .setOutputFormat(0, FirebaseModelDataType.FLOAT32, intArrayOf(1, 5))
        .build()

Выполнить вывод по входным данным

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

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

Java

Bitmap bitmap = getYourInputImage();
bitmap = Bitmap.createScaledBitmap(bitmap, 224, 224, true);

int batchNum = 0;
float[][][][] input = new float[1][224][224][3];
for (int x = 0; x < 224; x++) {
    for (int y = 0; y < 224; y++) {
        int pixel = bitmap.getPixel(x, y);
        // Normalize channel values to [-1.0, 1.0]. This requirement varies by
        // model. For example, some models might require values to be normalized
        // to the range [0.0, 1.0] instead.
        input[batchNum][x][y][0] = (Color.red(pixel) - 127) / 128.0f;
        input[batchNum][x][y][1] = (Color.green(pixel) - 127) / 128.0f;
        input[batchNum][x][y][2] = (Color.blue(pixel) - 127) / 128.0f;
    }
}

Kotlin+KTX

val bitmap = Bitmap.createScaledBitmap(yourInputImage, 224, 224, true)

val batchNum = 0
val input = Array(1) { Array(224) { Array(224) { FloatArray(3) } } }
for (x in 0..223) {
    for (y in 0..223) {
        val pixel = bitmap.getPixel(x, y)
        // Normalize channel values to [-1.0, 1.0]. This requirement varies by
        // model. For example, some models might require values to be normalized
        // to the range [0.0, 1.0] instead.
        input[batchNum][x][y][0] = (Color.red(pixel) - 127) / 255.0f
        input[batchNum][x][y][1] = (Color.green(pixel) - 127) / 255.0f
        input[batchNum][x][y][2] = (Color.blue(pixel) - 127) / 255.0f
    }
}

Затем создайте объект FirebaseModelInputs с вашими входными данными и передайте его, а также спецификацию ввода и вывода модели в метод run интерпретатора модели :

Java

FirebaseModelInputs inputs = new FirebaseModelInputs.Builder()
        .add(input)  // add() as many input arrays as your model requires
        .build();
firebaseInterpreter.run(inputs, inputOutputOptions)
        .addOnSuccessListener(
                new OnSuccessListener<FirebaseModelOutputs>() {
                    @Override
                    public void onSuccess(FirebaseModelOutputs result) {
                        // ...
                    }
                })
        .addOnFailureListener(
                new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        // Task failed with an exception
                        // ...
                    }
                });

Kotlin+KTX

val inputs = FirebaseModelInputs.Builder()
        .add(input) // add() as many input arrays as your model requires
        .build()
firebaseInterpreter.run(inputs, inputOutputOptions)
        .addOnSuccessListener { result ->
            // ...
        }
        .addOnFailureListener { e ->
            // Task failed with an exception
            // ...
        }

Если вызов выполнен успешно, вы можете получить выходные данные, вызвав метод getOutput() объекта, который передается обработчику успеха. Например:

Java

float[][] output = result.getOutput(0);
float[] probabilities = output[0];

Kotlin+KTX

val output = result.getOutput<Array<FloatArray>>(0)
val probabilities = output[0]

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

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

Java

BufferedReader reader = new BufferedReader(
        new InputStreamReader(getAssets().open("retrained_labels.txt")));
for (int i = 0; i < probabilities.length; i++) {
    String label = reader.readLine();
    Log.i("MLKit", String.format("%s: %1.4f", label, probabilities[i]));
}

Kotlin+KTX

val reader = BufferedReader(
        InputStreamReader(assets.open("retrained_labels.txt")))
for (i in probabilities.indices) {
    val label = reader.readLine()
    Log.i("MLKit", String.format("%s: %1.4f", label, probabilities[i]))
}

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

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

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

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

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