2021년 10월 7일 목요일

임베디드보드 및 웹 기반 딥러닝 모델 실행을 위한 Tensorflow Lite 사용하기

이 글은 임베디드보드에서 딥러닝 모델 실행을 위한 Tensorflow Lite 및 Tensorflow JS (TFJS) 사용 방법을 간략히 공유한다. 이 내용은 딥러닝 기반 에지(Edge) 컴퓨팅 구현이나 가벼운 웹브라우저 기반 딥러닝 응용 서비스 개발에 도움이 된다. 

머리말
텐서플로우 라이트와 TFJS는 개발자가 모바일, 임베디드 장치, IoT, 웹브라우저에서 딥러닝 모델을 실행할 수 있도록, 모델을 최적화하는 도구이다. 특징은 다음과 같다.
  • 특정 장치에서 실행되도록 딥러닝 모델을 최적화함. 모델 크기 및 전력 소모 축소.
  • 다중 플랫폼 지원. 안드로이드, iOS, 임베디드, Javascript 웹브라우저, 리눅스 등.
  • 하드웨어 가속 지원.

실행 개념
텐서플로우 라이트 실행 환경은 목표하는 플랫폼에 따라 다르다. 예를 들어, 임베디드 중 아두이노에서 개발한다면, 이에 대한 라이브러리가 준비되어 있어서, 이를 사용하면 된다. 
사용은 매우 쉬운 편인데, 기존 텐서플로우 딥러닝 모델을 작업한 후, 라이트버전으로 변환하면 된다. 변환함수를 당연히 제공하고, 변환시 모델의 실수 데이터 형식은 정수로 자동 변환된다. 참고로, 정수는 실수보다 크기가 4배 적고, 처리 속도는 훨씬 빠르다. 이런 이유로, GPU가 없는 컴퓨터에서도 딥러닝 모델을 빠르게 실행시킬 수 있다. 물론, 이 변환 후에는 실수형 연산 함수는 사용할 수 없다(API 호환성). 몇몇 플랫폼은 연산가속칩이 있다. 라이트는 이 기능을 이용할 수 있다.

변환된 모델은 FlatBuffers형식인 .tflite 확장자인 파일로 저장된다. 이 파일은 메타데이터를 포함할 수 있고, 이 경우, 텐서플로우 라이트 API를 사용하지 않고, 파일 로딩 후 바로 실행할 수 있다.

이제, 각 실행환경에서 사용방법을 확인해 본다.

CoLab기반 딥러닝 모델 생성 및 최적화
CoLab은 구글에서 지원하는 머신러닝 협업 및 연구용 플랫폼으로 대부분 기능이 무료이다. 
여기서는 CoLab을 사용해 MNIST를 이용해 CNN모델을 만들고, 이를 라이트버전으로 처리해본다. 직접 실행여기를 참고한다. 

다음 코드를 CoLab에 입력해 실행해 본다. 최적화하는 부분인 TFLiteConverter 함수 사용만 다르고, 나머지는 일반적인 텐서플로우 코딩과 같다는 것을 알 수 있다.

# 라이브러리 로딩
import logging
logging.getLogger("tensorflow").setLevel(logging.DEBUG)

import tensorflow as tf
from tensorflow import keras
import numpy as np
import pathlib

# MNIST 로딩
mnist = keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

# 입력 이미지 정규화 
train_images = train_images / 255.0
test_images = test_images / 255.0

# 딥러닝 모델 구조 정의
model = keras.Sequential([
  keras.layers.InputLayer(input_shape=(28, 28)),
  keras.layers.Reshape(target_shape=(28, 28, 1)),
  keras.layers.Conv2D(filters=12, kernel_size=(3, 3), activation=tf.nn.relu),
  keras.layers.MaxPooling2D(pool_size=(2, 2)),
  keras.layers.Flatten(),
  keras.layers.Dense(10)
])

# 필기체 이미지 분류 모델 학습. 1세대만 처리.
model.compile(optimizer='adam',
              loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
model.fit(
  train_images,
  train_labels,
  epochs=1,
  validation_data=(test_images, test_labels)
)

# TFLite버전으로 모델 변환
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()

tflite_models_dir = pathlib.Path("/tmp/mnist_tflite_models/") # 폴더 생성
tflite_models_dir.mkdir(exist_ok=True, parents=True)

tflite_model_file = tflite_models_dir/"mnist_model.tflite"
tflite_model_file.write_bytes(tflite_model)  # TFLite 파일로 파일 저장

# 최적화 옵션 켜서 모델 저장
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()
tflite_model_quant_file = tflite_models_dir/"mnist_model_quant.tflite"
tflite_model_quant_file.write_bytes(tflite_quant_model) 

!ls -lh {tflite_models_dir}  # CoLab 확인해 보면, 실제 최적화된 모델은 기존 모델의 1/4임

이제 저장된 TFLite 모델을 실행해보자. 아래 코드를 CoLab에 입력해 실행해 본다.
interpreter = tf.lite.Interpreter(model_path=str(tflite_model_file))  
interpreter.allocate_tensors()            # 최적화 안된 TFLite

interpreter_quant = tf.lite.Interpreter(model_path=str(tflite_model_quant_file))
interpreter_quant.allocate_tensors()   # 최적화된 TFLite

test_image = np.expand_dims(test_images[0], axis=0).astype(np.float32)  # 첫번째 테스트 이미지

input_index = interpreter.get_input_details()[0]["index"]
output_index = interpreter.get_output_details()[0]["index"]

interpreter.set_tensor(input_index, test_image)
interpreter.invoke()  # 모델 실행
predictions = interpreter.get_tensor(output_index)  # 예측 결과 얻기

import matplotlib.pylab as plt

plt.imshow(test_images[0])
template = "True:{true}, predicted:{predict}"
_ = plt.title(template.format(true= str(test_labels[0]),
                              predict=str(np.argmax(predictions[0]))))
plt.grid(False)

TFLite 모델 정확도를 평가해 보자. 아래 코드를 입력해 본다.

# TF Lite 모델 평가용 유틸리티 함수
def evaluate_model(interpreter):
  input_index = interpreter.get_input_details()[0]["index"]
  output_index = interpreter.get_output_details()[0]["index"]

  # 테스트 데이터셋 예측
  prediction_digits = []
  for test_image in test_images:
    # 전처리는 배치로 32비트 실수를 모델 데이터 형식으로 변환하도록 함
    test_image = np.expand_dims(test_image, axis=0).astype(np.float32)
    interpreter.set_tensor(input_index, test_image)

    # 모델 예측 실행
    interpreter.invoke()

    # 후처리는 가장 높은 정확도 probability 클래스를 찾음
    output = interpreter.tensor(output_index)
    digit = np.argmax(output()[0])
    prediction_digits.append(digit)

  # 그라운드 참값과 차이 비교
  accurate_count = 0
  for index in range(len(prediction_digits)):
    if prediction_digits[index] == test_labels[index]:
      accurate_count += 1
  accuracy = accurate_count * 1.0 / len(prediction_digits)

  return accuracy

print(evaluate_model(interpreter))
print(evaluate_model(interpreter_quant))

실제 평가해 보면, 각각 0.9671, 0.9669로 별 차이가 없다.

Javascript 웹 기반 딥러닝 실행
자바스크립트 웹 기반 딥러닝 실행을 위해, TFJS 라이브러리를 사용한다. 우선, 자바스크립트 node.js 를 이용해 딥러닝 모델을 작성한 후 실행해 본다(참고 - 여기와 TFJS Task API).

우선 터미널에서 아래 명령을 실행한다. 
mkdir tfjs
cd tfjs
npm install @tensorflow/tfjs-node
npm install @tensorflow/tfjs-node-gpu

demo.js 코드를 아래와 같이 작성한다.
const tf = require('@tensorflow/tfjs');  // tfjs 임포트

const model = tf.sequential();               // 시퀀스 모델 생성
model.add(tf.layers.dense({units: 100, activation: 'relu', inputShape: [10]}));    
model.add(tf.layers.dense({units: 1, activation: 'linear'})); 
model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});

const xs = tf.randomNormal ([100, 10]);   // 학습 입력 데이터
const ys = tf.randomNormal ([100, 1]);     // 결과 라벨 데이터

model.fit (xs, ys, 
{epochs : 100, 
callbacks : {onEpochEnd : (epoch, log) => console.log ( epoch + " = " + log.loss )} 
});  // 학습

터미널에서 아래 명령을 실행한다. 학습이 잘 되는 것을 확인할 수 있다.
node demo.js

웹브라우저에서 딥러닝 모델을 사용하는 방법을 확인해 보기 위해, 우분투 리눅스 터미널에서 아래 명령을 실행하고 관련 예제를 다운로드한다.

git clone https://github.com/tensorflow/tfjs-models
cd tfjs-models

테스트할 딥러닝 모델을 다운로드 설치한다. 기타 필요한 모델은 여기서 확인한다.
npm i @tensorflow-models/coco-ssd
npm i @tensorflow-models/mobilenet
npm i @tensorflow-models/deeplab

yarn 프로젝트 관리 및 빌드 도구를 설치한다. yarn에 대한 설명은 여기를 참고하라.
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install yarn
sudo npm install --global cross-env

패키지를 빌드한다.
yarn
yarn presubmit

설치된 딥러닝 모델 테스트를 위해 아래 명령을 실행한다.
cd coco-ssd
cd demo
yarn
yarn build-deps

yarn run
watch

웹서버가 실행되었다면, http://localhost:1234/ 에서 결과를 확인해 본다. 다음과 같이 정상적으로 웹브라우저에서 딥러닝이 실행되는 것을 확인할 수 있다.

COCO-SSD 모델의 demo 폴더 구조는 다음과 같다. 

주요 코드는 다음과 같다. 아래 index.html에서 웹화면 레이아웃을 정의한다.
 
<!doctype html>
<html>

<body>
  <h1>TensorFlow.js Object Detection</h1>
  <select id='base_model'>
    <option value="lite_mobilenet_v2">SSD Lite Mobilenet V2</option>
    <option value="mobilenet_v1">SSD Mobilenet v1</option>
    <option value="mobilenet_v2">SSD Mobilenet v2</option>
  </select>
  <button type="button" id="run">Run</button>
  <button type="button" id="toggle">Toggle Image</button>
  <div>
    <img id="image" />
    <canvas id="canvas" width="600" height="399"></canvas>
  </div>
</body>
<script src="index.js"></script>

</html>

실제 동작코드는 index.js 자바스크립트에 코딩된다.
import '@tensorflow/tfjs-backend-cpu';      // tfjs 패키지 임포트
import '@tensorflow/tfjs-backend-webgl';

import * as cocoSsd from '@tensorflow-models/coco-ssd';  // coco-ssd 모델 임포트

import imageURL from './image1.jpg';      // 폴더에 정의된 이미지 임포트
import image2URL from './image2.jpg';

let modelPromise;

window.onload = () => modelPromise = cocoSsd.load();

const button = document.getElementById('toggle');   // 토글버튼 클릭시 이미지 교체
button.onclick = () => {
  image.src = image.src.endsWith(imageURL) ? image2URL : imageURL;
};

const select = document.getElementById('base_model');
select.onchange = async (event) => {
  const model = await modelPromise;   // 모델 선택 변경 시 모델 재로딩
  model.dispose();
  modelPromise = cocoSsd.load(
      {base: event.srcElement.options[event.srcElement.selectedIndex].value});
};

const image = document.getElementById('image');
image.src = imageURL;

const runButton = document.getElementById('run');
runButton.onclick = async () => {   // 실행 버튼 클릭시, 이미지 입력 후 모델 예측 실행
  const model = await modelPromise;
  console.log('model loaded');
  console.time('predict1');
  const result = await model.detect(image);
  console.timeEnd('predict1');


  const c = document.getElementById('canvas');  // 캔버스 요소 획득 후 이미지 렌더링
  const context = c.getContext('2d');
  context.drawImage(image, 0, 0);
  context.font = '10px Arial';

  console.log('number of detections: ', result.length);   // 예측 결과 이미지 및 라벨 렌더링
  for (let i = 0; i < result.length; i++) {
    context.beginPath();
    context.rect(...result[i].bbox);
    context.lineWidth = 1;
    context.strokeStyle = 'green';
    context.fillStyle = 'green';
    context.stroke();
    context.fillText(
        result[i].score.toFixed(3) + ' ' + result[i].class, result[i].bbox[0],
        result[i].bbox[1] > 10 ? result[i].bbox[1] - 5 : 10);
  }
};

TFJS 라이브러리가 대부분의 딥러닝 모델 사용 과정을 캡슐화하여, 실제 사용되는 함수는 그리 많지 않음을 알 수 있다. 직접 모델을 훈련하고, 웹에서 서비스를 제공하기 위한 목적으로 아래 Tutorial을 참고할 수 있다.

마무리
TFLite를 이용하면, GPU가 없는 다양한 플랫폼에서 적은 메모리와 정확도 손실로 딥러닝 모델을 실행할 수 있다.

레퍼런스

댓글 없음:

댓글 쓰기