از یک مدل TensorFlow Lite برای استنباط با ML Kit در اندروید استفاده کنید

شما می‌توانید از ML Kit برای انجام استنتاج روی دستگاه با یک مدل TensorFlow Lite استفاده کنید.

این API به اندروید SDK سطح ۱۶ (Jelly Bean) یا جدیدتر نیاز دارد.

قبل از اینکه شروع کنی

  1. اگر هنوز Firebase را به پروژه اندروید خود اضافه نکرده‌اید، آن را اضافه کنید.
  2. وابستگی‌های کتابخانه‌های اندروید ML Kit را به فایل 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 مراجعه کنید.

مدل خود را میزبانی یا بسته‌بندی کنید

قبل از اینکه بتوانید از یک مدل TensorFlow Lite برای استنتاج در برنامه خود استفاده کنید، باید مدل را در دسترس ML Kit قرار دهید. ML Kit می‌تواند از مدل‌های TensorFlow Lite که از راه دور با استفاده از Firebase میزبانی می‌شوند، همراه با فایل باینری برنامه یا هر دو استفاده کند.

با میزبانی یک مدل در Firebase، می‌توانید مدل را بدون انتشار نسخه جدید برنامه به‌روزرسانی کنید و می‌توانید از Remote Config و A/B Testing برای ارائه پویای مدل‌های مختلف به مجموعه‌های مختلف کاربران استفاده کنید.

اگر تصمیم دارید مدل را فقط با میزبانی آن در Firebase ارائه دهید و آن را با برنامه خود همراه نکنید، می‌توانید حجم اولیه دانلود برنامه خود را کاهش دهید. البته به خاطر داشته باشید که اگر مدل با برنامه شما همراه نباشد، هیچ یک از عملکردهای مرتبط با مدل تا زمانی که برنامه شما برای اولین بار مدل را دانلود نکند، در دسترس نخواهد بود.

با ترکیب مدل خود با برنامه‌تان، می‌توانید مطمئن شوید که ویژگی‌های یادگیری ماشین برنامه‌تان حتی زمانی که مدل میزبانی‌شده توسط Firebase در دسترس نیست، همچنان کار می‌کنند.

میزبانی مدل‌ها در فایربیس

برای میزبانی مدل TensorFlow Lite خود در Firebase:

  1. در بخش ML Kit از کنسول Firebase ، روی تب Custom کلیک کنید.
  2. روی افزودن مدل سفارشی (یا افزودن مدل دیگر ) کلیک کنید.
  3. نامی را مشخص کنید که برای شناسایی مدل شما در پروژه Firebase استفاده خواهد شد، سپس فایل مدل TensorFlow Lite (که معمولاً به .tflite یا .lite ختم می‌شود) را آپلود کنید.
  4. در فایل مانیفست برنامه خود، اعلام کنید که مجوز اینترنت مورد نیاز است:
    <uses-permission android:name="android.permission.INTERNET" />

پس از افزودن یک مدل سفارشی به پروژه Firebase خود، می‌توانید با استفاده از نامی که مشخص کرده‌اید، به مدل در برنامه‌های خود ارجاع دهید. در هر زمانی، می‌توانید یک مدل جدید TensorFlow Lite را آپلود کنید و برنامه شما مدل جدید را دانلود کرده و پس از راه‌اندازی مجدد بعدی، شروع به استفاده از آن خواهد کرد. می‌توانید شرایط دستگاه مورد نیاز برای به‌روزرسانی مدل توسط برنامه خود را تعریف کنید (به زیر مراجعه کنید).

مدل‌ها را با یک برنامه بسته‌بندی کنید

برای اتصال مدل TensorFlow Lite به برنامه‌تان، فایل مدل (که معمولاً به .tflite یا .lite ختم می‌شود) را در پوشه assets/ برنامه‌تان کپی کنید. (ممکن است لازم باشد ابتدا با کلیک راست روی app/ folder و سپس کلیک روی New > Folder > Assets Folder، پوشه را ایجاد کنید.)

سپس، کد زیر را به فایل 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() در model manager بررسی کنید.

اگرچه شما فقط باید قبل از اجرای مفسر این را تأیید کنید، اگر هم یک مدل میزبانی شده از راه دور و هم یک مدل بسته‌بندی شده به صورت محلی دارید، انجام این بررسی هنگام نمونه‌سازی مفسر مدل منطقی است: اگر مدل از راه دور دانلود شده است، یک مفسر از آن و در غیر این صورت از مدل محلی ایجاد کنید.

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() در model manager انجام دهید:

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 برای بررسی مدل خود استفاده کنید. برای مثال:

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 x224x224x3 از مقادیر float را به عنوان ورودی دریافت کند که نشان‌دهنده دسته‌ای از N تصویر سه کاناله (RGB) با ابعاد 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() از شیء که به شنونده‌ی success ارسال شده است، خروجی را دریافت کنید. برای مثال:

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 در حافظه محلی ذخیره می‌کند.

در تئوری، این بدان معناست که هر کسی می‌تواند مدل شما را کپی کند. با این حال، در عمل، اکثر مدل‌ها آنقدر مختص به یک برنامه هستند و با بهینه‌سازی‌ها مبهم شده‌اند که خطر آن مشابه خطر جداسازی و استفاده مجدد از کد شما توسط رقبا است. با این وجود، قبل از استفاده از یک مدل سفارشی در برنامه خود، باید از این خطر آگاه باشید.

در اندروید API سطح ۲۱ (لالی‌پاپ) و جدیدتر، مدل در دایرکتوری‌ای دانلود می‌شود که از پشتیبان‌گیری خودکار مستثنی است.

در اندروید API سطح ۲۰ و بالاتر، مدل در دایرکتوری به نام com.google.firebase.ml.custom.models در حافظه داخلی app-private دانلود می‌شود. اگر پشتیبان‌گیری از فایل را با استفاده BackupAgent فعال کرده باشید، می‌توانید این دایرکتوری را مستثنی کنید.