שימוש במודל 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. לוחצים על Add custom model (הוספת מודל מותאם אישית) או על Add another model (הוספת מודל נוסף).
  3. מציינים שם שישמש לזיהוי המודל בפרויקט Firebase, ואז מעלים את קובץ המודל של TensorFlow Lite (בדרך כלל מסתיים ב-.tflite או ב-.lite).
  4. במניפסט של האפליקציה, מצהירים על הצורך בהרשאת INTERNET:
    <uses-permission android:name="android.permission.INTERNET" />

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

איך מחברים מודלים לאפליקציה

כדי לארוז את מודל TensorFlow Lite באפליקציה, מעתיקים את קובץ המודל (בדרך כלל עם הסיומת .tflite או .lite) לתיקייה assets/ של האפליקציה. (יכול להיות שתצטרכו ליצור את התיקייה קודם. לשם כך, לוחצים לחיצה ימנית על התיקייה app/ ואז על New > Folder > Assets Folder).

לאחר מכן, מוסיפים את הקטע הבא לקובץ build.gradle של האפליקציה כדי לוודא ש-Gradle לא ילחץ את המודלים בזמן ה-build של האפליקציה:

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 את המספר והמאפיינים ('הצורה') של המערכים שבהם המודלים משתמשים.

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

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.

לדוגמה, מודל לסיווג תמונות בנקודות צפות עשוי לקבל כקלט מערך של ערכים של float בגודל Nx224x224x3, שמייצגים קבוצה של 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], תוכלו ליצור מערך קלט מאובייקט 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
            // ...
        }

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