استخدام نموذج 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 Optimization Converter.

استضافة النموذج أو تجميعه

قبل أن تتمكّن من استخدام نموذج TensorFlow Lite للاستنتاج في تطبيقك، يجب إتاحة النموذج لأداة تعلّم الآلة. ويمكن أن تستخدم حزمة ML Kit نماذج TensorFlow Lite المُستضافة عن بُعد باستخدام Firebase أو المرفقة مع البرنامج الثنائي للتطبيق أو كليهما.

وعند استضافة نموذج على Firebase، يمكنك تعديل النموذج بدون طرح إصدار جديد من التطبيق، ويمكنك استخدام "الإعداد عن بُعد" و"اختبار A/B" لعرض نماذج مختلفة ديناميكيًا لمجموعات مختلفة من المستخدمين.

إذا اخترت توفير النموذج من خلال استضافته مع 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 في تطبيقك، عليك أولاً ضبط حزمة تعلّم الآلة في المواقع الجغرافية التي يتوفّر فيها نموذجك، والتي تشمل استخدام Firebase عن بُعد أو مساحة التخزين المحلية أو كليهما. في حال تحديد نموذج محلي وآخر بعيد، يمكنك استخدام النموذج عن بُعد إذا كان متاحًا، والعودة إلى النموذج المُخزَّن محليًا في حال عدم توفُّر النموذج عن بُعد.

ضبط نموذج مستضاف على Firebase

إذا استضفت النموذج باستخدام Firebase، أنشِئ عنصر FirebaseCustomRemoteModel، مع تحديد الاسم الذي حدّدته للنموذج عند تحميله:

Java

FirebaseCustomRemoteModel remoteModel =
        new FirebaseCustomRemoteModel.Builder("your_model").build();

Kotlin+KTX

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

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

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

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

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

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. ويجب ضبط حزمة تعلّم الآلة بعدد المصفوفات التي يستخدمها نموذجك وأبعادها ("الشكل").

إذا كنت لا تعرف شكل ونوع بيانات مدخلات ومخرجات النموذج، يمكنك استخدام أداة الترجمة 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 صور ثلاثية القنوات (RGB) بحجم 224x224، وينتج عنها كمخرج منها 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+KTX

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

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

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

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

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، يمكنك اختيار استبعاد هذا الدليل.