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

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

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

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

Kotlin+KTX

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] עם ערכי נקודה צפה (floating-point), אפשר ליצור קלט ByteBuffer מאובייקט Bitmap כמו בדוגמה הבאה:

Kotlin+KTX

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 גדול מספיק כדי להכיל את הפלט של המודל, להעביר את מאגר הקלט וחוצץ הפלט ל-TensorFlow Lite run(). לדוגמה, לצורת פלט של נקודה צפה (floating-point) [1 1000] ערכים:

Kotlin+KTX

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

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 סריאלי סטנדרטי ב אחסון מקומי.

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

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

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