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

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

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

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

  1. בקטע ML Kit במסוף Firebase, לוחצים על הכרטיסייה Custom (מותאם אישית).
  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

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, אולי תרצו להחריג את הספרייה הזו.