שימוש במודל 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 model downloader, צריך להוסיף את TensorFlow Lite SDK לאפליקציה.

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:33.7.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:25.0.1")
    // Also add the dependency for the TensorFlow Lite library and specify its version implementation("org.tensorflow:tensorflow-lite:2.3.0")
    }
    מחפשים מודול ספרייה ספציפי ל-Kotlin? החל מ-אוקטובר 2023 (Firebase BoM 32.5.0), מפתחי Kotlin ומפתחי Java יוכלו להסתמך על מודול הספרייה הראשי (פרטים נוספים זמינים בשאלות הנפוצות לגבי היוזמה הזו).
  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 עם המודל.

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

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

סוג ההורדה תיאור
LOCAL_MODEL אחזור הדגם המקומי מהמכשיר. אם אין מודל מקומי זמין, הפונקציה מתנהגת כמו LATEST_MODEL. כדאי להשתמש בסוג ההורדה הזה אם אתם לא רוצים לבדוק אם יש עדכונים למדגם. לדוגמה, אתם משתמשים ב-Remote Config כדי לאחזר שמות של מודלים, ותמיד מעלים מודלים בשמות חדשים (מומלץ).
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'>

הרצת המתורגמן

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

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

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

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

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