استخدام نموذج TensorFlow Lite للاستنتاج باستخدام "مجموعة أدوات تعلُّم الآلة" على Android

يمكنك استخدام "حزمة تعلُّم الآلة" لإجراء الاستنتاج على الجهاز باستخدام نموذج TensorFlow Lite.

تتطلّب واجهة برمجة التطبيقات هذه استخدام الإصدار 16 من حزمة تطوير البرامج (SDK) لنظام التشغيل Android (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 وعدم تجميعه مع تطبيقك، يمكنك تقليل حجم التنزيل الأولي لتطبيقك. يُرجى العِلم أنّه في حال عدم تجميع النموذج مع تطبيقك، لن تكون أي وظيفة ذات صلة بالنموذج متاحة إلى أن ينزِّل تطبيقك النموذج للمرة الأولى.

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

استضافة النماذج على Firebase

لاستضافة نموذج TensorFlow Lite على Firebase:

  1. في قسم ML Kit ضمن وحدة تحكّم Firebase، انقر على علامة التبويب مخصّص.
  2. انقر على إضافة نموذج مخصّص (أو إضافة نموذج آخر).
  3. حدِّد اسمًا سيتم استخدامه لتحديد النموذج في مشروعك على Firebase، ثم حمِّل ملف نموذج TensorFlow Lite (ينتهي عادةً ب.tflite أو .lite).
  4. في بيان تطبيقك، يجب الإفصاح عن أنّ إذن INTERNET مطلوب:
    <uses-permission android:name="android.permission.INTERNET" />

بعد إضافة نموذج مخصّص إلى مشروعك على Firebase، يمكنك الإشارة إلى النموذج في تطبيقاتك باستخدام الاسم الذي حدّدته. يمكنك في أي وقت تحميل نموذج TensorFlow Lite جديد، وسينزِّل تطبيقك النموذج الجديد وي يبدأ استخدامه عند إعادة تشغيل التطبيق في المرة التالية. يمكنك تحديد شروط الجهاز المطلوبة لمحاولة تطبيقك تحديث النموذج (راجِع ما يلي).

تجميع النماذج مع تطبيق

لتجميع نموذج TensorFlow Lite مع تطبيقك، انسخ ملف النموذج (الذي ينتهي عادةً ب.tflite أو .lite) إلى مجلد assets/ في تطبيقك. (قد تحتاج إلى إنشاء المجلد أولاً من خلال النقر بزر الماوس الأيمن على مجلد app/، ثم النقر على جديد > مجلد > مجلد مواد العرض).

بعد ذلك، أضِف ما يلي إلى ملف build.gradle في تطبيقك لضمان عدم ضغط Gradle للنماذج عند إنشاء التطبيق:

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.

على سبيل المثال، قد يأخذ نموذج تصنيف الصور بنقطة عائمة كمدخل Nصفيفًا بحجم ‎224×224×3 من قيم float، يمثّل مجموعة منN صور بدقة 224×224 وثلاث قنوات (RGB)، وينتج كمخرج قائمة بقيمة float 1,000، يمثّل كلّ منها احتمال أن تكون الصورة عضوًا في إحدى الفئات الـ 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
            // ...
        }

في حال نجاح المكالمة، يمكنك الحصول على الإخراج من خلال استدعاء طريقة 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 التسلسلي العادي في مساحة التخزين المحلية.

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

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

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