Firmware Technology

이민예 19-08-11

궁금한점

Mission

  1. 총 모델 사이즈가 352KB 이내의 딥러닝 모델 개발

  2. C++ 언어로 센서로부터 들어오는 입력 받고, 모델 실행하고, LED로 출력함.

    1. 언어 라이브러리 잘 선택해야함. (하드웨어는 일단, CC2652)

Reference

https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro

Tensorflow Lite for MicroControllers

마이크로컨트롤러 환경에서 프로그램은 C/C++ 언어를 사용하여 만들어진다. Caffe2 / TensorFlow Lite for MicroControllers 은 C++ 의 클래스 라이브러리를 통해 구현되어 있으므로, 각 API의 사용법을 알아두는 것은 필수적이다.

  • 하드웨어 계층

    • 즉, MicroController를 말함.

    • 마이크로컨트롤러 프로그래밍 기본은 ..

      • MCU 레지스터 조작 !!

        • MPU 레지스터

          • 연산을 수행하는데 필요한 피연산자와 연산 결과를 임시로 저장하는 임시 기억 장소 (안중요)

        • 입출력 레지스터

          • MPU 외에도 주변 장치와 데이터를 교환하기 위한 장치를 제어하는 데 필요! 번지로 레지스터 조작 가능함 (중요)

      • 쉽지 않음

  • 하드웨어 종속적 소프트웨어 계층

    • 마이크로컨트롤러의 종류에 따라 다르게 구현

    • 레지스터 조작 작업을 함수로 구현

  • 하드웨어 독립적 소프트웨어 계층 :

    • 서로 다른 MCU의 서로 다른 레지스터를 같은 함수로 조작가능

하지만, 두 API 모두 지원하는 마이크로컨트롤러가 제약적이기 때문에(즉, 다양한 제작사마다, 다른 조합의 MCU가 있고, API는 하드웨어 종속적이기 때문에) API 선정시, 마이크로 컨트롤러의 제작사를 고려하여야 하며, 또는 같은 방법으로 제어할 수 있는 방법을 찾아봐야한다. 텐서플로 같은 경우, 지원하지 않을 경우 이식하는 과정을 추가하여 해야하며, C++11 를 지원하여야 한다.

Arm CMSIS-NN software library => ML on Arm Cortex-M

Reference

https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/deploying-convolutional-neural-network-on-cortex-m-with-cmsis-nn

마이크로컨트롤러란 (MCU)?

마이크로 컨트롤러는 연산을 담당하는 마이크로프로세서(CPU)와 이를 사용하기 위한 메모리, 입출력 장치 등의 필요한 기능이 함께 직접된 장치. 마이크로컨트롤러만으로 LED 센서와 같은 장치들을 사용할 수 있기 때문에, 작은컴퓨터, 혹은 마이컴이라 불림. 마이크로 프로세서는 단순하고, 신뢰성이 있으며, 저렴하고, 저전력인 장점이 있으나, 매우 제한된 용량, 연산 성능을 가지고 있음.

MCU 제조 회사 별 특징

  • STMicroelectronics : ARM 기반의 고성능 마이크로프로세서로 유명함.

  • Texas Instruments (TI) : 세계적으로 DSP 분야에서 독보적인 선두주자임.

자사는 TI (Texas Instruments) 사의 ADS1292 칩셋 및 CC2642 칩셋을 사용하여, 동일한 제조사를 사용하여 호환성을 확보하였다. ADS1292 칩셋과 CC2642 MCU는 개발보드에 탑재하여 구동되며, ADS1292 칩셋의 연산은 사람 몸에 흐르는 전극에서 오는 복잡한 심전도 신호 (ECG) 를 센싱한 후, CC2642 MCU의 BLE5 통신 기술로 데이터를 외부 장치로 전달 및 의미있는 결과 LED로 출력함.

본 프로젝트는

  • [ DATA ] ADS1292센서에서 추출한 심전도 데이터를 가지고,

  • [ MODEL ] CC2642 마이크로컨트롤러에 경량화된 딥러닝 모델을 탑재하여

  • [ PREDICTION ] 유의미한 결과 (정상/비정상)를 LED 센서를 통하여 출력하여야 한다.

따라서, 환자는 심장박동이 불규칙한 부정맥을 파악하여, 다양한 심장질환, 사고를 예방할 수 있다.

시제품 개발보드 살펴보기

MCU 가 CC2642이라는 마이크로컨트롤러이며, 아래는 시제품 보드임. 시제품 보드는, CC2642와 ADS1292 를 탑재하고 있으며, CC2642를 주변 회로와의 연결을 통해, 보다 편리하게 사용할 수 있도록 제작된 마이크로컨트롤러 보드임. 개발이 되면, 소스코드를 패치형 MCU 에 임폴트해야함.

현재 개발 사항 : 실제 패치는 다시 개발을 해야하며, 하드웨어 개발자와 패치에 들어갈 MCU 제작사에 대한 논의가 필요한 것으로 보임.

  • MCU/BLE :

    CC2642 MCU 주요 사양

    • 코어 : Arm Cortex-M4F

    • 동작 주파수 : 48MHz

    • CC2642 마이크로컨트롤러는 32bit(??)의 Cortex-M4 코어를 기반으로 하고 있으며, 최대 48 MHz의 속도로 동작할 수 있다.

    • 메모리 :

      • RAM (휘발성)

        • SRAM (Static RAM)

          • 8KB of Cache SRAM (Alternatively available as general-purpose RAM)

          • 80KB of ultra-low leakage SRAM. The SRAM is protected by parity to ensure high reliability of operation.

      • ROM (비휘발성)

        • ROM : 256KB of ROM for protocols and library functions

        • 플래시 메모리 : 352KB of in-system Programmable Flash

    • 동작 전압 : 3.8V

    • Micro 5 pin connector : usb케이블과 커넥되어 (1)전원공급 (2)컴파일한 프로그램 업로드 (3)디버깅메시지 출력 등을 수행함.

    • Ant. : 헬스케어 기기에 탑재되는 기술

    • ECG signal input : 심전도 센서

    • 그 외 : Button / Battery / External Memory / ECG signal processor

Development Workflow ( 모델 훈련부터 탑재까지 )

1.경량화된 딥러닝 모델 개발

딥러닝 모델이 기기에 탑재 가능할 수 있도록, 기기에 메모리, 프로세서 등을 고려하여 설계해야함.

  • 1차 실험 모델 :

  • 개발 환경 :

    • 언어 : TensorFlow

      • tf.keras (케라스 API 로 텐서플로우 모델 구축)

    • IDE : 주피터 , vi

  • 데이터 : 1차원의 시계열 로우 ECG 데이터 (ex. 6000 * 1 ??)

2.TensorFlow Lite FlatBuffer 로 변환

# Convert the model to the TensorFlow Lite format with quantization
converter = tf.lite.TFLiteConverter.from_keras_model(model_2)
converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]
tflite_model = converter.convert()

# Save the model to disk
open("sine_model_quantized.tflite", "wb").write(tflite_model)

참고 : MobileTechnology

3.FlatBuffer을 C Byte Array 로 변환

  • xxd 유틸리티를 이용하여 .tflite를 .c 파일 (.c/.cpp/.cc)로 변환함.

# Install xxd if it is not available
!apt-get -qq install xxd
# Save the file as a C source file
!xxd -i sine_model_quantized.tflite > sine_model_quantized.cc
# Print the source file
!cat sine_model_quantized.cc
sine_model_quantized.cc
unsigned char converted_model_tflite[] = {
  0x18, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x0e, 0x00,
  // <Lines omitted>
};
unsigned int converted_model_tflite_len = 18200;

위와 같이 변환하면, 소스폴더에 위치 시키고 헤더파일 안에서 extern 함수를 통해 외부에 있는 전역변수로 가져옴. 메모리의 효율성을 위해 배열 선언을 const 로 변경해주는 것이 좋음. (ex. const unsigned char )

tiny_conv_micro_features_model_data.h
#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_TINY_CONV_MICRO_FEATURES_MODEL_DATA_H_
#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_TINY_CONV_MICRO_FEATURES_MODEL_DATA_H_

extern const unsigned char g_tiny_conv_micro_features_model_data[];
extern const int g_tiny_conv_micro_features_model_data_len;

#endif  // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_TINY_CONV_MICRO_FEATURES_MODEL_DATA_H_

4.TensorFlow Lite for MicroControllers C++ library

  • 준비물

    • 마이크로 컨트롤러를 위한 개발환경 : CCSTUDIO-TMAX4 Code Composer

    • 보드 : CC2642 (CC2652)

  • 개발 언어 : C++

    • 대부분 C/C++ 사용함

    • C++는 코드의 재사용성이 높지만, 컴파일 후 실행 파일의 크기가 커지는 것이 단점이나, 마이크로컨트롤러의 플래시 메모리가 증가함에 따라 실행 파일의 크기가 큰 문제가 되지 않다고 함..

마이크로컨트롤러 프로그래밍에서는 레지스터 조작이 기본이 됨. 하지만, 레지스터를 조작한다는 것은 그리 쉽지 않음. 따라서, 레지스터 조작을 간편하게 할 수 있도로 레지스터 조작 작업을 함수로 구현한 하드웨어 종속적인 소프트웨어 계층이 필요함.

  • 메인함수

main.cc
#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h"
#include "tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h"
#include "tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h"
#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h"
#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h"
#include "tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h"
#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h"
#include "tensorflow/lite/experimental/micro/micro_error_reporter.h"
#include "tensorflow/lite/experimental/micro/micro_interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"

int main(int argc, char* argv[]) {
  // Set up logging.
  tflite::MicroErrorReporter micro_error_reporter;
  tflite::ErrorReporter* error_reporter = &micro_error_reporter;

  // Map the model into a usable data structure. This doesn't involve any
  // copying or parsing, it's a very lightweight operation.
  const tflite::Model* model =
      ::tflite::GetModel(g_tiny_conv_micro_features_model_data);
  if (model->version() != TFLITE_SCHEMA_VERSION) {
    error_reporter->Report(
        "Model provided is schema version %d not equal "
        "to supported version %d.\n",
        model->version(), TFLITE_SCHEMA_VERSION);
    return 1;
  }

  // This pulls in all the operation implementations we need.
  tflite::ops::micro::AllOpsResolver resolver;

  // Create an area of memory to use for input, output, and intermediate arrays.
  // The size of this will depend on the model you're using, and may need to be
  // determined by experimentation.
  const int tensor_arena_size = 10 * 1024;
  uint8_t tensor_arena[tensor_arena_size];

  // Build an interpreter to run the model with.
  tflite::MicroInterpreter interpreter(model, resolver, tensor_arena,
                                       tensor_arena_size, error_reporter);
  interpreter.AllocateTensors();

  // Get information about the memory area to use for the model's input.
  TfLiteTensor* model_input = interpreter.input(0);
  if ((model_input->dims->size != 4) || (model_input->dims->data[0] != 1) ||
      (model_input->dims->data[1] != kFeatureSliceCount) ||
      (model_input->dims->data[2] != kFeatureSliceSize) ||
      (model_input->type != kTfLiteUInt8)) {
    error_reporter->Report("Bad input tensor parameters in model");
    return 1;
  }

  // Prepare to access the audio spectrograms from a microphone or other source
  // that will provide the inputs to the neural network.
  FeatureProvider feature_provider(kFeatureElementCount,
                                   model_input->data.uint8);

  RecognizeCommands recognizer(error_reporter);

  int32_t previous_time = 0;
  // Keep reading and analysing audio data in an infinite loop.
  while (true) {
    // Fetch the spectrogram for the current time.
    const int32_t current_time = LatestAudioTimestamp();
    int how_many_new_slices = 0;
    TfLiteStatus feature_status = feature_provider.PopulateFeatureData(
        error_reporter, previous_time, current_time, &how_many_new_slices);
    if (feature_status != kTfLiteOk) {
      error_reporter->Report("Feature generation failed");
      return 1;
    }
    previous_time = current_time;
    // If no new audio samples have been received since last time, don't bother
    // running the network model.
    if (how_many_new_slices == 0) {
      continue;
    }

    // Run the model on the spectrogram input and make sure it succeeds.
    TfLiteStatus invoke_status = interpreter.Invoke();
    if (invoke_status != kTfLiteOk) {
      error_reporter->Report("Invoke failed");
      return 1;
    }

    // The output from the model is a vector containing the scores for each
    // kind of prediction, so figure out what the highest scoring category was.
    TfLiteTensor* output = interpreter.output(0);
    uint8_t top_category_score = 0;
    for (int category_index = 0; category_index < kCategoryCount;
         ++category_index) {
      const uint8_t category_score = output->data.uint8[category_index];
      if (category_score > top_category_score) {
        top_category_score = category_score;
      }
    }

    const char* found_command = nullptr;
    uint8_t score = 0;
    bool is_new_command = false;
    TfLiteStatus process_status = recognizer.ProcessLatestResults(
        output, current_time, &found_command, &score, &is_new_command);
    if (process_status != kTfLiteOk) {
      error_reporter->Report(
          "RecognizeCommands::ProcessLatestResults() failed");
      return 1;
    }
    // Do something based on the recognized command. The default implementation
    // just prints to the error console, but you should replace this with your
    // own function for a real application.
    RespondToCommand(error_reporter, current_time, found_command, score,
                     is_new_command);
  }

  return 0;
}
  • TensorFlow Lite for MicroControllers C++ library 헤더 파일 임폴트

#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h"
#include "tensorflow/lite/experimental/micro/micro_error_reporter.h"
#include "tensorflow/lite/experimental/micro/micro_interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
  • 모델 헤더 파일 임폴트

#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h"
  • tflite::ErrorReporter 가 tflite::MicroErrorReporter 를 참조에 의해 호출하면, micro_error_reporter가 인터프리터에 들어가서 기기에서 일어나는 다양한 로그들을 기록함.

tflite::MicroErrorReporter micro_error_reporter;
tflite::ErrorReporter* error_reporter = &micro_error_reporter;
  • C byte array 로 변환된 모델 적재

const tflite::Model* model =
    ::tflite::GetModel(g_tiny_conv_micro_features_model_data);
if (model->version() != TFLITE_SCHEMA_VERSION) {
  error_reporter->Report(
      "Model provided is schema version %d not equal "
      "to supported version %d.\n",
      model->version(), TFLITE_SCHEMA_VERSION);
  return 1;
}
  • AllOpsResolver를 통해 텐서플로우 연산에 접근함

tflite::ops::micro::AllOpsResolver resolver;
  • 입력, 출력, 은닉에 필요한 메모리를 미리 할당함.

const int tensor_arena_size = 10 * 1024;
uint8_t tensor_arena[tensor_arena_size];
tflite::SimpleTensorAllocator tensor_allocator(tensor_arena,
                                               tensor_arena_size);
  • 인터프리터 초기화함.

tflite::MicroInterpreter interpreter(model, resolver, &tensor_allocator,
                                     error_reporter);
  • 인풋 데이터 Shape 확인함.

TfLiteTensor* model_input = interpreter.input(0);
if ((model_input->dims->size != 4) || (model_input->dims->data[0] != 1) ||
    (model_input->dims->data[1] != kFeatureSliceCount) ||
    (model_input->dims->data[2] != kFeatureSliceSize) ||
    (model_input->type != kTfLiteUInt8)) {
  error_reporter->Report("Bad input tensor parameters in model");
  return 1;
}
  • 센서로부터 들어온 데이터를 텐서화함.

  FeatureProvider feature_provider(kFeatureElementCount,
                                   model_input->data.uint8);
TfLiteStatus feature_status = feature_provider.PopulateFeatureData(
    error_reporter, previous_time, current_time, &how_many_new_slices);
  • 모델 실행함.

TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
  error_reporter->Report("Invoke failed");
  return 1;
}
  • 결과를 얻음.

TfLiteStatus invoke_status = interpreter.Invoke();
if (invoke_status != kTfLiteOk) {
  error_reporter->Report("Invoke failed");
  return 1;
}

5.기기 빌드

Tensorflow Lite 는 제약적인 텐서플로우 연산을 제공하며, 다양한 구조를 가지고 있는 MicroController 의 특성상 지원하는 플랫폼이 제한적일수 있음.

https://www.sparkfun.com/products/15170

Cython을 활용한 알고리즘 개발

파이썬은 컴파일 과정을 거치지 않고, 실시간 텍스트를 분석하며 실행되는 Script 언어이다. 반면, c언어는 미리 기계어로 컴파일된 실행파일을 수행하는 컴파일러 언어이며, Script 언어와 비교하여 약 171배 수행 속도가 빠르다.

따라서, 본 프로젝트<심전도 판별 인공지능 알고리즘 모듈 경량화> 에서 알고리즘 모듈 실행 속도를 최적화하기 위하여, Cython언어를 고려한다. 대부분의 Deep Learning Framework (Tensorflow, Caffe, Pytorch) 는 Cython 패키지를 지원하며, 딥러닝 프레임워크 아래 기준을 가지고 선택한다.‌

딥러닝 프레임워크 선택 기준

  • 알고리즘 모듈 크기

  • 알고리즘 실행 속도

  • 시스템 아키텍처와의 호환성

본 문서에서는 System Architecture ( Google 의 Firebase, Android )를 고려하여 Tensorflow를 선정한다.

Cython코드 작성

HelloTensorflow.pyx
import numpy as np
import tensorflow as tf

cdef float ExecuteTensorFlow():
        x = tf.placeholder(tf.float32, [1, 2])
        w = tf.constant([0.2, 0.3], tf.float32)
        b = tf.constant( -0.1, tf.float32)
        linearModel = tf.matmul(x, tf.reshape(w, shape=[2, 1])) + b

        session = tf.Session()
        result = session.run(linearModel, feed_dict={x: np.reshape([-0.1, -0.4], [1, 2])} )

        return np.reshape(result, [])

setup.py를 활용한 Cython 빌드

setup.py
from distutils.core import setup
from Cython.Build import cythonize

setup(name='Hello Tensorflow app',
      ext_modules=cythonize("helloTensorflow.pyx"))
 python setup.py build_ext --inplace

setup.py는 Cython 파일 ( helloTensorflow.pyx ) 을 cython 컴파일러를 통해 .c파일로 변환한 후, c 컴파일러는 .helloTensorflow.c 를 컴파일하여 실행파일 helloTensorflow.so (shared object) 을 생성한다.‌

  • helloTensorflow.pyx ------------( cython 컴파일러)------------> helloTensorflow.c

  • helloTensorflow.c ----------------( c 컴파일러) ------------> helloTensorflow.so

펌웨어에서 인공지능 알고리즘 실행

펌웨어 기능 명세

  • BLE (Bluetooth Low Energy) 를 통해실시간 심전도 데이터 전송

  • 심전도 판별 인공지능 알고리즘 실행 및 결과 산출

  • 산출 결과를 LED로 표시

정적 vs 동적 라이브러리

정적 라이브러리 (linux :.a /windows: .lib) 는 .c 컴파일 시, .o로 라이브러리 함수들의 복사가 일어나기 때문에, 메인 메모리의 많은 용량을 차지한다. 따라서, 동적 라이브러리를 사용하여 복사가 일어나지 않고, 실행시간에 사용할 수 있도록 한다.

펌웨어에서 외부 동적 라이브러리 실행

심전도 판별 인공지능 알고리즘은메인함수를 포함하지 않음. 펌웨어에서 다음 2가지를 통해, 인공지능 알고리즘 사용이 가능하다.

  • 환경변수 LD_LIBRARY_PATH 설정

  • 동적 라이브러리 배포

환경변수 LD_LIBRARY_PATH 설정

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

LD_LIBRARY_PATH는 동적 라이브러리의 경로를 기재한다. 위 코드에서는 동적라이브러리가 현재 워킹 디렉토리에 있기 때문에 "." 로 설정한다.

동적 라이브러리 배포

gcc firmware.c -L . -l tools -o result

펌웨어 실행

./result

추가 명령어

동적 라이브러리 함수 명세

nm libtools.so

동적 라이브러리 dependancy 확인

ldd libtools.so

부록

Cython 튜토리얼

Python은 변수 선언시 타입을 선언하지 않아도 된다. 매 스크립트 실행 시 타입을 체크하는 방식으로 이를 Dynamic Typing이라고 하며, 속도가 느리다. 반면, cython은 변수 선언시, 타입을 지정해주어야 한다.‌

python 변수 선언

x = 5.0

cython 변수 선언

cdef float x = 5.0

python 함수 선언

def test(x):
    y = 0
    for i in range(x):
        y += i
    return y

cython 함수 선언

cpdef int test(int x):
    cdef int y = 0
    cdef int i
    for i in range(x):
        y += i
    return y

시간 체크

import timeit

cy = timeit.timeit('''example_cy.test(5000)''',setup='import example_cy',number=100)
py = timeit.timeit('''example.test(5000)''',setup='import example', number=100)

print(cy, py)
print('Cython is {}x faster'.format(py/cy))

‌python과 cython의 수행속도를 비교한 결과, cython이 171배 빠르다.

Last updated