Utilizza un modello TensorFlow Lite per l'inferenza con ML Kit su Android

Puoi utilizzare ML Kit per eseguire l'inferenza sul dispositivo con un modello TensorFlow Lite .

Questa API richiede Android SDK livello 16 (Jelly Bean) o versione successiva.

Prima di iniziare

  1. Se non l'hai già fatto, aggiungi Firebase al tuo progetto Android .
  2. Aggiungi le dipendenze per le librerie Android ML Kit al file Gradle del modulo (a livello di app) (in genere 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. Converti il ​​modello TensorFlow che desideri utilizzare nel formato TensorFlow Lite. Vedere TOCO: Convertitore di ottimizzazione TensorFlow Lite .

Ospita o raggruppa il tuo modello

Prima di poter utilizzare un modello TensorFlow Lite per l'inferenza nella tua app, devi rendere il modello disponibile per ML Kit. ML Kit può utilizzare modelli TensorFlow Lite ospitati in remoto utilizzando Firebase, in bundle con il file binario dell'app o entrambi.

Ospitando un modello su Firebase, puoi aggiornare il modello senza rilasciare una nuova versione dell'app e puoi utilizzare Remote Config e A/B Testing per servire dinamicamente modelli diversi a diversi gruppi di utenti.

Se scegli di fornire il modello solo ospitandolo con Firebase e non raggruppandolo con la tua app, puoi ridurre le dimensioni di download iniziali della tua app. Tieni presente, tuttavia, che se il modello non è incluso nella tua app, qualsiasi funzionalità relativa al modello non sarà disponibile finché l'app non scaricherà il modello per la prima volta.

Raggruppando il tuo modello con la tua app, puoi assicurarti che le funzionalità ML della tua app continuino a funzionare quando il modello ospitato da Firebase non è disponibile.

Ospita modelli su Firebase

Per ospitare il tuo modello TensorFlow Lite su Firebase:

  1. Nella sezione ML Kit della console Firebase , fai clic sulla scheda Personalizzato .
  2. Fai clic su Aggiungi modello personalizzato (o Aggiungi un altro modello ).
  3. Specifica un nome che verrà utilizzato per identificare il tuo modello nel tuo progetto Firebase, quindi carica il file del modello TensorFlow Lite (di solito termina con .tflite o .lite ).
  4. Nel manifest della tua app, dichiara che è richiesta l'autorizzazione INTERNET:
    <uses-permission android:name="android.permission.INTERNET" />
    

Dopo aver aggiunto un modello personalizzato al tuo progetto Firebase, puoi fare riferimento al modello nelle tue app utilizzando il nome specificato. In qualsiasi momento, puoi caricare un nuovo modello TensorFlow Lite e la tua app scaricherà il nuovo modello e inizierà a utilizzarlo al successivo riavvio dell'app. Puoi definire le condizioni del dispositivo richieste affinché la tua app tenti di aggiornare il modello (vedi sotto).

Raggruppa i modelli con un'app

Per raggruppare il tuo modello TensorFlow Lite con la tua app, copia il file del modello (che solitamente termina con .tflite o .lite ) nella cartella assets/ della tua app. (Potrebbe essere necessario creare prima la cartella facendo clic con il pulsante destro del mouse app/ cartella, quindi facendo clic su Nuovo > Cartella > Cartella risorse .)

Quindi, aggiungi quanto segue al file build.gradle della tua app per assicurarti che Gradle non comprima i modelli durante la creazione dell'app:

android {

    // ...

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

Il file del modello verrà incluso nel pacchetto dell'app e disponibile per ML Kit come risorsa non elaborata.

Carica il modello

Per utilizzare il tuo modello TensorFlow Lite nella tua app, configura innanzitutto ML Kit con le posizioni in cui il tuo modello è disponibile: in remoto utilizzando Firebase, nello spazio di archiviazione locale o entrambi. Se specifichi sia un modello locale che uno remoto, puoi utilizzare il modello remoto se è disponibile e ricorrere al modello archiviato localmente se il modello remoto non è disponibile.

Configura un modello ospitato da Firebase

Se hai ospitato il tuo modello con Firebase, crea un oggetto FirebaseCustomRemoteModel , specificando il nome che hai assegnato al modello quando lo hai caricato:

Java

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

Kotlin+KTX

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

Avvia quindi l'attività di download del modello, specificando le condizioni alle quali desideri consentire il download. Se il modello non è presente sul dispositivo o se è disponibile una versione più recente del modello, l'attività scaricherà in modo asincrono il modello da 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.
    }

Molte app avviano l'attività di download nel codice di inizializzazione, ma puoi farlo in qualsiasi momento prima di dover utilizzare il modello.

Configurare un modello locale

Se hai raggruppato il modello con la tua app, crea un oggetto FirebaseCustomLocalModel , specificando il nome file del modello 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 interprete dal tuo modello

Dopo aver configurato le origini del modello, crea un oggetto FirebaseModelInterpreter da uno di essi.

Se disponi solo di un modello raggruppato localmente, crea semplicemente un interprete dal tuo oggetto 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)

Se disponi di un modello ospitato in remoto, dovrai verificare che sia stato scaricato prima di eseguirlo. È possibile verificare lo stato dell'attività di download del modello utilizzando il metodo isModelDownloaded() del gestore modelli.

Anche se devi solo confermarlo prima di eseguire l'interprete, se disponi sia di un modello ospitato in remoto che di un modello raggruppato localmente, potrebbe avere senso eseguire questo controllo quando istanzia l'interprete del modello: crea un interprete dal modello remoto se è stato scaricato e altrimenti dal modello locale.

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

Se disponi solo di un modello ospitato in remoto, dovresti disabilitare le funzionalità relative al modello, ad esempio disattivare o nascondere parte della tua interfaccia utente, finché non confermi che il modello è stato scaricato. Puoi farlo collegando un ascoltatore al metodo download() del gestore del modello:

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

Specificare l'input e l'output del modello

Successivamente, configura i formati di input e output dell'interprete del modello.

Un modello TensorFlow Lite prende come input e produce come output uno o più array multidimensionali. Questi array contengono valori byte , int , long o float . È necessario configurare ML Kit con il numero e le dimensioni ("forma") degli array utilizzati dal modello.

Se non conosci la forma e il tipo di dati dell'input e dell'output del tuo modello, puoi utilizzare l'interprete Python TensorFlow Lite per ispezionare il tuo modello. Per esempio:

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

Dopo aver determinato il formato dell'input e dell'output del tuo modello, puoi configurare l'interprete del modello della tua app creando un oggetto FirebaseModelInputOutputOptions .

Ad esempio, un modello di classificazione delle immagini a virgola mobile potrebbe prendere come input un array N x224x224x3 di valori float , che rappresenta un batch di immagini N 224x224 a tre canali (RGB), e produrre come output un elenco di 1000 valori float , ciascuno rappresentante il probabilità che l'immagine appartenga a una delle 1000 categorie previste dal modello.

Per un modello di questo tipo, configureresti l'input e l'output dell'interprete del modello come mostrato di seguito:

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

Eseguire l'inferenza sui dati di input

Infine, per eseguire l'inferenza utilizzando il modello, ottieni i dati di input ed esegui le trasformazioni sui dati necessarie per ottenere un array di input della forma corretta per il tuo modello.

Ad esempio, se disponi di un modello di classificazione delle immagini con una forma di input di [1 224 224 3] valori a virgola mobile, potresti generare un array di input da un oggetto Bitmap come mostrato nell'esempio seguente:

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

Quindi, crea un oggetto FirebaseModelInputs con i tuoi dati di input e passalo insieme alle specifiche di input e output del modello al metodo run dell'interprete del modello :

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

Se la chiamata ha esito positivo, puoi ottenere l'output chiamando il metodo getOutput() dell'oggetto passato al listener di successo. Per esempio:

Java

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

Kotlin+KTX

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

Il modo in cui utilizzi l'output dipende dal modello che stai utilizzando.

Ad esempio, se stai eseguendo la classificazione, come passaggio successivo potresti associare gli indici del risultato alle etichette che rappresentano:

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

Appendice: Sicurezza del modello

Indipendentemente da come rendi i tuoi modelli TensorFlow Lite disponibili per ML Kit, ML Kit li archivia nel formato protobuf serializzato standard nell'archivio locale.

In teoria, ciò significa che chiunque può copiare il tuo modello. Tuttavia, in pratica, la maggior parte dei modelli sono così specifici per l'applicazione e offuscati dalle ottimizzazioni che il rischio è simile a quello dei concorrenti che disassemblano e riutilizzano il codice. Tuttavia, dovresti essere consapevole di questo rischio prima di utilizzare un modello personalizzato nella tua app.

Nel livello API Android 21 (Lollipop) e versioni successive, il modello viene scaricato in una directory esclusa dal backup automatico .

Sul livello API Android 20 e versioni precedenti, il modello viene scaricato in una directory denominata com.google.firebase.ml.custom.models nella memoria interna privata dell'app. Se hai abilitato il backup dei file utilizzando BackupAgent , potresti scegliere di escludere questa directory.