콘솔로 이동

Android에서 ML Kit를 통해 TensorFlow Lite 모델을 사용하여 추론

ML Kit를 통해 TensorFlow Lite 모델을 사용하여 기기별 추론을 수행할 수 있습니다.

이 API에는 Android SDK 수준 16(Jelly Bean) 이상이 필요합니다.

GitHub의 ML Kit 빠른 시작 샘플에서 이 API의 사용 예를 참조하거나 Codelab을 사용해 보세요.

시작하기 전에

  1. 아직 추가하지 않았다면 Android 프로젝트에 Firebase를 추가합니다.
  2. 프로젝트 수준 build.gradle 파일의 buildscriptallprojects 섹션에 Google의 Maven 저장소가 포함되어야 합니다.
  3. 모듈(앱 수준) Gradle 파일(일반적으로 app/build.gradle)에 ML Kit Android 라이브러리의 종속 항목을 추가합니다.
    dependencies {
      // ...
    
      implementation 'com.google.firebase:firebase-ml-model-interpreter:19.0.0'
    }
    
  4. 사용하려는 TensorFlow 모델을 TensorFlow Lite 형식으로 변환합니다. TOCO: TensorFlow Lite 최적화 변환기를 참조하세요.

모델 호스팅 또는 번들로 묶기

앱에서 추론을 위해 TensorFlow Lite 모델을 사용하려면 ML Kit에서 모델을 사용할 수 있도록 설정해야 합니다. ML Kit는 Firebase를 사용하여 원격으로 호스팅되는 TensorFlow Lite 모델 또는 앱 바이너리와 번들로 묶은 TensorFlow Lite 모델을 사용하거나 두 모델을 모두 사용할 수 있습니다.

Firebase에서 모델을 호스팅하면 앱 버전을 새롭게 출시하지 않고 모델을 업데이트할 수 있고, 원격 구성과 A/B 테스팅을 사용하여 다양한 사용자 집합에 다른 모델을 동적으로 제공할 수 있습니다.

모델을 앱과 번들로 묶지 않고 Firebase에서 호스팅하여 제공하는 방법만 선택한 경우 앱의 초기 다운로드 크기를 줄일 수 있습니다. 하지만 모델을 앱과 번들로 묶어 제공하지 않는 경우 앱에서 처음으로 모델을 다운로드하기 전에는 모델 관련 기능을 사용할 수 없습니다.

모델을 앱과 번들로 묶으면 Firebase 호스팅 모델을 사용할 수 없는 경우에도 앱의 ML 기능이 계속 작동하도록 할 수 있습니다.

Firebase에서 모델 호스팅

Firebase에서 TensorFlow Lite 모델을 호스팅하는 방법은 다음과 같습니다.

  1. Firebase ConsoleML Kit 섹션에서 커스텀 탭을 클릭합니다.
  2. 맞춤 모델 추가 또는 다른 모델 추가를 클릭합니다.
  3. Firebase 프로젝트에서 모델을 식별하는 데 사용할 이름을 지정한 다음 일반적으로 .tflite 또는 .lite로 끝나는 TensorFlow 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를 사용하는 원격 위치, 로컬 저장소 또는 둘 다)로 ML Kit를 구성합니다. 로컬 모델과 원격 모델을 둘 다 지정하면 ML Kit는 원격 모델을 사용할 수 있으면 원격 모델을 사용하고, 그렇지 않으면 로컬에 저장된 모델을 대신 사용합니다.

Firebase 호스팅 모델 구성

Firebase로 모델을 호스팅하는 경우에는 업로드할 때 모델에 할당한 이름, ML Kit에서 모델을 처음 다운로드해야 하는 조건, 업데이트가 제공되는 시점을 지정하여 FirebaseRemoteModel 객체를 만듭니다.

자바

FirebaseModelDownloadConditions.Builder conditionsBuilder =
        new FirebaseModelDownloadConditions.Builder().requireWifi();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    // Enable advanced conditions on Android Nougat and newer.
    conditionsBuilder = conditionsBuilder
            .requireCharging()
            .requireDeviceIdle();
}
FirebaseModelDownloadConditions conditions = conditionsBuilder.build();

// Build a remote model source object by specifying the name you assigned the model
// when you uploaded it in the Firebase console.
FirebaseRemoteModel cloudSource = new FirebaseRemoteModel.Builder("my_cloud_model")
        .enableModelUpdates(true)
        .setInitialDownloadConditions(conditions)
        .setUpdatesDownloadConditions(conditions)
        .build();
FirebaseModelManager.getInstance().registerRemoteModel(cloudSource);

Kotlin

var conditionsBuilder: FirebaseModelDownloadConditions.Builder =
        FirebaseModelDownloadConditions.Builder().requireWifi()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    // Enable advanced conditions on Android Nougat and newer.
    conditionsBuilder = conditionsBuilder
            .requireCharging()
            .requireDeviceIdle()
}
val conditions = conditionsBuilder.build()

// Build a remote model object by specifying the name you assigned the model
// when you uploaded it in the Firebase console.
val cloudSource = FirebaseRemoteModel.Builder("my_cloud_model")
        .enableModelUpdates(true)
        .setInitialDownloadConditions(conditions)
        .setUpdatesDownloadConditions(conditions)
        .build()
FirebaseModelManager.getInstance().registerRemoteModel(cloudSource)

로컬 모델 구성

모델을 앱과 번들로 묶은 경우에는 TensorFlow Lite 모델의 파일 이름을 지정하고 모델에 다음 단계에서 사용할 이름을 할당하여 FirebaseLocalModel 객체를 만듭니다.

자바

FirebaseLocalModel localSource =
        new FirebaseLocalModel.Builder("my_local_model")  // Assign a name to this model
                .setAssetFilePath("my_model.tflite")
                .build();
FirebaseModelManager.getInstance().registerLocalModel(localSource);

Kotlin

val localSource = FirebaseLocalModel.Builder("my_local_model") // Assign a name to this model
        .setAssetFilePath("my_model.tflite")
        .build()
FirebaseModelManager.getInstance().registerLocalModel(localSource)

모델에서 인터프리터 만들기

모델 위치를 구성한 후 원격 모델 이름이나 로컬 모델 이름 또는 둘 다 사용하여 FirebaseModelOptions 객체를 만들고 이 객체를 사용하여 FirebaseModelInterpreter의 인스턴스를 가져옵니다.

자바

FirebaseModelOptions options = new FirebaseModelOptions.Builder()
        .setRemoteModelName("my_cloud_model")
        .setLocalModelName("my_local_model")
        .build();
FirebaseModelInterpreter firebaseInterpreter =
        FirebaseModelInterpreter.getInstance(options);

Kotlin

val options = FirebaseModelOptions.Builder()
        .setRemoteModelName("my_cloud_model")
        .setLocalModelName("my_local_model")
        .build()
val interpreter = FirebaseModelInterpreter.getInstance(options)

기기에서 모델을 사용 가능한지 확인

권장: 로컬로 번들된 모델을 구성하지 않은 경우 원격 모델이 기기에 다운로드되었는지 확인합니다.

원격으로 호스팅된 모델을 실행하는 경우 기기에서 모델을 아직 사용할 수 없으면 호출이 실패하고 모델이 백그라운드에서 기기에 자동으로 다운로드됩니다. 다운로드가 완료되면 모델을 실행할 수 있습니다.

모델 다운로드 작업을 보다 명시적으로 처리하려는 경우 모델 다운로드 작업을 시작하고 ensureModelDownloaded()를 호출하여 상태를 확인할 수 있습니다.

자바

FirebaseModelManager.getInstance().ensureModelDownloaded(remoteModel)
        .addOnSuccessListener(
            new OnSuccessListener<Void>() {
              @Override
              public void onSuccess() {
                // Model downloaded successfully. Okay to use the model.
              }
            })
        .addOnFailureListener(
            new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception e) {
                // Model couldn’t be downloaded or other internal error.
                // ...
              }
            });

Kotlin

FirebaseModelManager.getInstance().ensureModelDownloaded(remoteModel)
        .addOnSuccessListener {
            // Model downloaded successfully. Okay to use the model.
        }
        .addOnFailureListener {
            // Model couldn’t be downloaded or other internal error.
            // ...
        }

필요한 경우 ensureModelDownloaded() 메소드가 모델 다운로드를 시작하고 다운로드가 끝나면 성공 리스너를 호출합니다. 모델을 이미 사용할 수 있는 경우 이 메소드는 즉시 성공 리스너를 호출합니다.

모델의 입력 및 출력 지정

다음으로 모델 인터프리터의 입력과 출력 형식을 구성합니다.

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 객체를 만들어 앱의 모델 인터프리터를 구성할 수 있습니다.

예를 들어 부동 소수점 이미지 분류 모델은 N 224x224 3채널(RGB) 이미지 배치를 나타내는 Nx224x224x3 float 값 배열을 입력으로 사용하여 1,000개의 float 값 목록을 출력할 수 있습니다. 여기에서 각각의 값은 이미지가 모델이 예측하는 1,000가지 카테고리 중 하나에 속할 확률을 나타냅니다.

이러한 모델의 경우 다음과 같이 모델 인터프리터의 입력과 출력을 구성합니다.

자바

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 객체에서 입력 배열을 생성할 수 있습니다.

자바

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 메소드에 전달합니다.

자바

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(
                object : OnFailureListener {
                    override fun onFailure(e: Exception) {
                        // Task failed with an exception
                        // ...
                    }
                })

호출이 성공하면 성공 리스너에 전달된 객체의 getOutput() 메소드를 호출하여 출력을 가져올 수 있습니다. 예를 들면 다음과 같습니다.

자바

float[][] output = result.getOutput(0);
float[] probabilities = output[0];

Kotlin

val output = result.getOutput<Array<FloatArray>>(0)
val probabilities = output[0]

출력을 사용하는 방법은 사용 중인 모델에 따라 다릅니다.

예를 들어 분류를 수행하면 결과의 색인을 색인이 나타내는 라벨에 매핑할 수 있습니다.

자바

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 형식으로 모델을 저장합니다.

즉, 이론적으로 누구나 모델을 복사할 수 있습니다. 하지만 실제로는 대부분의 모델이 애플리케이션별로 너무나 다르며 최적화를 통해 난독화되므로 위험도는 경쟁업체가 내 코드를 분해해서 재사용하는 것과 비슷한 수준입니다. 그렇지만 앱에서 맞춤 모델을 사용하기 전에 이러한 위험성을 알고 있어야 합니다.

Android API 수준 21(Lollipop) 이상에서는 자동 백업에서 제외된 디렉토리에 모델이 다운로드됩니다.

Android API 수준 20 이하에서는 앱의 비공개 내부 저장소에 있는 com.google.firebase.ml.custom.models라는 디렉토리에 모델이 다운로드됩니다. BackupAgent를 사용하여 파일 백업을 사용 설정했다면 이 디렉토리를 제외할 수 있습니다.