Usa un modelo de TensorFlow Lite para las 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 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 agregaste Firebase a tu app, sigue los pasos en la guía de introducción para hacerlo.
  2. Incluye las dependencias para el Kit de AA en el archivo build.gradle del nivel de tu app:
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-model-interpreter:17.0.3'
    }
    
  3. 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 Pruebas A/B 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: en la nube mediante Firebase, en el almacenamiento local, o en ambos. Si especificas tanto una fuente de modelo local como una de nube, el Kit de AA usará la fuente de nube cuando esté disponible y, si no, usará el modelo en el almacenamiento local.

Configura una fuente de modelo alojada en Firebase

Si alojaste tu modelo en Firebase, crea un objeto FirebaseCloudModelSource y especifica el nombre que asignaste al modelo cuando lo subiste, así como las condiciones en las que el Kit de AA debe descargar el modelo de forma inicial y cuando exista una actualización disponible.

Java
Android

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 FirebaseCloudModelSource object by specifying the name you assigned the model
// when you uploaded it in the Firebase console.
FirebaseCloudModelSource cloudSource = new FirebaseCloudModelSource.Builder("my_cloud_model")
        .enableModelUpdates(true)
        .setInitialDownloadConditions(conditions)
        .setUpdatesDownloadConditions(conditions)
        .build();
FirebaseModelManager.getInstance().registerCloudModelSource(cloudSource);

Kotlin
Android

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 FirebaseCloudModelSource object by specifying the name you assigned the model
// when you uploaded it in the Firebase console.
val cloudSource = FirebaseCloudModelSource.Builder("my_cloud_model")
        .enableModelUpdates(true)
        .setInitialDownloadConditions(conditions)
        .setUpdatesDownloadConditions(conditions)
        .build()
FirebaseModelManager.getInstance().registerCloudModelSource(cloudSource)

Cómo configurar una fuente de modelo local

Si empaquetaste el modelo con tu app, crea un objeto FirebaseLocalModelSource, especifica el nombre de archivo del modelo de TensorFlow Lite y asigna al modelo el nombre que usarás en el próximo paso.

Java
Android

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

Kotlin
Android

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

Crea un intérprete a partir de fuentes de modelo

Después de configurar las fuentes de modelo, crea un objeto FirebaseModelOptions con los nombres de la fuente de nube, la fuente local, o ambas opciones, y úsalo para obtener una instancia de FirebaseModelInterpreter.

Java
Android

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

Kotlin
Android

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

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 coma flotante puede tomar como entrada un arreglo Nx224x224x3 de valores float, que representa un lote de N imágenes de 224 x 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
Android

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
Android

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 una matriz de entrada a partir de un objeto Bitmap como se muestra en el siguiente ejemplo:

Java
Android

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
Android

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, de la siguiente manera:

Java
Android

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
Android

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 agente de escucha que detectó el resultado correcto para obtener la salida. Por ejemplo:

Java
Android

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

Kotlin
Android

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
Android

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
Android

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 del modelo

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 app, y ofuscados además por las optimizaciones, que el riesgo es comparable a que alguien de la competencia desensamble y vuelva a usar tu código. Con todo, 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 a través de BackupAgent, tienes la opción de excluir este directorio.

Enviar comentarios sobre...

Si necesitas ayuda, visita nuestra página de asistencia.