שימוש במודל TensorFlow Lite לצורך הסקת מסקנות בעזרת ML Kit ב-Android

אפשר להשתמש ב-ML Kit כדי לבצע הסקת מסקנות במכשיר באמצעות מודל TensorFlow Lite.

ל-API הזה נדרשת Android SDK ברמה 16 (Jelly Bean) ואילך.

לפני שמתחילים

  1. אם עדיין לא עשיתם זאת, מוסיפים את Firebase לפרויקט Android.
  2. הוספת יחסי התלות של ספריות 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'
    }
  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, ולא ב-Firebase לצרף אותו לאפליקציה שלך, אפשר להקטין את גודל ההורדה הראשוני של האפליקציה. עם זאת, חשוב לזכור שאם המודל לא נכלל בחבילה עם האפליקציה שלכם, שקשורה למודלים לא יהיו זמינים עד שהאפליקציה תוריד את בפעם הראשונה.

שילוב המודל עם האפליקציה מאפשר לוודא שיש בה תכונות ללמידת מכונה ימשיכו לפעול כשהמודל שמתארח ב-Firebase לא זמין.

אירוח מודלים ב-Firebase

כדי לארח את מודל TensorFlow Lite ב-Firebase:

  1. בקטע ML Kit במסוף Firebase, לוחצים על את הכרטיסייה בהתאמה אישית.
  2. לוחצים על הוספת מודל מותאם אישית (או על הוספת מודל נוסף).
  3. מציינים שם שישמש לזיהוי המודל ב-Firebase ולאחר מכן להעלות את קובץ המודל TensorFlow Lite (בדרך כלל מסתיים ב .tflite או .lite).
  4. במניפסט של האפליקציה, צריך להצהיר שנדרשת הרשאת 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+KTX

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+KTX

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+KTX

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+KTX

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

אם יש לך רק מודל שמתארח מרחוק, עליך להשבית את התכונה שקשורה למודלים פונקציונליות - לדוגמה, הצגה באפור או הסתרה של חלק מממשק המשתמש - עד מוודאים שבוצעה הורדה של המודל. אפשר לעשות זאת על ידי צירוף listen ל-method 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+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.
    }

ציון הקלט והפלט של המודל

בשלב הבא, צריך להגדיר את פורמט הקלט והפלט של המתרגם של המודל.

מודל 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.

לדוגמה, מודל סיווג תמונות עם נקודה צפה (floating-point) עשוי לקחת כקלט Nמערך x224x224x3 של ערכי float, שמייצגים קבוצה של N תמונות תלת-ערוציות (RGB) בגודל 224x224, ומפיקות כפלט רשימה של 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+KTX

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], תוכלו ליצור מערך קלט מאובייקט 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+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
    }
}

בשלב הבא, יוצרים אובייקט 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+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
            // ...
        }

אם הקריאה מסתיימת בהצלחה, אפשר לקבל את הפלט על ידי קריאה ל-method‏ getOutput() של האובייקט שמוענק למאזין להצלחה. לדוגמה:

Java

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

Kotlin+KTX

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

נספח: אבטחת המודל

בלי קשר לאופן שבו המודלים של TensorFlow Lite יהיו זמינים ML Kit, ML Kit מאחסן אותם בפורמט פרוטוב טורי סטנדרטי ב- אחסון מקומי.

בתיאוריה, המשמעות היא שכל אחד יכול להעתיק את המודל שלכם. אבל, לפעמים בפועל, רוב המודלים הם ספציפיים לאפליקציה ומעורפלים (obfuscation) והסיכון הזה דומה לזה של מתחרים פירוק שימוש חוזר בקוד. עם זאת, חשוב להיות מודעים לסיכון זה לפני השימוש מודל מותאם אישית באפליקציה.

ב-Android API ברמה 21 (Lollipop) ואילך, הורדת המודל מתבצעת ספרייה שהיא לא נכלל בגיבוי האוטומטי.

ב-Android API ברמה 20 ואילך, המערכת מורידה את המודל לספרייה בשם com.google.firebase.ml.custom.models במצב פרטי באפליקציה ואחסון פנימי. אם הפעלתם את הגיבוי של הקבצים באמצעות BackupAgent, תוכלו להחריג את התיקייה הזו.