استخدام نموذج 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، عليك أيضًا إضافة حزمة تطوير البرامج (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 على 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

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 التسلسلي العادي في مساحة التخزين المحلية.

من الناحية النظرية، يعني ذلك أنّه يمكن لأي شخص نسخ نموذجك. ومع ذلك، من الناحية العملية، تكون معظم النماذج خاصة بالتطبيق ومبهمة بسبب عمليات التحسين، ما يجعل المخاطر مشابهة لمخاطر قيام المنافسين بتفكيك وإعادة استخدام الرمز البرمجي الخاص بك. ومع ذلك، يجب أن تكون على دراية بهذه المخاطر قبل استخدام نموذج مخصّص في تطبيقك.

في المستوى 21 من واجهة برمجة التطبيقات لنظام التشغيل Android (Lollipop) والإصدارات الأحدث، يتم تنزيل النموذج إلى دليل مستبعَد من ميزة "الاحتفاظ بنسخة احتياطية تلقائيًا".

في المستوى 20 من واجهة برمجة التطبيقات لنظام التشغيل Android والإصدارات الأقدم، يتم تنزيل النموذج إلى دليل باسم com.google.firebase.ml.custom.models في مساحة التخزين الداخلية الخاصة بالتطبيق. إذا فعّلت ميزة الاحتفاظ بنسخة احتياطية من الملفات باستخدام BackupAgent، يمكنك اختيار استبعاد هذا الدليل.