Użyj niestandardowego modelu TensorFlow Lite na Androidzie

Jeśli Twoja aplikacja korzysta z niestandardowych modeli TensorFlow Lite , możesz użyć Firebase ML do wdrożenia swoich modeli. Wdrażając modele w Firebase, możesz zmniejszyć początkowy rozmiar aplikacji do pobrania i zaktualizować modele ML swojej aplikacji bez wydawania nowej wersji aplikacji. Dzięki zdalnej konfiguracji i testom A/B możesz dynamicznie udostępniać różne modele różnym grupom użytkowników.

Modele TensorFlow Lite

Modele TensorFlow Lite to modele ML zoptymalizowane do działania na urządzeniach mobilnych. Aby uzyskać model TensorFlow Lite:

Zanim zaczniesz

  1. Jeśli jeszcze tego nie zrobiłeś, dodaj Firebase do swojego projektu na Androida .
  2. W pliku Gradle modułu (na poziomie aplikacji) (zwykle <project>/<app-module>/build.gradle.kts lub <project>/<app-module>/build.gradle ) dodaj zależność dla Firebase ML biblioteka pobierania modeli dla Androida. Zalecamy używanie Firebase Android BoM do kontrolowania wersji bibliotek.

    Ponadto w ramach konfigurowania narzędzia do pobierania modeli Firebase ML musisz dodać do swojej aplikacji pakiet SDK TensorFlow Lite.

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:32.7.3"))
    
        // 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")
    }

    Korzystając z Firebase Android BoM , Twoja aplikacja będzie zawsze korzystać z kompatybilnych wersji bibliotek Firebase Android.

    (Alternatywa) Dodaj zależności biblioteki Firebase bez użycia BoM

    Jeśli zdecydujesz się nie używać BoM Firebase, musisz określić każdą wersję biblioteki Firebase w jej wierszu zależności.

    Pamiętaj, że jeśli używasz w swojej aplikacji wielu bibliotek Firebase, zdecydowanie zalecamy używanie BoM do zarządzania wersjami bibliotek, co gwarantuje, że wszystkie wersje będą kompatybilne.

    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")
    }
    Szukasz modułu bibliotecznego specyficznego dla Kotlina? Począwszy od października 2023 r. (Firebase BoM 32.5.0) zarówno programiści Kotlin, jak i Java mogą polegać na głównym module biblioteki (więcej informacji można znaleźć w często zadawanych pytaniach dotyczących tej inicjatywy ).
  3. W manifeście aplikacji zadeklaruj, że wymagane jest pozwolenie na INTERNET:
    <uses-permission android:name="android.permission.INTERNET" />

1. Wdróż swój model

Wdrażaj niestandardowe modele TensorFlow za pomocą konsoli Firebase lub pakietów SDK Firebase Admin Python i Node.js. Zobacz Wdrażanie modeli niestandardowych i zarządzanie nimi .

Po dodaniu niestandardowego modelu do projektu Firebase możesz odwoływać się do modelu w swoich aplikacjach, używając określonej nazwy. W dowolnym momencie możesz wdrożyć nowy model TensorFlow Lite i pobrać nowy model na urządzenia użytkowników, wywołując funkcję getModel() (patrz poniżej).

2. Pobierz model na urządzenie i zainicjuj interpreter TensorFlow Lite

Aby użyć modelu TensorFlow Lite w swojej aplikacji, najpierw użyj pakietu SDK Firebase ML, aby pobrać najnowszą wersję modelu na urządzenie. Następnie utwórz instancję interpretera TensorFlow Lite z modelem.

Aby rozpocząć pobieranie modelu, wywołaj metodę getModel() narzędzia do pobierania modelu, określając nazwę nadaną modelowi podczas jego przesyłania, czy chcesz zawsze pobierać najnowszy model oraz warunki, na jakich chcesz zezwolić na pobieranie.

Możesz wybrać jeden z trzech sposobów pobierania:

Typ pobierania Opis
LOCAL_MODEL Pobierz model lokalny z urządzenia. Jeśli nie jest dostępny żaden model lokalny, zachowuje się to jak LATEST_MODEL . Użyj tego typu pobierania, jeśli nie chcesz sprawdzać dostępności aktualizacji modelu. Na przykład używasz funkcji Remote Config do pobierania nazw modeli i zawsze przesyłasz modele pod nowymi nazwami (zalecane).
LOCAL_MODEL_UPDATE_IN_BACKGROUND Pobierz model lokalny z urządzenia i rozpocznij aktualizację modelu w tle. Jeśli nie jest dostępny żaden model lokalny, zachowuje się to jak LATEST_MODEL .
NAJNOWSZY MODEL Zdobądź najnowszy model. Jeśli model lokalny jest najnowszą wersją, zwraca model lokalny. W przeciwnym razie pobierz najnowszy model. To zachowanie będzie blokowane do czasu pobrania najnowszej wersji (niezalecane). Używaj tego zachowania tylko w przypadkach, gdy wyraźnie potrzebujesz najnowszej wersji.

Powinieneś wyłączyć funkcje związane z modelem — na przykład wyszarzić lub ukryć część interfejsu użytkownika — do czasu potwierdzenia, że ​​model został pobrany.

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

Wiele aplikacji rozpoczyna zadanie pobierania w kodzie inicjującym, ale można to zrobić w dowolnym momencie, zanim będzie konieczne użycie modelu.

3. Wykonaj wnioskowanie na danych wejściowych

Uzyskaj kształty wejściowe i wyjściowe swojego modelu

Interpreter modelu TensorFlow Lite przyjmuje jako dane wejściowe i generuje jako dane wyjściowe jedną lub więcej tablic wielowymiarowych. Tablice te zawierają wartości byte , int , long lub float . Zanim będziesz mógł przekazać dane do modelu lub wykorzystać jego wyniki, musisz znać liczbę i wymiary („kształt”) tablic używanych przez Twój model.

Jeśli sam zbudowałeś model lub jeśli format wejściowy i wyjściowy modelu jest udokumentowany, możesz już mieć te informacje. Jeśli nie znasz kształtu i typu danych wejściowych i wyjściowych swojego modelu, możesz użyć interpretera TensorFlow Lite, aby sprawdzić swój model. Na przykład:

Pyton

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

Przykładowe wyjście:

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

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

Uruchom tłumacza

Po określeniu formatu danych wejściowych i wyjściowych modelu uzyskaj dane wejściowe i wykonaj wszelkie przekształcenia danych, które są niezbędne, aby uzyskać dane wejściowe o odpowiednim kształcie dla modelu.

Na przykład, jeśli masz model klasyfikacji obrazów z wejściowym kształtem [1 224 224 3] wartości zmiennoprzecinkowych, możesz wygenerować wejściowy bufor ByteBuffer z obiektu Bitmap , jak pokazano w poniższym przykładzie:

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

Następnie przydziel bufor ByteBuffer wystarczająco duży, aby pomieścić dane wyjściowe modelu, i przekaż bufor wejściowy i bufor wyjściowy do metody run() interpretera TensorFlow Lite. Na przykład dla kształtu wyjściowego [1 1000] wartości zmiennoprzecinkowych:

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

Sposób wykorzystania danych wyjściowych zależy od używanego modelu.

Na przykład, jeśli przeprowadzasz klasyfikację, w następnym kroku możesz zmapować indeksy wyniku na etykiety, które reprezentują:

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

Dodatek: Bezpieczeństwo modelu

Niezależnie od tego, w jaki sposób udostępnisz modele TensorFlow Lite w Firebase ML, Firebase ML przechowuje je w standardowym serializowanym formacie protobuf w pamięci lokalnej.

Teoretycznie oznacza to, że każdy może skopiować Twój model. Jednak w praktyce większość modeli jest tak specyficzna dla aplikacji i zaciemniona optymalizacjami, że ryzyko jest podobne do ryzyka, jakie stwarza konkurencja, która demontuje i ponownie wykorzystuje Twój kod. Niemniej jednak powinieneś zdawać sobie sprawę z tego ryzyka, zanim użyjesz niestandardowego modelu w swojej aplikacji.

W systemie Android API poziomu 21 (Lollipop) i nowszych model jest pobierany do katalogu, który jest wykluczony z automatycznego tworzenia kopii zapasowych .

W przypadku interfejsu API systemu Android na poziomie 20 i starszych model jest pobierany do katalogu o nazwie com.google.firebase.ml.custom.models w prywatnej pamięci wewnętrznej aplikacji. Jeśli włączyłeś tworzenie kopii zapasowych plików za pomocą BackupAgent , możesz wykluczyć ten katalog.