안드로이드 스튜디오

5.6 TensorFlow.Lite Mobile IOT Digit Classifier 예제 해설

coding art 2020. 4. 3. 12:59
728x90

 

MNIST 데이터를 사용하여 TensorFlow 손글씨 인식 머신 러닝은 하나의 레이어만을 사용하드라도 92.5% 인식률을 보여주며 뉴럴 네트워크를 사용할 경우 98% 수준으로 향상된 인식률을 보여 준다. 더 나아가 뉴럴네트워크에 CNN 기법이 적용되면 적어도 98.5% 수준의 인식률을 보여 주며 CNNDropout Ensemble 기법을 함께 사용하면 쉽게 99.5%의 정밀도를 얻을 수 있다.

하지만 이러한 머신러닝 코드를 데스크 탑이 아닌 모바일 스마트폰에서 직접 실행하기에는 컴퓨팅 부담으로 인해 실현 가능성이 거의 없다고 봐도 무방하다. 이러한 점에 대한 구글의 대안이 바로 TensorFlow Lite 이다. 직접 모바일에서 빌드하여 실행하는 것이 아닌 학습 결과만을 탑재하여 모바일 상에서의 대기(latency)시간과 binary 형태의 코드 크기를 획기적으로 줄여 보자는 것이다. 아무리 데스크탑의 컴퓨팅 파워가 우수하드라도 아무래도 카메라나 비데오 실시간 입력 작업에서는 모바일에 비해 상당한 불편이 뒤 따른다. 따라서 모바일 상에서 직접 실시간으로 머신러닝 학습 결과를 실행할 수 있도록 입출력 과정을 포함한 앱 코드 작업이 필요한 것이다.

 

개념적으로는 그렇게 어려운 것은 아니겠지만 코드 작성을 위해서는 적어도 TensorFlow 머신 러닝에 관해서 충분한 이해와 더불어 동시에 수준급 안드로이드 스튜디오 코드 작성 능력이 동시에 요구되기 때문에 어려움이 수반될 수도 있다.

TensorFlow 학습 방법과 결과를 담는 과정은 이미 해설이 이루어졌으므로 안드로이드 스튜디오에 그 결과를 탑재하는 과정과 탑재된 결과를 사용하는 응용 코드를 작성해 보기로 한다. 현재 구글 홈페이지에서 제공하는 Digit Classifer 예제는 java가 아닌 Kotlin으로 작성되어 코드의 문법적 이해에 다소 혼선이 있을 수 있음에 주의하자. Java 방식의 개념에 익숙하므로 Kotlin 언어를 이해 못할 바는 아니지만 뭔가 거꾸로 되어 있다는 느낌이 들 수 있다.

 

스텝1 Digit Classifier 안드로이드 코드의 AndroidManifest.xml 을 살펴보자. 특별한 점은 없으나 <uses-sdk /> 말고는 별다른 점은 없어 보인다. 프로젝트 Tree구성을 살펴보면 Kotlin 으로 작성된 MainActivity DigitClassifier로 구성되어 있으며 나머지는 모두 xml 언어가 그대로 사용된다.

 

Kotlin 언어로 안드로이드 코드 작성을 위해서는 New Poroject 시작 국면에서 languageJava 가 아닌 Kotlin 으로 선택해 두어야 한다. 안드로이드 스튜디오를 열고 들어가서 Gradle을 마친 후 MainActiviy를 열어 보면 파일 명이 MainActivity.kt 로 표기됨을 확인할 수 있을 것이다.아울러 끝에 첨부된 adroid_digit.zip 파일 압축 해제 후 AndroidStudioProjects 폴더에 넣어서 오픈 후에 build.gradle(Modeule:app) 에서 2가지 즉 “tflite” tensorflow lite 버전을 확인해 보자. 현재 안드로이드 스튜디오 버전 3.6 사용 중이다.

스텝2 activity_main.xml에서 Default 레이아웃을 살펴보자. TextView 에는 “Please draw a digit” 안내 문구가 들어 있으며 Button 은 손글씨 화면을 지우기 위한 “Clear” 버튼이다. 한편 widget.DrawView 앞에 인도 프로그래머 이름이 들어있는데 뺴면 코드 실행이 안 되는데 아주 weird 하게 느껴진다.

 

스탭3 MainActivity.kt 헤더 영역에 import 되는 라이브러리를 살펴보자. 손 글씨 그래픽 처리를 위해 graphics.Color, view.MotionEvent, draw.widget.DrawView 와 같은 라이브러리가 필요하다. graphics.Color는 흑백 처리에필요하며, view.MotionEvent DrawView 화면에 손 글씨에 의한 궤적을 남기기 위한 MotionEvent를 완료했을 때 Digit Classifier 클래스를 불러내기 위해서 필요하다.

 

Kotlin언어에서 클라스 설정은 MainActivity extends to AppCompatActivity 로 표현하는 자바와는 좀 다르며 라이브러리 명칭과 변수설정도 그 순서가 자바와는 반대이며 Kotlin에서 변수명 입력 시에 밑줄이 쳐지며 문장 분리 기호(;)도 없는 특징을 볼 수 있다. Digit Classifier 처리를 위한 스마트폰 화면을 구성하기 위해 onCreate, onDestroy, classifyDrawing 과 같은 method 들이 필요하다. onDestroy 용법은 파이선 OpenCV에서 사용하는 destroywindow 와 유사하다. 마지막 줄의 companion object {∙∙∙}classifyDrawing() 처리 시 에러가 발생하면 그 위치가 MainActivity임을 지적 출력 해준다.

 

MainActivity.kt 코드에서 사용하는 변수 draw_view를 가시적으로 살펴보자. activity_main.xml의 디자인 상태에서 손 글씨를 써 넣을 부분을 클릭해 보면 TextView gkeksdml @id/draw_view 가 속성 창에서 id임이 확인 가능하다.    

MainActivity.kt onCreate() method의 헤더 영역에서 선언되는 drawView와 함께 선굵기, 선색상 및 배경색이 지정된다. 아울러 화면을 지우기 위한 clearButton과 그 자리에 인식률 값을 출력할 수 있도록 predictedTextView도 설정된다.

onCreate() method의 이어지는 코드는 draw_view 영역에서 null 처리되어 있는 drawView?에 대해서 onTouchEvent() method를 사용하여 이벤트 발생 여부를 검출하는 것이다. 즉 선긋기에 의한 경로 오브젝트가 발생하는지 모니터링 하게 된다. 여기서 가장 중요한 사실은 MotionEvent를 모니터링 함에 있어서 손글씨 Digit 쓰기가 완료 되는지를 확인하기 위해서 ACTION_UP 상태 변화를 감지해 내는 것이다. 언제 Drawing 시작되어 움직였는지 여부는 ACTION_DOWN, ACTION_MOVE가 있지만 Digit Classifier 작업에서는 터치를 끝낸다는 시점 즉 ACTION_UP만 파악되면 클라스 Digit Classifier를 불러 인스턴스 digitClassifier 처리를 통해 그려진 내용을 inferring 하면 되는 것이다. 마지막으로 에러가 발생하면 TAG를 출력하는데 별도로 companion object로 처리해 둘 필요가 있다.

스텝4 마지막으로 클라스 DigitClassfier 코드를 살펴보자. 프로젝트 Tree 에 보면 assets 폴더에 mnist.tfflite 가 있는데 이는 keras MNIST를 학습한 결과물이다. 사용자가 생성할 수도 있으나 현재의 코드 전체가 구글의 GitHub에서 가져왔으며 이미 들어 있으므로 그대로 사용하도록 하자. 라이브러리에 AssetManager tensorflow.lite.Interpreter 가 있다.

MainActivity.kt에서 ACTION_UP 이 검출되면 classifyDrawing() method를 호출함에 따라 그 method 내부에서 클라스 DigitClassifier를 인스턴스 처리 할 수 있도록 불러오게 되어 있다. 처음에 isInitialized false로 설정되어 있는데 method initializeInterpreter() 내부에서 true 로 설정이 되면 AssetManager가 폴더로부터 mnist.tflite를 끄집어내어 머신러닝 작업이 이루어지게 된다. 한편 executorService 에 의한 일종의 Service 가 이루어지는데 이는 즉 머신 러닝 작업은 스마트폰 시스템 내부 Background에서 실행이 이루어짐을 의미한다.

initializaton 과정도 블로그 검색 사례에 의하면 직관적으로 assetManager “mnist.tflite”를 즉각 불러 실행시키지만 현재의 코드는 다소 우회적으로 처리되어 있는 듯하다. @Throws(IOEXception::class)IO 에러가 일어날 경우 메시지 출력 요청으로서 현재이 코드가 버그 없이 실행이 잘되기 때문에 굳이 필요하지는 않은 부분으로 삭제 가능하다.

Interpreter에 의해 model을 읽었으면 다음과 같이 그 shape 크기를 계산해 보자. 이 값은 Image Classification 작업에서 대단히 중요하다고 알려져 있다.

스텝5 구체적인 머신 러닝 작업은 MainActivity.kt에서 ACTION_UP 검출에 따라 classfyDrawing() method를 불러 낼 때에 스마트폰 draw_viewBitmap 값을 넘겨주게 된다.

classify() methodprivate이므로 DigitClassifier 내부에서만 사용되며 classifyAsync() private이 아니므로 MainActivity.kt에서 DigitClassifier 인스턴스 처리 시에 사용한다.

아울러 intialization을 위해 executorService를 시작하여 Background 실행이 완료되었으면 close() method를 사용하여 executorService를 종료한다.

넘겨받은 Bitmap 인스턴스를 대상으로 Resize 작업을 하여 앞서 계산된 shape 값에 맞춰 Byte 인스턴스로 변환하여 ByteBuffer에 저장한다. 아울러 출력 결과를 저장하기 위한 어레이 설정 후 interpreter를 실행하여 result가 얻어지면 Mainactivity.kt에서 classifyAsync() method를 실행하여 Backgound 작업을 종결한다. 성공적으로 결과가 얻어졌으면 addOnSuccessListener에 의해 스마트폰 화면에 결과를 출력하고 실패 시에는 에러 메시지를 Logcat에 출력한다.

아나콘다나 구글 Colaboratory에서 Keras 코드를 실행을 통해 얻을 수 있는 MNIST 학습결과를 안드로이드 스마트폰에서 Interpreter 형식으로 가져다 스마트폰에서 직접 입력한 손글씨 숫자를 인식할 수 있는 Mobile IOT 코드인 Digit Classifier의 코드 구조에 대해서 알아보았다. 이 코드의 인식률은 결국 Interpreter 형식으로 가져dhs “mnist.tflite” 의 성능에 의존하므로 인식률을 올리기 위해서는 아나콘다나 구글 Colaboratory에서 Keras 코드를 개량해야 할 필요가 있을 것이다.

 

첨부된 코드를 다운받아 실행해 보자.

android_digit.zip

 

 
android_digit.zip
0.52MB