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

يمكنك استخدام ML Kit لإجراء استنتاج على الجهاز باستخدام نموذج 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() في &quot;أداة إدارة النماذج&quot;.

على الرغم من أنّه عليك تأكيد ذلك قبل تشغيل المترجم فقط، إذا كان لديك نموذج مستضاف عن بُعد ونموذج مجمّع محليًا، قد يكون من المنطقي إجراء هذا التحقّق عند إنشاء مثيل لمترجم النموذج: إنشاء مترجم من النموذج البعيد إذا تم تنزيله، ومن النموذج المحلي في الحالات الأخرى.

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 باستخدام عدد المصفوفات وسماتها ("الشكل") التي يستخدمها النموذج.

إذا كنت لا تعرف شكل ونوع بيانات الإدخال والإخراج للنموذج، يمكنك استخدام مفسّر TensorFlow Lite Python لفحص النموذج. على سبيل المثال:

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.

على سبيل المثال، قد يتلقّى نموذج تصنيف الصور ذو النقطة العائمة كمدخل مصفوفة Nx224x224x3 من قيم float، تمثّل مجموعة من صور N 224x224 ثلاثية القنوات (أحمر وأخضر وأزرق)، وينتج كناتج قائمة من 1000 قيمة float، يمثّل كل منها احتمال أن تكون الصورة عضوًا في إحدى الفئات الـ 1000 التي يتوقّعها النموذج.

بالنسبة إلى هذا النموذج، عليك ضبط مدخلات ومخرجات مترجم النموذج كما هو موضّح أدناه:

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 والإصدارات الأقدم، يتم تنزيل النموذج إلى دليل باسم com.google.firebase.ml.custom.models في مساحة التخزين الداخلية الخاصة بالتطبيق. إذا فعّلت ميزة الاحتفاظ بنسخة احتياطية من الملفات باستخدام BackupAgent، يمكنك اختيار استبعاد هذا الدليل.