שימוש במודל TensorFlow Lite בהתאמה אישית ב-Android

אם באפליקציה שלכם נעשה שימוש במודלים מותאמים אישית של TensorFlow Lite, אתם יכולים להשתמש ב-Firebase ML כדי לפרוס את המודלים. פריסת מודלים באמצעות Firebase מאפשרת להקטין את גודל ההורדה הראשונית של האפליקציה ולעדכן את מודלי ה-ML של האפליקציה בלי להשיק גרסה חדשה שלה. בנוסף, באמצעות Remote Config ו-A/B Testing, אפשר להציג באופן דינמי מודלים שונים לקבוצות שונות של משתמשים.

מודלים של TensorFlow Lite

מודלים של TensorFlow Lite הם מודלים של למידת מכונה שעברו אופטימיזציה להפעלה במכשירים ניידים. כדי לקבל מודל TensorFlow Lite:

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

  1. אם עדיין לא הוספתם את Firebase לפרויקט Android, אתם צריכים להוסיף אותו.
  2. בקובץ Gradle של המודול (ברמת האפליקציה) (בדרך כלל <project>/<app-module>/build.gradle.kts או <project>/<app-module>/build.gradle), מוסיפים את התלות בספריית Firebase ML להורדת מודלים ל-Android. מומלץ להשתמש ב-Firebase Android BoM כדי לשלוט בניהול גרסאות של הספרייה.

    בנוסף, כחלק מההגדרה של הורדת המודלים של Firebase ML, צריך להוסיף את ה-SDK של TensorFlow Lite לאפליקציה.

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

    כשמשתמשים ב-Firebase Android BoM, האפליקציה תמיד משתמשת בגרסאות תואמות של ספריות Firebase ל-Android.

    (אפשרות חלופית)  מוסיפים תלות של הפרויקט בספריות של Firebase בלי להשתמש ב-BoM

    אם אתם בוחרים לא להשתמש ב-Firebase BoM, אתם צריכים לציין כל גרסה של ספריית Firebase בשורת התלות שלה.

    שימו לב: אם אתם משתמשים בכמה ספריות של Firebase באפליקציה, מומלץ מאוד להשתמש ב-BoM כדי לנהל את גרסאות הספרייה, וכך לוודא שכל הגרסאות תואמות.

    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:26.1.1")
    // Also add the dependency for the TensorFlow Lite library and specify its version implementation("org.tensorflow:tensorflow-lite:2.3.0")
    }
  3. במניפסט של האפליקציה, מצהירים שנדרשת הרשאת INTERNET:
    <uses-permission android:name="android.permission.INTERNET" />

1. פריסת המודל

אפשר לפרוס את המודלים המותאמים אישית של TensorFlow באמצעות מסוף Firebase או באמצעות ערכות ה-SDK של Firebase Admin ל-Python ול-Node.js. פריסה וניהול של מודלים בהתאמה אישית

אחרי שמוסיפים מודל בהתאמה אישית לפרויקט ב-Firebase, אפשר להפנות למודל באפליקציות באמצעות השם שציינתם. בכל שלב אפשר לפרוס מודל חדש של TensorFlow Lite ולהוריד אותו למכשירים של המשתמשים באמצעות קריאה ל-getModel() (ראו בהמשך).

2. הורדת המודל למכשיר והפעלה של מפרש TensorFlow Lite

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

כדי להתחיל את הורדת המודל, קוראים ל-method‏ getModel() של מוריד המודלים, מציינים את השם שהקציתם למודל כשהעליתם אותו, אם אתם רוצים להוריד תמיד את המודל העדכני ביותר, ואת התנאים שבהם אתם רוצים לאפשר הורדה.

אפשר לבחור מבין שלוש התנהגויות הורדה:

סוג ההורדה תיאור
LOCAL_MODEL מקבלים את המודל המקומי מהמכשיר. אם אין מודל מקומי זמין, ההתנהגות תהיה כמו של LATEST_MODEL. השתמשו בסוג ההורדה הזה אם אתם לא מעוניינים לבדוק אם יש עדכונים למודל. לדוגמה, אתם משתמשים בהגדרת תצורה מרחוק כדי לאחזר שמות של מודלים, ואתם תמיד מעלים מודלים עם שמות חדשים (מומלץ).
LOCAL_MODEL_UPDATE_IN_BACKGROUND מקבלים את המודל המקומי מהמכשיר ומתחילים לעדכן את המודל ברקע. אם אין מודל מקומי זמין, ההתנהגות תהיה כמו של LATEST_MODEL.
LATEST_MODEL להוריד את המודל העדכני. אם המודל המקומי הוא הגרסה העדכנית, הפונקציה מחזירה את המודל המקומי. אם לא, צריך להוריד את המודל העדכני. ההתנהגות הזו תחסום את האפליקציה עד להורדת הגרסה העדכנית (לא מומלץ). השתמשו בהתנהגות הזו רק במקרים שבהם אתם צריכים במפורש את הגרסה האחרונה.

כדאי להשבית את הפונקציונליות שקשורה למודל – לדוגמה, להפוך חלק מממשק המשתמש לאפור או להסתיר אותו – עד שמוודאים שהמודל הורד.

Kotlin

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

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

3. ביצוע הסקה על נתוני קלט

קבלת צורות הקלט והפלט של המודל

מפרש המודלים של TensorFlow Lite מקבל כקלט ומפיק כפלט מערך רב-ממדי אחד או יותר. המערכים האלה מכילים ערכים מסוג byte,‏ int,‏ long או float. לפני שמעבירים נתונים למודל או משתמשים בתוצאה שלו, צריך לדעת את מספר המערכים שבהם המודל משתמש ואת המאפיינים שלהם ("צורה").

אם בניתם את המודל בעצמכם, או אם הפורמט של הקלט והפלט של המודל מתועד, יכול להיות שכבר יש לכם את המידע הזה. אם אתם לא יודעים את הצורה וסוג הנתונים של הקלט והפלט של המודל, אתם יכולים להשתמש במפרש של TensorFlow Lite כדי לבדוק את המודל. לדוגמה:

Python

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

פלט לדוגמה:

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

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

הפעלת המפרש

אחרי שקובעים את הפורמט של הקלט והפלט של המודל, מקבלים את נתוני הקלט ומבצעים את כל השינויים הנדרשים בנתונים כדי לקבל קלט בצורה המתאימה למודל.

לדוגמה, אם יש לכם מודל לסיווג תמונות עם צורת קלט של ערכי נקודה צפה (floating-point) של [1 224 224 3], אתם יכולים ליצור קלט ByteBuffer מאובייקט Bitmap, כמו בדוגמה הבאה:

Kotlin

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

לאחר מכן, מקצים ByteBuffer גדול מספיק כדי להכיל את הפלט של המודל ומעבירים את מאגר הקלט ומאגר הפלט ל-method‏ run() של המפרש של TensorFlow Lite. לדוגמה, לצורת פלט של [1 1000] ערכים של נקודה צפה:

Kotlin

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

אופן השימוש בפלט תלוי במודל שבו אתם משתמשים.

לדוגמה, אם אתם מבצעים סיווג, בשלב הבא תוכלו למפות את האינדקסים של התוצאה לתוויות שהם מייצגים:

Kotlin

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

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

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

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

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

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