ML Kit umożliwia przeprowadzanie wnioskowania na urządzeniu za pomocą modelu TensorFlow Lite.
Ten interfejs API wymaga pakietu Android SDK na poziomie 16 (Jelly Bean) lub nowszym.
Zanim zaczniesz
- Jeśli nie korzystasz jeszcze z Firebase, dodaj tę usługę do projektu aplikacji na Androida.
- Dodaj zależności dla bibliotek ML Kit na Androida do pliku Gradle modułu (na poziomie aplikacji) (zwykle
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' }
- Przekonwertuj model TensorFlow, którego chcesz użyć, na format TensorFlow Lite. Zobacz TOCO: TensorFlow Lite Optimizing Converter.
Hostowanie lub pakowanie modelu
Zanim zaczniesz używać modelu TensorFlow Lite do wnioskowania w aplikacji, musisz udostępnić go ML Kit. ML Kit może używać modeli TensorFlow Lite hostowanych zdalnie za pomocą Firebase, dołączonych do pliku binarnego aplikacji lub obu tych opcji.
Dzięki hostowaniu modelu w Firebase możesz go aktualizować bez publikowania nowej wersji aplikacji. Możesz też używać Remote Config i A/B Testing, aby dynamicznie udostępniać różne modele różnym grupom użytkowników.
Jeśli zdecydujesz się udostępnić model tylko przez hostowanie go w Firebase, a nie dołączyć go do aplikacji, możesz zmniejszyć początkowy rozmiar pobierania aplikacji. Pamiętaj jednak, że jeśli model nie jest dołączony do aplikacji, żadne funkcje z nim związane nie będą dostępne, dopóki aplikacja nie pobierze go po raz pierwszy.
Dzięki dołączeniu modelu do aplikacji możesz mieć pewność, że funkcje ML aplikacji będą nadal działać, gdy model hostowany w Firebase będzie niedostępny.
Hostowanie modeli w Firebase
Aby hostować model TensorFlow Lite w Firebase:
- W sekcji ML Kit w Firebasekonsoli kliknij kartę Niestandardowe.
- Kliknij Dodaj model niestandardowy (lub Dodaj kolejny model).
- Podaj nazwę, która będzie używana do identyfikowania modelu w projekcie Firebase, a potem prześlij plik modelu TensorFlow Lite (zwykle z rozszerzeniem
.tflite
lub.lite
). - W pliku manifestu aplikacji zadeklaruj, że wymagane jest uprawnienie INTERNET:
<uses-permission android:name="android.permission.INTERNET" />
Po dodaniu modelu niestandardowego do projektu Firebase możesz odwoływać się do niego w swoich aplikacjach, podając określoną nazwę. W dowolnym momencie możesz przesłać nowy model TensorFlow Lite, a aplikacja pobierze go i zacznie używać przy następnym ponownym uruchomieniu. Możesz określić warunki, jakie musi spełniać urządzenie, aby aplikacja mogła spróbować zaktualizować model (patrz poniżej).
Łączenie modeli z aplikacją
Aby połączyć model TensorFlow Lite z aplikacją, skopiuj plik modelu (zwykle kończący się na .tflite
lub .lite
) do folderu assets/
aplikacji. (Może być konieczne utworzenie folderu. W tym celu kliknij prawym przyciskiem myszy folder app/
, a następnie kliknij Nowy > Folder > Folder zasobów).
Następnie dodaj do pliku build.gradle
w aplikacji ten ciąg, aby mieć pewność, że Gradle nie skompresuje modeli podczas kompilowania aplikacji:
android {
// ...
aaptOptions {
noCompress "tflite" // Your model's file extension: "tflite", "lite", etc.
}
}
Plik modelu zostanie dołączony do pakietu aplikacji i będzie dostępny dla ML Kit jako surowy zasób.
Wczytywanie modelu
Aby używać modelu TensorFlow Lite w aplikacji, najpierw skonfiguruj ML Kit, podając lokalizacje, w których model jest dostępny: zdalnie za pomocą Firebase, w pamięci lokalnej lub w obu tych miejscach. Jeśli określisz model lokalny i zdalny, możesz użyć modelu zdalnego, jeśli jest dostępny, a jeśli nie jest, możesz wrócić do modelu przechowywanego lokalnie.Konfigurowanie modelu hostowanego w Firebase
Jeśli model jest hostowany w Firebase, utwórz obiekt FirebaseCustomRemoteModel
, podając nazwę przypisaną do modelu podczas przesyłania:
Java
FirebaseCustomRemoteModel remoteModel =
new FirebaseCustomRemoteModel.Builder("your_model").build();
Kotlin
val remoteModel = FirebaseCustomRemoteModel.Builder("your_model").build()
Następnie rozpocznij pobieranie modelu, określając warunki, w jakich chcesz zezwolić na pobieranie. Jeśli modelu nie ma na urządzeniu lub dostępna jest nowsza wersja modelu, zadanie asynchronicznie pobierze model z 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.
}
Wiele aplikacji rozpoczyna pobieranie w kodzie inicjującym, ale możesz to zrobić w dowolnym momencie przed użyciem modelu.
Konfigurowanie modelu lokalnego
Jeśli model został dołączony do aplikacji, utwórz obiekt FirebaseCustomLocalModel
, podając nazwę pliku modelu TensorFlow Lite:
Java
FirebaseCustomLocalModel localModel = new FirebaseCustomLocalModel.Builder()
.setAssetFilePath("your_model.tflite")
.build();
Kotlin
val localModel = FirebaseCustomLocalModel.Builder()
.setAssetFilePath("your_model.tflite")
.build()
Tworzenie interpretera na podstawie modelu
Po skonfigurowaniu źródeł modelu utwórz z jednego z nich FirebaseModelInterpreter
obiekt.
Jeśli masz tylko model dołączony lokalnie, utwórz interpreter z obiektu 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)
Jeśli masz model hostowany zdalnie, przed jego uruchomieniem musisz sprawdzić, czy został pobrany. Stan zadania pobierania modelu możesz sprawdzić za pomocą metody isModelDownloaded()
menedżera modeli.
Chociaż musisz potwierdzić to tylko przed uruchomieniem interpretera, jeśli masz zarówno model hostowany zdalnie, jak i model dołączony lokalnie, warto przeprowadzić to sprawdzenie podczas tworzenia instancji interpretera modelu: utwórz interpreter z modelu zdalnego, jeśli został pobrany, a w przeciwnym razie z modelu lokalnego.
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)
}
Jeśli masz tylko model hostowany zdalnie, wyłącz funkcje związane z modelem, np. wyszarz lub ukryj część interfejsu, dopóki nie potwierdzisz, że model został pobrany. Możesz to zrobić, dołączając odbiornik do metody download()
menedżera modeli:
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.
}
Określanie danych wejściowych i wyjściowych modelu
Następnie skonfiguruj formaty wejściowe i wyjściowe interpretera modelu.
Model TensorFlow Lite przyjmuje jako dane wejściowe i generuje jako dane wyjściowe co najmniej jedną tablicę wielowymiarową. Tablice te zawierają wartości byte
, int
, long
lub float
. Musisz skonfigurować ML Kit, podając liczbę i wymiary („kształt”) tablic używanych przez model.
Jeśli nie znasz kształtu i typu danych wejściowych i wyjściowych modelu, możesz użyć interpretera Pythona TensorFlow Lite, aby sprawdzić model. Przykład:
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'>
Po określeniu formatu danych wejściowych i wyjściowych modelu możesz skonfigurować interpreter modelu aplikacji, tworząc obiekt FirebaseModelInputOutputOptions
.
Na przykład model klasyfikacji obrazów zmiennoprzecinkowych może przyjmować jako dane wejściowe tablicę Nx224x224x3 wartości float
, która reprezentuje partię Nobrazów 224x224 z 3 kanałami (RGB), a jako dane wyjściowe może generować listę 1000 wartości float
, z których każda reprezentuje prawdopodobieństwo, że obraz należy do jednej z 1000 kategorii, które model przewiduje.
W przypadku takiego modelu skonfiguruj dane wejściowe i wyjściowe interpretera modelu w sposób pokazany poniżej:
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()
Przeprowadzanie wnioskowania na podstawie danych wejściowych
Na koniec, aby przeprowadzić wnioskowanie za pomocą modelu, pobierz dane wejściowe i wykonaj na nich wszelkie przekształcenia niezbędne do uzyskania tablicy wejściowej o odpowiednim kształcie dla Twojego modelu.Jeśli na przykład masz model klasyfikacji obrazów o kształcie danych wejściowych [1 224 224 3] z wartościami zmiennoprzecinkowymi, możesz wygenerować tablicę wejściową z obiektu Bitmap
, jak pokazano w tym przykładzie:
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 } }
Następnie utwórz obiekt FirebaseModelInputs
z danymi wejściowymi i przekaż go oraz specyfikację wejścia i wyjścia modelu do metody run
interpretera modelu:
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 // ... }
Jeśli połączenie się powiedzie, możesz uzyskać dane wyjściowe, wywołując metodę getOutput()
obiektu przekazanego do detektora sukcesu. Przykład:
Java
float[][] output = result.getOutput(0); float[] probabilities = output[0];
Kotlin
val output = result.getOutput<Array<FloatArray>>(0) val probabilities = output[0]
Sposób wykorzystania danych wyjściowych zależy od używanego modelu.
Jeśli na przykład przeprowadzasz klasyfikację, w następnym kroku możesz przypisać indeksy wyniku do etykiet, które reprezentują:
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])) }
Dodatek: bezpieczeństwo modelu
Niezależnie od tego, w jaki sposób udostępniasz modele TensorFlow Lite w ML Kit, ML Kit przechowuje je w standardowym serializowanym formacie protobuf w pamięci lokalnej.
Teoretycznie oznacza to, że każdy może skopiować Twój model. W praktyce jednak większość modeli jest tak ściśle powiązana z aplikacją i zaciemniona przez optymalizacje, że ryzyko jest podobne do ryzyka związanego z rozłożeniem i ponownym wykorzystaniem kodu przez konkurencję. Zanim jednak użyjesz w aplikacji modelu niestandardowego, musisz mieć świadomość tego ryzyka.
Na Androidzie w wersji API 21 (Lollipop) i nowszych model jest pobierany do katalogu, który jest wykluczony z automatycznej kopii zapasowej.
Na Androidzie w wersji API 20 i starszej model jest pobierany do katalogu o nazwie com.google.firebase.ml.custom.models
w pamięci wewnętrznej aplikacji. Jeśli włączysz tworzenie kopii zapasowych plików za pomocą BackupAgent
, możesz wykluczyć ten katalog.