Utilice un modelo de TensorFlow Lite para realizar inferencias con ML Kit en Android

Puede usar ML Kit para realizar inferencias en el dispositivo con un modelo de TensorFlow Lite .

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

Antes de que empieces

  1. Si aún no lo has hecho, agrega Firebase a tu proyecto de Android .
  2. Agregue las dependencias de las bibliotecas de Android ML Kit al archivo Gradle de su módulo (nivel de aplicación) (generalmente 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. Convierta el modelo de TensorFlow que desea usar al formato TensorFlow Lite. Consulte TOCO: Convertidor de optimización de TensorFlow Lite .

Aloja o agrupa tu modelo

Antes de poder usar un modelo de TensorFlow Lite para realizar inferencias en su aplicación, debe poner el modelo a disposición del kit de aprendizaje automático. ML Kit puede usar modelos de TensorFlow Lite alojados de forma remota mediante Firebase, incluidos con el binario de la aplicación o ambos.

Al alojar un modelo en Firebase, puedes actualizar el modelo sin lanzar una nueva versión de la aplicación, y puedes usar Remote Config y A/B Testing para servir dinámicamente diferentes modelos a diferentes conjuntos de usuarios.

Si elige proporcionar el modelo únicamente alojándolo en Firebase y no incluirlo con su aplicación, puede reducir el tamaño de descarga inicial de su aplicación. Sin embargo, tenga en cuenta que si el modelo no está incluido con su aplicación, cualquier funcionalidad relacionada con el modelo no estará disponible hasta que su aplicación descargue el modelo por primera vez.

Al combinar tu modelo con tu aplicación, puedes asegurarte de que las funciones de aprendizaje automático de tu aplicación sigan funcionando cuando el modelo alojado en Firebase no esté disponible.

Modelos de host en Firebase

Para alojar su modelo de TensorFlow Lite en Firebase:

  1. En la sección ML Kit de Firebase console , haz clic en la pestaña Personalizado .
  2. Haga clic en Agregar modelo personalizado (o Agregar otro modelo ).
  3. Especifica un nombre que se utilizará para identificar tu modelo en tu proyecto de Firebase, luego carga el archivo del modelo de TensorFlow Lite (que generalmente termina en .tflite o .lite ).
  4. En el manifiesto de su aplicación, declare que se requiere permiso de INTERNET:
    <uses-permission android:name="android.permission.INTERNET" />
    

Después de agregar un modelo personalizado a tu proyecto de Firebase, puedes hacer referencia al modelo en tus aplicaciones usando el nombre que especificaste. En cualquier momento, puede cargar un nuevo modelo de TensorFlow Lite y su aplicación descargará el nuevo modelo y comenzará a usarlo la próxima vez que se reinicie. Puede definir las condiciones del dispositivo requeridas para que su aplicación intente actualizar el modelo (ver más abajo).

Combinar modelos con una aplicación

Para agrupar su modelo de TensorFlow Lite con su aplicación, copie el archivo del modelo (que generalmente termina en .tflite o .lite ) a la carpeta assets/ de su aplicación. (Es posible que primero deba crear la carpeta haciendo clic derecho en la app/ carpeta y luego haciendo clic en Nuevo > Carpeta > Carpeta de activos ).

Luego, agregue lo siguiente al archivo build.gradle de su aplicación para asegurarse de que Gradle no comprima los modelos al compilar la aplicación:

android {

    // ...

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

El archivo del modelo se incluirá en el paquete de la aplicación y estará disponible para ML Kit como un recurso sin procesar.

Cargar el modelo

Para usar tu modelo de TensorFlow Lite en tu aplicación, primero configura ML Kit con las ubicaciones donde tu modelo está disponible: de forma remota usando Firebase, en almacenamiento local o ambos. Si especifica un modelo local y remoto, puede usar el modelo remoto si está disponible y recurrir al modelo almacenado localmente si el modelo remoto no está disponible.

Configurar un modelo alojado en Firebase

Si alojaste tu modelo con Firebase, crea un objeto FirebaseCustomRemoteModel , especificando el nombre que le asignaste al modelo cuando lo subiste:

Java

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

Kotlin+KTX

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

Luego, inicie la tarea de descarga del modelo, especificando las condiciones bajo las cuales desea permitir la descarga. Si el modelo no está en el dispositivo, o si hay una versión más reciente del modelo disponible, la tarea descargará el modelo de forma asincrónica desde 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.
    }

Muchas aplicaciones inician la tarea de descarga en su código de inicialización, pero puede hacerlo en cualquier momento antes de necesitar usar el modelo.

Configurar un modelo local

Si incluiste el modelo con tu aplicación, crea un objeto FirebaseCustomLocalModel y especifica el nombre de archivo del modelo de TensorFlow Lite:

Java

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

Kotlin+KTX

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

Crea un intérprete a partir de tu modelo.

Después de configurar las fuentes de tu modelo, crea un objeto FirebaseModelInterpreter a partir de una de ellas.

Si solo tiene un modelo empaquetado localmente, simplemente cree un intérprete a partir de su objeto 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)

Si tiene un modelo alojado de forma remota, deberá comprobar que se haya descargado antes de ejecutarlo. Puede verificar el estado de la tarea de descarga del modelo utilizando el método isModelDownloaded() del administrador de modelos.

Aunque solo tiene que confirmar esto antes de ejecutar el intérprete, si tiene un modelo alojado de forma remota y un modelo empaquetado localmente, podría tener sentido realizar esta verificación al crear una instancia del intérprete del modelo: cree un intérprete a partir del modelo remoto si se ha descargado y, en caso contrario, del modelo local.

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

Si solo tiene un modelo alojado de forma remota, debe desactivar la funcionalidad relacionada con el modelo (por ejemplo, atenuar u ocultar parte de su interfaz de usuario) hasta que confirme que el modelo se ha descargado. Puedes hacerlo adjuntando un oyente al método download() del administrador de modelos:

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

Especificar la entrada y salida del modelo.

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

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 ML Kit con el número y las dimensiones ("forma") de las matrices que utiliza tu modelo.

Si no conoce la forma y el tipo de datos de la entrada y salida de su modelo, puede usar el intérprete de Python de TensorFlow Lite para inspeccionar su 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'>

Una vez que hayas determinado el formato de entrada y salida de tu modelo, puedes configurar el intérprete de modelo de tu aplicación creando un objeto FirebaseModelInputOutputOptions .

Por ejemplo, un modelo de clasificación de imágenes de punto flotante podría tomar como entrada una matriz N x224x224x3 de valores float , que representa un lote de N imágenes de tres canales (RGB) de 224x224, y producir como salida una lista de 1000 valores float , cada uno de los cuales representa el probabilidad de que la imagen sea miembro de una de las 1000 categorías que predice el modelo.

Para dicho modelo, configuraría la entrada y salida del intérprete del 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+KTX

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

Realizar inferencias sobre datos de entrada.

Finalmente, para realizar inferencias usando el modelo, obtenga sus datos de entrada y realice cualquier transformación en los datos que sea necesaria para obtener una matriz de entrada con la forma correcta para su modelo.

Por ejemplo, si tiene un modelo de clasificación de imágenes con una forma de entrada de [1 224 224 3] valores de punto flotante, podría generar una matriz 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+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
    }
}

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 del 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+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
            // ...
        }

Si la llamada tiene éxito, puede obtener el resultado llamando al método getOutput() del objeto que se pasa al oyente exitoso. Por ejemplo:

Java

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

Kotlin+KTX

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

La forma de utilizar la salida depende del modelo que esté utilizando.

Por ejemplo, si está realizando una clasificación, como siguiente paso, puede 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+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]))
}

Apéndice: Seguridad del modelo

Independientemente de cómo haga que sus modelos de TensorFlow Lite estén disponibles para ML Kit, ML Kit los almacena en el formato protobuf serializado estándar en el almacenamiento local.

En teoría, esto significa que cualquiera puede copiar su modelo. Sin embargo, en la práctica, la mayoría de los modelos son tan específicos de la aplicación y están tan confusos por las optimizaciones que el riesgo es similar al de que los competidores desensamblen y reutilicen su código. Sin embargo, debes ser consciente de este riesgo antes de utilizar un modelo personalizado en tu aplicación.

En el nivel de API de Android 21 (Lollipop) y posteriores, el modelo se descarga en un directorio que está excluido de la copia de seguridad automática .

En el nivel de API de Android 20 y anteriores, el modelo se descarga en un directorio denominado com.google.firebase.ml.custom.models en el almacenamiento interno privado de la aplicación. Si habilitó la copia de seguridad de archivos usando BackupAgent , puede optar por excluir este directorio.