אפשר להשתמש ב-ML Kit כדי לבצע הסקה במכשיר באמצעות מודל TensorFlow Lite.
ה-API הזה דורש Android SDK ברמה 16 (Jelly Bean) ומעלה.
לפני שמתחילים
- אם עדיין לא עשיתם זאת, אתם צריכים להוסיף את Firebase לפרויקט Android.
- מוסיפים את התלויות של ספריות ML Kit Android לקובץ Gradle של המודול (ברמת האפליקציה) (בדרך כלל
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' }
- ממירים את מודל TensorFlow שרוצים להשתמש בו לפורמט TensorFlow Lite. מידע נוסף זמין במאמר בנושא TOCO: TensorFlow Lite Optimizing Converter.
אירוח או חבילה של המודל
כדי להשתמש במודל TensorFlow Lite להסקת מסקנות באפליקציה, צריך להפוך את המודל לזמין ל-ML Kit. ML Kit יכול להשתמש במודלים של TensorFlow Lite שמתארחים מרחוק באמצעות Firebase, שמצורפים לקובץ הבינארי של האפליקציה או בשניהם.
אירוח מודל ב-Firebase מאפשר לכם לעדכן את המודל בלי להשיק גרסה חדשה של האפליקציה, ואתם יכולים להשתמש ב-Remote Config וב-A/B Testing כדי להציג באופן דינמי מודלים שונים לקבוצות שונות של משתמשים.
אם בוחרים לספק את המודל רק באמצעות אירוח שלו ב-Firebase, ולא לצרף אותו לאפליקציה, אפשר להקטין את גודל ההורדה הראשוני של האפליקציה. עם זאת, חשוב לזכור שאם המודל לא מצורף לאפליקציה, כל הפונקציות שקשורות למודל לא יהיו זמינות עד שהאפליקציה תוריד את המודל בפעם הראשונה.
אם תצרפו את המודל לאפליקציה, תוכלו לוודא שתכונות ה-ML של האפליקציה ימשיכו לפעול גם אם המודל שמתארח ב-Firebase לא יהיה זמין.
אירוח מודלים ב-Firebase
כדי לארח את מודל TensorFlow Lite ב-Firebase:
- בקטע ML Kit במסוף Firebase, לוחצים על הכרטיסייה Custom (מותאם אישית).
- לוחצים על הוספת מודל בהתאמה אישית (או על הוספת מודל נוסף).
- מציינים שם שישמש לזיהוי המודל בפרויקט Firebase, ואז מעלים את קובץ המודל של TensorFlow Lite (בדרך כלל מסתיים ב-
.tflite
או ב-.lite
). - במניפסט של האפליקציה, מציינים שנדרשת הרשאת INTERNET:
<uses-permission android:name="android.permission.INTERNET" />
אחרי שמוסיפים מודל בהתאמה אישית לפרויקט Firebase, אפשר להפנות למודל באפליקציות באמצעות השם שצוין. בכל שלב תוכלו להעלות מודל חדש של TensorFlow Lite, והאפליקציה תוריד את המודל החדש ותתחיל להשתמש בו בפעם הבאה שהיא תופעל מחדש. אתם יכולים להגדיר את התנאים במכשיר שנדרשים כדי שהאפליקציה תנסה לעדכן את המודל (ראו בהמשך).
חבילות של מודלים עם אפליקציה
כדי לארוז את מודל TensorFlow Lite עם האפליקציה, מעתיקים את קובץ המודל (בדרך כלל מסתיים ב-.tflite
או ב-.lite
) לתיקייה assets/
של האפליקציה. (יכול להיות שתצטרכו ליצור את התיקייה קודם. לשם כך, לוחצים לחיצה ימנית על התיקייה app/
ואז לוחצים על חדש > תיקייה > תיקיית נכסים).
לאחר מכן, מוסיפים את השורות הבאות לקובץ build.gradle
של האפליקציה כדי לוודא ש-Gradle לא יבצע דחיסה של המודלים בזמן בניית האפליקציה:
android {
// ...
aaptOptions {
noCompress "tflite" // Your model's file extension: "tflite", "lite", etc.
}
}
קובץ המודל ייכלל בחבילת האפליקציה ויהיה זמין ל-ML Kit כנכס גולמי.
טעינת המודל
כדי להשתמש במודל TensorFlow Lite באפליקציה, קודם צריך להגדיר את ML Kit עם המיקומים שבהם המודל זמין: מרחוק באמצעות Firebase, באחסון מקומי או בשניהם. אם מציינים גם מודל מקומי וגם מודל מרוחק, אפשר להשתמש במודל המרוחק אם הוא זמין, ואם הוא לא זמין, המערכת תחזור למודל שמאוחסן באופן מקומי.הגדרת מודל שמתארח ב-Firebase
אם איחסנתם את המודל ב-Firebase, צריך ליצור אובייקט FirebaseCustomRemoteModel
ולציין את השם שהקציתם למודל כששלחתם אותו:
Java
FirebaseCustomRemoteModel remoteModel =
new FirebaseCustomRemoteModel.Builder("your_model").build();
Kotlin
val remoteModel = FirebaseCustomRemoteModel.Builder("your_model").build()
לאחר מכן מתחילים את משימת ההורדה של המודל, ומציינים את התנאים שבהם רוצים לאפשר הורדה. אם המודל לא נמצא במכשיר, או אם יש גרסה חדשה יותר של המודל, המשימה תוריד את המודל מ-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.
}
הרבה אפליקציות מתחילות את משימת ההורדה בקוד האתחול שלהן, אבל אפשר לעשות את זה בכל שלב לפני שצריך להשתמש במודל.
הגדרת מודל מקומי
אם צרפתם את המודל לאפליקציה, יוצרים אובייקט FirebaseCustomLocalModel
ומציינים את שם הקובץ של מודל TensorFlow Lite:
Java
FirebaseCustomLocalModel localModel = new FirebaseCustomLocalModel.Builder()
.setAssetFilePath("your_model.tflite")
.build();
Kotlin
val localModel = FirebaseCustomLocalModel.Builder()
.setAssetFilePath("your_model.tflite")
.build()
יצירת מתורגמן מהמודל
אחרי שמגדירים את מקורות המודל, יוצרים אובייקט FirebaseModelInterpreter
מאחד מהם.
אם יש לכם רק מודל שמשולב באופן מקומי, פשוט יוצרים מתורגמן מהאובייקט 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)
אם יש לכם מודל שמתארח מרחוק, תצטרכו לוודא שהוא הורד לפני שתפעילו אותו. אפשר לבדוק את סטטוס ההורדה של המודל באמצעות השיטה isModelDownloaded()
של הכלי לניהול מודלים.
למרות שצריך לאשר את זה רק לפני שמריצים את המפענח, אם יש לכם גם מודל שמתארח מרחוק וגם מודל שצורף באופן מקומי, כדאי לבצע את הבדיקה הזו כשיוצרים מופע של מפענח המודל: ליצור מפענח מהמודל המרוחק אם הוא הורד, וממפענח המודל המקומי אחרת.
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)
}
אם יש לכם רק מודל שמתארח מרחוק, אתם צריכים להשבית את הפונקציונליות שקשורה למודל – למשל, להאפיר או להסתיר חלק מממשק המשתמש – עד שתאשרו שהמודל הורד. כדי לעשות את זה, צריך לצרף מאזין לשיטה download()
של מנהל המודלים:
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.
}
מציינים את הקלט והפלט של המודל
בשלב הבא, מגדירים את פורמטי הקלט והפלט של מתורגמן המודל.
מודל TensorFlow Lite מקבל כקלט ומפיק כפלט מערך רב-ממדי אחד או יותר. המערכים האלה מכילים את הערכים byte
, int
, long
או float
. צריך להגדיר את ML Kit עם המספר והממדים ("צורה") של המערכים שהמודל משתמש בהם.
אם אתם לא יודעים מה הצורה וסוג הנתונים של הקלט והפלט של המודל, אתם יכולים להשתמש במתורגמן TensorFlow Lite Python כדי לבדוק את המודל. לדוגמה:
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'>
אחרי שקובעים את הפורמט של הקלט והפלט של המודל, אפשר להגדיר את מתורגמן המודל של האפליקציה על ידי יצירת אובייקט FirebaseModelInputOutputOptions
.
לדוגמה, מודל סיווג תמונות בנקודה צפה עשוי לקבל כקלט מערך Nx224x224x3 של ערכי float
, שמייצג אצווה של N תמונות 224x224 עם שלושה ערוצים (RGB), ולהפיק כפלט רשימה של 1,000 ערכי float
, שכל אחד מהם מייצג את ההסתברות שהתמונה היא חלק מאחת מ-1,000 הקטגוריות שהמודל חוזה.
במקרה של מודל כזה, צריך להגדיר את הקלט והפלט של כלי ההסבר של המודל כמו שמוצג בהמשך:
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()
ביצוע הסקה על נתוני קלט
לבסוף, כדי לבצע הסקה באמצעות המודל, צריך לקבל את נתוני הקלט ולבצע את כל השינויים בנתונים שנדרשים כדי לקבל מערך קלט בצורה הנכונה עבור המודל.לדוגמה, אם יש לכם מודל לסיווג תמונות עם צורת קלט של [1 224 224 3] ערכים מסוג floating-point, תוכלו ליצור מערך קלט מאובייקט Bitmap
כמו בדוגמה הבאה:
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 } }
לאחר מכן, יוצרים אובייקט FirebaseModelInputs
עם נתוני הקלט, ומעבירים אותו ואת מפרט הקלט והפלט של המודל לשיטה run
של המתורגמן של המודל:
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 // ... }
אם השיחה מצליחה, אפשר לקבל את הפלט על ידי קריאה לשיטה getOutput()
של האובייקט שמועבר למאזין ההצלחה. לדוגמה:
Java
float[][] output = result.getOutput(0); float[] probabilities = output[0];
Kotlin
val output = result.getOutput<Array<FloatArray>>(0) val probabilities = output[0]
אופן השימוש בפלט תלוי במודל שבו משתמשים.
לדוגמה, אם אתם מבצעים סיווג, בשלב הבא תוכלו למפות את האינדקסים של התוצאה לתוויות שהם מייצגים:
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])) }
נספח: אבטחת מודלים
לא משנה איך אתם מעמידים את מודלי TensorFlow Lite לרשות ML Kit, ML Kit שומר אותם בפורמט protobuf סדרתי רגיל באחסון המקומי.
תיאורטית, המשמעות היא שכל אחד יכול להעתיק את המודל שלכם. עם זאת, בפועל, רוב המודלים הם ספציפיים לאפליקציה ומוסתרים על ידי אופטימיזציות, כך שהסיכון דומה לסיכון שמתחרים יפרקו את הקוד שלכם וישתמשו בו מחדש. עם זאת, חשוב להיות מודעים לסיכון הזה לפני שמשתמשים במודל מותאם אישית באפליקציה.
ב-Android API ברמה 21 (Lollipop) ואילך, המודל מורד לספרייה שלא נכללת בגיבוי האוטומטי.
ב-Android API ברמה 20 ומטה, המודל מוריד לספרייה
בשם com.google.firebase.ml.custom.models
באחסון הפנימי הפרטי של האפליקציה. אם הפעלתם גיבוי של קבצים באמצעות BackupAgent
,
אולי תרצו להחריג את הספרייה הזו.