Use um modelo personalizado do TensorFlow Lite no Android

Se seu aplicativo usa modelos personalizados do TensorFlow Lite , você pode usar o Firebase ML para implantar seus modelos. Ao implantar modelos com o Firebase, você pode reduzir o tamanho inicial do download do seu aplicativo e atualizar os modelos de ML do seu aplicativo sem lançar uma nova versão do seu aplicativo. E, com a Configuração remota e o teste A/B, você pode servir dinamicamente diferentes modelos para diferentes conjuntos de usuários.

Modelos do TensorFlow Lite

Os modelos TensorFlow Lite são modelos de ML otimizados para execução em dispositivos móveis. Para obter um modelo do TensorFlow Lite:

Antes de você começar

  1. Adicione o Firebase ao seu projeto Android , caso ainda não o tenha feito.
  2. No arquivo Gradle do módulo (nível do aplicativo) (geralmente <project>/<app-module>/build.gradle.kts ou <project>/<app-module>/build.gradle ), adicione a dependência para o Firebase ML biblioteca de download de modelos para Android. Recomendamos usar o Firebase Android BoM para controlar o controle de versão da biblioteca.

    Além disso, como parte da configuração do downloader de modelo do Firebase ML, você precisa adicionar o SDK do TensorFlow Lite ao seu aplicativo.

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:32.8.0"))
    
        // Add the dependency for the Firebase ML model downloader library
        // When using the BoM, you don't specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-ml-modeldownloader")
    // Also add the dependency for the TensorFlow Lite library and specify its version implementation("org.tensorflow:tensorflow-lite:2.3.0")
    }

    Ao usar o Firebase Android BoM , seu aplicativo sempre usará versões compatíveis das bibliotecas do Firebase Android.

    (Alternativa) Adicionar dependências da biblioteca Firebase sem usar o BoM

    Se você optar por não usar o Firebase BoM, deverá especificar cada versão da biblioteca do Firebase em sua linha de dependência.

    Observe que se você usa várias bibliotecas do Firebase no seu aplicativo, é altamente recomendável usar a BoM para gerenciar as versões da biblioteca, o que garante que todas as versões sejam compatíveis.

    dependencies {
        // Add the dependency for the Firebase ML model downloader library
        // When NOT using the BoM, you must specify versions in Firebase library dependencies
        implementation("com.google.firebase:firebase-ml-modeldownloader:24.2.3")
    // Also add the dependency for the TensorFlow Lite library and specify its version implementation("org.tensorflow:tensorflow-lite:2.3.0")
    }
    Procurando um módulo de biblioteca específico para Kotlin? A partir de outubro de 2023 (Firebase BoM 32.5.0) , tanto os desenvolvedores Kotlin quanto os Java podem depender do módulo da biblioteca principal (para obter detalhes, consulte o FAQ sobre esta iniciativa ).
  3. No manifesto do seu aplicativo, declare que a permissão INTERNET é necessária:
    <uses-permission android:name="android.permission.INTERNET" />

1. Implante seu modelo

Implante seus modelos personalizados do TensorFlow usando o console do Firebase ou os SDKs Firebase Admin Python e Node.js. Consulte Implantar e gerenciar modelos customizados .

Depois de adicionar um modelo personalizado ao seu projeto do Firebase, você poderá referenciar o modelo nos seus aplicativos usando o nome especificado. A qualquer momento, você pode implantar um novo modelo do TensorFlow Lite e fazer download do novo modelo nos dispositivos dos usuários chamando getModel() (veja abaixo).

2. Baixe o modelo para o dispositivo e inicialize um interpretador TensorFlow Lite

Para usar o modelo do TensorFlow Lite no seu aplicativo, primeiro use o SDK do Firebase ML para fazer download da versão mais recente do modelo para o dispositivo. Em seguida, instancie um interpretador do TensorFlow Lite com o modelo.

Para iniciar o download do modelo, chame o método getModel() do downloader do modelo, especificando o nome que você atribuiu ao modelo quando o carregou, se deseja sempre baixar o modelo mais recente e as condições sob as quais deseja permitir o download.

Você pode escolher entre três comportamentos de download:

Tipo de download Descrição
LOCAL_MODEL Obtenha o modelo local do dispositivo. Se não houver nenhum modelo local disponível, ele se comportará como LATEST_MODEL . Use este tipo de download se não estiver interessado em verificar atualizações de modelo. Por exemplo, você usa o Configuração remota para recuperar nomes de modelos e sempre faz upload de modelos com novos nomes (recomendado).
LOCAL_MODEL_UPDATE_IN_BACKGROUND Obtenha o modelo local do dispositivo e comece a atualizar o modelo em segundo plano. Se não houver nenhum modelo local disponível, ele se comportará como LATEST_MODEL .
LATEST_MODEL Obtenha o modelo mais recente. Se o modelo local for a versão mais recente, retorna o modelo local. Caso contrário, baixe o modelo mais recente. Este comportamento será bloqueado até que a versão mais recente seja baixada (não recomendado). Use esse comportamento apenas nos casos em que você precisa explicitamente da versão mais recente.

Você deve desativar a funcionalidade relacionada ao modelo (por exemplo, esmaecer ou ocultar parte da sua UI) até confirmar que o download do modelo foi feito.

Kotlin+KTX

val conditions = CustomModelDownloadConditions.Builder()
        .requireWifi()  // Also possible: .requireCharging() and .requireDeviceIdle()
        .build()
FirebaseModelDownloader.getInstance()
        .getModel("your_model", DownloadType.LOCAL_MODEL_UPDATE_IN_BACKGROUND,
            conditions)
        .addOnSuccessListener { model: CustomModel? ->
            // Download complete. Depending on your app, you could enable the ML
            // feature, or switch from the local model to the remote model, etc.

            // The CustomModel object contains the local path of the model file,
            // which you can use to instantiate a TensorFlow Lite interpreter.
            val modelFile = model?.file
            if (modelFile != null) {
                interpreter = Interpreter(modelFile)
            }
        }

Java

CustomModelDownloadConditions conditions = new CustomModelDownloadConditions.Builder()
    .requireWifi()  // Also possible: .requireCharging() and .requireDeviceIdle()
    .build();
FirebaseModelDownloader.getInstance()
    .getModel("your_model", DownloadType.LOCAL_MODEL_UPDATE_IN_BACKGROUND, conditions)
    .addOnSuccessListener(new OnSuccessListener<CustomModel>() {
      @Override
      public void onSuccess(CustomModel model) {
        // Download complete. Depending on your app, you could enable the ML
        // feature, or switch from the local model to the remote model, etc.

        // The CustomModel object contains the local path of the model file,
        // which you can use to instantiate a TensorFlow Lite interpreter.
        File modelFile = model.getFile();
        if (modelFile != null) {
            interpreter = new Interpreter(modelFile);
        }
      }
    });

Muitos aplicativos iniciam a tarefa de download em seu código de inicialização, mas você pode fazer isso a qualquer momento antes de usar o modelo.

3. Realize inferência nos dados de entrada

Obtenha as formas de entrada e saída do seu modelo

O interpretador de modelo do TensorFlow Lite recebe como entrada e produz como saída uma ou mais matrizes multidimensionais. Essas matrizes contêm valores byte , int , long ou float . Antes de poder passar dados para um modelo ou usar seu resultado, você deve saber o número e as dimensões ("formato") dos arrays que seu modelo usa.

Se você mesmo construiu o modelo ou se o formato de entrada e saída do modelo estiver documentado, talvez você já tenha essas informações. Se você não conhece a forma e o tipo de dados de entrada e saída do seu modelo, poderá usar o interpretador do TensorFlow Lite para inspecionar seu modelo. Por exemplo:

Pitão

import tensorflow as tf

interpreter = tf.lite.Interpreter(model_path="your_model.tflite")
interpreter.allocate_tensors()

# Print input shape and type
inputs = interpreter.get_input_details()
print('{} input(s):'.format(len(inputs)))
for i in range(0, len(inputs)):
    print('{} {}'.format(inputs[i]['shape'], inputs[i]['dtype']))

# Print output shape and type
outputs = interpreter.get_output_details()
print('\n{} output(s):'.format(len(outputs)))
for i in range(0, len(outputs)):
    print('{} {}'.format(outputs[i]['shape'], outputs[i]['dtype']))

Exemplo de saída:

1 input(s):
[  1 224 224   3] <class 'numpy.float32'>

1 output(s):
[1 1000] <class 'numpy.float32'>

Execute o intérprete

Depois de determinar o formato da entrada e da saída do seu modelo, obtenha os dados de entrada e execute quaisquer transformações nos dados que sejam necessárias para obter uma entrada com o formato correto para o seu modelo.

Por exemplo, se você tiver um modelo de classificação de imagem com um formato de entrada de [1 224 224 3] valores de ponto flutuante, poderá gerar um ByteBuffer de entrada a partir de um objeto Bitmap , conforme mostrado no exemplo a seguir:

Kotlin+KTX

val bitmap = Bitmap.createScaledBitmap(yourInputImage, 224, 224, true)
val input = ByteBuffer.allocateDirect(224*224*3*4).order(ByteOrder.nativeOrder())
for (y in 0 until 224) {
    for (x in 0 until 224) {
        val px = bitmap.getPixel(x, y)

        // Get channel values from the pixel value.
        val r = Color.red(px)
        val g = Color.green(px)
        val b = Color.blue(px)

        // Normalize channel values to [-1.0, 1.0]. This requirement depends on the model.
        // For example, some models might require values to be normalized to the range
        // [0.0, 1.0] instead.
        val rf = (r - 127) / 255f
        val gf = (g - 127) / 255f
        val bf = (b - 127) / 255f

        input.putFloat(rf)
        input.putFloat(gf)
        input.putFloat(bf)
    }
}

Java

Bitmap bitmap = Bitmap.createScaledBitmap(yourInputImage, 224, 224, true);
ByteBuffer input = ByteBuffer.allocateDirect(224 * 224 * 3 * 4).order(ByteOrder.nativeOrder());
for (int y = 0; y < 224; y++) {
    for (int x = 0; x < 224; x++) {
        int px = bitmap.getPixel(x, y);

        // Get channel values from the pixel value.
        int r = Color.red(px);
        int g = Color.green(px);
        int b = Color.blue(px);

        // Normalize channel values to [-1.0, 1.0]. This requirement depends
        // on the model. For example, some models might require values to be
        // normalized to the range [0.0, 1.0] instead.
        float rf = (r - 127) / 255.0f;
        float gf = (g - 127) / 255.0f;
        float bf = (b - 127) / 255.0f;

        input.putFloat(rf);
        input.putFloat(gf);
        input.putFloat(bf);
    }
}

Em seguida, aloque um ByteBuffer grande o suficiente para conter a saída do modelo e passe o buffer de entrada e o buffer de saída para o método run() do interpretador do TensorFlow Lite. Por exemplo, para um formato de saída de [1 1000] valores de ponto flutuante:

Kotlin+KTX

val bufferSize = 1000 * java.lang.Float.SIZE / java.lang.Byte.SIZE
val modelOutput = ByteBuffer.allocateDirect(bufferSize).order(ByteOrder.nativeOrder())
interpreter?.run(input, modelOutput)

Java

int bufferSize = 1000 * java.lang.Float.SIZE / java.lang.Byte.SIZE;
ByteBuffer modelOutput = ByteBuffer.allocateDirect(bufferSize).order(ByteOrder.nativeOrder());
interpreter.run(input, modelOutput);

Como você usa a saída depende do modelo que você está usando.

Por exemplo, se você estiver realizando uma classificação, como próxima etapa, você pode mapear os índices do resultado para os rótulos que eles representam:

Kotlin+KTX

modelOutput.rewind()
val probabilities = modelOutput.asFloatBuffer()
try {
    val reader = BufferedReader(
            InputStreamReader(assets.open("custom_labels.txt")))
    for (i in probabilities.capacity()) {
        val label: String = reader.readLine()
        val probability = probabilities.get(i)
        println("$label: $probability")
    }
} catch (e: IOException) {
    // File not found?
}

Java

modelOutput.rewind();
FloatBuffer probabilities = modelOutput.asFloatBuffer();
try {
    BufferedReader reader = new BufferedReader(
            new InputStreamReader(getAssets().open("custom_labels.txt")));
    for (int i = 0; i < probabilities.capacity(); i++) {
        String label = reader.readLine();
        float probability = probabilities.get(i);
        Log.i(TAG, String.format("%s: %1.4f", label, probability));
    }
} catch (IOException e) {
    // File not found?
}

Apêndice: Segurança do modelo

Independentemente de como você disponibiliza seus modelos do TensorFlow Lite para o Firebase ML, o Firebase ML os armazena no formato protobuf serializado padrão no armazenamento local.

Em teoria, isso significa que qualquer pessoa pode copiar o seu modelo. No entanto, na prática, a maioria dos modelos são tão específicos da aplicação e ofuscados por otimizações que o risco é semelhante ao dos concorrentes desmontarem e reutilizarem seu código. No entanto, você deve estar ciente desse risco antes de usar um modelo personalizado em seu aplicativo.

Na API Android nível 21 (Lollipop) e mais recente, o modelo é baixado em um diretório que é excluído do backup automático .

Na API Android de nível 20 e anteriores, o modelo é baixado em um diretório chamado com.google.firebase.ml.custom.models no armazenamento interno privado do aplicativo. Se você ativou o backup de arquivos usando BackupAgent , poderá optar por excluir esse diretório.