TFLite 모델 Inference(배포하기)

Updated:

TFLite에 대한 개발 workflow를 연습할 수 있는 GitHub repo my-tf-training에 대한 설명서이다.

이번 글에서는 변환 과정을 완료한 TFLite 모델을 이용하여 Inference를 실행해보자. TFLite 모델은 IOT, Embedded 기기 등에서 실행되는 것이 목적이다. 이를 위해서 여러가지 플랫폼과 언어를 사용할 배포를 지원한다. 모바일 생태계에서는 대표적으로 안드로이드와 iOS 플랫폼에서 TFLite inference를 지원한다. 언어는 C++, Java, Python 등을 사용할 수 있다.

여기에서는 C++, Python 그리고 Android 어플을 사용해서 TFLite 모델을 Inference 실행하는 방법을 살펴보겠다. 3가지 플랫폼(or 언어)으로 Inference를 살펴보는 예시이며, 3가지 예제를 모두 이해해야 하는 것은 아니다. 각 개발자들은 ML 모델을 서비스할 상황에 맞게 어떤 플랫폼(or 언어)을 사용해서 inference를 수행할지 선택할 수 있다. 또한 여기서는 3가지 언어에 대한 예제를 보였지만, 3가지 이외의 언어도 있으니 필요하다면 정보를 더 찾아보면 되겠다.

생성한 네트워크 모델이 간단한 행태였기 때문에, Inference 코드도 복잡하지는 않다. TFLite 모델 생성에서 변환 후 Inference까지의 과정을 살펴보기는 것이 목적이기에 간단하 모델을 사용한 것이다. 실제로 ML 모델을 선정하고 Inference 코드를 작성하게 되면 이렇게 간단한 코드가 되지는 않는다.

1. 파이썬으로 TFLite 모델 Inference

051-inference-tflite-in-python.py에서 python 예시를 코드를 확인할 수 있다.

tflite 모델 파일을 로드하고 allocate_tensor() 함수로 텐서들을 초기화한다.

interpreter = tf.lite.Interpreter(model_path="./saved_model/my_model.tflite")
interpreter.allocate_tensors()

input data를 만들 차례이다. numpy의 randint 함수를 사용해서 0~1000 사이의 값을 input_shape 만큼 생성해서 input_data 변수에 저장할 것이다.

# Test model on random input data.
input_shape = input_details[0]['shape']
#input_data = np.array(np.random.random_sample(input_shape), dtype=np.float32)
input_data = np.array(np.random.randint(0,1000, size=input_shape), dtype=np.float32)

input_data 변수를 tflite model의 input으로 할당하고, inference를 실행한다. invoke가 inference를 실행하는 함수이다.

interpreter.set_tensor(input_details[0]['index'], input_data)

interpreter.invoke()

invoke 실행 후에, get_tensor() 함수를 사용해서 output 텐서에 저장되어 있는 값을 꺼내오면 된다.

output_data = interpreter.get_tensor(output_details[0]['index'])

python 파일을 실행해본 결과이다. 학습 시켰던 모델에 맞는 답이 대충 맞는 듯하다.

$ python3 051-inference-tflite-in-python.py 
input : [[81.]]
output : [[8107.7734]]

전체 과정을 간단히 정리하면 아래 순서와 같다.

  1. tflite 모델을 로드하고 텐서들을 초기화
  2. input 값을 설정
  3. invoke를 실행
  4. ouput의 tensor 값을 꺼내오면 끝

2. CPP로 TFLite 모델 Inference

tensroflow git repo에 친절하게 cpp를 사용한 예제가 있다. 이 예제 파일을 이용하면 간단히 cpp에서의 TFLite 모델 interence를 구현할 수 있다.

minimal.cc를 조금 수정하여 우리 모델에 맞는 inference를 구현한 commit 1445444를 반영해 놓았다. commit 1445444의 minimal.cc 파일을 살펴보자.

tflite 모델을 로드하고 빌드 과정을 거친다. 이 예제에서는 tflite 모델 파일을 실행 시에 인자로 넣게 되어 있다.

// Load model
std::unique_ptr<tflite::FlatBufferModel> model =
    tflite::FlatBufferModel::BuildFromFile(filename);

// Build the interpreter
tflite::ops::builtin::BuiltinOpResolver resolver;
InterpreterBuilder builder(*model, resolver);
std::unique_ptr<Interpreter> interpreter;
builder(&interpreter);

전체 텐서들을 초기화 시킨다.

// Allocate tensor buffers.
TFLITE_MINIMAL_CHECK(interpreter->AllocateTensors() == kTfLiteOk);

input 값으로 10을 넣어보자.

// Fill input buffers
// TODO(user): Insert code to fill input tensors
interpreter->typed_input_tensor<float>(0)[0] = 10;

모델의 input 값이 설정되었으니 inference를 실행한다.

// Run inference
TFLITE_MINIMAL_CHECK(interpreter->Invoke() == kTfLiteOk);

이제 output 텐서 값을 꺼내오면 끝이다.

// Read output buffers
// TODO(user): Insert getting data out code.
float result = interpreter->typed_output_tensor<float>(0)[0];
fprintf(stderr, "minimal : %d\n", int(result));

이제 minimal 프로젝트를 빌드해보자. 작업을 한 tensorflow repo의 최상위 폴더 위치에서 아래 bazel build를 실행하면 miminal 프로젝트 빌드가 실행된다.(물론 build 전에 bazel 설치가 되어 있어야 한다.)

$ bazel build //tensorflow/lite/examples/minimal:minimal

빌드가 완료되면 bazel-bin 폴더가 생겼을 것이다. 아래 경로를 따라 가면 minimal 바이너리 파일이 존재한다.

tensorflow/bazel-bin/tensorflow/lite/examples/minimal/minimal

아래처럼 tflite 모델 파일을 인자로 기재해야 한다. cc 파일 작성할때 인자로 받게 해두었기 때문이다. 로그 좀 나오는데 끝 부분을 보면 inference 결과가 출력되어 있다.

tensorflow/bazel-bin/tensorflow/lite/examples/minimal$ ./minimal my_model.tflite
...
minimal : 1010

코드 작성 시에 넣은 input 값이 10이었으니, cpp inference도 대락 정상으로 수행되었다.

3. Android 플랫폼에서의 TFLite 모델 Inference

053-inference-tflite-in-java-on-android 폴더에 안드로이드 어플을 사용한 TFLite 모델 Inference 코드를 작성해두었다. 주요 코드를 같이 살펴보자.

빌드를 위한 gradle 설정. my_model.tflite 파일을 aasets 폴더에 추가하는데 noCompress 옵셥을 달아줘야 한다.

aaptOptions {
    noCompress "tflite"
}

noCompress 옵셥이 없으면, 디폴트로 압축을 실행하며 아래와같은 런타임 에러가 발생한다.

06-07 11:42:16.498 17630 17630 E AndroidRuntime: Caused by: java.lang.IllegalArgumentException: Model ByteBuffer should be either a MappedByteBuffer of the model file, or a direct ByteBuffer using ByteOrder.nativeOrder() which contains bytes of model content.
06-07 11:42:16.498 17630 17630 E AndroidRuntime: 	at org.tensorflow.lite.NativeInterpreterWrapper.<init>(NativeInterpreterWrapper.java:56)
06-07 11:42:16.498 17630 17630 E AndroidRuntime: 	at org.tensorflow.lite.Interpreter.<init>(Interpreter.java:239)

tensorflow-lite 바이너리 추가 설정들이다. 아래 방법은 JCenter에 등록되어 있는 TensorFlow Lite AAR 파일을 다운로드해서 어플 빌드에 사용하는 방법이다. nightly는 특정 버전을 기재하지 않고 최신 버전을 사용하겠다는 의미이다. 간단히 어플을 빌드하게 위해서는 JCenter 파일을 사용하면 되는데, 개발 상황에 맞게 수정한 바이너리도 사용 가능하다. 이런 경우 tensorflow lite를 로킬 빌드해야 한다. (tensorflow lite 바이너리의 로컬 빌드 방법은 따로 정리를 할 예정이다.)

// Build off of nightly TensorFlow Lite
implementation('org.tensorflow:tensorflow-lite:0.0.0-nightly') { changing = true }
implementation('org.tensorflow:tensorflow-lite-gpu:0.0.0-nightly') { changing = true }
implementation('org.tensorflow:tensorflow-lite-support:0.0.0-nightly') { changing = true }

MainActivity.java 파일에 interpreter를 사용한 inference 과정이 있다. input 변수에 15를 할당하였고, run을 실행하면 inference가 수행된다. python, cpp 코드에서 invoke에 해당하는 코드가 run이 함수이다.

tflite = new Interpreter(tfliteModel, tfliteOptions);

float[][] inputs = new float[1][1];
inputs[0][0] = 15.0f;
float[][] outputs = new float[1][1];
tflite.run(inputs, outputs);
Log.e(TAG, "Tensorflow Lite result : " + outputs[0][0]);

Android Studio로 빌드를 수행하면 아래와같은 결과를 볼 수 있다. 간단한 예제 코드이므로 UI는 없고, 로그로 결과값을 볼 수 있게 해두었다. input 값이 15였는데 결과를 보니 제대로 동작한 것으로 보인다.

06-07 14:26:04.595 18901 18901 E MyActivity: Tensorflow Lite Start
06-07 14:26:04.602 18901 18901 I tflite  : Initialized TensorFlow Lite runtime.
06-07 14:26:04.604 18901 18901 E MyActivity: Tensorflow Lite result : 1510.2397

본 예제에서는 NnApiDelegate를 사용하는 예제 코드도 추가되어 있다. useNnApi 플래그를 true로 변경해주면, NnApiDelegate를 사용해서 interence를 수행한다.

protected Boolean useNnApi = false;

Delegate는 Android 플랫폼에서 이용할 수 있는 HW 가속기이다. 현재 사용 가능한 Delegat는 NNAPI, GPU, DSP 등이 있다. HW 가속기이므로 tflite가 지원하는 HWd이어야 한다. Delegate는 굳이 사용하지 않아도 되지만, HW 가속기를 제대로 사용하면 성능을 극대화할 수 있다. 여기서는 예를 보여주기 위해서 넣어주었다. 특히, GpuDelegate 코드는 본 예제에서는 import는 하였지만 전혀 사용하지 않고 있다.

useNnApi 플래그를 true로 설정하고, NnApiDelegate를 사용해서 interence를 수행한 로그이다.

06-07 11:43:21.446 17875 17875 E MyActivity: Tensorflow Lite Start
06-07 11:43:21.454 17875 17875 I tflite  : Created TensorFlow Lite delegate for NNAPI.
06-07 11:43:21.456 17875 17875 I tflite  : Initialized TensorFlow Lite runtime.
06-07 11:43:21.457 17875 17875 I Manager : DeviceManager::DeviceManager
06-07 11:43:21.457 17875 17875 I Manager : findAvailableDevices
06-07 11:43:21.460 17875 17875 E MyActivity: Tensorflow Lite result : 1510.2397

HW Delegate는 복잡한 모델을 사용할때(=큰 사이즈 모델) 성능을 최적화하기 위해서 필수로 인식되고 있다. TensorFlow를 이용해 모델을 학습할때 GPU 가속기를 사용하는 것과 의미가 유사하다. HW Delegate도 흥미로운 주제로 따로 정리하는 글을 준비해보겠다.



Leave a comment