Ir a la consola

Usa un modelo de TensorFlow Lite para realizar inferencias con el Kit de AA en Android

Puedes usar el Kit de AA para realizar inferencias en el dispositivo con un modelo de TensorFlow Lite.

Esta API requiere un SDK de Android nivel 16 (Jelly Bean) o posterior.

Consulta la muestra de la guía de inicio rápido del Kit de AA en GitHub para ver un ejemplo de esta API en uso, o bien prueba el codelab.

Antes de comenzar

  1. Si aún no lo has hecho, agrega Firebase a tu proyecto de Android.
  2. En tu archivo build.gradle de nivel de proyecto, asegúrate de incluir el repositorio Maven de Google en las secciones buildscript y allprojects.
  3. Agrega las dependencias para las bibliotecas de Android del Kit de AA al archivo Gradle (generalmente app/build.gradle) de tu módulo (nivel de app):
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-model-interpreter:19.0.0'
    }
    
  4. Convierte el modelo de TensorFlow que deseas usar al formato de TensorFlow Lite. Consulta TOCO: Convertidor de optimización de TensorFlow Lite.

Aloja o empaqueta tu modelo

Si quieres usar un modelo de TensorFlow Lite para las inferencias en tu app, primero debes hacer que esté disponible para el Kit de AA. El Kit de AA puede usar modelos de TensorFlow Lite alojados de forma remota con Firebase, empaquetados con el objeto binario de la app o ambas opciones.

Al alojar un modelo en Firebase, puedes actualizarlo sin lanzar una nueva versión de la app, y puedes usar Remote Config y A/B Testing para entregar de forma dinámica diferentes modelos a distintos conjuntos de usuarios.

Si eliges proporcionar el modelo únicamente mediante el alojamiento con Firebase y no empaquetarlo con tu app, puedes reducir el tamaño de descarga inicial de la app. Sin embargo, ten en cuenta que si el modelo no se empaqueta con la app, las funcionalidades relacionadas con el modelo no estarán disponibles hasta que la app descargue el modelo por primera vez.

Si empaquetas el modelo con la app, puedes asegurarte de que las funciones de AA de tu app estén activas cuando el modelo alojado en Firebase no esté disponible.

Cómo alojar modelos en Firebase

Para alojar tu modelo de TensorFlow Lite en Firebase, sigue estos pasos:

  1. Haz clic en la pestaña Personalizado de la sección Kit de AA de Firebase console.
  2. Haz clic en Agregar modelo personalizado (o Agregar otro modelo).
  3. Ingresa el nombre que se usará para identificar el modelo en tu proyecto de Firebase. Luego, sube el archivo del modelo de TensorFlow Lite (generalmente, con la extensión .tflite o .lite).
  4. En el manifiesto de tu app, declara que se requiera el permiso de INTERNET:
    <uses-permission android:name="android.permission.INTERNET" />
    

Después de agregar un modelo personalizado al proyecto de Firebase, podrás usar el nombre que especificaste para hacer referencia al modelo en tus apps. Puedes subir un nuevo modelo de TensorFlow Lite en cualquier momento. Tu app descargará el nuevo modelo y comenzará a usarlo cuando se reinicie. Podrás definir las condiciones del dispositivo que tu app requiera para intentar actualizar el modelo (ver a continuación).

Cómo empaquetar modelos con una app

Para empaquetar el modelo de TensorFlow Lite con tu app, copia el archivo del modelo (generalmente, con la extensión .tflite o .lite) en la carpeta assets/ de la app. Es posible que primero debas crear la carpeta. Para ello, haz clic con el botón derecho en la carpeta app/ y, luego, en Nuevo > Carpeta > Carpeta de elementos.

Luego, agrega lo siguiente al archivo build.gradle de tu app para asegurarte de que Gradle no comprima los modelos al crear la app:

android {

    // ...

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

El archivo del modelo se incluirá en el paquete de la app y estará disponible para el Kit de AA como elemento sin procesar.

Carga el modelo

Para usar el modelo de TensorFlow Lite en tu app, primero configura el Kit de AA con las ubicaciones donde se encuentra disponible el modelo: de manera remota mediante Firebase, en el almacenamiento local, o en ambos. Si especificas un modelo local y uno remoto, el Kit de AA usará el modelo remoto si está disponible y, en caso de no estarlo, usará el modelo almacenado localmente.

Configura un modelo alojado en Firebase

Si alojaste tu modelo en Firebase, crea un objeto FirebaseRemoteModel, indica el nombre que le asignaste cuando lo subiste y especifica las condiciones en las que el Kit de AA debería descargarlo de forma inicial y cuando haya actualizaciones disponibles.

Java

FirebaseModelDownloadConditions.Builder conditionsBuilder =
        new FirebaseModelDownloadConditions.Builder().requireWifi();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    // Enable advanced conditions on Android Nougat and newer.
    conditionsBuilder = conditionsBuilder
            .requireCharging()
            .requireDeviceIdle();
}
FirebaseModelDownloadConditions conditions = conditionsBuilder.build();

// Build a remote model source object by specifying the name you assigned the model
// when you uploaded it in the Firebase console.
FirebaseRemoteModel cloudSource = new FirebaseRemoteModel.Builder("my_cloud_model")
        .enableModelUpdates(true)
        .setInitialDownloadConditions(conditions)
        .setUpdatesDownloadConditions(conditions)
        .build();
FirebaseModelManager.getInstance().registerRemoteModel(cloudSource);

Kotlin

var conditionsBuilder: FirebaseModelDownloadConditions.Builder =
        FirebaseModelDownloadConditions.Builder().requireWifi()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    // Enable advanced conditions on Android Nougat and newer.
    conditionsBuilder = conditionsBuilder
            .requireCharging()
            .requireDeviceIdle()
}
val conditions = conditionsBuilder.build()

// Build a remote model object by specifying the name you assigned the model
// when you uploaded it in the Firebase console.
val cloudSource = FirebaseRemoteModel.Builder("my_cloud_model")
        .enableModelUpdates(true)
        .setInitialDownloadConditions(conditions)
        .setUpdatesDownloadConditions(conditions)
        .build()
FirebaseModelManager.getInstance().registerRemoteModel(cloudSource)

Configura un modelo local

Si empaquetaste el modelo con la app, crea un objeto FirebaseLocalModel, especifica el nombre de archivo del modelo de TensorFlow Lite y asígnale un nombre que usarás en el siguiente paso.

Java

FirebaseLocalModel localSource =
        new FirebaseLocalModel.Builder("my_local_model")  // Assign a name to this model
                .setAssetFilePath("my_model.tflite")
                .build();
FirebaseModelManager.getInstance().registerLocalModel(localSource);

Kotlin

val localSource = FirebaseLocalModel.Builder("my_local_model") // Assign a name to this model
        .setAssetFilePath("my_model.tflite")
        .build()
FirebaseModelManager.getInstance().registerLocalModel(localSource)

Crea un intérprete a partir de tu modelo

Después de que configures las ubicaciones de tu modelo, crea un objeto FirebaseModelOptions con los nombres del modelo remoto, el modelo local o ambos, y úsalo para obtener una instancia de FirebaseModelInterpreter:

Java

FirebaseModelOptions options = new FirebaseModelOptions.Builder()
        .setRemoteModelName("my_cloud_model")
        .setLocalModelName("my_local_model")
        .build();
FirebaseModelInterpreter firebaseInterpreter =
        FirebaseModelInterpreter.getInstance(options);

Kotlin

val options = FirebaseModelOptions.Builder()
        .setRemoteModelName("my_cloud_model")
        .setLocalModelName("my_local_model")
        .build()
val interpreter = FirebaseModelInterpreter.getInstance(options)

Asegúrate de que el modelo esté disponible en el dispositivo

Recomendado: Si no configuraste un modelo empaquetado de manera local, asegúrate de que el modelo remoto se haya descargado en el dispositivo.

Si ejecutas un modelo alojado de manera remota y este aún no está disponible en el dispositivo, la llamada fallará y el modelo se descargará automáticamente en segundo plano en el dispositivo. Podrás ejecutar el modelo correctamente cuando la descarga se haya completado.

Llama a ensureModelDownloaded() para iniciar la tarea de descarga del modelo y revisar su estado, si quieres controlar esta tarea de manera más explícita:

Java

FirebaseModelManager.getInstance().ensureModelDownloaded(remoteModel)
        .addOnSuccessListener(
            new OnSuccessListener<Void>() {
              @Override
              public void onSuccess() {
                // Model downloaded successfully. Okay to use the model.
              }
            })
        .addOnFailureListener(
            new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception e) {
                // Model couldn’t be downloaded or other internal error.
                // ...
              }
            });

Kotlin

FirebaseModelManager.getInstance().ensureModelDownloaded(remoteModel)
        .addOnSuccessListener {
            // Model downloaded successfully. Okay to use the model.
        }
        .addOnFailureListener {
            // Model couldn’t be downloaded or other internal error.
            // ...
        }

Si es necesario, el método ensureModelDownloaded() comenzará a descargar el modelo y llamará al objeto de escucha que detecta el resultado correcto cuando se complete la descarga. Si el modelo ya está disponible, el método llamará al objeto de escucha que detecta el resultado correcto inmediatamente.

Especifica la entrada y salida del modelo

A continuación, configura los formatos de entrada y salida del intérprete de modelo.

Un modelo de TensorFlow Lite toma como entrada y produce como salida una o más matrices multidimensionales. Estas matrices contienen valores byte, int, long o float. Debes configurar el Kit de AA con la cantidad y las dimensiones ("shape") de las matrices que usa tu modelo.

Si no conoces la forma y el tipo de datos de la entrada y la salida del modelo, puedes usar el intérprete Python de TensorFlow Lite para inspeccionar tu modelo. Por ejemplo:

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'>

Después de determinar el formato de entrada y salida del modelo, puedes crear un objeto FirebaseModelInputOutputOptions para configurar el intérprete de modelo de tu app.

Por ejemplo, un modelo de clasificación de imágenes de punto flotante puede tomar como entrada un arreglo N × 224 × 224 × 3 de valores float, que representa un lote de N imágenes de 224 × 224 de tres canales (RGB), y puede producir como salida una lista de 1,000 valores float, cada uno de los cuales representa la probabilidad de que la imagen pertenezca a una de las 1,000 categorías que predice el modelo.

Para un modelo de ese tipo, debes configurar la entrada y la salida del intérprete de modelo como se muestra a continuación:

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

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

Realiza inferencias sobre los datos de entrada

Por último, para realizar inferencias con el modelo, obtén tus datos de entrada y realiza todas las transformaciones en los datos que sean necesarias para obtener una matriz de entrada con la forma correcta para tu modelo.

Por ejemplo, si tienes un modelo de clasificación de imágenes con una forma de entrada de valores de punto flotante [1 224 224 3], puedes generar un arreglo de entrada a partir de un objeto Bitmap como se muestra en el siguiente ejemplo:

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

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
    }
}

Luego, crea un objeto FirebaseModelInputs con tus datos de entrada y pásalo junto con la especificación de entrada y salida del modelo al método run del intérprete de modelo:

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

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

Si la llamada se ejecuta correctamente, puedes llamar al método getOutput() del objeto transferido al objeto de escucha que detectó el resultado correcto para obtener la salida. Por ejemplo:

Java

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

Kotlin

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

La manera de usar el resultado depende del modelo que uses.

Por ejemplo, si realizas una clasificación, el paso siguiente puede ser asignar los índices del resultado a las etiquetas que representan:

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

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]))
}

Apéndice: Seguridad de los modelos

Sin importar cómo hagas que estén disponibles tus modelos de TensorFlow Lite para el Kit de AA, este los almacena en el formato estándar de protobuf serializado en el almacenamiento local.

En teoría, eso significa que cualquier persona puede copiar tu modelo. Sin embargo, en la práctica, la mayoría de los modelos son tan específicos para la aplicación, y ofuscados por las optimizaciones, que el riesgo es comparable a que alguien de la competencia desensamble y vuelva a usar tu código. No obstante, debes estar al tanto de ese riesgo antes de usar un modelo personalizado en tu app.

En la API de Android nivel 21 (Lollipop) o posterior, el modelo se descarga en un directorio excluido de las copias de seguridad automáticas.

En una API de Android nivel 20 o anterior, el modelo se descarga en un directorio llamado com.google.firebase.ml.custom.models en el almacenamiento interno privado de la app. Si habilitas la copia de seguridad con BackupAgent, tienes la opción de excluir este directorio.