Utiliser un modèle TensorFlow Lite pour effectuer des inférences avec ML Kit sur Android

Vous pouvez utiliser ML Kit pour effectuer des inférences sur l'appareil avec un modèle TensorFlow Lite.

Cette API nécessite le SDK Android de niveau 16 (Jelly Bean) ou version ultérieure.

Avant de commencer

  1. Si ce n'est pas encore fait, ajoutez Firebase à votre projet Android.
  2. Ajoutez les dépendances des bibliothèques Android ML Kit au fichier Gradle de votre module (au niveau de l'application) (généralement 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. Convertissez le modèle TensorFlow que vous souhaitez utiliser au format TensorFlow Lite. Consultez TOCO: Convertisseur d'optimisation TensorFlow Lite.

Héberger ou regrouper votre modèle

Avant de pouvoir utiliser un modèle TensorFlow Lite pour l'inférence dans votre application, vous devez le mettre à la disposition de ML Kit. ML Kit peut utiliser des modèles TensorFlow Lite hébergés à distance à l'aide de Firebase, groupés avec le binaire de l'application ou les deux.

En hébergeant un modèle sur Firebase, vous pouvez le mettre à jour sans publier de nouvelle version de l'application. Vous pouvez également utiliser Remote Config et A/B Testing pour diffuser dynamiquement différents modèles à différents ensembles d'utilisateurs.

Si vous choisissez de ne fournir le modèle qu'en l'hébergeant avec Firebase et de ne pas le regrouper avec votre application, vous pouvez réduire la taille de téléchargement initiale de votre application. Gardez à l'esprit, cependant, que si le modèle n'est pas regroupé avec votre application, aucune fonctionnalité liée au modèle ne sera disponible tant que votre application ne l'aura pas téléchargé pour la première fois.

En regroupant votre modèle avec votre application, vous pouvez vous assurer que les fonctionnalités ML de votre application continuent de fonctionner lorsque le modèle hébergé par Firebase n'est pas disponible.

Héberger des modèles sur Firebase

Pour héberger votre modèle TensorFlow Lite sur Firebase:

  1. Dans la section Kit ML de la console Firebase, cliquez sur l'onglet Personnalisé.
  2. Cliquez sur Ajouter un modèle personnalisé (ou Ajouter un autre modèle).
  3. Spécifiez un nom qui servira à identifier votre modèle dans votre projet Firebase, puis importez le fichier de modèle TensorFlow Lite (qui se termine généralement par .tflite ou .lite).
  4. Dans le fichier manifeste de votre application, déclarez que l'autorisation INTERNET est requise:
    <uses-permission android:name="android.permission.INTERNET" />

Une fois que vous avez ajouté un modèle personnalisé à votre projet Firebase, vous pouvez le référencer dans vos applications à l'aide du nom que vous avez spécifié. Vous pouvez importer un nouveau modèle TensorFlow Lite à tout moment. Votre application le téléchargera et commencera à l'utiliser lors du prochain redémarrage. Vous pouvez définir les conditions requises pour que votre application tente de mettre à jour le modèle (voir ci-dessous).

Grouper des modèles avec une application

Pour regrouper votre modèle TensorFlow Lite avec votre application, copiez le fichier de modèle (qui se termine généralement par .tflite ou .lite) dans le dossier assets/ de votre application. (Vous devrez peut-être d'abord créer le dossier en effectuant un clic droit sur le dossier app/, puis en cliquant sur New > Folder > Assets Folder (Nouveau > Dossier > Dossier d'éléments).)

Ajoutez ensuite les éléments suivants au fichier build.gradle de votre application pour vous assurer que Gradle ne compresse pas les modèles lors de la compilation de l'application:

android {

    // ...

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

Le fichier de modèle sera inclus dans le package de l'application et disponible pour ML Kit en tant qu'élément brut.

Charger le modèle

Pour utiliser votre modèle TensorFlow Lite dans votre application, configurez d'abord ML Kit avec les emplacements où votre modèle est disponible: à distance à l'aide de Firebase, dans l'espace de stockage local ou les deux. Si vous spécifiez à la fois un modèle local et un modèle distant, vous pouvez utiliser le modèle distant s'il est disponible et utiliser le modèle stocké localement s'il ne l'est pas.

Configurer un modèle hébergé par Firebase

Si vous avez hébergé votre modèle avec Firebase, créez un objet FirebaseCustomRemoteModel en spécifiant le nom que vous avez attribué au modèle lorsque vous l'avez importé:

Java

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

Kotlin

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

Lancez ensuite la tâche de téléchargement du modèle, en spécifiant les conditions dans lesquelles vous souhaitez autoriser le téléchargement. Si le modèle n'est pas sur l'appareil ou si une version plus récente du modèle est disponible, la tâche télécharge le modèle de manière asynchrone depuis 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

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

De nombreuses applications démarrent la tâche de téléchargement dans leur code d'initialisation, mais vous pouvez le faire à tout moment avant d'avoir besoin d'utiliser le modèle.

Configurer un modèle local

Si vous avez groupé le modèle avec votre application, créez un objet FirebaseCustomLocalModel en spécifiant le nom de fichier du modèle TensorFlow Lite:

Java

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

Kotlin

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

Créer un interpréteur à partir de votre modèle

Après avoir configuré vos sources de modèle, créez un objet FirebaseModelInterpreter à partir de l'une d'elles.

Si vous ne disposez que d'un modèle groupé localement, créez simplement un interpréteur à partir de votre objet FirebaseCustomLocalModel:

Java

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

Kotlin

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

Si vous disposez d'un modèle hébergé à distance, vous devez vérifier qu'il a été téléchargé avant de l'exécuter. Vous pouvez vérifier l'état de la tâche de téléchargement du modèle à l'aide de la méthode isModelDownloaded() du gestionnaire de modèles.

Bien que vous ne deviez le confirmer qu'avant d'exécuter l'interprète, si vous disposez à la fois d'un modèle hébergé à distance et d'un modèle groupé localement, il peut être judicieux d'effectuer cette vérification lors de l'instanciation de l'interprète du modèle: créez un interprète à partir du modèle distant s'il a été téléchargé, et à partir du modèle local dans le cas contraire.

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

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 vous ne disposez que d'un modèle hébergé à distance, vous devez désactiver les fonctionnalités liées au modèle (par exemple, griser ou masquer une partie de votre UI) jusqu'à ce que vous confirmiez que le modèle a été téléchargé. Pour ce faire, joignez un écouteur à la méthode download() du gestionnaire de modèles:

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

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

Spécifier l'entrée et la sortie du modèle

Ensuite, configurez les formats d'entrée et de sortie de l'interprète de modèle.

Un modèle TensorFlow Lite utilise un ou plusieurs tableaux multidimensionnels en entrée et produit un ou plusieurs tableaux multidimensionnels en sortie. Ces tableaux contiennent des valeurs byte, int, long ou float. Vous devez configurer ML Kit avec le nombre et les dimensions ("forme") des tableaux utilisés par votre modèle.

Si vous ne connaissez pas la forme et le type de données des entrées et des sorties de votre modèle, vous pouvez utiliser l'interprète Python TensorFlow Lite pour inspecter votre modèle. Exemple :

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

Une fois que vous avez déterminé le format d'entrée et de sortie de votre modèle, vous pouvez configurer l'interprète de modèle de votre application en créant un objet FirebaseModelInputOutputOptions.

Par exemple, un modèle de classification d'images à virgule flottante peut prendre en entrée un tableau Nx224x224x3 de valeurs float, représentant un lot d'N images tricanaux (RVB) de 224 x 224, et produire en sortie une liste de 1 000 valeurs float, chacune représentant la probabilité que l'image appartienne à l'une des 1 000 catégories que le modèle prédit.

Pour un tel modèle, vous devez configurer l'entrée et la sortie de l'interprète de modèle comme indiqué ci-dessous:

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

Effectuer des inférences sur les données d'entrée

Enfin, pour effectuer une inférence à l'aide du modèle, obtenez vos données d'entrée et effectuez toutes les transformations nécessaires pour obtenir un tableau d'entrée de la forme appropriée pour votre modèle.

Par exemple, si vous disposez d'un modèle de classification d'images avec une forme d'entrée de valeurs à virgule flottante [1 224 224 3], vous pouvez générer un tableau d'entrée à partir d'un objet Bitmap, comme illustré dans l'exemple suivant:

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

Ensuite, créez un objet FirebaseModelInputs avec vos données d'entrée, puis transmettez-le ainsi que les spécifications d'entrée et de sortie du modèle à la méthode run de l'interprète de modèle:

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 { e ->
            // Task failed with an exception
            // ...
        }

Si l'appel aboutit, vous pouvez obtenir la sortie en appelant la méthode getOutput() de l'objet transmis à l'écouteur de succès. Exemple :

Java

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

Kotlin

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

La manière dont vous utilisez la sortie dépend du modèle que vous utilisez.

Par exemple, si vous effectuez une classification, vous pouvez ensuite mapper les indices du résultat sur les libellés qu'ils représentent:

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

Annexe: Sécurité du modèle

Quelle que soit la manière dont vous mettez vos modèles TensorFlow Lite à la disposition de ML Kit, ML Kit les stocke au format protobuf sérialisé standard dans l'espace de stockage local.

En théorie, cela signifie que n'importe qui peut copier votre modèle. Toutefois, en pratique, la plupart des modèles sont tellement spécifiques à l'application et masqués par des optimisations que le risque est semblable à celui de vos concurrents qui désassemblent et réutilisent votre code. Toutefois, vous devez être conscient de ce risque avant d'utiliser un modèle personnalisé dans votre application.

Sous Android API 21 (Lollipop) ou version ultérieure, le modèle est téléchargé dans un répertoire exclu de la sauvegarde automatique.

Sous Android API 20 ou version antérieure, le modèle est téléchargé dans un répertoire nommé com.google.firebase.ml.custom.models dans l'espace de stockage interne privé de l'application. Si vous avez activé la sauvegarde de fichiers à l'aide de BackupAgent, vous pouvez choisir d'exclure ce répertoire.