블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 

'cloud'에 해당되는 글 58

  1. 2017.08.16 Tensorflow Object Detection API를 이용한 물체 인식 #1-설치와 사용하기
  2. 2017.08.15 얼굴 인식 모델을 만들어보자 #6 - CloudML을 이용하여 예측하기
  3. 2017.06.22 얼굴 인식 모델을 만들어보자 #4 -클라우드를 이용하여 학습 시키기
  4. 2017.05.16 연예인 얼굴 인식 모델을 만들어보자 - #1. 학습 데이타 준비하기 (2)
  5. 2017.03.23 클라우드에 최적화된 하둡 배포 아키텍쳐 생각하기
  6. 2017.03.20 구글 클라우드의 서버리스 서비스 Cloud Functions (1)
  7. 2017.03.14 연예인 얼굴 인식 서비스를 만들어보자 #1 - 학습 데이타 준비하기 (1)
  8. 2017.03.10 구글의 IOT 솔루션
  9. 2017.01.24 한시간에 만드는 대용량 로그수집 분석 시스템 (1)
  10. 2017.01.09 딥러닝을 이용한 숫자 이미지 인식 #1/2-학습 (5)
  11. 2016.12.15 구글 빅쿼리 사용시 count(distinct)의 값이 정확하지 않은 문제
  12. 2016.11.26 Docker Kubernetes의 UI
  13. 2016.11.15 파이어베이스를 이용한 유니티 게임 로그 분석 (2)
  14. 2016.10.04 수학포기자를 위한 딥러닝-#1 머신러닝과 딥러닝 개요 (5)
  15. 2016.09.22 노트7의 소셜 반응을 분석해 보았다. - #3 제플린 노트북을 이용한 상세 데이타 분석
  16. 2016.09.20 노트7의 소셜 반응을 분석해 보았다. (3)
  17. 2016.09.01 파이어베이스 애널러틱스를 이용한 모바일 데이타 분석- #3 빅쿼리에 연동하여 모든 데이타를 분석하기
  18. 2016.08.25 실시간 데이타 분석 플랫폼 Dataflow - #5 데이타 플로우 프로그래밍 모델 (1)
  19. 2016.07.13 구글 클라우드의 대용량 분산 큐 서비스인 Pub/Sub 소개 #1
  20. 2016.06.16 구글 빅데이타 플랫폼 빅쿼리 아키텍쳐 소개
 


Tensorflow Object Detection API


조대협 ( http://bcho.tistory.com)


Tensorflow Object Detection API는, Tensorflow 를 이용하여 이미지를 인식할 수 있도록 개발된 모델로, 라이브러리 형태로 제공되며, 각기 다른 정확도와 속도를 가지고 있는 5개의 모델을 제공한다. 머신러닝이나 텐서플로우에 대한 개념이 거의 없더라도 라이브러리 형태로 손쉽게 사용할 수 있으며, 직접 사용자 데이타를 업로드해서 학습을 하여, 내 시나리오에 맞는 Object Detection System을 손쉽게 만들 수 있다.


Object Detection API를 설치하기 위해서는 텐서플로우 1.x 와 파이썬 2.7x 버전이 사전 설치되어 있어야 한다. 이 글에서는 파이썬 2.7.13과 텐서플로우 2.7.13 버전을 기준으로 하고, 맥에 설치하는 것을 기준으로 한다. 리눅스나 다른 플랫폼 설치는 원본 설치 문서 https://github.com/tensorflow/models/blob/master/object_detection/g3doc/installation.md 를 참고하기 바란다.


설치 및 테스팅

Protocol Buffer 설치

Object Detection API는 내부적으로 Protocol Buffer를 사용한다. MAC에서 Protocol Buffer를 설치 하는 방법은 https://github.com/google/protobuf/tree/master/pythonhttp://bcho.tistory.com/1182 를 참고하기 바란다.

설치가 되었는지를 확인하려면, 프롬프트 상에서 protoc 명령을 실행해보면 된다.

파이썬 라이브러리 설치

프로토콜 버퍼 설치가 끝났으면, 필요한 파이썬 라이브러리를 설치한다.

% pip install pillow

% pip install lxml

% pip install jupyter

% pip install matplotlib

Object Detection API 다운로드 및 설치

Object Detection API 설치는 간단하게, 라이브러리를 다운 받으면 된다. 설치할 디렉토리로 들어가서 git clone 명령어를 통해서, 라이브러리를 다운로드 받자

% git clone https://github.com/tensorflow/models

Protocol Buffer 컴파일

다음 프로토콜 버퍼를 사용하기 위해서 protoc로 proto 파일을 컴파일 한데, Object Detection API를 설치한 디렉토리에서 models 디렉토리로 들어간 후에, 다음 명령어를 수행한다.


protoc object_detection/protos/*.proto --python_out=.

PATH 조정하기

설치가 끝났으면 Object Detection API를 PATH와 파이썬 라이브러리 경로인 PYTHONPATH에 추가한다. 맥에서는 사용자 홈디렉토리의 .bash_profile 에 추가 하면되낟.

PYTHONPATH 환경 변수에 {Object Detection API 설치 디렉토리}/models/slim 디렉토리와 Object Detection API 설치 디렉토리}/models/models 디렉토리를 추가한다.

같은 디렉토리를 PATH에도 추가해준다.


export PYTHONPATH=$PYTHONPATH:/Users/terrycho/dev/workspace/objectdetection/models:/Users/terrycho/dev/workspace/objectdetection/models/slim

export PATH=$PATH:/Users/terrycho/dev/workspace/objectdetection/models:/Users/terrycho/dev/workspace/objectdetection/models/slim

테스팅

설치가 제대로 되었는지를 확인하기 위해서 {Object Detection API 설치 디렉토리}/models/ 디렉토리에서 다음 명령을 실행해보자


% python object_detection/builders/model_builder_test.py


문제 없이 실행이 되었으면 제대로 설치가 된것이다.


사용하기

설치가 끝났으면 실제로 사용해 보자, Object Detection API를 인스톨한 디렉토리 아래 models/object_detection/object_detection_tutorial.ipynb 에 테스트용 노트북 파일이 있다. 이 파일을 주피터 노트북 (http://jupyter.org/)을 이용하여 실행해보자.
(원본 코드 https://github.com/tensorflow/models/blob/master/object_detection/object_detection_tutorial.ipynb)


실행을 하면 결과로 아래와 같이 물체를 인식한 결과를 보여준다.




이 중에서 중요한 부분은 Model Preparation이라는 부분으로,

여기서 하는 일은 크게 아래 3가지와 같다.

  • Export 된 모델 다운로드

  • 다운로드된 모델 로딩

  • 라벨맵 로딩


Export 된 모델 다운로드

Object Detection API는 여러가지 종류의 미리 훈련된 모델을 가지고 있다.

모델 종류는 https://github.com/tensorflow/models/blob/master/object_detection/g3doc/detection_model_zoo.md 를 보면 되는데,  다음과 같은 모델들을 지원하고 있다.  COCO mAP가 높을 수 록 정확도가 높은 모델인데, 대신 예측에 걸리는 속도가 더 느리다.


Model name

Speed

COCO mAP

Outputs

ssd_mobilenet_v1_coco

fast

21

Boxes

ssd_inception_v2_coco

fast

24

Boxes

rfcn_resnet101_coco

medium

30

Boxes

faster_rcnn_resnet101_coco

medium

32

Boxes

faster_rcnn_inception_resnet_v2_atrous_coco

slow

37

Boxes


모델은 *.gz 형태로 다운로드가 되는데, 이 파일안에는 다음과 같은 내용들이 들어있다.

  • Check point (model.ckpt.data-00000-of-00001, model.ckpt.index, model.ckpt.meta)
    텐서플로우 학습 체크 포인트로, 나중에, 다른 데이타를 학습 시킬때 Transfer Learning을 이용할때, 텐서플로우 그래프에 이 체크포인트를 로딩하여, 그 체크포인트 당시의 상태로 학습 시켜놓을 수 있다. 이 예제에서는 사용하지 않지만, 다른 데이타를 이용하여 학습할때 사용한다.

  • 학습된 모델 그래프 (frozen_inference_graph.pb)
    학습이 완료된 그래프에 대한 내용을 Export 해놓은 파일이다. 이 예제에서는 이 모델 파일을 다시 로딩하여 Prediction을 수행한다.

  • Graph proto (pgrah.pbtxt)


기타 파일들

이외에도 기타 다른 파일들이 있는데, 다른 파일들은 이미 Object Detection API 안에 이미 다운로드 되어 있다.

  • 라벨맵
    라벨맵은 {Object Detection API 설치 디렉토리}/models/object_detection/data  디렉토리 안에 몇몇 샘플 모델에 대한 라벨맵이 저장되어 있다. 라벨맵은 모델에서 사용한 분류 클래스에 대한 정보로 name,id,display_name 식으로 정의되며, name은 텍스트 라벨, id는 라벨을 숫자로 표현한 값 (반드시 1부터 시작해야 한다.), display_name은 Prediction 결과를 원본 이미지에서 인식한 물체들을 박스처리해서 출력하는데 이때 박스에 어떤 물체인지 출력해주는 문자열에 들어가는 텍스트 이다.
    여기서 사용한 라벨맵은 mscoco_label_map.pbtxt 파일이 사용되었다.

  • 학습 CONFIG 파일
    모델 학습과 예측에 사용되는 각종 설정 정보를 저장한 파일로 위에서 미리 정의된 모델별로 각각 다른 설정 파일을 가지고 있으며 설정 파일의 위치는  {Object Detection API 설치 디렉토리}/models/object_detection/samples/configs 에 {모델명}.config 에 저장되어 있다.


다운로드된 모델과 라벨맵 로딩

위에서 많은 파일이 다운되고 언급되었지만 예측 (Prediction)에는 학습된 그래프 모델을 저장한 frozen_inference_graph.pb 파일과, 분류 라벨이 저장된 mscoco_label_map.pbtxt 두 개만 사용된다.


다음 코드 부분에서 모델을 다운 로드 받고, 모델 파일과 라벨 파일의 경로를 지정하였다.


# What model to download.

MODEL_NAME = 'ssd_mobilenet_v1_coco_11_06_2017'

MODEL_FILE = MODEL_NAME + '.tar.gz'

DOWNLOAD_BASE = 'http://download.tensorflow.org/models/object_detection/'


# Path to frozen detection graph. This is the actual model that is used for the object detection.

PATH_TO_CKPT = MODEL_NAME + '/frozen_inference_graph.pb'


# List of the strings that is used to add correct label for each box.

PATH_TO_LABELS = os.path.join('data', 'mscoco_label_map.pbtxt')


NUM_CLASSES = 90


그리고 마지막 부분에 분류 클래스의 수를 설정한다. 여기서는 90개의 클래스로 정의하였다.

만약에 모델을 바꾸고자 한다면 PATH_TO_CKPT를 다른 모델 파일로 경로만 변경해주면 된다.


다음으로  frozen_inference_graph.pb  로 부터 모델을 읽어서 그래프를 재생성하였다.


detection_graph = tf.Graph()

with detection_graph.as_default():

 od_graph_def = tf.GraphDef()

 with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid:

   serialized_graph = fid.read()

   od_graph_def.ParseFromString(serialized_graph)

   tf.import_graph_def(od_graph_def, name='')


나머지 부분은 이미지를 읽어서, 로딩된 모델을 이용하여 물체를 Detection 하는 코드이다.


여기까지 간단하게 Tensorflow Object Detection API를 설치 및 사용하는 방법에 대해서 알아보았다.

다음 글에서는 다른 데이타로 모델을 학습해서 예측하는 부분에 대해서 알아보도록 하겠다.


참고 자료





저작자 표시 비영리
신고

CloudML을 이용하여 예측하기

조대협 (http://bcho.tistory.com)


지난글 (http://bcho.tistory.com/1189) 에서 학습된 모델을 *.pb 파일 포맷으로 Export 하였다. 그러면 이 Export 된 모델을 이용하여 예측 (prediction)을 하는 방법에 대해서 알아보겠다. 앞글에서도 언급했듯이, 예측은 Google CloudML을 이용한다.

전체 코드를 https://github.com/bwcho75/facerecognition/blob/master/CloudML%20Version/face_recog_model/%2528wwoo%2529%2BML%2BEngine%2Bprediction.ipynb 를 참고하기 바란다.

Export된 모델을 CloudML에 배포하기

학습된 모델을 CloudML에 배포하기 위해서는 export된 *.pb 파일과 variables 폴더를 구글 클라우드 스토리지 ( GCS / Google Cloud Storage) 에 업로드해야 한다.

아니면 학습때 모델 Export를 GCS로 시킬 수 도 있다.


아래는 terrycho-face-recog-export 라는 GCS 버킷아래 /export 디렉토리에, export 된 *.pb 파일과 variables 폴더가 저장된 모습이다.


다음 구글 클라우드 콘솔에서 ML Engine을 선택하여, Models 메뉴를 고른다. 이 메뉴는 모델을 배포하고 Prediction을 해주는 기능이다.



Models 화면으로 들어오면 Create Model 버튼이 나온다. 이 버튼을 이용해서 모델을 생성한다.





모델 생성시에 아래와 같이 단순하게 모델이름을 넣어주면된다.




모델 이름을 넣어준후에, 해당 모델에 실제 Export된 모델 파일을 배포해줘야 하는데, CloudML은 버전 기능을 제공한다. 그래서 아래 그림과 같이 Create Version 버튼을 눌러서 새로운 버전을 생성한다.





Create Version 메뉴에서는 Name에 버전명을 쓰고, Source에는 Export된 *.pb 파일과 variables 폴더가 저장된 GCS 경로를 선택한다.



아래는 terrycho-face-recog-export 버킷을 선택한 후, 그 버킷안에 export 폴더를 선택하는 화면이다.



선택을 해서 배포를 하면 아래와 같이 v7 버전이름을 모델이 배포가 된다.


배포된 모델로 예측 (Prediction)하기

그러면 배포된 모델을 사용해서 예측을 해보자. 아래가 전체코드이다.


from googleapiclient import discovery

from oauth2client.client import GoogleCredentials

import numpy

import base64

import logging


from IPython.display import display, Image


cropped_image = "croppedjolie.jpg"

display(Image(cropped_image))


PROJECT = 'terrycho-ml'

MODEL_NAME = 'face_recog'

MODEL_VERSION ='v7'



def call_ml_service(img_str):

   parent = 'projects/{}/models/{}/versions/{}'.format(PROJECT, MODEL_NAME,MODEL_VERSION)

   pred = None


   request_dict = {

       "instances": [

           {

               "image": {

                   "b64": img_str

               }

           }

       ]

   }


   try:

       credentials = GoogleCredentials.get_application_default()

       cloudml_svc = discovery.build('ml', 'v1', credentials=credentials)

       request = cloudml_svc.projects().predict(name=parent, body=request_dict)

       response = request.execute()

       print(response)

       #pred = response['predictions'][0]['scores']

       #pred = numpy.asarray(pred)


   except Exception, e:

       logging.exception("Something went wrong!")


   return pred



# base64 encode the same image

with open(cropped_image, 'rb') as image_file:

   encoded_string = base64.b64encode(image_file.read())


# See what ML Engine thinks

online_prediction = call_ml_service(encoded_string)


print online_prediction


코드를 살펴보면

       credentials = GoogleCredentials.get_application_default()

       cloudml_svc = discovery.build('ml', 'v1', credentials=credentials)


에서 discovery.build를 이용해서 구글 클라우드 API 중, ‘ML’ 이라는 API의 버전 ‘v1’을 불러왔다. CloudML 1.0 이다. 다음 credentials는 get_application_default()로 디폴트 credential을 사용하였다.

다음으로, CloudML에 request 를 보내야 하는데, 코드 윗쪽으로 이동해서 보면


def call_ml_service(img_str):

   parent = 'projects/{}/models/{}/versions/{}'.format(PROJECT, MODEL_NAME,MODEL_VERSION)

   pred = None


   request_dict = {

       "instances": [

           {

               "image": {

                   "b64": img_str

               }

           }

       ]

   }


를 보면 request body에 보낼 JSON을 request_dict로 정의하였다. 이때, 이미지를 “b64”라는 키로 img_str을 넘겼는데, 이 부분은 이미지 파일을 읽어서 base64 스트링으로 인코딩 한 값이다.

request = cloudml_svc.projects().predict(name=parent, body=request_dict)


다음 request 를 만드는데, 앞에서 선언한 cloudml_svc객체를 이용하여 prediction request 객체를 생성한다. 이때 parent 에는 모델의 경로가 들어가고 body에는 앞서 정의한 이미지가 들어있는 JSON 문자열이 된다.


   parent = 'projects/{}/models/{}/versions/{}'.format(PROJECT, MODEL_NAME,MODEL_VERSION)


Parent에는 모델의 경로를 나타내는데, projects/{프로젝트명}/models/{모델명}/versions/{버전명} 형태로 표현되며, 여기서는 projects/terrycho-ml/models/face_recog/versions/v7 의 경로를 사용하였다.


이렇게 request 객체가 만들어지면 이를 request.execute()로 이를 호출하고 결과를 받는다.

       response = request.execute()

       print(response)


결과를 받아서 출력해보면 다음과 같은 결과가 나온다.




2번째 라벨이 0.99% 확률로 유사한 결과가 나온것을 볼 수 있다. 라벨 순서 대로 첫번째가 제시카 알바, 두번째가 안젤리나 졸리, 세번째가 니콜 키드만, 네번째가 설현, 다섯번째가 빅토리아 베컴이다.


이제 까지 여러회에 걸쳐서 텐서플로우를 이용하여 CNN 모델을 구현하고, 이 모델을 기반으로 얼굴 인식을 학습 시키고 예측 시키는 모델 개발까지 모두 끝 맞췄다.


실제 운영 환경에서 사용하기에는 모델이 단순하지만, 여기에 CNN 네트워크만 고도화하면 충분히 사용할만한 모델을 개발할 수 있을 것이라고 본다. CNN 네트워크에 대한 이론 보다는 실제 구현하면서 데이타 전처리 및 학습과, 학습된 모델의 저장 및 이를 이용한 예측 까지 전체 흐름을 설명하기 위해서 노력하였다.


다음은 이 얼굴 인식 모델을 실제 운영환경에서 사용할만한 수준의 품질이 되는 모델을 사용하는 방법을 설명하고자 한다.

직접 CNN 모델을 만들어도 되지만, 얼마전에, 발표된 Tensorflow Object Detection API (https://github.com/tensorflow/models/tree/master/object_detection)는 높은 정확도를 제공하는 이미지 인식 모델을 라이브러리 형태로 제공하고 있다. 다음 글에서는 이 Object Detection API를 이용하여 연예인 얼굴을 학습 시키고 인식하는 모델을 개발하고 학습 및 예측 하는 방법에 대해서 알아보도록 하겠다.



저작자 표시 비영리
신고

얼굴 인식 모델을 만들어보자 #4 클라우드를 이용하여 학습 시키기

(머신러닝 학습 및 예측 시스템의 운영환경화)


조대협 (http://bcho.tistory.com)

앞에서 모델을 만들고 학습도 다했다. 이제, 이 모델을 실제 운영 환경에서 운영할 수 있는 스케일로 포팅을 하고자 한다.


로컬 환경 대비 실제 운영 환경으로 확장할때 고려해야 하는 사항은


  • 대규모 학습 데이타를 저장할 수 있는 공간

  • 대규모 학습 데이타를 전처리하기 위한 병렬 처리 환경
    이 내용은 이미  http://bcho.tistory.com/1177에서 다루었다.

  • 대규모 학습 데이타를 빠르게 학습 시킬 수 있는 컴퓨팅 파워

  • 학습된 데이타를 이용한 대규모 예측 서비스를 할 수 있는 기능


위의 요건을 만족하면서 텐서플로우로 환경을 올리는 방법은 여러가지가 있지만, 클라우드를 선택하기로 한다.

이유는

  • 첫번째 모델 개발에 집중하고, 텐서플로우의 설치 및 운영 등에 신경쓰지 않도록 한다. 단순한 텐서플로우 설치뿐만 아니라 여러 장비를 동시에 이용하여 분산 학습을 하려면, 클러스터 구성 및 유지가 부담이 된다.

  • 클라우드 컴퓨팅 파워를 이용하여, 대규모 데이타에 대한 전처리를 수행하고 개개별 학습 속도를 높이는 것은 물론이고, 모델을 튜닝하여 동시에 여러 모델을 학습 시킬 수 있다.

  • 대용량 학습 데이타를 저장하기 위한 스토리지 인프라에 대한 구성 및 운영 비용을 절감한다.


즉 설정이나 운영은 클라우드에 맏겨 놓고, 클라우드의 무한한 자원과 컴퓨팅 파워를 이용하여 빠르게 모델을 학습하기 위함이다.

구글 클라우드


아무래도 일하는 성격상 구글 클라우드를 먼저 볼 수 밖에 없는데, 구글 클라우드에서는 텐서플로우의 매니지드 서비스인 CloudML을 제공한다.


CloudML은 별도의 설치나 환경 설정 없이 텐서플로우로 만든 모델을 학습 시키거나 학습된 결과로 예측을 하는 것이 가능하다. 주요 특징을 보면 다음과 같다.


  • 학습시에, 별도의 설정 없이 텐서플로우 클러스터 크기 조절이 가능하다. 싱글 머신에서 부터 GPU 머신 그리고 여러대의 클러스터 머신 사용이 가능하다

  • 하이퍼 패러미터 튜닝이 가능하다. DNN의 네트워크의 폭과 깊이도 하이퍼 패러미터로 지정할 수 있으며, CloudML은 이런 하이퍼패러미터의 최적값을 자동으로 찾아준다.

  • 예측 서비스에서는 Tensorflow Serv를 별도의 빌드할 필요 없이 미리 환경 설정이 다되어 있으며 (bazel 빌드의 끔직함을 겪어보신 분들은 이해하실듯) gRPC가 아닌 간단한 JSON 호출로 예측 (PREDICTION) 요청을 할 수 있다

  • 분당 과금이다. 이게 강력한 기능인데, 구글 클라우드는 기본적으로 분당 과금으로 CPU를 사용하던, GPU를 사용하던 정확히 사용한 만큼만 과금하기 때문에, 필요할때 필요한 만큼만 사용하면 된다. 일부 클라우드의 경우에는 시간당 과금을 사용하기 때문에, 8대의 GPU머신에서 1시간 5분을 학습하더라도 8대에 대해서 2시간 요금을 내야하기 때문에 상대적으로 비용 부담이 높다.

  • 가장 큰 메리트는 TPU (Tensorflow Processing Unit)을 지원한다는 것인데, 딥러닝 전용 GPU라고 생각하면 된다. 일반적인 CPU또는 GPU대비 15~30배 정도 빠른 성능을 제공한다.


    현재는 Close Alpha로 특정 사용자에게만 시범 서비스를 제공하고 있지만 곧 CloudML을 통해서 일반 사용자에게도 서비스가 제공될 예정이다.

CloudML을 이용하여 학습하기

코드 수정

CloudML에서 학습을 시키려면 약간의 코드를 수정해야 한다. 수정해야 하는 이유는 학습 데이타를 같이 올릴 수 없기 때문인데, 여기에는 두 가지 방법이 있다.


  • 학습 데이타를 GCS (Google Cloud Storage)에 올려놓은 후, 학습이 시작되기 전에 로컬 디렉토리로 복사해 오거나

  • 또는 학습 데이타를 바로 GCS로 부터 읽어오도록 할 수 있다.


첫번째 방법은 gsutil 이라는 GCS 명령어를 이용하여 학습 시작전에 GCS에서 학습 데이타를 카피해오면 되고,

두번째 방법은 학습 데이타의 파일명을 GCS 로 지정하면 된다.

예를 들어 텐서 플로우 코드에서 이미지 파일을 아래와 같이 로컬 경로에서 읽어왔다면

   image =  tf.image.decode_jpeg(tf.read_file(“/local/trainingdata/”+image_file),channels=FLAGS.image_color)


GCS에서 읽어오려면 GCS 경로로 바꿔 주면 된다. GCS 버킷명이 terrycho-training-data라고 하면

   image =  tf.image.decode_jpeg(tf.read_file(“gs://terrycho-training-data/trainingdata/”+image_file),channels=FLAGS.image_color)


첫번째 방법의 경우에는 데이타가 아주 많지 않고, 분산 학습이 아닌경우 매우 속도가 빠르다. 두번째 방법의 경우에는 데이타가 아주아주 많아서 분산 학습이 필요할때 사용한다. 아무래도 로컬 파일 억세스가 GCS 억세스 보다 빠르기 때문이다.


다음은 첫번째 방식으로 학습 데이타를 로컬에 복사해서 학습하는 방식의 코드이다.


https://github.com/bwcho75/facerecognition/blob/master/CloudML%20Version/face_recog_model/model_localfile.py

코드 내용은 앞서 만들 모델 코드와 다를것이 없고 단지 아래 부분과, 파일 경로 부분만 다르다

def gcs_copy(source, dest):

   print('Recursively copying from %s to %s' %

       (source, dest))

   subprocess.check_call(['gsutil', '-q', '-m', 'cp', '-R']

       + [source] + [dest]


gcs_copy 함수는 GCS의 source 경로에서 파일을 dest 경로로 복사해주는 명령이다.


def prepare_data():

   # load training and testing data index file into local

   gcs_copy( 'gs://'+DESTINATION_BUCKET+'/'+TRAINING_FILE,'.')

   gcs_copy( 'gs://'+DESTINATION_BUCKET+'/'+VALIDATION_FILE,'.')

   

   # loading training and testing images to local

   image_url = 'gs://'+DESTINATION_BUCKET+'/images/*'


   if not os.path.exists(FLAGS.local_image_dir):

        os.makedirs(FLAGS.local_image_dir)

   gcs_copy( image_url,FLAGS.local_image_dir)

   

prepare_data()    

main()


그리고 prepare_data를 이용해서, 학습과 테스트용 이미지 목록 파일을 복사하고, 이미지들도 로컬에 복사한다.

로컬에 데이타 복사가 끝나면 main()함수를 호출하여 모델을 정의하고 학습을 시작한다.



디렉토리 구조

코드를 수정하였으면, CloudML을 이용하여 학습을 하려면, 파일들을 패키징 해야 한다. 별 다를것은 없고


[작업 디렉토리]

  • __init__.py

  • {모델 파일명}.py


식으로 디렉토리를 구성하면 된다.

얼굴 학습 모델을 model_localfile.py라는 이름으로 저장하였다


명령어

이제 학습용 모델이 준비되었으면, 이 모델을 CloudML에 집어 넣으면 된다.

명령어가 다소 길기 때문에, 쉘 스크립트로 만들어놓거나 또는 파이썬 노트북에 노트 형식으로 만들어 놓으면 사용이 간편하다. 다음은 파이썬 노트북으로 만들어놓은 내용이다.


import google.auth

import os

import datetime


os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/Users/terrycho/keys/terrycho-ml.json"

job_name = 'preparefacedata'+ datetime.datetime.now().strftime('%y%m%d%H%M%S')


리모트로 구글 클라우드의 CloudML을 호출하기 때문에, GOOGLE_APPLICATION_CREDIENTIALS에 서비스 어카운트 파일을 지정한다.

그리고 CloudML에 학습을 실행하면, 각 학습은 JOB으로 등록되는데, 손쉽게 JOB을 찾아서 모니터링 하거나 중지할 수 있도록, JOB ID를 현재 시간으로 생성한다.



print job_name

# Job name whatever you want

JOB_NAME=job_name

# the directory of folder that include your source and init file

PACKAGE_PATH='/Users/terrycho/anaconda/work/face_recog/face_recog_model'

# format: folder_name.source_file_name

MODULE_NAME='face_recog_model.model_localfile'

# bucket you created

STAGING_BUCKET='gs://terrycho-face-recog-stage'

# I recommand "europe-west1" region because there are not enough GPUs in US region for you.....

REGION='us-east1'

# Default is CPU computation. set BASIC_GPU to use Tesla K80 !

SCALE_TIER='BASIC_GPU'


# Submit job with these settings

!gcloud ml-engine jobs submit training $JOB_NAME \

--package-path=$PACKAGE_PATH \

--module-name=$MODULE_NAME \

--staging-bucket=$STAGING_BUCKET \

--region=$REGION \

--scale-tier=$SCALE_TIER \


다음은 cloudml 명령어를 실행하면 된다. 각 인자를 보면

  • JOB_NAME은 학습 JOB의 이름이다.

  • package-path는 __init__.py와 학습 모델 및 관련 파일들이 있는 디렉토리가 된다.

  • module-name은 package-path안에 있는 학습 실행 파일이다.

  • staging-bucket은 CloudML에서 학습 코드를 올리는 임시 Google Cloud Storage로, Google Cloud Storage 만든 후에, 그 버킷 경로를 지정하면 된다.

  • region은 CloudML을 사용한 리전을 선택한다.

  • 마지막으로 scale-tier는 학습 머신의 사이즈를 지정한다.

스케일 티어

설명

BASIC

싱글 머신. CPU

BASIC_GPU

싱글 머신 + K80 GPU

STANDARD_1

분산 머신

PREMIUM_1

대규모 분산 머신

CUSTOM

사용자가 클러스터 크기를 마음대로 설정


일반적인 모델은 BASIC_GPU를 사용하면 되고, 모델이 분산 학습이 가능하도록 개발되었으면 STANDARD_1 이나 PREMIUM_1을 사용하면 된다.


이렇게 명령을 수행하면 모델코드가 CloudML로 전송되고, 전송된 코드는 CloudML에서 실행된다.

학습 모니터링

학습이 시작되면 JOB을 구글 클라우드 콘솔의 CloudML 메뉴에서 모니터링을 할 수 있다.




다음은 CloudML에서의 JOB 목록이다.  (진짜 없어 보인다…)




실행중인 JOB에서 STOP 버튼을 누르면 실행중인 JOB을 정지시킬 수도 있고, View Logs 버튼을 누르면, 학습 JOB에서 나오는 로그를 볼 수 있다. ( 텐서플로우 코드내에서 print로 찍은 내용들도 모두 여기 나온다.)




여기까지 간단하게나마 CloudML을 이용하여 모델을 학습하는 방법을 알아보았다.

본인의 경우 연예인 인식 모델을 MAC PRO 15” i7 (NO GPU)에서 학습한 경우 7000 스텝가지 약 8시간이 소요되었는데, CloudML의 BASIC_GPU를 사용하였을때는 10,000 스탭에 약 1시간 15분 정도 (GCS를 사용하지 않고 직접 파일을 로컬에 복사해놓고 돌린 경우) 가 소요되었다. (빠르다)


여기서 사용된 전체 코드는 https://github.com/bwcho75/facerecognition/tree/master/CloudML%20Version 에 있다.


  • model_gcs.py 는 학습데이타를 GCS에서 부터 읽으면서 학습하는 버전이고

  • model_localfile.py는 학습데이타를 로컬 디스크에 복사해놓고 학습하는 버전이다.


다음 글에서는 학습된 모델을 배포하여 실제로 예측을 실행할 수 있는 API를 개발해보도록 하겠다.

저작자 표시 비영리
신고

연예인 얼굴 인식 서비스를 만들어보자 #1 - 데이타 준비하기

 

CNN 에 대한 이론 공부와 텐서 플로우에 대한 기본 이해를 끝내서 실제로 모델을 만들어보기로 하였다.

CNN을 이용한 이미지 인식중 대중적인 주제로 얼굴 인식 (Face recognition)을 주제로 잡아서, 이 모델을 만들기로 하고 아직 실력이 미흡하여 호주팀에서 일하고 있는 동료인 Win woo 라는 동료에게 모델과 튜토리얼 개발을 부탁하였다.

 

이제 부터 연재하는 연예인 얼굴 인식 서비스는 Win woo 가 만든 코드를 기반으로 하여 설명한다. (코드 원본 주소 : https://github.com/wwoo/tf_face )

 

얼굴 데이타를 구할 수 있는곳

먼저 얼굴 인식 모델을 만들려면, 학습을 시킬 충분한 데이타가 있어야 한다. 사람 얼굴을 일일이 구할 수 도 없고, 구글이나 네이버에서 일일이 저장할 수 도 없기 때문에, 공개된 데이타셋을 활용하였는데, PubFig (Public Figures Face Database - http://www.cs.columbia.edu/CAVE/databases/pubfig/) 를 사용하였다.


 

이 데이타셋에는 약 200명에 대한 58,000여장의 이미지를 저장하고 있는데, 이 중의 일부만을 사용하였다.

Download 페이지로 가면, txt 파일 형태 (http://www.cs.columbia.edu/CAVE/databases/pubfig/download/dev_urls.txt) 로 아래와 같이

 

Abhishek Bachan 1 http://1.bp.blogspot.com/_Y7rzCyUABeI/SNIltEyEnjI/AAAAAAAABOg/E1keU_52aFc/s400/ash_abhishek_365x470.jpg 183,60,297,174 f533da9fbd1c770428c8961f3fa48950
Abhishek Bachan 2 http://1.bp.blogspot.com/_v9nTKD7D57Q/SQ3HUQHsp_I/AAAAAAAAQuo/DfPcHPX2t_o/s400/normal_14thbombaytimes013.jpg 49,71,143,165 e36a8b24f0761ec75bdc0489d8fd570b
Abhishek Bachan 3 http://2.bp.blogspot.com/_v9nTKD7D57Q/SL5KwcwQlRI/AAAAAAAANxM/mJPzEHPI1rU/s400/ERTYH.jpg 32,68,142,178 583608783525c2ac419b41e538a6925d

 

사람이름, 이미지 번호, 다운로드 URL, 사진 크기, MD5 체크섬을 이 필드로 저장되어 있다.

이 파일을 이용하여 다운로드 URL에서 사진을 다운받아서, 사람이름으로된 폴더에 저장한다.

물론 수동으로 할 수 없으니 HTTP Client를 이용하여, URL에서 사진을 다운로드 하게 하고, 이를 사람이름 폴더 별로 저장하도록 해야 한다.

 

HTTP Client를 이용하여 파일을 다운로드 받는 코드는 일반적인 코드이기 때문에 별도로 설명하지 않는다.

본인의 경우에는 Win이 만든 https://github.com/wwoo/tf_face/blob/master/tf/face_extract/pubfig_get.py 코드를 이용하여 데이타를 다운로드 받았다.

사용법은  https://github.com/wwoo/tf_face 에 나와 있는데,

 

$> python tf/face_extract/pubfig_get.py tf/face_extract/eval_urls.txt ./data

를 실행하면 ./data 디렉토리에 이미지를 다운로드 받아서 사람 이름별 폴더에 저장해준다.

evals_urls.txt에는 위에서 언급한 dev_urls.txt 형태의 데이타가 들어간다.


사람 종류가 너무 많으면 데이타를 정재하는 작업이 어렵고, (왜 어려운지는 뒤에 나옴) 학습 시간이 많이 걸리기 때문에, 약 47명의 데이타를 다운로드 받아서 작업하였다.

학습 데이타 준비에 있어서 경험

쓰레기 데이타 골라내기

데이타를 다운받고 나니, 아뿔사!! PubFig 데이타셋이 오래되어서 없는 이미지도 있고 학습에 적절하지 않은 이미지도 있다.


주로 학습에 적절하지 않은 데이타는 한 사진에 두사람 이상의 얼굴이 있거나, 이미지가 사라져서 위의 우측 그림처럼, 이미지가 없는 형태로 나오는 경우인데, 이러한 데이타는 어쩔 수 없이 눈으로 한장한장 다 걸러내야만 했는데, 이런 간단한 데이타 필터링 처리는 Google Cloud Vision API를 이용하여, 얼굴이 하나만 있는 사진만을 사용하도록 하여 필터링을 하였다.

학습 데이타의 분포

처음에 학습을 시작할때, 분류별로 데이타의 수를 다르게 하였다. 어렵게 모은 데이타를 버리기가 싫어서 모두 다 넣고 학습 시켰는데, 그랬더니 학습이 쏠리는 현상이 발생하였다.

예를 들어 안젤리나 졸리 300장, 브래드피트 100장, 제시카 알바 100장 이런식으로 학습을 시켰더니, 이미지 예측에서 안젤리나 졸리로 예측하는 경우가 많아졌다. 그래서 학습을 시킬때는 데이타수가 작은 쪽으로 맞춰서 각 클래스당 학습 데이타수가 같도록 하였다. 즉 위의 데이타의 경우에는 안젤리나 졸리 100장, 브래드피트 100장, 제시카 알바 100장식으로 데이타 수를 같게 해야했다.

라벨은 숫자로

라벨의 가독성을 높이기 위해서 라벨을 영문 이름으로 사용했는데, CNN 알고리즘에서 최종 분류를 하는 알고리즘은 softmax 로 그 결과 값을 0,1,2…,N식으로 라벨을 사용하기 때문에, 정수형으로 변환을 해줘야 하는데, 텐서 플로우 코드에서는 이게 그리 쉽지않았다. 그래서 차라리 처음 부터 학습 데이타를 만들때는 라벨을 정수형으로 만드는것이 더 효과적이다

얼굴 각도, 표정,메이크업, 선글라스 도 중요하다

CNN 알고리즘을 마법처럼 생각해서였을까? 데이타만 있다면 어떻게든 학습이 될 줄 알았다. 그러나 얼굴의 각도가 많이 다르거나 표정이 심하게 차이가 난 경우에는 다른 사람으로 인식이 되기 때문에 가능하면 비슷한 표정에 비슷한 각도의 사진으로 학습 시키는 것이 정확도를 높일 수 있다.


 

얼굴 각도의 경우 구글 클라우드 VISION API를 이용하면 각도를 추출할 수 있기 때문에 20도 이상 차이가 나는 사진은 필터링 하였고, 표정 부분도 VISION API를 이용하면 감정도를 분석할 수 있기 때문에 필터링이 가능하다. (아래서 설명하는 코드에서는 감정도 분석 부분은 적용하지 않았다)

또한 선글라스를 쓴 경우에도 다른 사람으로 인식할 수 있기 때문에 VISION API에서 물체 인식 기능을 이용하여 선글라스가 검출된 경우에는 학습 데이타에서 제거하였다.

이외에도 헤어스타일이나 메이크업이 심하게 차이가 나는 경우에는 다른 사람으로 인식되는 확률이 높기 때문에 이런 데이타도 가급적이면 필터링을 하는것이 좋다.

웹 크라울링의 문제점

데이타를 쉽게 수집하려고 웹 크라울러를 이용해서 구글 이미지 검색에서 이미지를 수집해봤지만, 정확도는 매우 낮게 나왔다.


 

https://www.youtube.com/watch?v=k5ioaelzEBM

<그림. 설현 얼굴을 웹 크라울러를 이용하여 수집하는 화면>

 

아래는 웹 크라울러를 이용하여 EXO 루한의 사진을 수집한 결과중 일부이다.


웹크라울러로 수집한 데이타는, 앞에서 언급한 쓰레기 데이타들이 너무 많다. 메이크업, 표정, 얼굴 각도, 두명 이상 있는 사진들이 많았고, 거기에 더해서 그 사람이 아닌 사람의 얼굴 사진까지 같이 수집이 되는 경우가 많았다.

웹 크라울링을 이용한 학습 데이타 수집은 적어도 얼굴 인식용 데이타 수집에 있어서는 좋은 방법은 아닌것 같다. 혹여나 웹크라울러를 사용하더라도 반드시 수동으로 직접 데이타를 검증하는 것이 좋다.

학습 데이타의 양도 중요하지만 질도 매우 중요하다

아이돌 그룹인 EXO와 레드벨벳의 사진을 웹 크라울러를 이용해서 수집한 후에 학습을 시켜보았다. 사람당 약 200장의 데이타로 8개 클래스 정도를 테스트해봤는데 정확도가 10%가 나오지를 않았다.

대신 데이타를 학습에 좋은 데이타를 일일이 눈으로 확인하여 클래스당 30장 정도를 수집해서 학습 시킨 결과 60% 정도의 정확도를 얻을 수 있었다.  양도 중요하지만 학습 데이타의 질적인 면도 중요하다.

중복데이타 처리 문제

데이타를 수집해본 결과, 중복되는 데이타가 생각보다 많았다. 중복 데이타를 걸러내기 위해서 파일의 MD5 해쉬 값을 추출해낸 후 이를 비교해서 중복되는 파일을 제거하였는데, 어느정도 효과를 볼 수 있었지만, 아래 이미지와 같이 같은 이미지지만, 편집이나 리사이즈가 된 이미지의 경우에는 다른 파일로 인식되서 중복 체크에서 검출되지 않았다.


연예인 얼굴 인식은 어렵다

얼굴 인식 예제를 만들면서 재미를 위해서 한국 연예인 얼굴을 수집하여 학습에 사용했는데, 제대로 된 학습 데이타를 구하기가 매우 어려웠다. 앞에서 언급한데로 메이크업이나 표정 변화가 너무 심했고, 어렸을때나 나이먹었을때의 차이등이 심했다. 간단한 공부용으로 사용하기에는 좋은 데이타는 아닌것 같다.

그러면 학습에 좋은 데이타는?

그러면 얼굴 인식 학습에 좋은 데이타는 무엇일까? 테스트를 하면서 내린 자체적인 결론은 정면 프로필 사진류가 제일 좋다. 특히 스튜디오에서 찍은 사진은 같은 조명에 같은 메이크업과 헤어스타일로 찍은 경우가 많기 때문에 학습에 적절하다. 또는 동영상의 경우에는 프레임을 잘라내면 유사한 표정과 유사한 각도, 조명등에 대한 데이타를 많이 얻을 수 있기 때문에 좋은 데이타 된다.

얼굴 추출하기

그러면 앞의 내용을 바탕으로 해서, 적절한 학습용 얼굴 이미지를 추출하는 프로그램을 만들어보자

포토샵으로 일일이 할 수 없기 때문에 얼굴 영역을 인식하는 API를 사용하기로한다. OPEN CV와 같은 오픈소스 라이브러리를 사용할 수 도 있지만 구글의 VISION API의 경우 얼굴 영역을 아주 잘 잘라내어주고,  얼굴의 각도나 표정을 인식해서 필터링 하는 기능까지 코드 수십줄만 가지고도 구현이 가능했기 때문에, VISION API를 사용하였다. https://cloud.google.com/vision/

VISION API ENABLE 하기

VISION API를 사용하기 위해서는 해당 구글 클라우드 프로젝트에서 VISION API를 사용하도록 ENABLE 해줘야 한다.

VISION API를 ENABLE하기 위해서는 아래 화면과 같이 구글 클라우드 콘솔 > API Manager 들어간후


 

+ENABLE API를 클릭하여 아래 그림과 같이 Vision API를 클릭하여 ENABLE 시켜준다.

 



 

SERVICE ACCOUNT 키 만들기

다음으로 이 VISION API를 호출하기 위해서는 API 토큰이 필요한데, SERVICE ACCOUNT 라는 JSON 파일을 다운 받아서 사용한다.

구글 클라우드 콘솔에서 API Manager로 들어간후 Credentials 메뉴에서 Create creadential 메뉴를 선택한후, Service account key 메뉴를 선택한다


 

다음 Create Service Account key를 만들도록 하고, accountname과 id와 같은 정보를 넣는다. 이때 중요한것이 이 키가 가지고 있는 사용자 권한을 설정해야 하는데, 편의상 모든 권한을 가지고 있는  Project Owner 권한으로 키를 생성한다.

 

(주의. 실제 운영환경에서 전체 권한을 가지는 키는 보안상의 위험하기 때문에 특정 서비스에 대한 접근 권한만을 가지도록 지정하여 Service account를 생성하기를 권장한다.)

 


 

Service account key가 생성이 되면, json 파일 형태로 다운로드가 된다.

여기서는 terrycho-ml-80abc460730c.json 이름으로 저장하였다.

 

예제 코드

그럼 예제를 보자 코드의 전문은 https://github.com/bwcho75/facerecognition/blob/master/com/terry/face/extract/crop_face.py 에 있다.

 

이 코드는 이미지 파일이 있는 디렉토리를 지정하고, 아웃풋 디렉토리를 지정해주면 이미지 파일을 읽어서 얼굴이 있는지 없는지를 체크하고 얼굴이 있으면, 얼굴 부분만 잘라낸 후에, 얼굴 사진을 96x96 사이즈로 리사즈 한후에,

70%의 파일들은 학습용으로 사용하기 위해서 {아웃풋 디렉토리/training/} 디렉토리에 저장하고

나머지 30%의 파일들은 검증용으로 사용하기 위해서 {아웃풋 디렉토리/validate/} 디렉토리에 저장한다.

 

그리고 학습용 파일 목록은 다음과 같이 training_file.txt에 파일 위치,사람명(라벨) 형태로 저장하고

/Users/terrycho/traning_datav2/training/wsmith.jpg,Will Smith

/Users/terrycho/traning_datav2/training/wsmith061408.jpg,Will Smith

/Users/terrycho/traning_datav2/training/wsmith1.jpg,Will Smith

 

검증용 파일들은 validate_file.txt에 마찬가지로  파일위치와, 사람명(라벨)을 저장한다.

사용 방법은 다음과 같다.

python com/terry/face/extract/crop_face.py “원본 파일이있는 디렉토리" “아웃풋 디렉토리"

(원본 파일 디렉토리안에는 {사람이름명} 디렉토리 아래에 사진들이 쭈욱 있는 구조라야 한다.)

 

자 그러면, 코드의 주요 부분을 살펴보자

 

VISION API 초기화 하기

  def __init__(self):

       # initialize library

       #credentials = GoogleCredentials.get_application_default()

       scopes = ['https://www.googleapis.com/auth/cloud-platform']

       credentials = ServiceAccountCredentials.from_json_keyfile_name(

                       './terrycho-ml-80abc460730c.json', scopes=scopes)

       self.service = discovery.build('vision', 'v1', credentials=credentials)

 

초기화 부분은 Google Vision API를 사용하기 위해서 OAuth 인증을 하는 부분이다.

scope를 googleapi로 정해주고, 인증 방식을 Service Account를 사용한다. credentials 부분에 service account key 파일인 terrycho-ml-80abc460730c.json를 지정한다.

 

얼굴 영역 찾아내기

다음은 이미지에서 얼굴을 인식하고, 얼굴 영역(사각형) 좌표를 리턴하는 함수를 보자

 

   def detect_face(self,image_file):

       try:

           with io.open(image_file,'rb') as fd:

               image = fd.read()

               batch_request = [{

                       'image':{

                           'content':base64.b64encode(image).decode('utf-8')

                           },

                       'features':[

                           {

                           'type':'FACE_DETECTION',

                           'maxResults':MAX_FACE,

                           },

                           {

                           'type':'LABEL_DETECTION',

                           'maxResults':MAX_LABEL,

                           }

                                   ]

                       }]

               fd.close()

       

           request = self.service.images().annotate(body={

                           'requests':batch_request, })

           response = request.execute()

           if 'faceAnnotations' not in response['responses'][0]:

                print('[Error] %s: Cannot find face ' % image_file)

                return None

               

           face = response['responses'][0]['faceAnnotations']

           label = response['responses'][0]['labelAnnotations']

           

           if len(face) > 1 :

               print('[Error] %s: It has more than 2 faces in a file' % image_file)

               return None

           

           roll_angle = face[0]['rollAngle']

           pan_angle = face[0]['panAngle']

           tilt_angle = face[0]['tiltAngle']

           angle = [roll_angle,pan_angle,tilt_angle]

           

           # check angle

           # if face skew angle is greater than > 20, it will skip the data

           if abs(roll_angle) > MAX_ROLL or abs(pan_angle) > MAX_PAN or abs(tilt_angle) > MAX_TILT:

               print('[Error] %s: face skew angle is big' % image_file)

               return None

           

           # check sunglasses

           for l in label:

               if 'sunglasses' in l['description']:

                 print('[Error] %s: sunglass is detected' % image_file)  

                 return None

           

           box = face[0]['fdBoundingPoly']['vertices']

           left = box[0]['x']

           top = box[1]['y']

               

           right = box[2]['x']

           bottom = box[2]['y']

               

           rect = [left,top,right,bottom]

               

           print("[Info] %s: Find face from in position %s and skew angle %s" % (image_file,rect,angle))

           return rect

       except Exception as e:

           print('[Error] %s: cannot process file : %s' %(image_file,str(e)) )

           

 

 

맨 처음에는 얼굴 영역을 추출하기전에, 같은 파일이 예전에 사용되었는지를 확인한다.

           image = Image.open(fd)  

 

           # extract hash from image to check duplicated image

           m = hashlib.md5()

           with io.BytesIO() as memf:

               image.save(memf, 'PNG')

               data = memf.getvalue()

               m.update(data)

 

           if image_hash in global_image_hash:

               print('[Error] %s: Duplicated image' %(image_file) )

               return None

           global_image_hash.append(image_hash)

 

이미지에서 md5 해쉬를 추출한후에, 이 해쉬를 이용하여 학습 데이타로 사용된 파일들의 해쉬와 비교한다. 만약에 중복되는 것이 없으면 이 해쉬를 리스트에 추가하고 다음 과정을 수행한다.

 

VISION API를 이용하여, 얼굴 영역을 추출하는데, 위의 코드에서 처럼 image_file을 읽은후에, batch_request라는 문자열을 만든다. JSON 형태의 문자열이 되는데, 이때 image라는 항목에 이미지 데이타를 base64 인코딩 방식으로 인코딩해서 전송한다. 그리고 VISION API는 얼굴인식뿐 아니라 사물 인식, 라벨인식등 여러가지 기능이 있기 때문에 그중에서 타입을 ‘FACE_DETECTION’으로 정의하여 얼굴 영역만 인식하도록 한다.

 

request를 만들었으면, VISION API로 요청을 보내면 응답이 오는데, 이중에서 response 엘리먼트의 첫번째 인자 ( [‘responses’][0] )은 첫번째 얼굴은 뜻하는데, 여기서 [‘faceAnnotation’]을 하면 얼굴에 대한 정보만을 얻을 수 있다. 이중에서  [‘fdBoundingPoly’] 값이 얼굴 영역을 나타내는 사각형이다. 이 갑ㄱㅅ을 읽어서 left,top,right,bottom 값에 세팅한 후 리턴한다.

 

그리고 얼굴의 각도 (상하좌우옆)를 추출하여, 얼국 각도가 각각 20도 이상 더 돌아간 경우에는 학습 데이타로 사용하지 않고 필터링을 해냈다.

다음은 각도를 추출하고 필터링을 하는 부분이다.

           roll_angle = face[0]['rollAngle']

           pan_angle = face[0]['panAngle']

           tilt_angle = face[0]['tiltAngle']

           angle = [roll_angle,pan_angle,tilt_angle]

           

           # check angle

           # if face skew angle is greater than > 20, it will skip the data

           if abs(roll_angle) > MAX_ROLL or abs(pan_angle) > MAX_PAN or abs(tilt_angle) > MAX_TILT:

               print('[Error] %s: face skew angle is big' % image_file)

               return None

 

 

VISION API에서 추가로 “FACE DETECTION” 뿐만 아니라 “LABEL_DETECTION” 을 같이 수행했는데 이유는 선글라스를 쓰고 있는 사진을 필터링하기 위해서 사용하였다. 아래는 선글라스 있는 사진을 검출하는  코드이다.

           # check sunglasses

           for l in label:

               if 'sunglasses' in l['description']:

                 print('[Error] %s: sunglass is detected' % image_file)  

                 return None

 

얼굴 잘라내고 리사이즈 하기

앞의 detect_face에서 필터링하고 찾아낸 얼굴 영역을 가지고 그 부분만 전체 사진에서 잘라내고, 잘라낸 얼굴을 학습에 적합하도록 같은 크기 (96x96)으로 리사이즈 한다.

이런 이미지 처리를 위해서 PIL (Python Imaging Library - http://www.pythonware.com/products/pil/)를 사용하였다.

   def crop_face(self,image_file,rect,outputfile):

       try:

           fd = io.open(image_file,'rb')

           image = Image.open(fd)  

           crop = image.crop(rect)

           im = crop.resize(IMAGE_SIZE,Image.ANTIALIAS)

           im.save(outputfile,"JPEG")

           fd.close()

           print('[Info] %s: Crop face %s and write it to file : %s' %(image_file,rect,outputfile) )

       except Exception as e:

           print('[Error] %s: Crop image writing error : %s' %(image_file,str(e)) )

image_file을 인자로 받아서 , rect 에 정의된 사각형 영역 만큼 crop를 해서 잘라내고, resize 함수를 이용하여 크기를 96x96으로 조정한후 (참고 IMAGE_SIZE = 96,96 로 정의되어 있다.) outputfile 경로에 저장하게 된다.        

 

실행을 해서 정재된 데이타는 다음과 같다.



  

생각해볼만한점들

이 코드는 간단한 토이 프로그램이기 때문에 간단하게 작성했지만 실제 운영환경에 적용하기 위해서는 몇가지 고려해야 할 사항이 있다.

먼저, 이 코드는 싱글 쓰레드로 돌기 때문에 속도가 상대적으로 느리다 그래서 멀티 쓰레드로 코드를 수정할 필요가 있으며, 만약에 수백만장의 사진을 정재하기 위해서는 한대의 서버로 되지 않기 때문에, 원본 데이타를 여러 서버로 나눠서 처리할 수 있는 분산 처리 구조가 고려되어야 한다.

또한, VISION API로 사진을 전송할때는 BASE64 인코딩된 구조로 서버에 이미지를 직접 전송하기 때문에, 자칫 이미지 사이즈들이 크면 네트워크 대역폭을 많이 잡아먹을 수 있기 때문에 가능하다면 식별이 가능한 크기에서 리사이즈를 한 후에, 서버로 전송하는 것이 좋다. 실제로 필요한 얼굴 크기는 96x96 픽셀이기 때문에 필요없이 1000만화소 고화질의 사진들을 전송해서 네트워크 비용을 낭비하지 않기를 바란다.

 

다음은 이렇게 정재한 파일들을 텐서플로우에서 읽어서 실제로 학습하는 모델을 만들어보겠다.


위의 코드를 멀티 프로세스&멀티쓰레드로 돌리는 아키텍쳐와 코드는 http://bcho.tistory.com/1177 글을 참고하기 바란다.

 

저작자 표시 비영리
신고

클러스터 상에서 하둡 배포 아키텍쳐


조대협 (http://bcho.tistory.com)


오늘 빅데이타 관련 교육을 받다가 클라우드 상에서 하둡 클러스터 활용에 대한 영감을 받은 부분이 있어서 정리해보고자 한다. 하둡의 경우에는 On-prem 환경에 적절하게 디자인이 된 오픈 소스라서, 이걸 클라우드에서 사용할 경우에도 on-prem에서 사용하는 형태와 유사하게 사용하는 경우가 많다. 일종의 습관 또는 관성이라고 해야 하나? 인프라가 바뀌면 그 장점에 맞는 아키텍쳐를 선택해야 하는데, 이 부분을 놓치고 있지 않았나 싶다.

Job별 클러스터를 생성하는 아키텍쳐

job을 수행하는 방법을 보면, 일반적으로 On-Prem에서 사용하는 방법은 하나의 하둡 클러스터에 Job을 실행하고 Job이 끝나면 다음 Job을 수행하는 방식을 사용한다.



이러한 경험(습관)때문인지 보통 클라우드 상에 하둡 클러스터를 만들어놓고 그 클러스터에 On-prem과 마찬가지 방식으로 Job을 넣고 그 작업이 끝나면 같은 클러스터에 Job을 넣는 방식을 사용한다.

그런데 클라우드의 특성상 자원이 거의 무한대인데, 여러개의 Job을 하나의 클러스터에서 순차적으로 실행할 필요가 있을까?


Job별로 클러스터를 생성해서 연산을 하고 Job이 끝나면 클러스터를 끄는 모델을 사용하면, 동시에 여러 작업을 빠르게 처리할 수 있지 않을까?



이 경우 문제가 될 수 있는 것이 같은 데이타셋을 가지고 여러가지 Job을 실행할 경우 기존의 On-Prem의 경우에는 동시에 같은 데이타셋에 접근할 경우 오버로드가 심하기 때문에, 어렵지만 클라우드의 경우에는 바로 HDFS를 사용하는 것보다는 GCS와 같은 클라우드 스토리지를 사용하는데, 여기서 오는 장점이 이런 클라우드 스토리지에 저장된 데이타는 동시에 여러 하둡 클러스터에서 접근이 가능하다.


이러한 아키텍쳐를 생각하게된 이유는 구글 클라우드의 특성 때문인데, 구글 클라우드의 과금 모델은 분당 과금 모델을 사용한다. 그래서 Job별로 클러스터를 전개했다가 작업이 끝난 다음에 끄더라도 비용 낭비가 없다. 시간당 과금인 경우에는 클러스터를 껐다켰다 하면 비용 낭비가 발생할 수 있기 때문에 바람직하지 않을 수 있다. 예를 들어서 1시간 10분이 걸리는 작업과 20분이 걸리는 작업을 각각 다른 클러스터에서 돌린다면 2시간, 그리고 1시간 비용이 과금되기 때문에 총 3시간이 과금된다.

그러나 구글의 분당 과금을 적용하면 총 1시간30분이 과금되기 때문에 비용이 1/2로 절감된다.


또한 구글 클라우드의 경우 하둡 클러스터 배포시간이 통상 90초이내로 빠르기때문에, Job마다 클러스터를 생성하고 끈다고 해도, 실행 시간이 크게 늘어나지 않는다.


클러스터별로 인프라를 최적화

만약에 위에처럼 Job별로 클러스터를 나눈다면, Job 의 특성에 맞춰서 클러스터의 인스턴스 수나 구조(?)를 변경하는 것도 가능하다.

즉 많은 컴퓨팅 작업이 필요한 Job이라면 클러스터에 많은 인스턴스를 넣고, 그렇지 않다면 인스턴스 수를 줄일 수 있다.




조금 더 나가면, 상태 정보 같이 HDFS에 저장하는 데이타가 적을 경우 워커노드가 중간에 정지되더라도 전체 연산에 크게 문제가 없을 경우에는 Preemptible VM과 같이 가격은 싸지만 저장 기능이 없는 VM의 수를 늘려서 전체 비용을 낮출 수 있다.




여기서 한걸음 더 나가면, 구글 클라우드의 인스턴스 타입중의 하나가 Cutomizable 인스턴스로 CPU와 메모리 수를 마음대로 조정할 수 있다. 즉 Job이 메모리를 많이 사용하는 Spark과 같은 Job일 경우 클러스터를 구성하는 인스턴스에 CPU보다 메모리를 상대적으로 많이 넣고 구성하는 것들이 가능하다.


아키텍쳐 설계도 의미가 있었지만, 그동안 간과하고 있었던 점이 기술이나 인프라가 바뀌면 활용 하는 방법도 다시 고민해야 하는데, 그동안의 관성 때문인지 기존 시스템을 활용하는 방법을 그대로 의심 없이 사용하려 했다는 것에 다시한번 생각할 필요가 있었던 주제였다.






저작자 표시 비영리
신고

Google Cloud Function


조대협 (http://bcho.tistory.com)

기본 개념

구글 클라우드 펑션은 서버리스 아키텍쳐를 구현하기 위한 구글 클라우드 서비스이다. 아마존 웹서비스의 람다와 같은 기능이라고 보면 된다.




이벤트가 발생하면, 이벤트에 따라서, 코드를 수행해주는 형태인데, 이벤트의 종류는 다음과 같다.

  • Pub/Sub 메세지 큐에서 들어오는 메세지

  • Firebase 모바일 SDK에 의해서 발생되는 이벤트

  • Google Cloud Storage 서비스에 의해서 파일이 생성,수정,삭데 되었을때

  • 마지막으로 HTTP로 들어오는 요청 (REST API)


개발환경

프로그래밍 언어는 node.js 6.9.1 버전을 기반으로 되어 있으며, node.js의 package.json을 이용하여 왠만한 의존성 모듈은 설치가 가능하다. (node.js express 등)  개발을 위해서는 로컬에 에뮬레이터를 설치하여 개발이 가능하다. https://cloud.google.com/functions/docs/emulator

Hello World

그러면 간단하게, 구글 클라우드 펑션을 사용해보자

클라우드 펑션은 크게 두가지 타입의 펑션이 있는데

  • HTTP 펑션
    HTTP 펑션은 HTTP 로 입력을 받는 형태로 function 펑션이름(req,res)
    req는 HTTP Request, res는 HTTP Response 이다.

  • 백그라운드 펑션
    백르라운드 펑션은 GCS,Pub/Sub 등의 이벤트로 트리거링 되는 펑션으로 function 펑션이름(event,callback)형태로 정의된다. event 객체 안에, GCS나 Pub/Sub 에서 발생된 이벤트 정보가 전송된다.


간단하게 웹에서 Hello World를 출력하는 펑션을 개발해보자.

예제 코드

Index.js 에 다음과 같은 코드를 작성한다


exports.helloworld = function helloworld (req, res) {

       switch(req.method){

         case 'GET':

          res.send('Hello world');

       }

};

위의 코드는 helloworld 라는 이름의 펑션으로 HTTP GET 요청이 들어왔을때, ‘Hello world’ 문자열을 출력하도록 하는 펑션이다.

배포 하기

배포는 크게 Web UI와 CLI (Command Line Interface) 두 가지로 할 수 있다.

배포에 앞서서, 먼저 GCS (Google Cloud Storage) 버킷을 생성해야 한다. 클라우드 펑션은 배포 코드를 클라우드 스토리지 버킷에 저장해놓고 (스테이징 용도) 배포하기 때문이다.


클라우드 스토리지 버킷은 Web UI에서도 생성할 수 있지만 간단하게 CLI 명령을 이용해서 다음과 “terrycho-cloudfunction”이라는 이름의 버킷을 생성한다


%gsutil mb gs://terrycho-cloudfunction

Command Line Interface (CLI)로 배포하기

CLI로 배포하기 위해서는 CLI 명령인 gcloud 명령을 업그레이드 해야 한다. 다음 명령을 수행하면 쉽게 업그레이드할 수 있다.

% gcloud components update beta

% gcloud components install


다음 배포 명령을 실행해보자

% gcloud beta functions deploy helloworld --stage-bucket gs://terrycho-cloudfunction --trigger-http



Web Console로 배포하기

또는 Web Console을 이용할 수 도 있다.

다음은 helloworld 펑션을 us-central1 리전에 128M 메모리 사이즈로 배포하는 화면이다

코드는 ZIP 파일로 직접 업로드 하거나 구글 클라우드 스토리지에 ZIP으로 업로드 하거나 또는 아래 그림과 같이 inline editor에 간단한 코드는 직접 넣을 수 있다.


그리고 마지막으로 export할 모듈명을 정의한다.




실행하기

클라우드 펑션을 배포 하였으면 이제 실행해보자

HTTP 펑션이기 때문에 HTTP URL을 알아야 하는데,  HTTP URL규칙은 다음과 같다.


https://[리전이름]-[프로젝트이름].cloudfunctions.net/[펑션이름]                             


앞에서 만든 펑션은 us-central1에 배포하였고, 프로젝트명은 terrycho-sandbox이고 펑션 이름은 helloworld 이기 때문에 URL과 실행 결과는 다음과 같다


모니터링

모니터링은 CLI와 웹콘솔 양쪽으로 모두 가능하지만 웹 콘솔에서 로그를 확인해보겠다. 펑션 화면에서 펑션을 선택한 후에 우측 메뉴에서 아래 그림과 같이 “See logs”를 누룬다.


로그를 확인해보면 다음과 같다.



펑션이 시작된 것을 확인할 수 있다. 7d로 시작하는 펑션과 de로 시작하는 펑션 인스턴스 두개가 생성된 것을 볼 수 있고 7d로 시작된 펑션의 실행 시간이 702 ms가 걸린것을 확인할 수 있다.

가격 정책

가격 정책 계산 방법은


(가격) = (호출 횟수) + (컴퓨팅 자원 사용량 ) + (네트워크 비용)


으로 구성된다.

  • 호출 횟수는 클라우드 펑션이 호출되는 횟수로 한달에 2백만건까지 무료이며 2백만건 이후로는 백만건당 0.4$ 가 부과 된다.

  • 컴퓨팅 자원은 사용한 메모리와 CPU 파워를 기반으로 100ms 단위 과금이다.


    예를 들어 1.4GHz CPU 1024MB 메모리를 250 ms 사용했다면, 컴퓨팅 자원 사용 비용은 0.000001650 * 3 (250ms는 올림하여 300ms로 계산한다.)

  • 네트워크 비용은 들어오는 비용은 무료이며 인터넷으로 나가는 비용에 대해서만 월 5$를 부과한다.

무료 티어

무료 티어는 매달 2백만콜에 대해서 400,000GB 초, 200,000GHZ 초의 용량 + 5GB 아웃바운드 트래픽에 대해서 무료로 제공한다

결론

사실 클라우드 펑션과 같은 서버리스 서비스는 새로운 기술은 아니다.

그러나 구글 클라우드 플랫폼과 연계되어서, 간단한 HTTP 서비스 개발은 물론, GCS에 저장된 파일에 대한 ETL 처리등 다양한 분야에 사용이 가능하며

특히나 타 클라우드 대비 특징은, 모바일 SDK 파이어베이스의 백앤드로 연동이 가능하기 때문에 모바일 개발자 입장에서 복잡한 API 인증등을 개발할 필요 없이  간단하게 서버 백앤드를 개발할 수 있다는 장점을 가지고 있기 때문에 개발 생산성 향상에 많은 도움이 되리라고 본다.


저작자 표시 비영리
신고

연예인 얼굴 인식 서비스를 만들어보자 #1 - 학습데이타 준비하기


조대협 (http://bcho.tistory.com)


CNN 에 대한 이론 공부와 텐서 플로우에 대한 기본 이해를 끝내서 실제로 모델을 만들어보기로 하였다.

CNN을 이용한 이미지 인식중 대중적인 주제로 얼굴 인식 (Face recognition)을 주제로 잡아서, 이 모델을 만들기로 하고 아직 실력이 미흡하여 호주팀에서 일하고 있는 동료인 Win woo 라는 동료에게 모델과 튜토리얼 개발을 부탁하였다.


이제 부터 연재하는 연예인 얼굴 인식 서비스는 Win woo 가 만든 코드를 기반으로 하여 설명한다. (코드 원본 주소 : https://github.com/wwoo/tf_face )

얼굴 데이타를 내려 받자

먼저 얼굴 인식 모델을 만들려면, 학습을 시킬 충분한 데이타가 있어야 한다. 사람 얼굴을 일일이 구할 수 도 없고, 구글이나 네이버에서 일일이 저장할 수 도 없기 때문에, 공개된 데이타셋을 활용하였는데, PubFig (Public Figures Face Database - http://www.cs.columbia.edu/CAVE/databases/pubfig/) 를 사용하였다.



이 데이타셋에는 약 200명에 대한 58,000여장의 이미지를 저장하고 있는데, 이 중의 일부만을 사용하였다.

Download 페이지로 가면, txt 파일 형태 (http://www.cs.columbia.edu/CAVE/databases/pubfig/download/dev_urls.txt) 로 아래와 같이


Abhishek Bachan 1 http://1.bp.blogspot.com/_Y7rzCyUABeI/SNIltEyEnjI/AAAAAAAABOg/E1keU_52aFc/s400/ash_abhishek_365x470.jpg 183,60,297,174 f533da9fbd1c770428c8961f3fa48950
Abhishek Bachan 2 http://1.bp.blogspot.com/_v9nTKD7D57Q/SQ3HUQHsp_I/AAAAAAAAQuo/DfPcHPX2t_o/s400/normal_14thbombaytimes013.jpg 49,71,143,165 e36a8b24f0761ec75bdc0489d8fd570b
Abhishek Bachan 3 http://2.bp.blogspot.com/_v9nTKD7D57Q/SL5KwcwQlRI/AAAAAAAANxM/mJPzEHPI1rU/s400/ERTYH.jpg 32,68,142,178 583608783525c2ac419b41e538a6925d


사람이름, 이미지 번호, 다운로드 URL, 사진 크기, MD5 체크섬을 이 필드로 저장되어 있다.

이 파일을 이용하여 다운로드 URL에서 사진을 다운받아서, 사람이름으로된 폴더에 저장한다.

물론 수동으로 할 수 없으니 HTTP Client를 이용하여, URL에서 사진을 다운로드 하게 하고, 이를 사람이름 폴더 별로 저장하도록 해야 한다.


HTTP Client를 이용하여 파일을 다운로드 받는 코드는 일반적인 코드이기 때문에 별도로 설명하지 않는다.

본인의 경우에는 Win이 만든 https://github.com/wwoo/tf_face/blob/master/tf/face_extract/pubfig_get.py 코드를 이용하여 데이타를 다운로드 받았다.

사용법은  https://github.com/wwoo/tf_face 에 나와 있는데,


$> python tf/face_extract/pubfig_get.py tf/face_extract/eval_urls.txt ./data

를 실행하면 ./data 디렉토리에 이미지를 다운로드 받아서 사람 이름별 폴더에 저장해준다.

evals_urls.txt에는 위에서 언급한 dev_urls.txt 형태의 데이타가 들어간다.


사람 종류가 너무 많으면 데이타를 정재하는 작업이 어렵고, (왜 어려운지는 뒤에 나옴) 학습 시간이 많이 걸리기 때문에, 약 47명의 데이타를 다운로드 받아서 작업하였다.

쓰레기 데이타 골라내기

데이타를 다운받고 나니, 아뿔사!! PubFig 데이타셋이 오래되어서 없는 이미지도 있고 학습에 적절하지 않은 이미지도 있다.


주로 학습에 적절하지 않은 데이타는 한 사진에 두사람 이상의 얼굴이 있거나, 이미지가 사라져서 위의 우측 그림처럼, 이미지가 없는 형태로 나오는 경우인데, 이러한 데이타는 어쩔 수 없이 눈으로 한장한장 다 걸러내야만 하였다.

아마 이 작업이 가장 오랜 시간이 걸린 작업이 아닐까도 한다. 더불어서 머신러닝이 정교한 수학이나 알고리즘이 아니라 노가다라고 불리는 이유를 알았다.

얼굴 추출하기

다음 학습에 가능한 데이타를 잘 골라내었으면, 학습을 위해서 사진에서 얼굴만을 추출해내야 한다. 포토샵으로 일일이 할 수 없기 때문에 얼굴 영역을 인식하는 API를 사용하기로한다. OPEN CV와 같은 오픈소스 라이브러리를 사용할 수 도 있지만 구글의 VISION API의 경우 얼굴 영역을 아주 잘 잘라내어주고, 코드 수십줄만 가지고도 얼굴 영역을 알아낼 수 있기 때문에 구글 VISION API를 사용하였다.

https://cloud.google.com/vision/




VISION API ENABLE 하기

VISION API를 사용하기 위해서는 해당 구글 클라우드 프로젝트에서 VISION API를 사용하도록 ENABLE 해줘야 한다.

VISION API를 ENABLE하기 위해서는 아래 화면과 같이 구글 클라우드 콘솔 > API Manager 들어간후




+ENABLE API를 클릭하여 아래 그림과 같이 Vision API를 클릭하여 ENABLE 시켜준다.




SERVICE ACCOUNT 키 만들기

다음으로 이 VISION API를 호출하기 위해서는 API 토큰이 필요한데, SERVICE ACCOUNT 라는 JSON 파일을 다운 받아서 사용한다.

구글 클라우드 콘솔에서 API Manager로 들어간후 Credentials 메뉴에서 Create creadential 메뉴를 선택한후, Service account key 메뉴를 선택한다



다음 Create Service Account key를 만들도록 하고, accountname과 id와 같은 정보를 넣는다. 이때 중요한것이 이 키가 가지고 있는 사용자 권한을 설정해야 하는데, 편의상 모든 권한을 가지고 있는  Project Owner 권한으로 키를 생성한다.


(주의. 실제 운영환경에서 전체 권한을 가지는 키는 보안상의 위험하기 때문에 특정 서비스에 대한 접근 권한만을 가지도록 지정하여 Service account를 생성하기를 권장한다.)




Service account key가 생성이 되면, json 파일 형태로 다운로드가 된다.

여기서는 terrycho-ml-80abc460730c.json 이름으로 저장하였다.


예제 코드

그럼 예제를 보자 코드의 전문은 https://github.com/bwcho75/facerecognition/blob/master/com/terry/face/extract/crop_face.py 에 있다.


이 코드는 이미지 파일이 있는 디렉토리를 지정하고, 아웃풋 디렉토리를 지정해주면 이미지 파일을 읽어서 얼굴이 있는지 없는지를 체크하고 얼굴이 있으면, 얼굴 부분만 잘라낸 후에, 얼굴 사진을 96x96 사이즈로 리사즈 한후에,

70%의 파일들은 학습용으로 사용하기 위해서 {아웃풋 디렉토리/training/} 디렉토리에 저장하고

나머지 30%의 파일들은 검증용으로 사용하기 위해서 {아웃풋 디렉토리/validate/} 디렉토리에 저장한다.


그리고 학습용 파일 목록은 다음과 같이 training_file.txt에 파일 위치,사람명(라벨) 형태로 저장하고

/Users/terrycho/traning_datav2/training/wsmith.jpg,Will Smith

/Users/terrycho/traning_datav2/training/wsmith061408.jpg,Will Smith

/Users/terrycho/traning_datav2/training/wsmith1.jpg,Will Smith


검증용 파일들은 validate_file.txt에 마찬가지로  파일위치와, 사람명(라벨)을 저장한다.

사용 방법은 다음과 같다.

python com/terry/face/extract/crop_face.py “원본 파일이있는 디렉토리" “아웃풋 디렉토리"

(원본 파일 디렉토리안에는 {사람이름명} 디렉토리 아래에 사진들이 쭈욱 있는 구조라야 한다.)


자 그러면, 코드의 주요 부분을 살펴보자


VISION API 초기화 하기

  def __init__(self):

       # initialize library

       #credentials = GoogleCredentials.get_application_default()

       scopes = ['https://www.googleapis.com/auth/cloud-platform']

       credentials = ServiceAccountCredentials.from_json_keyfile_name(

                       './terrycho-ml-80abc460730c.json', scopes=scopes)

       self.service = discovery.build('vision', 'v1', credentials=credentials)


초기화 부분은 Google Vision API를 사용하기 위해서 OAuth 인증을 하는 부분이다.

scope를 googleapi로 정해주고, 인증 방식을 Service Account를 사용한다. credentials 부분에 service account key 파일인 terrycho-ml-80abc460730c.json를 지정한다.


얼굴 영역 찾아내기

다음은 이미지에서 얼굴을 인식하고, 얼굴 영역(사각형) 좌표를 리턴하는 함수를 보자


   def detect_face(self,image_file):

       try:

           with io.open(image_file,'rb') as fd:

               image = fd.read()

               batch_request = [{

                       'image':{

                           'content':base64.b64encode(image).decode('utf-8')

                           },

                       'features':[{

                           'type':'FACE_DETECTION',

                           'maxResults':MAX_RESULTS,

                           }]

                       }]

               fd.close()

       

           request = self.service.images().annotate(body={

                           'requests':batch_request, })

           response = request.execute()

           if 'faceAnnotations' not in response['responses'][0]:

                print('[Error] %s: Cannot find face ' % image_file)

                return None

               

           face = response['responses'][0]['faceAnnotations']

           box = face[0]['fdBoundingPoly']['vertices']

           left = box[0]['x']

           top = box[1]['y']

               

           right = box[2]['x']

           bottom = box[2]['y']

               

           rect = [left,top,right,bottom]

               

           print("[Info] %s: Find face from in position %s" % (image_file,rect))

           return rect

       except Exception as e:

           print('[Error] %s: cannot process file : %s' %(image_file,str(e)) )

 

VISION API를 이용하여, 얼굴 영역을 추출하는데, 위의 코드에서 처럼 image_file을 읽은후에, batch_request라는 문자열을 만든다. JSON 형태의 문자열이 되는데, 이때 image라는 항목에 이미지 데이타를 base64 인코딩 방식으로 인코딩해서 전송한다. 그리고 VISION API는 얼굴인식뿐 아니라 사물 인식, 라벨인식등 여러가지 기능이 있기 때문에 그중에서 타입을 ‘FACE_DETECTION’으로 정의하여 얼굴 영역만 인식하도록 한다.


request를 만들었으면, VISION API로 요청을 보내면 응답이 오는데, 이중에서 response 엘리먼트의 첫번째 인자 ( [‘responses’][0] )은 첫번째 얼굴은 뜻하는데, 여기서 [‘faceAnnotation’]을 하면 얼굴에 대한 정보만을 얻을 수 있다. 이중에서  [‘fdBoundingPoly’] 값이 얼굴 영역을 나타내는 사각형이다. 이 갑ㄱㅅ을 읽어서 left,top,right,bottom 값에 세팅한 후 리턴한다.


얼굴 잘라내고 리사이즈 하기

앞의 detect_face에서 찾아낸 얼굴 영역을 가지고 그 부분만 전체 사진에서 잘라내고, 잘라낸 얼굴을 학습에 적합하도록 같은 크기 (96x96)으로 리사이즈 한다.

이런 이미지 처리를 위해서 PIL (Python Imaging Library - http://www.pythonware.com/products/pil/)를 사용하였다.

   def crop_face(self,image_file,rect,outputfile):

       try:

           fd = io.open(image_file,'rb')

           image = Image.open(fd)  

           crop = image.crop(rect)

           im = crop.resize(IMAGE_SIZE,Image.ANTIALIAS)

           im.save(outputfile,"JPEG")

           fd.close()

           print('[Info] %s: Crop face %s and write it to file : %s' %(image_file,rect,outputfile) )

       except Exception as e:

           print('[Error] %s: Crop image writing error : %s' %(image_file,str(e)) )

image_file을 인자로 받아서 , rect 에 정의된 사각형 영역 만큼 crop를 해서 잘라내고, resize 함수를 이용하여 크기를 96x96으로 조정한후 (참고 IMAGE_SIZE = 96,96 로 정의되어 있다.) outputfile 경로에 저장하게 된다.        


실행을 해서 정재된 데이타는 다음과 같다.


생각해볼만한점들

이 코드는 간단한 토이 프로그램이기 때문에 간단하게 작성했지만 실제 운영환경에 적용하기 위해서는 몇가지 고려해야 할 사항이 있다.

먼저, 이 코드는 싱글 쓰레드로 돌기 때문에 속도가 상대적으로 느리다 그래서 멀티 쓰레드로 코드를 수정할 필요가 있으며, 만약에 수백만장의 사진을 정재하기 위해서는 한대의 서버로 되지 않기 때문에, 원본 데이타를 여러 서버로 나눠서 처리할 수 있는 분산 처리 구조가 고려되어야 한다.

또한, VISION API로 사진을 전송할때는 BASE64 인코딩된 구조로 서버에 이미지를 직접 전송하기 때문에, 자칫 이미지 사이즈들이 크면 네트워크 대역폭을 많이 잡아먹을 수 있기 때문에 가능하다면 식별이 가능한 크기에서 리사이즈를 한 후에, 서버로 전송하는 것이 좋다. 실제로 필요한 얼굴 크기는 96x96 픽셀이기 때문에 필요없이 1000만화소 고화질의 사진들을 전송해서 네트워크 비용을 낭비하지 않기를 바란다.


다음은 이렇게 정재한 파일들을 텐서플로우에서 읽어서 학습 데이타로 활용하는 방법에 대해서 알아보겠다.


저작자 표시 비영리
신고

구글의 IOT 솔루션

클라우드 컴퓨팅 & NoSQL/M2M & IOT | 2017.03.10 10:31 | Posted by 조대협


구글의 IOT 솔루션


조대협 (http://bcho.tistory.com)


오늘 샌프란시스코 구글 NEXT 행사에서 IOT 솔루션에 대한 소개가 있었는데, 내용이 괜찮아서 정리를 해놓는다.



구글의 특징은 안드로이드 플랫폼, 클라우드 , 분석 플랫폼, 개발자 에코 시스템  등 End to End 에 걸쳐서 상당히 다양한 포트폴리오를 가지고 있다는 것이 장점인데 이를 잘 녹여낸 아키텍쳐 구성이다.

디바이스 OS

IOT는 라즈베리파이와 같은 임베디드 디바이스를 사용하는 것이 일반적인데, 이런 임베디드 시스템 운용에 어려운 점중의 하나가 보안이다.

장비에 따라서 보안적인 문제가 없는지 체크를 해야 하고, 주기적으로 기능 및 보안에 대한 업데이트를 해줘야 하는데, 구글의 Android IOT (https://developer.android.com/things/index.html) 플랫폼은 이를 다 자동으로 해준다.


더구나, 기존의 모바일 안드로이드 플랫폼을 기반으로 하기 때문에, 안드로이드 개발자 풀을 그대로 사용할 수 있다는 장점이 있다.

이미 Android IOT 플랫폼은 인텔,라즈베리파이등 여러 디바이스 업체와 협업을 하면서 Certi 작업을 하고 있기 때문에 잘 알려진 플랫폼이라면 보안 테스트나 별도의 기능 테스트 없이 바로 사용이 가능하다.


백앤드

IOT의 백앤드는 구글 클라우드 플랫폼을 이용한다.

  • 디바이스로 부터 수집된 데이타는 Pub/Sub 큐에 저장된후

  • DataFlow 프레임웍을 통해서 배치나 실시간 스트리밍 분석이 되고

  • 분석된 데이타는 빅테이블이나 빅쿼리에 저장된다. 분석이나 리포팅을 위해서는 빅쿼리, 타임 시리즈 데이타나 고속의 데이타 접근을 위해서는 빅테이블이 사용된다.

  • 이렇게 저장된 데이타들은 구글의 머신러닝 프레임웍 텐서플로우의 클라우드 런타임인 CloudML을 사용해서 분석 및 예측 모델을 만들게 된다.



머신러닝을 등에 탑재한  디바이스

구글이 재미있는 점은 텐서플로우라는 머신러닝 프레임웍을 가지고 있다는 것인데, 애초부터 텐서플로우의 디자인은 서버 뿐만 아니라, 클라이언트 그리고 IOT 디바이스에서 동작하게 디자인이 되었다. 그래서 학습된 모델을 디바이스로 전송하여, 디바이스에서 머신러닝을 이용한 예측이 가능하다.

예를 들어 방범용 카메라를 만들었을때, 방문자의 사진을 클라우드로 저장하는 시나리오가 있다고 하자.

그런데 매번 전송을 하면 배터리나 네트워크 패킷 요금이 문제가 될 수 있기 때문에, 텐서 플로우 기반의 얼굴 인식 모델을 탑재하여 등록되지 않은 사용자만 사진을 찍어서 클라우드로 전송하게 하는 등의 시나리오 구현이 가능하다.


파이어 베이스 연동

동영상을 보다가 놀란점 중의 하나는 파이어 베이스가 Android IOT에 연동이 된다.

아래 그림은 온도를 측정해서 팬의 속도를 조정하는 시나리오인데, 우측 하단에 보면 파이어베이스가 위치해 있다.



센서로 부터 온도를 측정한 다음, 디바이스 컨트롤러로 온도 조정 명령을 내리는 것을 파이어베이스 메시징 서비스를 이용하도록 되어 있다.


결론

Android IOT 서비스 하나만 IOT 서비스로 내놓은 것이 아니라 구글 클라우드 플랫폼, 텐서플로우에 파이어베이스까지 구글의 기존의 노하우들을 묶어서 포트폴리오를 만들어 내었고, 더구나 이러한 기술들이 개발자 에코 시스템이 이미 형성이 되어 있는 시스템인 점에서, IOT 개발에 있어서 누구나 쉽게 IOT 서비스를 개발할 수 있게 한다는데, 큰 의미가 있다고 본다.


저작자 표시 비영리
신고

'클라우드 컴퓨팅 & NoSQL > M2M & IOT' 카테고리의 다른 글

구글의 IOT 솔루션  (0) 2017.03.10
TI의 IOT 개발용 센서 키트  (0) 2016.03.17
MQTT 서버 간단 공부 노트  (2) 2014.02.13


한시간에 만드는 대용량 로그 수집 시스템

조대협 (http://bcho.tistory.com)


정정 및 참고 내용

2017.1.24 몇가지 내용을 정정합니다.

https://cloud.google.com/logging/quota-policy 를 보면 스택드라이버 로깅에 쿼타 제한이 초당 500건/계정으로 잡혀있어서. 일반적인 경우는 최대 500 TPS의 성능을 낼 수 있습니다. 그 이상의 성능이 필요하면, 여러 계정을 사용해야 합니다 또는 구글에 별도의 쿼타 증설 요청을 해야 합니다.

하루에, 최대 2천5백만건의 로그를 하나의 프로젝트를 통해서 수집이 가능합니다.


또한 프리티어의 경우에는 한달에 로그를 5GB  까지 수집이 가능한데, 이게 넘으면 로그가 더이상 수집되지 않습니다. 그래서 아래 내용 처럼 빅쿼리로 Export를 해서 로그가 5GB 이상 스택드라이버에 저장되지 않도록 해야 합니다. (차기전에 데이타를 퍼나르는)

애플리케이션 로그 이외에도, VM 로그등도 이 5GB의 용량을 공유하기 때문에, VM 로그등도 차기전에 GCS로 퍼 나르거나 또는 구글 Support 티켓을 통하여 애플리케이션 로그 이외의 로그를 수집하지 않도록 별도 요청해야 합니다. (로그 저장 용량에 대해서 비용을 지불하면, 이런 제약은 없음)


백앤드 시스템에서 중요한 컴포넌트중의 하나가, 클라이언트로 부터 로그를 수집 및 분석하는 시스템이다.

오늘 설명할 내용은 500 TPS (Transaction Per Sec)가 넘는 대용량 로그 수집 및 분석 시스템을  managed 서비스를 이용하여, 쉽고 빠르게 구축할 수 있는 방법에 대해서 소개하고자한다.


일반적인 로그 수집 및 분석 시스템 아키텍쳐

일반적으로 클라이언트에서 로그를 수집하여 분석 및 리포팅 하는 시스템의 구조는 다음과 같다.


  • 앞단의  API 서버가 로그를 클라이언트로 부터 수집하고 데이타를 정재한다.

  • 로그 저장소가 순간적으로 많은 트래픽을 감당할 수 없는 경우가 많기 때문에, 중간에 Message Q를 넣어서, 들어오는 로그를 Message Q에 저장하여 완충을 한다.

  • 이 Message Q로 부터 로그를 Message Consumer가 순차적으로 읽어서 Log Storage에 저장한다.

  • 저장된 로그는 Reporting 툴을 이용하여 시각화 한다.


이런 구조 이외에도 API 서버에서 파일로 로그를 저장한 후,  Fluentd나, LogStash 등의 로그 수집기를 이용하는 방법등 다양한 아키텍쳐가 존재한다.


이런 시스템을 구축하기 위한 일반적인 솔루션들은 다음과 같다.


컴포넌트

솔루션


API 서버

node.js, ruby, php 등 일반적인 웹서버


Message Q

Rabbit MQ와 같은 일반적인 큐
Kafaka 와 같은 대량 큐

AWS SQS나 구글 Pub/Sub 같은 클라우드 큐


Message Consumer

Multi Thread(or Process) + Timer를 조합하여 메세지를 폴링 방식으로 읽어오는 애플리케이션 개발


Log Storage

Hadoop, HBase 와 같은 하둡 제품

Drill,Druid와 같은 SQL 기반 빅데이타 플랫폼

Elastic Search


Reporting

Zeppeline, Jupyter 와 같은 노트북류

Kibana



구조나 개념상으로는 그리 복잡한 시스템은 아니지만, 저러한 솔루션을 모두 배우고, 설치하고 운영하는데 시간이 들고, 각각의 컴포넌트를 구현해야하기 때문에 꽤나 시간이 걸리는 작업이다.


그러면 이러한 로그 수집 및 분석 작업을 클라우드 서비스를 이용하여 단순화 할 수 없을까?

스택 드라이버

스택 드라이버는 구글 클라우드의 모니터링, 로깅 및 애플리케이션 성능 분석등 모니터링 분야에서 다양한 기능을 제공하는 서비스 이다.

그중에서 스택드라이버 로깅은 구글 클라우드나 아마존 또는 기타 인프라에 대한 모니터링과, Apache, MySQL과 같은 써드 파티 미들웨어에 대한 로그 수집 및 모니터링을 지원하는데, 이 외에도, 사용자가 애플리케이션에서 로깅한 데이타를 수집하여 모니터링할 수 있다.



스택 드라이버와 빅쿼리를 이용한 로그 수집 분석 시스템 구현

스택 드라이버 로깅의 재미있는 기능중 하나는 로그 EXPORT 기능인데, 로그 데이타를 구글 클라우드 내의 다른 서비스로 로그 데이타를 내보낼 수 있다.


  • GCS (Google Cloud Storage)로 주기적으로 파일로 로그 데이타를 내보내거나

  • Pub/Sub이나 Big Query로 실시간으로 데이타를 내보낼 수 있다.


그렇다면 스택 드라이버를 통해서 빅쿼리에 로그 데이타를 직접 저장한다면 복잡한 Message Q나, Message Consumer 등의 구현도 불필요하고, 로그 저장도 복잡한 오픈 소스를 이용한 개발이나 운영도 필요 없이, 매니지드 서비스인 빅쿼리를 이용하여 간략하게 구현할 수 있다.

스택 드라이버 로깅을 이용한 로그 수집 시스템 구현


스택 드라이버 애플리케이셔 로깅 기능을 이용하여 클라이언트로 부터 로그를 수집하여 분석하는 시스템의 아키텍쳐를 그려 보면 다음과 같다.




API 서버를 이용하여 클라이언트로 부터 로그를 수집하고, API 서버는 스택 드라이버 로깅 서비스로 로그를 보낸다. 스택 드라이버 로깅은 Export 기능을 이용하여, 수집된 로그를 실시간으로 빅쿼리로 전송한다. 빅쿼리에 저장된 로그는 구글 데이타 스튜디오 (http://datastudio.google.com)이나 제플린, 파이썬 주피터 노트북과 같은 리포팅 도구에 의해서 시각화 리포팅이 된다.

API 서버쪽에서 스택 드라이버 로깅으로 로그를 보내는 부분을 살펴보자

아래는 파이썬 Flask 를 이용하여 로그를 스택 드라이버로 보내는 코드이다.


import uuid

from flask import Flask

from google.cloud import logging


app = Flask(__name__)

logging_client = logging.Client()

tlogger = logging_client.logger(‘my-flask-log’)

slogger = logging_client.logger('struct_log')

@app.route('/')

def text_log():

   logstring = "This is random log "+ str(uuid.uuid4())

   tlogger.log_text(logstring)

   return logstring


@app.route('/slog')

def struct_log():

   struct  = "This is struct log "+ str(uuid.uuid4())

   slogger.log_struct({

               'name':'myterry',

               'text':struct,

               'key' : 'mykey'})      

   return struct


if __name__ == '__main__':

   app.run('0.0.0.0',7001)

   

google.cloud 패키지에서 logging 모듈을 임포트한 다음에, 로깅 클라이언트로 부터

tlogger = logging_client.logger(‘my-flask-log’)

slogger = logging_client.logger('struct_log')

로 각각 “my-flask-log”와 “struct_log”라는 이름을 가지는 logger 둘을 생성한다.

(뒤에서 언급하겠지만, 이 로거 단위로, 로그를 필터링 하거나, 또는 이 로거 단위로 로그 메세지를 다른 시스템으로 export 할 수 있다.)


다음, 로그를 쓸 때는 이 logger를 이용하여 로그를 써주기만 하면 된다.

   tlogger.log_text(logstring)

는 텍스트로 된 한줄 로그를 쓰는 명령이고,

   slogger.log_struct({

               'name':'myterry',

               'text':struct,

               'key' : 'mykey'})  

는 JSON과 같이 구조화된 계층 구조를 로그로 쓰는 방식이다.

이렇게 개발된 로그 수집용 API 서버의 코드는 직접 VM을 만들어서 Flask 서버를 깔고 인스톨 해도 되지만  앱앤진을 사용하면 코드만 배포하면, Flask 서버의 관리, 배포 및 롤백, 그리고 오토 스케일링등 모든 관리를 자동으로 해준다. 앱앤진을 이용한 코드 배포 및 관리에 대한 부분은 다음 문서 http://bcho.tistory.com/1125 를 참고 하기 바란다.

스택 드라이버에서 로그 확인

코드가 배포되고, 실제로 로그를 기록하기 시작했다면 스택 드라이버에 로그가 제대로 전달 및 저장되었는지 확인해보자. 구글 클라우드 콘솔에서 스택 드라이버 로깅으로 이동한 다음 아래 그림과 같이 리소스를 “Global” 을 선택한 후, 앞에 애플리케이션에서 남긴 “my-flask-log”와 “struct-log” 만을 선택해서 살펴보자





다음과 같이 로그가 출력되는 것을 확인할 수 있으며, struct_log의 예를 보면 로그의 내용은 time_stamp  와 프로젝트 정보와 같은 부가 정보와 함께, 애플리케이션에서 남긴 로그는 “jsonPayload” 앨리먼트 아래에 저장된것을 확인할 수 있다.



빅쿼리로 Export 하기

스택 드라이버로 로그가 전달되는 것을 확인했으면, 이 로그를 빅쿼리에 저장해보자. Export 기능을 이용해서 가능한다. 아래와 같이 스택 드라이버 로깅 화면에서 상단의 “CREATE EXPORT”  버튼을 누른다.

다음 리소스 (Global)과 로그 (struct_log)를 선택한 다음에,



Sink Name에 Export 이름을 적고 Sink Service는 BigQuery를 선택한다. 다음으로 Sink Destination에는 이 로그를 저장할 Big Query의 DataSet 이름을 넣는다.

마지막으로 Create Sink를 누르면, 이 로그는 이제부터 실시간으로 BigQuery의 structlog라는 데이타셋에 저장이 되면 테이블명은 아래 그림과 같이 strcut_log_YYYYMMDD와 같은 형태의 테이블로 생성이 된다.




테이블 프리뷰 기능을 이용하여 데이타가 제대로 들어갔는지 확인해보자. 아래와 같이 위의 코드에서 저장한 name,key,text는 테이블에서 jsonPayload.name, jsonPayload.key, jsonPayload.text 라는 필드로 각각 저장이 되게 된다.



빅쿼리는 실시간으로 데이타를 저장할때는 초당 100,000건까지 지원이 가능하기 때문에 이 시스템은 100,000TPS 까지 지원이 가능하고, 만약에 그 이상의 성능이 필요할때는 로그 테이블을 나누면(Sharding) 그 테이블 수 * 100,000 TPS까지 성능을 올릴 수 있다. 즉, 일별 테이블을 10개로 Sharding 하면, 초당 최대 1,000,000 TPS를 받는 로그 서비스를 만들 수 있으며, 이 테이블 Sharding은 빅쿼리 테이블 템플릿을 사용하면 쉽게 설정이 가능하다. (정정 빅쿼리는 100K TPS를 지원하나, 스택 드라이버가 500 TPS로 성능을 제한하고 있음)


이렇게 저장된 로그는 빅쿼리를 지원하는 각종 리포팅 툴을 이용하여 시각화가 가능하다.

시각화 도구는

을 참고하기 바란다.


이렇게 간단하게, 코드 몇줄과 설정 몇 가지로 100,000 500 TPS 를 지원하는 로그 서버를 만들어 보았다.

스택 드라이버를 이용한 로그 분석 수집 시스템의 확장

이 외에도 스택 드라이버는 빅쿼리뿐 아니라 다른 시스템으로의 연동과 매트릭에 대한 모니터링 기능을 가지고 있어서 다양한 확장이 가능한데, 몇가지 흥미로운 기능에 대해서 살펴보도록 하자.


실시간 스트리밍 분석 및 이벤트 핸들링

스택 드라이버 로깅의 Export 기능은, 하나의 로그를 여러 연동 시스템으로 Export를 할 수 있다. 앞에서는 빅쿼리로 로그를 Export 하였지만, 같은 Log를 Dataflow에 Export 하였을 경우, 로그 데이타를 실시간 스트림으로 받아서, 실시간 스트리밍 분석이 가능하다.


구글 데이타 플로우에 대한 설명은 아래 링크를 참고하기 바란다.


또는 실시간 스트리밍이 아니라, 로그 메세지 하나하나를 받아서 이벤트로 처리하고자 할 경우, Pub/Sub 큐에 넣은 후에, 그 뒤에 GAE또는 Cloud function (https://cloud.google.com/functions/) 에서 메세지를 받는 구조로 구현이 가능하다.


로그 모니터링

스택 드라이버 로깅은 단순히 로그를 수집할 뿐만 아니라 훨씬 더 많은 기능을 제공한다.

앞에서 스택 드라이버 로깅을 이용한 로그 수집 시스템을 만드는 방법을 알아보았지만, 부가적인 몇가지 기능이 같이 제공되는데 다음과 같다.

필터를 이용한 특정 로그 핸들링

logger를 통해서 수집된 로그에는 필터를 걸어서 특정 로그만 모니터링할 수 있다.

예를 들어서 text 문자열에 “error” 가 들어간 로그나, latency가 1초이상인 로그와 같이 특정 로그만을 볼 수 있다.

다음은 jsonPayload.text 로그 문자열에 “-a”로 시작하는 문자열이 있는 로그만 출력하도록 하는 것이다.



이 기능을 사용하면, 로그 메세지에서 특정 로그만 쉽게 검색하거나, 특정 에러 또는 특정 사용자의 에러, 특정 ErrorID 등으로 손쉽게 검색이 가능해서 로그 추적이 용이해진다.

매트릭 모니터링

다음은 메트릭이라는 기능인데, 로그를 가지고 모니터링 지표를 만들 수 있다.

예를 들어 하루 발생한 에러의 수 라던지, 평균 응답 시간등의 지표를 정의할 수 있다.

이렇게 정의된 지표는 대쉬보드에서 모니터링이 가능하고 또는 이러한 지표를 이용하여 이벤트로 사용할 수 있다. 응답시간이 얼마 이상 떨어지면 오토 스케일링을 하게 한다던가 또는 이메일로 관리자에게 ALERT을 보낸다던가의 기능 정의가 가능하다.


매트릭 생성

지표 정의는 로그 화면에서 필터에 로그 검색 조건을 넣은 채로, CREATE METRIC 버튼을 누르면 사용자가 지표를 매트릭으로 정의할 수 있다.



대쉬 보드 생성


이렇게 정의된 매트릭은 스택 드라이버 대쉬 보드 화면에서 불러다가 그래프로 시각화가 가능한데, 다음 그림은 struct_log의 전체 수와를 나타내는 매트릭과, struct_log에서 log text에 “-a”를 포함하는 로그의 수를 나타내는 메트릭을 정의하여 차트로 그리는 설정이다.



위에 의해서 생성된 차트를 보면 다음과 같이 전체 로그 수 대비 “-a”  문자열이 들어간 로그의 수를 볼 수 있다.


지금까지 스택드라이버 로깅과 빅쿼리를 이용하여 간단하게 대용량 로그 수집 서버를 만드는 방법을 살펴보았다. 두개의 제품을 이용해서 로그 수집 시스템을 구현하는 방법도 중요하지만, 이제는 개발의 방향이 이러한 대용량 시스템을 구현하는데, 클라우드 서비스를 이용하면 매우 짧은 시간내에 개발이 가능하고 저비용으로 운영이 가능하다. 요즘 개발의 트랜드를 보면 이렇게 클라우드 서비스를 이용하여 개발과 운영 노력을 최소화하고 빠른 개발 스피드로 개발을 하면서, 실제로 비지니스에 필요한 기능 개발 및 특히 데이타 분석 쪽에 많이 집중을 하는 모습이 보인다.


단순히 로그 수집 시스템의 하나의 레퍼런스 아키텍쳐에 대한 이해 관점 보다는 전체적인 개발 트렌드의 변화 측면에서 한번 더 생각할 수 있는 계기가 되면 좋겠다.


저작자 표시 비영리
신고

딥러닝을 이용한 숫자 이미지 인식 #1/2


조대협 (http://bcho.tistory.com)


지난 글(http://bcho.tistory.com/1154 ) 을 통해서 소프트맥스 회귀를 통해서, 숫자를 인식하는 모델을 만들어서 학습 시켜 봤다.

이번글에서는 소프트맥스보다 정확성이 높은 컨볼루셔널 네트워크를 이용해서 숫자 이미지를 인식하는 모델을 만들어 보겠다.


이 글의 목적은 CNN 자체의 설명이나, 수학적 이론에 대한 이해가 목적이 아니다. 최소한의 수학적 지식만 가지고, CNN 네트워크 모델을 텐서플로우로 구현하는데에 그 목적을 둔다. CNN을 이해하기 위해서는 Softmax 등의 함수를 이해하는게 좋기 때문에 가급적이면 http://bcho.tistory.com/1154 예제를 먼저 보고 이 문서를 보는게 좋다. 그 다음에 CNN 모델에 대한 개념적인 이해를 위해서 http://bcho.tistory.com/1149  문서를 참고하고 이 문서를 보는 것이 좋다.


이번 글은 CNN을 적용하는 것 이외에, 다음과 같은 몇가지 팁을 추가로 소개한다.

  • 학습이 된 모델을 저장하고 다시 로딩 하는 방법

  • 학습된 모델을 이용하여 실제로 주피터 노트북에서 글씨를 써보고 인식하는 방법

MNIST CNN 모델


우리가 만들고자 하는 모델은 두개의 컨볼루셔널 레이어(Convolutional layer)과, 마지막에 풀리 커넥티드 레이어 (fully connected layer)을 가지고 있는 컨볼루셔널 네트워크 모델(CNN) 이다.

모델의 모양을 그려보면 다음과 같다.


입력 데이타

입력으로 사용되는 데이타는 앞의 소프트맥스 예제에서 사용한 데이타와 동일한 손으로 쓴 숫자들이다. 각 숫자 이미지는 28x28 픽셀로 되어 있고, 흑백이미지이기 때문에 데이타는 28x28x1 행렬이 된다. (만약에 칼라 RGB라면 28x28x3이 된다.)

컨볼루셔널 계층

총 두 개의 컨볼루셔널 계층을 사용했으며, 각 계층에서 컨볼루셔널 필터를 사용해서, 특징을 추출한다음에, 액티베이션 함수 (Activation function)으로, ReLu를 적용한 후, 맥스풀링 (Max Pooling)을 이용하여, 주요 특징을 정리해낸다.

이와 같은 컨볼루셔널 필터를 두개를 중첩하여 적용하였다.

마지막 풀리 커넥티드 계층

컨볼루셔널 필터를 통해서 추출된 특징은 풀리 커넥티드 레이어(Fully connected layer)에 의해서 분류 되는데, 풀리 커넥티드 레이어는 하나의 뉴럴 네트워크를 사용하고, 그 뒤에 드롭아웃 (Dropout) 계층을 넣어서, 오버피팅(Overfitting)이 발생하는 것을 방지한다.  마지막으로 소프트맥스 (Softmax) 함수를 이용하여 0~9 열개의 숫자로 분류를 한다.


학습(트레이닝) 코드

이를 구현하기 위한 코드는 다음과 같다.


코드

import tensorflow as tf

import numpy as np

import matplotlib.pyplot as plt

from tensorflow.examples.tutorials.mnist import input_data



tf.reset_default_graph()


np.random.seed(20160704)

tf.set_random_seed(20160704)


# load data

mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)


# define first layer

num_filters1 = 32


x = tf.placeholder(tf.float32, [None, 784])

x_image = tf.reshape(x, [-1,28,28,1])


W_conv1 = tf.Variable(tf.truncated_normal([5,5,1,num_filters1],

                                         stddev=0.1))

h_conv1 = tf.nn.conv2d(x_image, W_conv1,

                      strides=[1,1,1,1], padding='SAME')


b_conv1 = tf.Variable(tf.constant(0.1, shape=[num_filters1]))

h_conv1_cutoff = tf.nn.relu(h_conv1 + b_conv1)


h_pool1 = tf.nn.max_pool(h_conv1_cutoff, ksize=[1,2,2,1],

                        strides=[1,2,2,1], padding='SAME')


# define second layer

num_filters2 = 64


W_conv2 = tf.Variable(

           tf.truncated_normal([5,5,num_filters1,num_filters2],

                               stddev=0.1))

h_conv2 = tf.nn.conv2d(h_pool1, W_conv2,

                      strides=[1,1,1,1], padding='SAME')


b_conv2 = tf.Variable(tf.constant(0.1, shape=[num_filters2]))

h_conv2_cutoff = tf.nn.relu(h_conv2 + b_conv2)


h_pool2 = tf.nn.max_pool(h_conv2_cutoff, ksize=[1,2,2,1],

                        strides=[1,2,2,1], padding='SAME')


# define fully connected layer

h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*num_filters2])


num_units1 = 7*7*num_filters2

num_units2 = 1024


w2 = tf.Variable(tf.truncated_normal([num_units1, num_units2]))

b2 = tf.Variable(tf.constant(0.1, shape=[num_units2]))

hidden2 = tf.nn.relu(tf.matmul(h_pool2_flat, w2) + b2)


keep_prob = tf.placeholder(tf.float32)

hidden2_drop = tf.nn.dropout(hidden2, keep_prob)


w0 = tf.Variable(tf.zeros([num_units2, 10]))

b0 = tf.Variable(tf.zeros([10]))

k = tf.matmul(hidden2_drop, w0) + b0

p = tf.nn.softmax(k)


#define loss (cost) function

t = tf.placeholder(tf.float32, [None, 10])

loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(k,t))

train_step = tf.train.AdamOptimizer(0.0001).minimize(loss)

correct_prediction = tf.equal(tf.argmax(p, 1), tf.argmax(t, 1))

accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))


# prepare session

sess = tf.InteractiveSession()

sess.run(tf.global_variables_initializer())

saver = tf.train.Saver()


# start training

i = 0

for _ in range(1000):

   i += 1

   batch_xs, batch_ts = mnist.train.next_batch(50)

   sess.run(train_step,

            feed_dict={x:batch_xs, t:batch_ts, keep_prob:0.5})

   if i % 500 == 0:

       loss_vals, acc_vals = [], []

       for c in range(4):

           start = len(mnist.test.labels) / 4 * c

           end = len(mnist.test.labels) / 4 * (c+1)

           loss_val, acc_val = sess.run([loss, accuracy],

               feed_dict={x:mnist.test.images[start:end],

                          t:mnist.test.labels[start:end],

                          keep_prob:1.0})

           loss_vals.append(loss_val)

           acc_vals.append(acc_val)

       loss_val = np.sum(loss_vals)

       acc_val = np.mean(acc_vals)

       print ('Step: %d, Loss: %f, Accuracy: %f'

              % (i, loss_val, acc_val))


saver.save(sess, 'cnn_session')

sess.close()



데이타 로딩 파트

그러면 코드를 하나씩 살펴보도록 하자.

맨 처음 블럭은 데이타를 로딩하고 각종 변수를 초기화 하는 부분이다.

import tensorflow as tf

import numpy as np

import matplotlib.pyplot as plt

from tensorflow.examples.tutorials.mnist import input_data


#Call tf.reset_default_graph() before you build your model (and the Saver). This will ensure that the variables get the names you intended, but it will invalidate previously-created graphs.


tf.reset_default_graph()


np.random.seed(20160704)

tf.set_random_seed(20160704)


# load data

mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)


Input_data 는 텐서플로우에 내장되어 있는 MNIST (손으로 쓴 숫자 데이타)셋으로, read_data_sets 메서드를 이요하여 데이타를 읽었다. 데이타 로딩 부분은 앞의 소프트맥스 MNIST와 같으니 참고하기 바란다.


여기서 특히 주목해야 할 부분은 tf.reset_default_graph()  인데, 주피터 노트북과 같은 환경에서 실행을 하게 되면, 주피터 커널을 리스타트하지 않는 이상 변수들의 컨택스트가 그대로 유지 되기 때문에, 위의 코드를 같은 커널에서 tf.reset_default_graph() 없이, 두 번 이상 실행하게 되면 에러가 난다. 그 이유는 텐서플로우 그래프를 만들어놓고, 그 그래프가 지워지지 않은 상태에서 다시 같은 그래프를 생성하면서 나오는 에러인데, tf.reset_default_graph() 메서드는 기존에 생성된 디폴트 그래프를 모두 삭제해서 그래프가 중복되는 것을 막아준다. 일반적인 파이썬 코드에서는 크게 문제가 없지만, 컨택스트가 계속 유지되는 주피터 노트북 같은 경우에는 발생할 수 있는 문제이니, 반드시 디폴트 그래프를 리셋해주도록 하자

첫번째 컨볼루셔널 계층

필터의 정의

다음은 첫번째 컨볼루셔널 계층을 정의 한다. 컨볼루셔널 계층을 이해하려면 컨볼루셔널 필터에 대한 개념을 이해해야 하는데, 다시 한번 되짚어 보자.

컨볼루셔널 계층에서 하는 일은 입력 데이타에 필터를 적용하여, 특징을 추출해 낸다.


이 예제에서 입력 받는 이미지 데이타는  28x28x1 행렬로 표현된 흑백 숫자 이미지이고, 예제 코드에서는 5x5x1 사이즈의 필터를 적용한다.

5x5x1 사이즈의 필터 32개를 적용하여, 총 32개의 특징을 추출할것이다.


코드

필터 정의 부분까지 코드로 살펴보면 다음과 같다.

# define first layer

num_filters1 = 32


x = tf.placeholder(tf.float32, [None, 784])

x_image = tf.reshape(x, [-1,28,28,1])


W_conv1 = tf.Variable(tf.truncated_normal([5,5,1,num_filters1],


x는 입력되는 이미지 데이타로, 2차원 행렬(28x28)이 아니라, 1차원 벡터(784)로 되어 있고, 데이타의 수는 무제한으로 정의하지 않았다. 그래서 placeholder정의에서 shape이 [None,784] 로 정의 되어 있다.  

예제에서는 연산을 편하게 하기 위해서 2차원 행렬을 사용할것이기 때문에, 784 1차원 벡터를 28x28x1 행렬로 변환을 해준다.

x_image는 784x무한개인 이미지 데이타 x를 , (28x28x1)이미지의 무한개 행렬로  reshape를 이용하여 변경하였다. [-1,28,28,1]은 28x28x1 행렬을 무한개(-1)로 정의하였다.


필터를 정의하는데, 필터는 앞서 설명한것과 같이 5x5x1 필터를 사용할것이고, 필터의 수는 32개이기 때문에, 필터 W_conv1의 차원(shape)은 [5,5,1,32] 가된다. (코드에서 32는 num_filters1 이라는 변수에 저장하여 사용하였다.) 그리고 W_conv1의 초기값은 [5,5,1,32] 차원을 가지는 난수를 생성하도록 tf.truncated_normal을 사용해서 임의의 수가 지정되도록 하였다.

필터 적용

필터를 정의했으면 필터를 입력 데이타(이미지)에 적용한다.


h_conv1 = tf.nn.conv2d(x_image, W_conv1,

                      strides=[1,1,1,1], padding='SAME')


필터를 적용하는 방법은 tf.nn.conv2d를 이용하면 되는데, 28x28x1 사이즈의 입력 데이타인 x_image에 앞에서 정의한 필터 W_conv1을 적용하였다.

스트라이드 (Strides)

필터는 이미지의 좌측 상단 부터 아래 그림과 같이 일정한 간격으로 이동하면서 적용된다.


이를 개념적으로 표현하면 다음과 같은 모양이 된다.


이렇게 필터를 움직이는 간격을 스트라이드 (Stride)라고 한다.

예제에서는 우측으로 한칸 그리고 끝까지 이동하면 아래로 한칸을 이동하도록 각각 가로와 세로의 스트라이드 값을 1로 세팅하였다.

코드에서 보면

h_conv1 = tf.nn.conv2d(x_image, W_conv1,

                      strides=[1,1,1,1], padding='SAME')

에서 strides=[1,1,1,1] 로 정의한것을 볼 수 있다. 맨앞과 맨뒤는 통상적으로 1을 쓰고, 두번째 1은 가로 스트라이드 값, 그리고 세번째 1은 세로 스트라이드 값이 된다.

패딩 (Padding)

위의 그림과 같이 필터를 적용하여 추출된 특징 행렬은 원래 입력된 이미지 보다 작게 된다.

연속해서 필터를 이런 방식으로 적용하다 보면 필터링 된 특징들이  작아지게되는데, 만약에 특징을  다 추출하기 전에 특징들이 의도하지 않게 유실되는 것을 막기 위해서 패딩이라는 것을 사용한다.


패딩이란, 입력된 데이타 행렬 주위로, 무의미한 값을 감싸서 원본 데이타의 크기를 크게 해서, 필터를 거치고 나온 특징 행렬의 크기가 작아지는 것을 방지한다.

또한 무의미한 값을 넣음으로써, 오버피팅이 발생하는 것을 방지할 수 있다. 코드상에서 padding 변수를 이용하여 패딩 방법을 정의하였다.


h_conv1 = tf.nn.conv2d(x_image, W_conv1,

                      strides=[1,1,1,1], padding='SAME')



padding=’SAME’을 주게 되면, 텐서플로우가 자동으로 패딩을 삽입하여 입력값과 출력값 (특징 행렬)의 크기가 같도록 한다. padding=’VALID’를 주게 되면, 패딩을 적용하지 않고 필터를 적용하여 출력값 (특징 행렬)의 크기가 작아진다.

활성함수 (Activation function)의 적용

필터 적용이 끝났으면, 이 필터링된 값에 활성함수를 적용한다. 컨볼루셔널 네트워크에서 일반적으로 사용하는 활성함수는 ReLu 함수이다.


코드

b_conv1 = tf.Variable(tf.constant(0.1, shape=[num_filters1]))

h_conv1_cutoff = tf.nn.relu(h_conv1 + b_conv1)


먼저 bias 값( y=WX+b 에서 b)인 b_conv1을 정의하고, tf.nn.relu를 이용하여, 필터된 결과(h_conv1)에 bias 값을 더한 값을 ReLu 함수로 적용하였다.

Max Pooling

추출된 특징 모두를 가지고 특징을 판단할 필요가 없이, 일부 특징만을 가지고도 특징을 판단할 수 있다. 즉 예를 들어서 고해상도의 큰 사진을 가지고도 어떤 물체를 식별할 수 있지만, 작은 사진을 가지고도 물체를 식별할 수 있다. 이렇게 특징의 수를 줄이는 방법을 서브샘플링 (sub sampling)이라고 하는데, 서브샘플링을 해서 전체 특징의 수를 의도적으로 줄이는 이유는 데이타의 크기를 줄이기 때문에, 컴퓨팅 파워를 절약할 수 있고, 데이타가 줄어드는 과정에서 데이타가 유실이 되기 때문에, 오버 피팅을 방지할 수 있다.


이러한 서브 샘플링에는 여러가지 방법이 있지만 예제에서는 맥스 풀링 (max pooling)이라는 방법을 사용했는데, 맥스 풀링은 풀링 사이즈 (mxn)로 입력데이타를 나눈후 그 중에서 가장 큰 값만을 대표값으로 추출하는 것이다.


아래 그림을 보면 원본 데이타에서 2x2 사이즈로 맥스 풀링을 해서 결과를 각 셀별로 최대값을 뽑아내었고, 이 셀을 가로 2칸씩 그리고 그다음에는 세로로 2칸씩 이동하는 stride 값을 적용하였다.


코드

h_pool1 = tf.nn.max_pool(h_conv1_cutoff, ksize=[1,2,2,1],

                        strides=[1,2,2,1], padding='SAME')


Max pooling은 tf.nn.max_pool이라는 함수를 이용해서 적용할 수 있는데, 첫번째 인자는 활성화 함수 ReLu를 적용하고 나온 결과 값인 h_conv1_cutoff 이고, 두 번째 인자인 ksize는 풀링 필터의 사이즈로 [1,2,2,1]은 2x2 크기로 묶어서 풀링을 한다는 의미이다.


다음 stride는 컨볼루셔널 필터 적용과 마찬가지로 풀링 필터를 가로와 세로로 얼마만큼씩 움직일 것인데, strides=[1,2,2,1]로, 가로로 2칸, 세로로 2칸씩 움직이도록 정의하였다.


행렬의 차원 변환

텐서플로우를 이용해서 CNN을 만들때 각각 개별의 알고리즘을 이해할 필요는 없지만 각 계층을 추가하거나 연결하기 위해서는 행렬의 차원이 어떻게 바뀌는지는 이해해야 한다.

다음 그림을 보자


첫번째 컨볼루셔널 계층은 위의 그림과 같이, 처음에 28x28x1 의 이미지가 들어가면 32개의 컨볼루셔널 필터 W를 적용하게 되고, 각각은 28x28x1의 결과 행렬을 만들어낸다. 컨볼루셔널 필터를 거치게 되면 결과 행렬의 크기는 작아져야 정상이지만, 결과 행렬의 크기를 입력 행렬의 크기와 동일하게 유지하도록 padding=’SAME’으로 설정하였다.

다음으로 bias 값 b를 더한후 (위의 그림에는 생략하였다) 에 이 값에 액티베이션 함수 ReLu를 적용하고 나면 행렬 크기에 변화 없이 28x28x1 행렬 32개가 나온다. 이 각각의 행렬에 size가 2x2이고, stride가 2인 맥스풀링 필터를 적용하게 되면 각각의 행렬의 크기가 반으로 줄어들어 14x14x1 행렬 32개가 리턴된다.


두번째 컨볼루셔널 계층


이제 두번째 컨볼루셔널 계층을 살펴보자. 첫번째 컨볼루셔널 계층과 다를 것이 없다.


코드

# define second layer

num_filters2 = 64


W_conv2 = tf.Variable(

           tf.truncated_normal([5,5,num_filters1,num_filters2],

                               stddev=0.1))

h_conv2 = tf.nn.conv2d(h_pool1, W_conv2,

                      strides=[1,1,1,1], padding='SAME')


b_conv2 = tf.Variable(tf.constant(0.1, shape=[num_filters2]))

h_conv2_cutoff = tf.nn.relu(h_conv2 + b_conv2)


h_pool2 = tf.nn.max_pool(h_conv2_cutoff, ksize=[1,2,2,1],

                        strides=[1,2,2,1], padding='SAME')


단 필터값인 W_conv2의 차원이 [5,5,32,64] ([5,5,num_filters1,num_filters2] 부분 )로 변경되었다.


W_conv2 = tf.Variable(

           tf.truncated_normal([5,5,num_filters1,num_filters2],

                               stddev=0.1))


필터의 사이즈가 5x5이고, 입력되는 값이 32개이기 때문에, 32가 들어가고, 총 64개의 필터를 적용하기 때문에 마지막 부분이 64가 된다.

첫번째 필터와 똑같이 stride를 1,1을 줘서 가로,세로로 각각 1씩 움직이고, padding=’SAME’으로 입력과 출력 사이즈를 같게 하였다.


h_pool2 = tf.nn.max_pool(h_conv2_cutoff, ksize=[1,2,2,1],

                        strides=[1,2,2,1], padding='SAME')


맥스풀링 역시 첫번째 필터와 마찬가지로 2,2 사이즈의 필터(ksize=[1,2,2,1]) 를 적용하고 stride값을 2,2로 줘서 (strides=[1,2,2,1]) 가로 세로로 두칸씩 움직이게 하여 결과의 크기가 반으로 줄어들게 하였다.


14x14 크기의 입력값 32개가 들어가서, 7x7 크기의 행렬 64개가 리턴된다.

풀리 커넥티드 계층

두개의 컨볼루셔널 계층을 통해서 특징을 뽑아냈으면, 이 특징을 가지고 입력된 이미지가 0~9 중 어느 숫자인지를 풀리 커넥티드 계층 (Fully connected layer)를 통해서 판단한다.


코드

# define fully connected layer

h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*num_filters2])


num_units1 = 7*7*num_filters2

num_units2 = 1024


w2 = tf.Variable(tf.truncated_normal([num_units1, num_units2]))

b2 = tf.Variable(tf.constant(0.1, shape=[num_units2]))

hidden2 = tf.nn.relu(tf.matmul(h_pool2_flat, w2) + b2)


keep_prob = tf.placeholder(tf.float32)

hidden2_drop = tf.nn.dropout(hidden2, keep_prob)


w0 = tf.Variable(tf.zeros([num_units2, 10]))

b0 = tf.Variable(tf.zeros([10]))

k = tf.matmul(hidden2_drop, w0) + b0

p = tf.nn.softmax(k)


입력된 64개의 7x7 행렬을 1차원 행렬로 변환한다.


h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*num_filters2])


다음으로 풀리 커넥티드 레이어에 넣는데, 이때 입력값은 64x7x7 개의 벡터 값을 1024개의 뉴런을 이용하여 학습한다.


w2 = tf.Variable(tf.truncated_normal([num_units1, num_units2]))

b2 = tf.Variable(tf.constant(0.1, shape=[num_units2]))


그래서 w2의 값은 [num_units1,num_units2]로 num_units1은 64x7x7 로 입력값의 수를, num_unit2는 뉴런의 수를 나타낸다. 다음 아래와 같이 이 뉴런으로 계산을 한 후 액티베이션 함수 ReLu를 적용한다.


hidden2 = tf.nn.relu(tf.matmul(h_pool2_flat, w2) + b2)


다음 레이어에서는 드롭 아웃을 정의하는데, 드롭 아웃은 오버피팅(과적합)을 막기 위한 계층으로, 원리는 다음 그림과 같이 몇몇 노드간의 연결을 끊어서 학습된 데이타가 도달하지 않도록 하여서 오버피팅이 발생하는 것을 방지하는 기법이다.


출처 : http://cs231n.github.io/neural-networks-2/


텐서 플로우에서 드롭 아웃을 적용하는 것은 매우 간단하다. 아래 코드와 같이 tf.nn.dropout 이라는 함수를 이용하여, 앞의 네트워크에서 전달된 값 (hidden2)를 넣고 keep_prob에, 연결 비율을 넣으면 된다.

keep_prob = tf.placeholder(tf.float32)

hidden2_drop = tf.nn.dropout(hidden2, keep_prob)


연결 비율이란 네트워크가 전체가 다 연결되어 있으면 1.0, 만약에 50%를 드롭아웃 시키면 0.5 식으로 입력한다.

드롭 아웃이 끝난후에는 결과를 가지고 소프트맥스 함수를 이용하여 10개의 카테고리로 분류한다.


w0 = tf.Variable(tf.zeros([num_units2, 10]))

b0 = tf.Variable(tf.zeros([10]))

k = tf.matmul(hidden2_drop, w0) + b0

p = tf.nn.softmax(k)

비용 함수 정의

여기까지 모델 정의가 끝났다. 이제 이 모델을 학습 시키기 위해서 비용함수(코스트 함수)를 정의해보자.

코스트 함수는 크로스엔트로피 함수를 이용한다.

#define loss (cost) function

t = tf.placeholder(tf.float32, [None, 10])

loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(k,t))

train_step = tf.train.AdamOptimizer(0.0001).minimize(loss)


k는 앞의 모델에 의해서 앞의 모델에서

k = tf.matmul(hidden2_drop, w0) + b0

p = tf.nn.softmax(k)


으로 softmax를 적용하기 전의 값이다.  Tf.nn.softmax_cross_entropy_with_logits 는 softmax가 포함되어 있는 함수이기 때문에, p를 적용하게 되면 softmax 함수가 중첩 적용되기 때문에, softmax 적용전의 값인 k 를 넣었다.


WARNING: This op expects unscaled logits, since it performs a softmax on logits internally for efficiency. Do not call this op with the output of softmax, as it will produce incorrect results

https://github.com/tensorflow/tensorflow/blob/master/tensorflow/g3doc/api_docs/python/functions_and_classes/shard7/tf.nn.softmax_cross_entropy_with_logits.md


t는 플레이스 홀더로 정의하였는데, 나중에 학습 데이타 셋에서 읽을 라벨 (그 그림이 0..9 중 어느 숫자인지)이다.


그리고 이 비용 함수를 최적화 하기 위해서 최적화 함수 AdamOptimizer를 사용하였다.

(앞의 소프트맥스 예제에서는 GradientOptimizer를 사용하였는데, 일반적으로 AdamOptimizer가 좀 더 무난하다.)

학습

이제 모델 정의와, 모델의 비용함수와 최적화 함수까지 다 정의하였다. 그러면 이 그래프들을 데이타를 넣어서 학습 시켜보자.  학습은 배치 트레이닝을 이용할것이다.


학습 도중 학습의 진행상황을 보기 위해서 학습된 모델을 중간중간 테스트할것이다. 테스트할때마다 학습의 정확도를 측정하여 출력하는데, 이를 위해서 정확도를 계산하는 함수를 아래와 같이 정의한다.


#define validation function

correct_prediction = tf.equal(tf.argmax(p, 1), tf.argmax(t, 1))

accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))


correct_prediction은 학습 결과와 입력된 라벨(정답)을 비교하여 맞았는지 틀렸는지를 리턴한다.

argmax는 인자에서 가장 큰 값의 인덱스를 리턴하는데, 0~9 배열이 들어가 있기 때문에 가장 큰 값이 학습에 의해 예측된 숫자이다. p는 예측에 의한 결과 값이고, t는 라벨 값이다 이 두 값을 비교하여 가장 큰 값이 있는 인덱스가 일치하면 예측이 성공한것이다.

correct_pediction은 bool 값이기 때문에, 이 값을 숫자로 바꾸기 위해서 tf.reduce_mean을 사용하여, accuracy에 저장하였다.


이제 학습을 세션을 시작하고, 변수들을 초기화 한다.

# prepare session

sess = tf.InteractiveSession()

sess.run(tf.global_variables_initializer())

saver = tf.train.Saver()


다음 배치 학습을 시작한다.

# start training

i = 0

for _ in range(10000):

   i += 1

   batch_xs, batch_ts = mnist.train.next_batch(50)

   sess.run(train_step,

            feed_dict={x:batch_xs, t:batch_ts, keep_prob:0.5})

   if i % 500 == 0:

       loss_vals, acc_vals = [], []

       for c in range(4):

           start = len(mnist.test.labels) / 4 * c

           end = len(mnist.test.labels) / 4 * (c+1)

           loss_val, acc_val = sess.run([loss, accuracy],

               feed_dict={x:mnist.test.images[start:end],

                          t:mnist.test.labels[start:end],

                          keep_prob:1.0})

           loss_vals.append(loss_val)

           acc_vals.append(acc_val)

       loss_val = np.sum(loss_vals)

       acc_val = np.mean(acc_vals)

       print ('Step: %d, Loss: %f, Accuracy: %f'

              % (i, loss_val, acc_val))


학습은 10,000번 루프를 돌면서 한번에 50개씩 배치로 데이타를 읽어서 학습을 진행하고, 500 번째 마다 중각 학습 결과를 출력한다. 중간 학습 결과에서는 10,000 중 몇번째 학습인지와, 비용값 그리고 정확도를 출력해준다.


코드를 보자


   batch_xs, batch_ts = mnist.train.next_batch(50)


MNIST 학습용 데이타 셋에서 50개 단위로 데이타를 읽는다. batch_xs에는 학습에 사용할 28x28x1 사이즈의 이미지와, batch_ts에는 그 이미지에 대한 라벨 (0..9중 어떤 수인지) 가 들어 있다.

읽은 데이타를 feed_dict를 통해서 피딩(입력)하고 트레이닝 세션을 시작한다.


  sess.run(train_step,

            feed_dict={x:batch_xs, t:batch_ts, keep_prob:0.5})


이때 마지막 인자에 keep_prob를 0.5로 피딩하는 것을 볼 수 있는데, keep_prob는 앞의 드롭아웃 계층에서 정의한 변수로 드롭아웃을 거치지 않을 비율을 정의한다. 여기서는 0.5 즉 50%의 네트워크를 인위적으로 끊도록 하였다.


배치로 학습을 진행하다가 500번 마다 중간중간 정확도와 학습 비용을 계산하여 출력한다.

   if i % 500 == 0:

       loss_vals, acc_vals = [], []


여기서 주목할 점은 아래 코드 처럼 한번에 검증을 하지 않고 테스트 데이타를 4등분 한후, 1/4씩 테스트 데이타를 로딩해서 학습비용(loss)와 학습 정확도(accuracy)를 계산하는 것을 볼 수 있다.


       for c in range(4):

           start = len(mnist.test.labels) / 4 * c

           end = len(mnist.test.labels) / 4 * (c+1)

           loss_val, acc_val = sess.run([loss, accuracy],

               feed_dict={x:mnist.test.images[start:end],

                          t:mnist.test.labels[start:end],

                          keep_prob:1.0})

           loss_vals.append(loss_val)

           acc_vals.append(acc_val)


이유는 한꺼번에 많은 데이타를 로딩해서 검증을 할 경우 메모리 문제가 생길 수 있기 때문에, 4번에 나눠 걸쳐서 읽고 검증한 다음에 아래와 같이 학습 비용은 4번의 학습 비용을 합하고, 정확도는 4번의 학습 정확도를 평균으로 내어 출력하였다.


       loss_val = np.sum(loss_vals)

       acc_val = np.mean(acc_vals)

       print ('Step: %d, Loss: %f, Accuracy: %f'

              % (i, loss_val, acc_val))

학습 결과 저장

학습을 통해서 최적의 W와 b값을 구했으면 이 값을 예측에 이용해야 하는데, W 값들이 많고, 이를 일일이 출력해서 파일로 저장하는 것도 번거롭고 해서, 텐서플로우에서는 학습된 모델을 저장할 수 있는 기능을 제공한다. 학습을 통해서 계산된 모든 변수 값을 저장할 수 있는데,  앞에서 세션을 생성할때 생성한 Saver (saver = tf.train.Saver())를 이용하면 현재 학습 세션을  저장할 수 있다.


코드

saver.save(sess, 'cnn_session')

sess.close()


이렇게 하면 현재 디렉토리에 cnn_session* 형태의 파일로 학습된 세션 값들이 저장된다.

그래서 추후 예측을 할때 다시 학습할 필요 없이 이 파일을 로딩해서, 모델의 값들을 복귀한 후에, 예측을 할 수 있다. 이 파일을 읽어서 예측을 하는 것은 다음글에서 다루기로 한다.


예제 코드 : https://github.com/bwcho75/tensorflowML/blob/master/MNIST_CNN_Training.ipynb


저작자 표시 비영리
신고

구글 빅쿼리 사용시 count(distinct)의 값이 정확하지 않은 문제


조대협 (http://bcho.tistory.com)


빅쿼리에서 count(distinct) 문을 사용하면, 종종 값이 부 정확하게 나오는 경우가 있다.

예를 들어서 아래의 두 쿼리는 같은 결과가 나와야 하는데, 아래 count(distinct id)를 쓴 쿼리는 다른 값을 리턴한다.

select count(*)

where id="mykey"

from mytable


select count(distinct id)

where id="mykey"

from mytable


빅쿼리에는 쿼리가 빅쿼리에 최적화된 SQL과 유사한 Legacy SQL 쿼리가 있고, ANSI SQL을 따르는 스탠다드 쿼리가 있다.

Legacy SQL 쿼리의 경우 확인해보니, 동작 방식이 다소 상이한 부분이 있다.
COUNT([DISTINCT] field [, n])
Returns the total number of non-NULL values in the scope of the function.

If you use the DISTINCT keyword, the function returns the number of distinct values for the specified field. Note that the returned value for DISTINCT is a statistical approximation and is not guaranteed to be exact.

Count(distinct) 함수의 경우 리턴 값이 1000이 넘을 경우에는 성능 향상을 위해서 정확한 값을 리턴하지 않고, 근사치 값 (approximation)을 리턴하도록 되어 있습니다. 그래서 상이한 결과가 나온다.

count(distinct platformadid,10000) 으로 하게 되면 리턴값이 10000을 넘을 경우에만 근사치 값을 리턴하게 됩니다. 즉 리턴값이 10000 이하이면 정확한 값을 리턴한다.

만약에 Legacy SQL에서 근사치 값이 아닌 정확한 값을 리턴 받으려면 count(distinct)
EXACT_COUNT_DISTINCT(field)
Returns the exact number of non-NULL, distinct values for the specified field. For better scalability and performance, use COUNT(DISTINCT field).

함수를 사용하시는 방법이 있고, 아니면 Legacy SQL 의 특성에 의한 오류를 방지하시려면 쿼리를 ANSI SQL 모드로 실행하시면 됩니다.
ANSI SQL 모드로 수행하는 방법은 아래와 같이 "Use Legacy SQL" Box를 unchecking 하시고, 테이블 이름을 []로 감싸지 마시고 ``로 감싸서 사용하시면 된다.


저작자 표시 비영리
신고

Docker Kubernetes의 UI

클라우드 컴퓨팅 & NoSQL/google cloud | 2016.11.26 23:38 | Posted by 조대협

Docker Kubernetes UI


조대협 (http://bcho.tistory.com)


오늘 도커 밋업에서 Kubernetes 발표가 있어서, 발표전에 데모를 준비하다 보니, 구글 클라우드의 Kubernetes 서비스인 GKE (Google Container Engine)에서 Kubernetes UI를 지원하는 것을 확인했다.


Google Container Service (GKE)


GKE는 구글 클라우드의 도커 클라우드 서비스이다. 도커 컨테이너를 관리해주는 서비스로는 Apache mesos, Docker Swarm 그리고 구글의 Kuberenetes 가 있는데, GKE는 이 Kuberentes 기반의 클라우드 컨테이너 서비스이다.


대부분의 이런 컨테이너 관리 서비스는 아직 개발중으로 운영에 적용하기에는 많은 부가적인 기능이 필요한데, 사용자 계정 인증이나, 로깅등이 필요하기 때문에, 운영환경에 적용하기는 아직 쉽지 않은데, GKE 서비스는 운영 환경에서 도커 서비스를 할 수 있도록 충분한 완성도를 제공한다. 이미 Pocketmon go 서비스도 이미 GKE를 사용하고 있다.


Kubernetes UI


예전에 Kubernetes를 테스트할 때 단점은 아직 모든 관리와 모니터링을 대부분 CLI로 해야 하기 때문에 사용성이 떨어지는데, 이번 GKE에서는 웹 UI 콘솔을 제공한다.


구글 GKE 콘솔에서 Kuberentes 클러스터를 선택하며 우측에 Connect 버튼이 나오는데, 


이 버튼을 누르면, Kubernetes 웹 UI를 띄울 수 있는 명령어가 출력된다.

아래와 같이 나온 명령어를 커맨드 창에서 실행시키고 htt://localhost:8001/ui 에 접속하면 Kubernetes 웹 콘솔을 볼 수 있다. 


Kubernetes 의 웹콘솔은 다음과 같은 모양이다.



Kubernetes의 주요 컴포넌트인 Pods, Service, Replication Controller , Nodes 등의 상태 모니터링은 물론이고, 배포 역시 이 웹 콘솔에서 가능하다.


예를 들어  gcr.io/terrycho-sandbox/hello-node:v1 컨테이너 이미지를 가지고, Pod 를 생성하고, Service를 정의해서 배포를 하려면 다음과 같은 명령을 이용해야 한다.


1. hello-node 라는 pod를 생성한다. 

% kubectl run hello-node --image=gcr.io/terrycho-sandbox/hello-node:v1 --port=8080


2. 생성된 pod를 service를 정의해서 expose 한다.

kubectl expose deployment hello-node --type="LoadBalancer"


이런 설정들을 CLI로 하면 익숙해지면 쉽지만 익숙해지기전까지는 번거로운데,

아래 그림과 같이, 간단하게 웹 UI에서 Pod와 서비스들을 한번에 정의할 수 있다.





배포가 완료된 후에는 각 Pod의 상황이나, Pod를 호스팅하고 있는 Nodes 들의 상황등 다양한 정보를 매우 쉽게 모니터링이 가능하다. (cf. CLI를 이용할 경우 CLI 명령어를 잘 알아야 가능하다.)


GKE에 대한 튜토리얼은 https://cloud.google.com/container-engine/docs/tutorials  에 있는데,

추천하는 튜토리얼은

가장 간단한 튜토리얼 node.js 웹앱을 배포하는  http://kubernetes.io/docs/hellonode/

와 WordPress와 MySQL을 배포하는 https://cloud.google.com/container-engine/docs/tutorials/persistent-disk/

을 추천한다.


도커가 아직까지 운영 환경에 사례가 국내에 많지 않고, GKE도 GUI 가 없어서 그다지 지켜보지 않았는데, 다시 파볼만한 정도의 완성도가 된듯.


참고로 테스트를 해보니 VM을 3개 만들어놓고 컨테이너를 7개인가 배포했는데, VM은 3개로 유지된다. 즉 하나의 VM에 여러개의 컨테이너가 배포되는 형태인데, 작은 서비스들이 많은 경우에는 자원 사용 효율이 좋을듯. 이런 관점에서 봤는때는 VM 기반의 서비스보다 컨테이너 서비스를 쓰는 장점이 확실히 보이는듯 하다




저작자 표시 비영리
신고

파이어베이스를 이용한 유니티 게임 로그 분석


조대협 (http://bcho.tistory.com)

모바일 로그 분석

일반적으로 모바일 로그 분석은 클라우드 기반의 무료 솔루션을 이용하다가 자체 구축으로 가는 경우가 많다.

클라우드 기반의 무료 로그 분석 솔루션으로는 구글 애널러틱스, 야후의 플러리, 트위터의 패브릭 그리고 구글의 파이어베이스 등이 있다.

이런 무료 로그 분석 솔루션들을 사용이 매우 간편하고, 핵심 지표를 쉽게 뽑아 줄 수 있으며, 별도의 운영이 필요 없다는 장점을 가지고 있다.

그러나 이런 클라우드 기반의 무료 솔루션의 경우에는 요약된 정보들만 볼 수 있고 또한 내가 원하는 지표를 마음대로 지정을 할 수 없기 때문에, 어느정도 서비스가 성장하고 팀의 여력이 되면 별도의 로그 수집 및 분석 솔루션을 만드는 것이 일반적이다.

오픈 소스 기반의 분석 솔루션

오픈 소스를 조합해서 모바일 로그 수집 시스템을 만들면 대략 다음과 같은 모양이 된다.


API 서버에서 로그를 수집해서 카프카등의 큐를 통해서 로그를 모으고, 실시간은 스파크 스트리밍, 배치는 하둡이나 스파크 스트리밍 프레임웍을 이용합니다. 대쉬 보드는 만드는 곳도 있지만, 주피터 노트북이나 제플린 노트북과 같은 노트북을 이용한다.

요즘은 데이타 저장 및 분석에 ELK (Elastic Search + Logstash + Kibana)와 같은 솔루션도 많이 사용하고 있다.


그런데 이런 오픈 소스 솔루션 기반으로 로그 분석 시스템을 개발하면 몇가지 문제가 발생한다.

  • 개발에 드는 노력
    이런 오픈소스 스택으로 시스템을 개발하려면, 이 프레임웍에 대해서 잘 아는 전문가가 필요합다. 일반적인 스타트업에서는 구하기도 힘들고, 기업이 어느정도 규모가 되더라도 빅데이타 관련 기술을 다룰 줄 아는 엔지니어는 여전히 귀한 엔지니어이고, 이런 엔지니어들이 있다하더라도, 시스템 설계및 구현에는 수개월의 기간이 소요 되게 된다.

  • 시스템 구매와 운영
    다음 문제는 모바일 데이타는 양이 많기 때문에, 위에서 언급한 빅데이타 관련 오픈 소스를 사용하게 되는데, 이러한 시스템은 하드웨어 자원이 수십에서 수백대가 필요하거니와, 이를 설치하고 운영하는 것 역시 쉽지 않다.
    로그를 수집하고 분석하는 로직을 만들어야 하는 엔지니어들이 정작 데이타 분석 보다는 시스템 운영과 유지보수에 많은 시간을 낭비해야 한다는 문제가 발생한다.
    규모가 작은 스타트업이나 엔지니어링 능력이 되지 않는 기업들은 이런 빅데이타 분석은 엄두도 내지 못하는 상황이 되고, 디테일한 데이타 분석을 하지 못하게 되니 자연히 경쟁력이 떨어지게 될 수 있다.

  • 연산 시간
    그리고 수집 수백대의 서버를 가지고 있다하더라도, 데이타 연산 시간은 수십분에서 수시간이 소요된다. 특히 데이타 분석 서버들이 분석을 하고 있을때는 다른 분석을 하고 싶은 사람들은 연산이 끝날때 까지 기다려야 하고, 수시간을 들여서 연산한 결과라도 연산이 잘못되었으면 다시 로직을 수정해서 수시간 동안 다시 연산을 해야 한다.
    비지니스 조직 입장에서는 지표 분석 결과를 얻는데, 수시간이 걸리니 의사 결정의 민첩성이 떨어지게 된다.

클라우드 기반의 분석 솔루션

근래에 이런 빅데이타 분석이 클라우드 컴퓨팅 기술과 만나면서 한번의 큰 변화를 겪게 되는데, 흔히들 빅데이타의 민주화라고 이야기 한다.  빅데이타 분석이 클라우드 컴퓨팅과 만나면서 겪은 큰 변화는 다음과 같다 .

클라우드 스케일의 연산

먼저 스케일이 달라집니다. 클라우드의 대용량 자원을 이용하여, 연산을 하기 때문에, 훨씬 더 빠른 연산을 저 비용에 할 수 있다.

예를 들어 구글의 빅쿼리의 경우에는 1000억개의 문자열(ROW)를  Regular expression을 이용하여 스트링 Like 검색을 하고 이를 group by 로 그룹핑하여 연산 하는 쿼리를 수행할때


“8600개의 CPU, 3600개의 디스크, 350GB의 네트워크 대역폭"


이 사용이 되고, 쿼리 수행 시간은 약 20~30초, 클라우드 사용 비용은 20$ (2만원) 정도가 소요 된다.

오픈 소스 기반으로 왠만한 규모로는 동시에 단일 연산으로 이렇게 수천개의 CPU를 같이 돌릴 수 있는 인프라를 사내에 가지고 있기도 힘들뿐 더러, 이만한 리소스를 20$라는 저렴한 비용에 사용하기란 거의 불가능에 가깝다.

이런 빠른 연산으로 인해서, 현업에서는 연산 결과를 기다리지 않고 바로바로 볼 수 있고, 비용 역시 저렴하기 때문에, 어느정도 자금력과 개발력이 있는 기업이 아니더라도 고성능의 빅데이타 분석 시스템 구현이 가능하게 된다.

NoOPS

다음 장점으로는 운영이 필요 없다는 것인데, 앞에서도 설명했듯이, 오픈 소스를 이용해서 빅데이타 분석 시스템을 직접 구축한 경우에는 시스템 인스톨과, 구성, 그리고 운영에 많은 시간이 소요 되는데, 클라우드 기반의 빅데이타 솔루션은 설정과 운영을 클라우드 서비스 제공자가 대행을 하기 때문에, 엔지니어링 팀은 별도의 설정과 유지보수 없이 본연의 역할인 데이타 분석에만 집중할 수 있게 된다. (아마 직접 하둡이나 스파크 클러스터를 운영해본 사람이라면 이 의미를 잘 이해하리라 본다.)


이렇게 클라우드가 빅데이타 영역에 도입되면서 이제는 빅데이타 분석이 뛰어난 엔지니어링 지식과 자금력이 없더라도 단시간내에 저비용으로 효율적인 데이타 분석이 가능하게 되었기 때문에, 이를 빅데이타의 민주화라고 부른다.

파이어베이스 애널러틱스

파이어베이스는 얼마전에 구글이 인수해서 클라우드 서비스 형태로 제공하고 있는 통합 모바일 개발 프레임웍이다. 웹은 지원하지 않고 모바일만 지원하는 형태의 프레임웍이며, 리얼타임 데이타 베이스, 광고 네트워크 통합, 푸쉬 서비스, 사용자 개인 인증 서비스등 여러가지 기능을 가지고 있는데, 그 중에서, 파이어베이스 애널러틱스는 모바일 빅데이타 분석에 최적화된 시스템이다.

빅쿼리와 파이어베이스의 조합

게임 체인저

파이어베이스는 모바일 데이타 분석에서 거의 게임 체인저라고 할만한 기술인데, 기존의 클라우드 기반의 모바일 데이타 분석 솔루션은 가장 큰 문제점이, 개발자가 정의한 로그 이벤트 (커스텀 로그)를 수집할 수 없다는 문제와  그리고 수집한 원본 데이타를 볼 수 없기 때문에, 원하는 지표를 마음대로 수집하고 분석하는 것이 불가능했다.

그런데 파이어베이스 애널러틱스는 이 두가지 기능을 지원하기 시작하였다.

커스텀 이벤트 정의를 통해서 개발자가 원하는 로그를 손쉽게 정의해서 수집이 가능하고, 또한 수집한 로그는 모두 구글의 빅데이타 저장 및 분석 플랫폼인 빅쿼리에 저장되고 바로 분석이 가능하다.

빅쿼리

파이어베이스 애널러틱스의 데이타는 빅쿼리에 저장이 되는데, 앞에서 예를 든것과 같이, 빅쿼리는 한번 연산에 수천개의 CPU와 디스크를 사용하여, 하둡이나 스파크에서 수시간이 걸리는 연산을 불과 수십초만에 처리가 가능하다.

빅쿼리의 또 다른 장점중의 하나는 이런 연산 속도 뿐만 아니라 RDBMS와는 다르게 JSON과 같이 트리형 (계층 구조를 가지는) 데이타형을 그대로 저장하고 쿼리가 가능하다는 것이다.


빅쿼리에 대한 자세한 설명은

를 참고하기 바란다.

파이어베이스 기반의 로그 분석

파이어베이스 애널러틱스는 뒤로는 빅쿼리 연동을 통해서 모든 원본 데이타의 수집과 분석을 지원하고 앞으로는 파이어베이스 에이전트를 모바일 디바이스에 탑재 하는 방식으로 최소한의 코드 개발로 모바일 앱으로 부터 모든 데이타를 수집할 수 있다.  파이어베이스 애널러틱스는 안드로이드와 iOS 플랫폼을 지원한다.

게임 프레임웍 지원

반가운 소식중의 하나는 파이어베이스 애널러틱스가 이제 유니티3D나, 언리얼(C++) 과 같은 게임 엔진을 지원한다. 현재 두 플랫폼에 대한 지원은 베타로 공개되어 있다.

코드 예제

그러면 파이어베이스 애널러틱스를 이용해서 로그를 수집하는 코드는 어떻게 삽입을 할까? 안드로이드와 유니티 3D의 예를 들어서 보자.

안드로이드 예제 코드

상세한 코드는 http://bcho.tistory.com/1131 를 참고하기 바란다.

코드 부분을 발췌해서 보면 다음과 같다.


//생략

:


import com.google.firebase.analytics.FirebaseAnalytics;


public class MainActivity extends AppCompatActivity {


 // add firebase analytics object

 private FirebaseAnalytics mFirebaseAnalytics;


   public void onSendEvent(View view){

     // 중간 생략

     Bundle bundle = new Bundle();

     bundle.putString(FirebaseAnalytics.Param.ITEM_ID, contentsId);

     bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, contentsName);

     bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, contentsCategory);

     mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);


 }

}



기본적으로 gradle 빌드 스크립트에 파이어베이스 애널러틱스 모듈을 import 하고, FirebaseAnalytics 객체만 선언해주면 기본적인 사용자 로그 (앱 실행, 종료등), 일일 방문자, 동시 접속자, 접속 디바이스 종류, 사용자 연령과 성별들을 모두 수집해준다.

빌드 스크립트 수정 및 소스코드에 한줄의 코드만 추가해주면 된다.

다음으로, 각각의 이벤트를 추가하고자 한다면, 위와 같이 Bundle 객체를 정의해서, 넘기고자 하는 인자를 정의해주고 logEvent라는 메서드를 호출해주면 파이어베이스로 로그가 전달된다.

유니티 3D 예제 코드

유니티 3D에서 파이어베이스에 로그를 남기는 것도 다르지 않다.

다음 코드를 보자


       Firebase.Analytics.Parameter[] param = {

           new Firebase.Analytics.Parameter("sessionid", sessionid),

           new Firebase.Analytics.Parameter("score", (string)ApplicationModel.score.ToString())

       };

       Firebase.Analytics.FirebaseAnalytics.LogEvent(ApplicationModel.EVENT.END_SESSION, param);


Parameter라는 배열로, 파이어베이스에 남길 로그의 인자들을 정의한후에, LogEvent 메서드를 이용하여 이벤트 명과, 앞에서 정의된 인자들 (Parameter)를 남겨주면 로그는 자동으로 파이어베이스로 전달된다.


파이어베이스 애널러틱스를 이용한 모바일 데이타 분석

그러면 파이어베이스를 이용하여 모바일 로그 분석을 어떻게 할 수 있는지 알아보자. 마침 유니티 3D가 얼마전 부터 베타로 지원이 되기 때문에, 간단한 게임을 이용한 로그 수집을 설명한다.

샘플 게임 설명

샘플에 사용한 게임은 간단한 RPG 형태의 게임으로 다음과 같이 구성된다.



시작 화면

시작화면에서는 로그 분석을 위해서, 사용자의 나이와 성별을 입력 받는다.


게임 화면

다음 게임이 시작되면, 화면을 터치하여 토끼 캐릭터를 이동 시키고, 돼지를 클릭하면 돼지를 공격한다.

돼지를 공격할때 마다 데미지는 돼지의 종류에 따라 일정 값 범위내에서 랜덤으로 판정되고, 생명 값이 남아있지 않으면 돼지가 죽게 된다.

맵내에 돼지는 7개가 유지되도록 되어 있으며, 돼지가 줄면, 돼지는 하늘에서 부터 떨어지게 되어 있다.

게임은 120초 동안 진행되며, 120초가 지나면 자동으로 종료된다.

종료 화면

게임이 종료되면 점수를 표시한다.

데이타  분석 지표 디자인

그러면 이 게임으로 어떻게 데이타를 분석할것인지에 대해서 고민해보자.

일일 접속 사용자나 사용자에 대한 사용 시간,횟수등은 파이어베이스 애널러틱스에서 기본적으로 수집이 되기 때문에, 조금 더 의미 있는 데이타를 수집해보도록 한다.

캐릭터 이동 히트맵

이 예제에서 다소 중점을 둔 부분중의 하나는 캐릭터 이동 히트맵이다.

게임에서 난이도 조정등에 사용할 수 있는 정보중의 하나가 NPC 캐릭터의 이동 동선과, 플레이어 캐릭터의 이동 동선이다. 주로 플레이어가 죽는 위치를 데드존 (Dead zone)이라고 하면, 이 데드존 위치를 찾아낼 수 있고, 이 데드존에서 플레이어와 NPC의 타입,레벨 등을 조사하여 난이도를 조정한다거나, 또는 AI(인공지능) 플레이어 캐릭터의 경우에는 이동 동선을 추적함으로써 맵 내에서 AI가 원하는 데로 잘 움직이는지를 추적해볼 수 있다.

아래는 데드존을 기반으로 캐릭터와 NPC의 레벨을 분석해놓은 예제이다.


<그림. 게임맵상에서 데드존의 플레이어와 NPC 캐릭터간의 레벨 분석 >


아래는 흥미로운 분석중의 한예인데, 게임맵에서, 각 위치별로 자주 발생하는 채팅 메세지를 표시한 내용이다.




<그림. 게임맵상에서 자주 사용되는 채팅 메세지 분석>


그림 출처 : http://www.cs.cornell.edu/courses/cs4152/2013sp/sessions/15-GameAnalytics.pdf


이런 시스템 역시 쉽게 개발이 가능한데, 파이어베이스 애널러틱스를 이용하여 채팅 로그를 수집한 후, 자연어 분석 API를 이용하면, 명사와 형용사등을 추출하여 자주 오가는 말들을 통계를 낼 수 있다.

http://bcho.tistory.com/1136 는 구글의 자연어 분석 API를 이용하여 트위터의 내용을 실시간으로 분석한 내용이다.

나이별  점수 분포

다음으로 일반적인 분석 시스템에서 수집되지 않는 커스텀 로그 분석 시나리오중 사용자 나이별 점수대를 분석해본다.

게임실행에서 종료까지 실행한 사용자

마지막으로 유용하게 사용되는 퍼널 분석의 예로 게임을 시작해서 종료할때까지의 도달율을 측정해봤다.

게임을 인스톨하고 시작한다음, 캐릭터를 움직이고, 캐릭터를 이용하여 공격을하고, 2분동안 플레이해서 게임을 종료한 사용자의 비율을 분석해본다.

로그 메세지 디자인

그러면 이러한 게임 로그를 분석하기 위해서 수집할 로그 메세지는 어떤 형태가 될지 디자인을 해보자.

로그 이벤트는 아래와 같이 7가지로 정의한다.

  • START_SESSION,END_SESSION 은 게임을 시작과 끝날때 발생하는 이벤트이다.

  • NPC_CREATE,NPC_MOVE,NPC_DIE 는 NPC(돼지)를 생성하고 이동하고, 그리고 죽었을때 각각 발생하는 이벤트이다. 이동은 이벤트의 수가 많기 때문에, 10초 단위로 수집하였다.

  • PLAYER_MOVE,PLAYER_ATTACK 은 플레이어 캐릭터의 이동과 NPC를 공격하는 이벤트를 수집한다.


각 이벤트를 플레이하는 판과 연결하기 위해서 각 플레이는 고유의 sessionid가 생성되서 게임이 시작될때부터 끝날때 까지 모든 이벤트에 저장된다.



Event name

Param

Key

Value

Type

Note


START_SESSION

This event is triggered when player press “START” button after submitting player’s age & gender

sessionid

Unique session Id for this play

String


age

Player’s age

String


sex

Player’s gender

String

true : man

false : woman

PLAYER_MOVE

It record location of player in game map periodically (every 2sec)

sessionid




Pos_X




Pox_Z




PLAYER_ATTACK

This event is occurred when player attack NPC.

sessionid

Unique session Id for this play



npc_id

Attacked NPC ID



type

Type of NPC



pos_X

NPC location X



pos_Z

NPC location Y



damage

Damage that NPC get in this attack



life

Left life for this NPC



NPC_CREATE

When new NPC is created, this event is logged.

sessionid

Unique session Id for this play



npc_id

Attacked NPC ID



type

Type of NPC



pos_X

NPC location X



pos_Y

NPC location Y



NPC_MOVE

Every 2sec for each NPC, it records the location of NPC.

sessionid

Unique session Id for this play



npc_id

Attacked NPC ID



type

Type of NPC



pos_X

NPC location X



pos_Y

NPC location Y



NPC_DIE

It is triggered when NPC is dead by attack

sessionid

Unique session Id for this play



npc_id

Attacked NPC ID



type

Type of NPC



pos_X

NPC location X



pos_Y

NPC location Y



END_SCENE

It is triggered when game stage(session) is over

sessionid

Unique session Id for this play



score

Score for this play




이렇게 정의된 로그는 파이어베이스 애널러틱스에 의해서 빅쿼리로 자동으로 저장되게 된다.

실시간 디버깅

이런 로깅을 삽입하면, 로그가 제대로 저장이 되는지 확인이 필요한데, 파이어베이스 애널러틱스는 특성상 로그 이벤트가 1000개가 쌓이거나 또는 컨버전 이벤트가 발생하거나 또는 1시간 주기로 로그를 서버에 전송하기 때문에 바로 올라오는 로그 메세지를 확인할 수 없다.

그래서 이번에 새로 소개되니 기능이 “DEBUG VIEW”라는 기능인데, 이 특정 디바이스에 디버깅 옵션을 지정하면, 실시간으로 올라오는 로그를 확인할 수 있다.

로그는 모바일앱에서 업로드한 후 약 10~20초 후에, 화면에 반영된다.



대쉬 보드를 이용한 지표 분석

대쉬 보드는 파이어 베이스 애널러틱스에서 기본으로 제공되는 지표로 모바일 서비스에 공통적으로 필요한 지표들을 분석하여 웹으로 출력해준다.

DAU/WAU/MAU 분석

가장 기본적인 지표로는 월간,주간,일간 방문자 수로를 그래프로 출력해준다.

평균 플레이 시간 분석

다음은 평균 플레이 시간으로, 사용자가 하루에 평균 얼마나 앱을 사용하였는지, 동시 접속자수 (Session)과,  한번 접속했을때 얼마나 오래 앱을 사용 하였는지 (Session duration)등을 분석하여 그래프로 출력해준다.


국가별 접속 내역 분석

다음은 국가별 접속 내용으로, 글로벌 서비스에는 필수로 필요한 분석 내용이다.


사용자 데모그래픽 정보 분석

사용자에 대한 데모 그래픽 정보 즉 성별과, 나이를 분석해주는데, 앱에 별도로 사용자 로그인 기능이 없거나, 사용자 정보를 추적하는 기능이 없더라도, 파이어베이스 애널러틱스는 여러군데에서 수집한 로그를 기반으로 사용자의 성별과 나이를 분석해 준다.



특정 이벤트에 대한 분석

다음은 특정 이벤트에 대한 분석이 가능하다. 게임에서 사용자가 스테이지를 넘어가는 이벤트등 파이어베이스에 정의된 이벤트 이외에도 사용자가 정의한 이벤트에 대한 분석이 가능하다.

또한 이벤트가 발생한 사용자에 대한 데모 그래픽 정보 (연령,성별,국가)를 같이 분석해서 해당 이벤트가 어떤 사용자 층에서 발생하였는지를 분석해 준다.


예를 들어 게임의 보너스 스테이지를 많이 클리어한 사용자의 통계만을 볼 수 있고, 그 보너스 스테이지를 클리어한 사용자의 나이,성별, 국가 정보등을 볼 수 있다.



게임 플레이 완료율에 대한 퍼널 분석

다음은 앞에서 데이타 분석 모델을 정의할때 정의한 문제로 사용자가 게임을 시작해서 플레이를 끝낸 사용자 까지를 퍼널(깔때기) 분석을 적용한 예이다.

해당 시간에 총 93번의 게임이 플레이 되었으며, 캐릭터까지는 이동하였으나, 공격을 하지 않은 플레이는 3번, 그리고 끝까지 게임 플레이를 끝낸 사용자는 총 62번으로 측정되었다.



이외에도 상품 구매에 대한(인앱)에 대한 분석이나, 디바이스 종류, 앱 버전, 그리고 어느 광고 네트워크에서 사용자가 인입되었는지 등의 분석등 다양한 분석이 가능한데, 대쉬보드의 자세한 지표에 대해서는 http://bcho.tistory.com/1132 를 참고하기 바란다.

노트북을 이용한 커스텀 로그 분석

앞에서는 파이어베이스에서 제공되는 로그와 분석 방법에 대해서만 분석을 진행하였다. 이번에는 커스텀 로그와 원본(raw)데이타를 이용한 데이타 분석에 대해서 알아보자.


모든 원본 데이타는 앞에서도 언급했듯이 구글의 빅쿼리에 저장되기 때문에, SQL 쿼리를 이용하여 자유롭게 데이타 분석이 가능하고 그래프로도 표현이 가능하다.

별도의 개발이 없이 자유롭게 쿼리를 실행하고 그래프로 표현할 수 있는 도구로는 노트북이 있는데, 빅쿼리는 주피터 노트북과 제플린이 지원된다. 주피처 노트북 오픈소스를 구글 클라우드에 맞춘 버전은 Google Cloud Datalab이라는 것이 있는데, 여기서는 데이타랩을 이용하여 분석하였다.

캐릭터 이동 히트맵 분석

앞에서 NPC_MOVE와 PLAYER_ATTACK을 이용하여, NPC의 이동 동선과, PLAYER가 공격을 한 위치를 수집하였다.

이를 히트맵으로 그려보면 다음과 같다.


좌측은 NPC가 주로 이동하는 경로이고 우측은 플레이어가 NPC를 주로 공격한 위치로, 많이 간곳일 수록 진하게 칠해진다.

NPC 캐릭터는 전체 맵에 걸쳐서 이동을 하는 것을 볼 수 있고, 주로 우측 나무 근처를 많이 움직이는 것을 볼 수 있다. 오른쪽 사용자가 공격한 위치를 보면 주로 중앙에 모여 있기 때문에 우측 나무 근처로 움직인 NPC는 생존 확률이 높았을 것으로 생각해볼 수 있다.

그리고 NPC 이동 맵에서 중간중간에 진하게 보이는 점은 NPC 가 생성되는 위치이기 때문에, 이동이 많이 관측되었다.

연령별 플레이 점수 분석

다음으로 플레이어 연령별 점수대를 보면, 최고 점수는 30대가 기록하였고, 대략 4900점대인데 반해서, 전체적인 평균 점수는 40대가 높은 것을 볼 수 있다. (이 데이타는 연령별로 수집된 데이타의 양이 그리 많지 않기 때문에 정확하지는 않다. 어디까지나 분석 예제용으로만 이해하기 바란다.)



분석에 사용된 코드는 아래에 있다. 이 코드는 데모용이고 최적화가 되어있지 않기 때문에, 운영 환경에서는 반드시 최적화를 해서 사용하기 바란다.


https://github.com/bwcho75/bigquery/blob/master/GameData/Game%20Data%20Demo.ipynb


참고로, 모든 데이타 분석은 주로 파이썬을 이용하였는데, 근래에 빅데이타 분석용 언어로 파이썬이 많이 사용되기 때문에, 파이썬을 공부해놓으면 좀 더 쉽게 데이타 분석이 가능하다. 또한 파이썬으로 데이타를 분석할때 많이 쓰이는 프레임웍으로는 팬다스 (pandas)와 넘파이 (numpy)가 있는데, 이 둘 역시 같이 익혀놓는것이 좋다.

파이어베이스 노티피케이션 서비스를 통한 이벤트 기반의 푸쉬 타게팅

파이어베이스 애널러틱스와 연계해서 유용하게 사용할 수 있는 기능은 파이어베이스 노티피케이션 이라는 서비스가 있다.


파이어 베이스 노티피케이션 서비스는 파이어베이스에서 제공되는 웹 콘솔을 이용하여 관리자가 모바일 서비스에 손쉽게 푸쉬 메세지를 보낼 수 있는 서비스이다.

푸쉬 타게팅을 위한 별도의 서버 시스템을 개발하지 않고도 마케팅이나 기획자등 비 개발인력이 타게팅된 푸쉬 메세지를 손쉽게 보낼 수 있게 디자인된 서비스인데, 특히 파이어 베이스 애널러틱스와 연계가 되면 세세한 타게팅이 가능하다.


이벤트 로그 기반의 타케팅

푸쉬 타겟을 정할때, 파이어베이스 애널러틱스에서 수집한 이벤트를 조건으로 해서 푸쉬를 타게팅할 수 있다.

예를 들어

  • 게임 스테이지 3 이상을 클리어한 플레이어한 푸쉬를 보낸다.

  • NPC를 10,000개 이상 죽인 플레이어에게 푸쉬를 보낸다.

  • 아이템을 100개이상 구매한 사용자에게 푸쉬를 보낸다.

와 같이 서비스에서 수집된 이벤트에 따라서 다양한 조건을 정의할 수 있다.



<그림. 파이어베이스 노티피케이션에서 특정 사용자 층을 타게팅 해서 보내는 화면 >


이런 타게팅은 파이어베이스 애널러틱스에서 Audience로 사용자 군을 정의한 후에, (로그 이벤트 조건이나 사용자 이벤트 조건 등), 이 조건에 타겟해서 푸쉬를 파이어베이스 노티피케이션 서비스에서 정의한다.

사용자 정보 기반의 타게팅

서비스의 로그 이벤트 정보뿐 아니라, 사용자에 대해서도 푸쉬 타게팅이 가능한데, 특정 성별이나 나이에 대해 푸쉬를 보내거나, 특정 단말을 사용하는 사용자, 특정 국가에 있는 사용자등 다양한 사용자 관련 정보로 푸쉬를 보낼 수 있다.

사용자 정보 역시 앞의 이벤트 로그 정보처럼 개발자가 커스텀 필드를 추가하여 사용자 정보를 로그에 수집할 수 있다.


스케쥴링

이런 타게팅 푸쉬는 바로 웹에서 보낼 수 도 있지만, 특정 시간에 맞춰서 미리 예약을 해놓는 것도 가능하다.  




비용 정책 분석

파이어베이스 애널러틱스에서 원본 데이타를 수집 및 분석 하려면 빅쿼리를 연동해야 하는데, 빅쿼리 연동은 파이어베이스의 무료 플랜으로는 사용이 불가능하다. Blaze 플랜으로 업그레이드 해야 하는데, Blaze 플랜은 사용한 만큼 비용을 내는 정책으로 다른 서비스를 사용하지 않고, 파이어베이스 애널러틱스와 빅쿼리 연동만을 사용할 경우에는 파이어베이스에 추가로 과금되는 금액은 없다. (0원이다.)

단 빅쿼리에 대한 저장 가격과 쿼리 비용은 과금이 되는데,  빅쿼리 저장 가격은 GB당 월 0.02$ 이고, 90일동안 테이블의 데이타가 변하지 않으면 자동으로 0.01$로 50%가 할인된다.

그리고 쿼리당 비용을 받는데, 쿼리는 GB 스캔당 0.005$가 과금된다.


자세한 가격 정책 및, 파이어베이스 애널러틱스에 대한 데이타 구조는 http://bcho.tistory.com/1133 를 참고하기 바란다.

저작자 표시 비영리
신고

수포자를 위한 딥러닝

#1 - 머신러닝의 개요

조대협(http://bcho.tistory.com)

들어가기에 앞서서 

몇년전부터 빅데이타와 머신러닝이 유행하면서 이분야를 공부해야겠다고 생각을 하고 코세라의 Andrew.NG 교수님의 강의도 듣고, 통계학 책도 보고, 수학적인 지식이 부족해서 고등학교 수학 참고서도 봤지만, 도저히 답이 나오지 않는다. 머신 러닝에 사용되는 알고리즘은 복잡도가 높고 일반적인 수학 지식으로 이해조차 어려운데, 실제 운영 시스템에 적용할 수 있는 수준의 알고리즘은 석박사급의 전문가적인 지식이 아니면 쉽게 만들 수 없는 것으로 보였다. 예를 들어 인공지능망(뉴럴네트워크:Neural Network) 알고리즘에 대한 원리는 이해할 수 있지만, 실제로 서비스에 사용되는 알고르즘을 보니 보통 60~90개의 계층으로 이루어져 있는데, (그냥 복잡하다는 이야기로 이해하면 됨) 이런 복잡한 알고리즘을 수학 초보자인 내가 만든다는 것은 거의 불가능에 가까워 보였고, 이런것을 만들기 위해서 몇년의 시간을 투자해서 머신러닝 전문가로 커리어패스를 전환할 수 는 있겠지만 많은 시간과 노력이 드는데 반해서, 이미 나에게는 소프트웨어 개발과 백앤드 시스템이라는 전문분야가 있어싸.

그래도 조금씩 보다보니, 머신 러닝에서 소개되는 알고리즘은 주로 사용되는 것은 약 20개 내외였고, 이미 다 정형화 되어 있어서 그 알고리즘을 만들어내기보다는, 가져다 쓰기만 하면 될 것 같다는 느낌이 들었다. 아직 많이 보지는 못했지만, 실제로 머신 러닝 기반의 시스템들은 나와 있는 알고리즘을 코드로 옮겨서 운영 환경에 올리는 경우가 대부분이었다.

비유를 하자면 우리가 복잡한 해쉬 리스트나, 소팅 알고리즘을 모르고도 간단하게 프로그래밍 언어에 있는 라이브러리를 가져다 쓰는 것과 같은 원리라고나 할까? 그래서, 완벽하게 이해하고 만들기 보다는 기본적인 원리를 파악하고 이미 공개된 알고리즘과 특히 레퍼런스 코드를 가져다가 운영환경에다 쓸 수 있는 정도의 수준을 목표로 하기로 했다.

이제 아주 아주 초보적인 수준의 이해를 가지고, 구글의 텐서플로우 기반으로 머신러닝과 딥러닝을 공부하면서 내용을 공유하고자 한다. 글을 쓰는 나역시도 수포자이며 머신러닝에 대한 초보자이기 때문에, 설명이 부족할 수 도 있고, 틀린 내용이 있을 수 있음을 미리 알리고 시작한다. (틀린 내용은 알려주세요)

머신러닝

머신 러닝은 데이타를 기반으로 학습을 시켜서 몬가를 예측하게 만드는 기법이다.

통계학적으로는 추측 통계학 (Inferential statistics)에 해당하는 영역인데, 근래에 들어서 알파고와 같은 인공지능이나 자동 주행 자동차, 로봇 기술등을 기반으로 주목을 받고 있다.



<그림. 구글의 자동 주행 자동차>


간단한 활용 사례를 보면

  • 학습된 컴퓨터에 의한 이메일 스팸 필터링

  • 편지지의 우편번호 글자 인식

  • 쇼핑몰이나 케이블 TV의 추천 시스템

  • 자연어 인식

  • 자동차 자율 주행

등을 볼 수 있다.


이러한 시나리오는 지속적인 샘플 데이타를 수집 및 정제하고 지속적으로 알고리즘을 학습해나감에 따라서 최적의 알고리즘을 찾아나가도록 한다.

쇼핑몰의 추천 시스템의 경우 사용자의 구매 패턴을 군집화하여 유사한 패턴을 찾아냄으로써 적절한 상품을 추천하는데, 예를 들어 30대 남성/미혼/연수입 5000만원/차량 보유한 사용자가 카메라,배낭등을 구매했을 경우 여행 상품을 구매할 확률이 높다는 것을 학습하였을때, 이러한 패턴의 사용자에게 여행 상품을 추천해주는 것과 같은 답을 제공할 수 있다.

지도 학습과 비지도 학습

머신러닝은 학습 방법에 따라서 지도 학습 (Supervised Learning)과 비지도 학습 (Unsupervised Learning)으로 분류될 수 있다.

지도 학습 (Supervised Learning)



예를 들어 학생에게 곱셈을 학습 시킬때,

“2*3=6이고, 2*4=8이야, 그러면 2*5= 얼마일까? “

처럼 문제에 대한 정답을 주고 학습을 한 후, 나중에 문제를 줬을때 정답을 구하도록 하는 것이 지도 학습 (Supervised Learning)이다.

비지도 학습 (Unsupervised learning)

반대로 비지도 학습은 정답을 주지않고 문제로만 학습을 시키는 방식을 비지도 학습이라고 한다.

예를 들어 영화에 대한 종류를 학습 시키기 위해서, 연령,성별과 영화의 종류 (액션, 드라마, SF)를 학습 시켰을때, 이를 군집화 해보면 20대 남성은 액션 영화를 좋아하고 20대 여성은 드라마 영화를 좋아 하는 것과 같은 군집된 결과를 얻을 수 있고, 이를 기반으로 20대 남성이 좋아하는 영화의 종류는 유사한 군집의 결과인 ”액션 영화" 라는 답을 내게 되낟.


여기서 문제에 대한 답을 전문적인 용어로 이야기 하면 라벨된 데이타 (Labeled data)라고 한다.


머신러닝의 대표적인 문제 Regression과 Classification 문제

머신러닝을 이용해서 해결하는 문제의 타입은 크게 regression과 classification 문제 두가지로 대표가 된다.

Classification

Classification은 입력값에 대한 결과값이 연속적이지 않고 몇개의 종류로 딱딱 나눠서 끊어지는 결과가 나오는 것을 이야기 한다. 예를 들어 종양의 크기가 0.3cm 이상이고 20대이면, 암이 양성, 또는 종양의 크기가 0.2cm 이하이고 30대이면, 암이 음성과 같이 결과 값이 ”양성암/음성암"과 같이 두개의 결과를 갖는 것이 예가 된다.


<종양 크기에 따른, 암의 양성/음성 여부에 대한 그래프>

또 다른 예로는 사진을 업로드 했을때, 사진의 물체를 인식할때 ”이사진은 개이다.” “이사진은 고양이이다.” 처럼 특정 종류에 대한 결과값이 나오는 것 역시 Classification 문제로 볼 수 있다.


Regression

Regression 문제는 결과값이 연속성을 가지고 있을때 Regression 문제라고 한다. 즉 택시의 주행거리에 따른 요금과 같은 문제인데, 변수 택시 주행 거리에 대해서, 결과 택시 값이 기대 되는 경우로 변수와 결과값이 연속적으로 이루어 지는 경우를 말한다.


<그림. 주행 거리에 따른 택시비 >

머신 러닝과 딥러닝

이러한 머신 러닝의 분야중, 인공 지능망 (뉴럴 네트워크 / Artificial neural network)라는 기법이 있는데, 사람의 뇌의 구조를 분석하여, 사람 뇌의 모양이 여러개의 뉴런이 모여서 이루어진것 처럼, 머신 러닝의 학습 모델을 두뇌의 모양과 같이 여러개의 계산 노드를 여러 층으로 연결해서 만들어낸 모델이다.


<알파고에 사용된 뉴럴네트워크 구조>


이 모델은 기존에 다른 기법으로 풀지 못하였던 복잡한 문제를 풀어낼 수 있었지만, 계층을 깊게 하면 계산이 복잡하여 연산이 불가능하다는  이유로 그간 관심을 가지고 있지 못했다가

캐나다의 CIFAR (Canadian Institute for Advanced Research) 연구소에서 2006년에 Hinton 교수가 ”A fast learning algorithm for deep belifef nets” 논문을 발표하게 되는데,  이 논문을 통해서 뉴럴네트워크에 입력하는 초기값을 제대로 입력하면 여러 계층의 레이어에서도 연산이 가능하다는 것을 증명하였고,  2007년 Yosua Bengio 라는 분이 ”Greedy Layer-Wise training of deep network” 라는 논문에서 깊게 신경망을 구축하면 굉장히 복잡한 문제를 풀 수 있다는 것을 증명해냈다.


이때 부터 뉴럴네트워크가 다시 주목을 받기 시작했는데,  이때 뉴럴 네트워크라는 모델을 사람들에게 부정적인 인식이 있었기 때문에, 다시 이 뉴럴 네트워크를 딥러닝 (Deep learning)이라는 이름으로 다시 브랜딩을 하였다.

그 이후에 IMAGENET 챌린지라는 머신러닝에 대한 일종의 컨테스트가 있는데, 이 대회는 이미지를 입력하고 머신 러닝을 통해서 컴퓨터가 이미지의 물체등을 인식할 수 있게 하는 대회로, 머신 러닝 알고리즘의 정확도를 측정하는 대회이다. 이 대회에서 2012년   Hinton 교수님 랩에 있던 Alex 라는 박사 과정의 학생이 딥러닝 기반의 머신 러닝 알고리즘으로 혁신 적인 결과를 내었고 지금은 이 딥러닝이 머신 러닝의 큰 주류중의 하나로 자리잡게 되었다.


<이미지넷에서 사용되는 이미지>



저작자 표시 비영리
신고