在 Android 上使用自訂 TensorFlow Lite 模型

如果您的應用程式使用自訂 TensorFlow Lite 模型,您可以使用 Firebase ML 部署模型。透過 Firebase 部署模型,您可以縮減應用程式的初始下載大小,也能更新應用程式的機器學習模型,而不必發布新版應用程式。此外,您也可以透過遠端設定和 A/B 測試功能,動態提供不同的模型給不同的使用者組合。

TensorFlow Lite 模型

TensorFlow Lite 模型是最佳化的機器學習模型,適合在行動裝置上執行。如要取得 TensorFlow Lite 模型,請按照下列步驟操作:

事前準備

  1. 如果您尚未將 Firebase 新增至 Android 專案,請先完成這項操作。
  2. 模組 (應用程式層級) Gradle 檔案 (通常是 <project>/<app-module>/build.gradle.kts<project>/<app-module>/build.gradle) 中,新增 Android 專用 Firebase 機器學習模型下載工具程式庫的依附元件。建議您使用 Firebase Android BoM 控管程式庫的版本管理。

    此外,在設定 Firebase 機器學習模型下載工具的過程中,您必須將 TensorFlow Lite SDK 新增至應用程式。

    dependencies {
        // Import the BoM for the Firebase platform
        implementation(platform("com.google.firebase:firebase-bom:33.1.1"))
    
        // 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:25.0.0")
    // Also add the dependency for the TensorFlow Lite library and specify its version implementation("org.tensorflow:tensorflow-lite:2.3.0")
    }
    在尋找 Kotlin 專用的程式庫模組嗎?2023 年 10 月 (Firebase BoM 32.5.0) 起,Kotlin 和 Java 開發人員都能使用主要的程式庫模組 (詳情請參閱這項計畫的常見問題)。
  3. 在應用程式的資訊清單中,宣告需要 INTERNET 權限:
    <uses-permission android:name="android.permission.INTERNET" />

1. 部署模型

您可以使用 Firebase 控制台或 Firebase Admin Python 和 Node.js SDK 部署自訂 TensorFlow 模型。請參閱「部署及管理自訂模型」。

在 Firebase 專案中加入自訂模型後,您就能使用指定的名稱在應用程式中參照模型。您隨時可以呼叫 getModel() 來部署新的 TensorFlow Lite 模型,並將新模型下載至使用者的裝置 (請見下方說明)。

2. 將模型下載至裝置,然後初始化 TensorFlow Lite 解譯器

如要在應用程式中使用 TensorFlow Lite 模型,請先使用 Firebase ML SDK 將最新版本的模型下載到裝置。接著,使用模型將 TensorFlow Lite 解譯器執行個體化。

如要開始下載模型,請呼叫模型下載工具的 getModel() 方法,指定您上傳模型時為其指派的名稱、是否一律下載最新的模型,以及要允許下載的條件。

有三種下載行為可供選擇:

下載類型 說明
LOCAL_MODEL 取得裝置的本機模型。如果沒有可用的本機模型,則行為會是 LATEST_MODEL。如果您不想檢查模型更新,請使用這個下載類型。舉例來說,您要使用遠端設定來擷取模型名稱,且一律會上傳新名稱下的模型 (建議做法)。
LOCAL_MODEL_UPDATE_IN_BACKGROUND 從裝置取得本機模型,並開始在背景更新模型。如果沒有可用的本機模型,則行為會是 LATEST_MODEL
LATEST_MODEL 取得最新模型。如果本機模型為最新版本,則傳回本機模型。否則請下載最新的模型。下載最新版本之前,這項行為會遭到封鎖 (不建議)。只有在已明確需要最新版本的情況下,才使用這項行為。

在確認下載模型之前,您應該停用模型相關功能,例如顯示為灰色或隱藏部分 UI。

Kotlin+KTX

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 模型解譯器會做為輸入內容,並產生一或多個多維度陣列的輸出內容。這些陣列包含 byteintlongfloat 值。您必須先瞭解模型所用陣列的數量和維度 (「形狀」),才能將資料傳送至模型或使用模型結果。

如果您是自行建構模型,或者系統記錄了模型的輸入和輸出格式,則您可能已有這項資訊。如果您不知道模型輸入和輸出內容的形狀和資料類型,可以使用 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] 浮點值,您可以從 Bitmap 物件產生輸入 ByteBuffer,如以下範例所示:

Kotlin+KTX

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,並將輸入緩衝區和輸出緩衝區傳遞至 TensorFlow Lite 解譯器的 run() 方法。舉例來說,如果是 [1 1000] 浮點值的輸出形狀:

Kotlin+KTX

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

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 格式儲存在本機儲存空間中。

理論上,這代表任何人都可以複製您的模型但實際上,大多數的模型都是專為特定應用程式而設計,並且經由最佳化進行混淆。這類模型的風險與競爭對手拆解及重複使用程式碼類似。儘管如此,在應用程式中使用自訂模型之前,請務必注意此風險。

在 Android API 級別 21 (Lollipop) 以上版本中,模型會下載至 從自動備份中排除的目錄。

在 Android API 級別 20 及以下級別中,模型會下載至應用程式私人內部儲存空間中名為 com.google.firebase.ml.custom.models 的目錄。如果您使用 BackupAgent 啟用檔案備份功能,可以選擇排除這個目錄。