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


Archive»


 
 

얼굴 인식 모델을 만들어보자 #5 학습된 모델을 Export 하기



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


앞의 글에서 CloudML을 이용하여 학습하는 부분까지 끝냈다. 그렇다면 학습된 모델을 이용하여 실제로 예측은 어떻게 할것인가? 여기에는 두가지 선택지가 있다.


첫번째는, 체크포인트로 저장된 파일을 이용하는 방식인데, 체크포인트에는 저장된 데이타는 텐서플로우 모델 그래프는 없고, 모델에서 사용된 변수 (Weight,bias etc) 만 저장하기 때문에, 이 데이타를 로딩하려면 텐서플로우 코드로 그래프를 그려준 다음에, 로딩을 해야한다. (상세 설명 http://bcho.tistory.com/1179 )


두번째는, 체크포인트처럼 변수만 저장하는 것이 아니라, 그래프를 함께 저장하는 방식으로 모델을 Protocol Buffer (http://bcho.tistory.com/1182) 타입으로 저장하는 방식이다. 이렇게 Protocol buffer (이하 pb)로 저장된 파일은 Prediction에 최적화된 엔진인 Tensorflow Serving (https://www.tensorflow.org/deploy/tfserve) 에 로딩하여 사용이 가능하다. 그런데, Tensorflow Serving의 경우 일일이 빌드를 해야 하는데, bazel 빌드 툴 (make,gradle과 같은 빌드툴. http://bcho.tistory.com/1160 )을 이용해서 빌드 및 배포를 해야 하는데 이 과정이 쉽지 않고, 또한 Tensorflow Serving에 배포된 모델을 호출하기 위해서는 Google protocol buffer (grpc)를 사용해야 한다.

이 과정에 많은 노력(삽질?)이 필요하고, 운영환경에 올리기 위해서는 모델 pb 파일에 대한 배포 프로세스 그리고 여러개의 Tensorflow Serving Cluster 설치 및 운영등의 이슈가 발생한다.


그래서 이를 플랫폼화하여 서비스로 만들어놓은 것이 Google CloudML Prediction 서비스이다. CloudML Prediction 서비스는 단순하게, 학습된 pb 파일만 배포하게 되면, 운영에 대한 이슈없이 대용량 서비스가 가능하고 grpc를 사용하지 않더라도 SDK를 이용하여 손쉽게 json으로 요청을 보냄으로써 prediction에 대한 구현이 가능하다.

모델 Export 하기

http://bcho.tistory.com/1180 에서 CloudML을 이용하여 얼굴 인식 모델을 학습 시켰다. 여기서 사용된 코드를 수정하여 학습이 끝나면, 모델(그래프와 변수값)을 Export 하는 코드를 추가해야 한다.

Export를 할때 주의할 점은 학습에 사용된 그래프를 그대로 Export 하는 것이 아니라 새로 그래프를 그려서 Export를 해야 한다. Export할 그래프는 Prediction을 위한 그래프이기 때문에, 학습에 사용된 그래프는 Dropout이나 또는 validation등을 위한 로직이 들어가 있기 때문에 이런 부분을 다 제거 하고 Prediction을 위한 그래프로 재정의하여 Export 해야한다.


얼굴 인식 모델에서 학습된 모델을 Export 하는 과정은

  1. 학습을 진행하고 학습 진행중에 체크포인트를 저장한다.

  2. 학습이 종료되면 Export를 위한 그래프를 새로 그린다.

  3. 체크포인트 파일에서 변수 값을 읽어서 2에서 그린 그래프에 채워넣는다.


자 그러면 코드를 보자. (전체 코드는 https://github.com/bwcho75/facerecognition/blob/master/CloudML%20Version/face_recog_model/model_localfile_export.py 에 저장되어 있다.)

체크 포인트 저장하기

코드 401 라인을 보면 아래와 같이 saver 객체를 이용하여 현재 학습이 종료된 세션의 값을 넘겨서 체크포인트 값으로 저장한다. 이 때 체크포인트 파일은 os.path.join(model_dir, 'face_recog') 에 저장한다.

       print('Save model')

       model_dir = os.path.join( FLAGS.base_dir , 'model')

       if not os.path.exists(model_dir):

           os.makedirs(model_dir)

       saver.save(sess, os.path.join(model_dir, 'face_recog'))

       print('Save model done '+model_dir)


다음으로 408 라인에서, 모델을 Export하는 함수 export_model 함수를 호출한다. 이때 첫번째 인자로는 체크포인트 파일 경로를 넘긴다.


       export_dir = os.path.join( FLAGS.base_dir , 'export')

       if  os.path.exists(export_dir):

          rmdir(export_dir)

       export_model(os.path.join(model_dir, 'face_recog'), export_dir)

Export용 그래프 그리기

274 라인의 def export_model(checkpoint, model_dir) 함수를 보자. 이 함수는 checkpoint 디렉토리를 입력받아서 model_dir에 모델을 export 해주는 함수이다.


앞에서도 설명했듯이 Export 용 그래프는 새롭게 그려줘야 하는데, 276~279 라인까지가 새롭게 그래프를 그리는 부분이다.

 with tf.Session(graph=tf.Graph()) as sess:


   images = tf.placeholder(tf.string)

   prediction = build_inference(images)


이미지를 입력할 input용 placeholer를 정의한다. 이때 중요한점이 우리가 학습에서는 float형 placeholder를 사용했는데, 여기서는 입력을 string으로 바꿨다. 이유는 모델을 학습한 후에 실제 운영 환경에 올렸을 때, 클라이언트 (웹이나 모바일)에서 이미지를 입력 받아서 학습된 모델을 호출할때 float 형 행렬로 넘기기에는 불편하고 데이타의 크기도 커진다. (행렬데이타를 [1,2,3,4…] 와 같은 문자열로 넘겨야 하기 때문에 ) 그래서 호출할때 데이타 전달을 쉽게 할 수 있도록 이미지를 문자열 바이너리로 입력 받도록 수정하였다.

다음 build_inference(images) 함수가 실제로 Export 용 그래프를 새로 그리는 부분인데

261 라인에 아래와 같이 정의 되어 있다.


def build_inference(image_bytes):

   # graph for prediction in CloudML

   #image_bytes = tf.placeholder(tf.string)

   rgb_image = tf.image.decode_jpeg(image_bytes[0],channels = FLAGS.image_color)

   rgb_image  = tf.image.convert_image_dtype(rgb_image, dtype=tf.float32)

   image_batch = tf.expand_dims(rgb_image, 0)

   #rgb_image_value = rgb_image.eval()

   #rgb_images = []

   #rgb_images.append(rgb_image_value)

   result = tf.nn.softmax(build_model(image_batch,keep_prob=1.0))

   

   return result


문자열로 입력받은 이미지 데이타는 배열형이기 때문에, [0] 로 첫번째 이미지를 골라내고 (이미지를 입력할때도 하나만 입력한다.) tf.image_decode_jpeg로 디코딩을 한후에, 타입을 tf_float32 형태의 행렬로 바꿔준다. 원래 우리가 사용했던 학습용 모델의 모양이 batch 형이기 때문에, tf.expand_dim으로 차원을 맞춰준다.

그 다음에 build_model() 함수를 이용하여 image_batch를 입력값으로 넣고 그래프를 그린다. dropout을 하지 않기 때문에, keep_prob=1.0 으로 한다. (build_model은 얼굴 인식 모델을 위해서 CNN 네트워크를 정의한 코드이다.)

build_model에 결과를 마지막에 softmax함수를 정의하여 result값을 리턴하도록 한다.

시그네쳐 정의하기

Tensorflow serving (CloudML inference)를 사용하기 위해서는 Tensorflow serving에 모델의 Input 과 Output 변수를 알려줘야 한다. 이를 시그네쳐라고 하는데,  SignatureDefs 를 이용하여 정의한다. (참고 https://github.com/tensorflow/serving/blob/master/tensorflow_serving/g3doc/signature_defs.md)


SignatureDefs는 용도에 따라서 Classification SignatureDef와 Predict SignatureDef 두가지로 나뉘어 진다. Cassification SignatureDef는 분류 모델에 최적화되어 정의된 시그네쳐로 출력값들이 클래스 종류나 클래스별 정확도등을 옵션으로 가질 수 있고, Predict SignatureDef는 분류 모델뿐 아니라 모든 모델에 범용적으로사용될 수 있는 형태로 입력과 출력값을 정의할 수 있다.


이 예제에서는 Predict Signature Def을 사용하였다.

   inputs = {'image': images}

   input_signatures = {}

   for key, val in inputs.iteritems():

     predict_input_tensor = meta_graph_pb2.TensorInfo()

     predict_input_tensor.name = val.name

     predict_input_tensor.dtype = val.dtype.as_datatype_enum

     input_signatures[key] = predict_input_tensor


코드에서는 images placeholder를 입력값으로 하여 “image”라는 이름의 입력 시그네쳐를 생성하였고, 마찬가지로 다음과 같이 출력 값은 prediction 변수를 “prediction”이라는 이름의 시그네쳐로 사용하여 정의하였다.

   outputs = {'prediction': prediction}

   output_signatures = {}

   for key, val in outputs.iteritems():

     predict_output_tensor = meta_graph_pb2.TensorInfo()

     predict_output_tensor.name = val.name

     predict_output_tensor.dtype = val.dtype.as_datatype_enum

     output_signatures[key] = predict_output_tensor


다음, 이렇게 생성한 시그네쳐 변수들을 ‘image’,’prediction’ 을 add_to_colleciton을 이용하여 텐서플로우 그래프에 추가하였다.


   inputs_name, outputs_name = {}, {}

   for key, val in inputs.iteritems():

     inputs_name[key] = val.name

   for key, val in outputs.iteritems():

     outputs_name[key] = val.name

   tf.add_to_collection('inputs', json.dumps(inputs_name))

   tf.add_to_collection('outputs', json.dumps(outputs_name))

체크포인트 데이타 로딩해서 Export 용 그래프에 채워넣기

Export할 그래프가 완성되었으면 여기에 학습된 값을 채워넣으면 된다.

학습된 값은 학습후에, 체크 포인트 파일에 저장되어있기 때문에, 이 체크 포인트 파일을 다시 로딩하자


init_op = tf.global_variables_initializer()

   sess.run(init_op)


   # Restore the latest checkpoint and save the model

   saver = tf.train.Saver()

   saver.restore(sess, checkpoint)


모델 저장

다음 최종적으로 모델을 저장하면 된다.

   predict_signature_def = signature_def_utils.build_signature_def(

       input_signatures, output_signatures,

       signature_constants.PREDICT_METHOD_NAME)


앞서 정의한 input,output 시그네쳐를 가지고, Predict Signature Def를 정의한다.

다음 SavedModelBuilder를 만들어서 디렉토리를 지정하고, add_meta_graph_and_variables 메서드를 이용하여, 정의한 시그네쳐를 넘겨주고, assets_collection을 통해서 그래프 값을 넘긴후, 최종적으로 save() 메서드를 이용하여 그 값을 저장한다.

   build = builder.SavedModelBuilder(model_dir)

   build.add_meta_graph_and_variables(

       sess, [tag_constants.SERVING],

       signature_def_map={

           signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:

               predict_signature_def

       },

       assets_collection=tf.get_collection(tf.GraphKeys.ASSET_FILEPATHS))

   build.save()


저장된 모델 확인

모델 저장이 완료되면 Export 디렉토리데 다음과 같이 파일들이 생성된다

  • saved_model.pb (파일) : 그래프를 저장하고 있는 모델 바이너리 파일이다.

  • variables (디렉토리) : 디렉토리로 변수 값을 저장하고 있는 파일들이 저장되어 있다.

정리

텐서플로우 자료나 튜토리얼을 보면 대부분 모델을 만들고 학습 하는 정도만 있고 Prediction(또는 Inference)는 대부분 체크포인트에 저장된 값을 그래프로 복원하는 방식을 사용하고 있지 Tensorflow Serving등을 사용하는 자료가 별로 없다. 그래서 정리를 해봤는데, 생각보다 어렵기는 하지만 코드를 찬찬히 살펴보니 Signature와 Graph Collection 을 개념을 이해하고 나면 여러 예제코드를 보면서 진행하면 어느정도 할 수 있지 않을까 싶다. 개념 자체가 어려운것 보다는 이를 지원하는 예제나 문서가 적기 때문이라고 보는데,이것도 텐서플로우가 활성화되는 중이니 많은 예제가 나오지 않을까 기대해 본다.


다음 글에서는 이번에 Export 한 모델 (*.pb)을 이용하여 구글 CloudML을 통해서 예측 (Inference) 하는 방법에 대해서 알아보겠다.


참고 자료


저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

 

얼굴 인식 모델을 만들어보자

#3 - 학습된 모델로 예측하기


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


앞글에 걸쳐서 얼굴 인식을 위한 데이타를 수집 및 정재하고, 이를 기반으로 얼굴 인식 모델을 학습 시켰다.

 

 

이번글에서는 학습이 된 데이타를 가지고, 사진을 넣어서 실제로 인식하는 코드를 만들어보자

전체 소스 코드는 https://github.com/bwcho75/facerecognition/blob/master/2.%2BFace%2BRecognition%2BPrediction%2BTest.ipynb 와 같다.

모델 로딩 하기

 

모델 학습에 사용한 CNN 모델을 똑같이 정의한다. conv1(),conv2(),conv3(),conv4(),fc1(),fc2(), build_model() 등 학습에 사용된 CNN 네트워크를 똑같이 정의하면 된다.

 

다음으로 이 모델에 학습된 값들을 채워 넣어야 한다.

# build graph

images = tf.placeholder(tf.float32,[None,FLAGS.image_size,FLAGS.image_size,FLAGS.image_color])

keep_prob = tf.placeholder(tf.float32) # dropout ratio

 

예측에 사용할 image 를 넘길 인자를  images라는 플레이스홀더로 정의하고, dropout 비율을 정하는 keep_prob도 플레이스 홀더로 정의한다.

 

prediction = tf.nn.softmax(build_model(images,keep_prob))

 

그래프를 만드는데, build_model에 의해서 나온 예측 결과에 softmax 함수를 적용한다. 학습시에는 softmax 함수의 비용이 크기 때문에 적용하지 않았지만, 예측에서는 결과를 쉽게 알아보기 위해서  softmax 함수를 적용한다. Softmax 함수는 카테고리 별로 확률을 보여줄때 전체 값을 1.0으로 해서 보여주는것인데, 만약에 Jolie,Sulyun,Victora 3개의 카테코리가 있을때 각각의 확률이 70%,20%,10%이면 Softmax를 적용한 결과는 [0.7,0.2,0.1] 식으로 출력해준다.

 

sess = tf.InteractiveSession()

sess.run(tf.global_variables_initializer())

 

다음 텐서플로우 세션을 초기화 하고,

 

saver = tf.train.Saver()

saver.restore(sess, 'face_recog')

 

마지막으로 Saver의 restore 함수를 이용하여 ‘face_recog’라는 이름으로 저장된 학습 결과를 리스토어 한다. (앞의 예제에서, 학습이 완료된 모델을 ‘face_recog’라는 이름으로 저장하였다.)

 

예측하기

로딩 된 모델을 가지고 예측을 하는 방법은 다음과 같다. 이미지 파일을 읽은 후에, 구글 클라우드 VISION API를 이용하여, 얼굴의 위치를 추출한후, 얼굴 이미지만 크롭핑을 한후에, 크롭된 이미지를 텐서플로우 데이타형으로 바꾼후에, 앞서 로딩한 모델에 입력하여 예측된 결과를 받게 된다.

 

얼굴 영역 추출하기

먼저 vision API로 얼굴 영역을 추출하는 부분이다. 앞의 이미지 전처리에 사용된 부분과 다르지 않다.

 

import google.auth

import io

import os

from oauth2client.client import GoogleCredentials

from google.cloud import vision

from PIL import Image

from PIL import ImageDraw

 

FLAGS.image_size = 96

 

# set service account file into OS environment value

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

 

위와 같이 구글 클라우드 Vision API를 사용하기 위해서 억세스 토큰을 Service Account 파일로 다운 받아서 위와 같이 GOOGLE_APPLICATION_CREDENTIALS 환경 변수에 세팅 하였다.

 

visionClient = vision.Client()

print ('[INFO] processing %s'%(imagefile))

 

#detect face

image = visionClient.image(filename=imagefile)

faces = image.detect_faces()

face = faces[0]

 

다음 vision API 클라이언트를 생성한 후에, detect_faces() 를 이용하여 얼굴 정보를 추출해낸다.

 

print 'number of faces ',len(faces)

 

#get face location in the photo

left = face.fd_bounds.vertices[0].x_coordinate

top = face.fd_bounds.vertices[0].y_coordinate

right = face.fd_bounds.vertices[2].x_coordinate

bottom = face.fd_bounds.vertices[2].y_coordinate

rect = [left,top,right,bottom]

 

추출된 얼굴 정보에서 첫번째 얼굴의 위치 (상하좌우) 좌표를 리턴 받는다.

얼굴 영역을 크롭하기

앞에서 입력 받은 상하좌우 좌표를 이용하여, 이미지 파일을 열고,  크롭한다.

 

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

image = Image.open(fd)

 

import matplotlib.pyplot as plt

# display original image

print "Original image"

plt.imshow(image)

plt.show()

 

 

# draw green box for face in the original image

print "Detect face boundary box "

draw = ImageDraw.Draw(image)

draw.rectangle(rect,fill=None,outline="green")

 

plt.imshow(image)

plt.show()

 

crop = image.crop(rect)

im = crop.resize((FLAGS.image_size,FLAGS.image_size),Image.ANTIALIAS)

plt.show()

im.save('cropped'+imagefile)

 

크롭된 이미지를 텐서플로우에서 읽는다.

 

print "Cropped image"

tfimage = tf.image.decode_jpeg(tf.read_file('cropped'+imagefile),channels=3)

tfimage_value = tfimage.eval()

 

크롭된 파일을 decode_jpeg() 메서드로 읽은 후에, 값을 tfimage.eval()로 읽어드린다.

 

tfimages = []

tfimages.append(tfimage_value)

 

앞에서 정의된 모델이 한개의 이미지를 인식하는게 아니라 여러개의 이미지 파일을 동시에 읽도록 되어 있기 때문에, tfimages라는 리스트를 만든 후, 인식할 이미지를 붙여서 전달한다.

 

plt.imshow(tfimage_value)

plt.show()

fd.close()

 

p_val = sess.run(prediction,feed_dict={images:tfimages,keep_prob:1.0})

name_labels = ['Jessica Alba','Angelina Jolie','Nicole Kidman','Sulhyun','Victoria Beckam']

i = 0

for p in p_val[0]:

   print('%s %f'% (name_labels[i],float(p)) )

   i = i + 1

 

tfimages 에 이미지를 넣어서 모델에 넣고 prediction 값을 리턴 받는다. dropout은 사용하지 않기 때문에, keep_prob을 1.0으로 한다.

나온 결과를 가지고 Jessica, Jolie,Nicole Kidman, Sulhyun, Victoria Beckam 일 확률을 각각 출력한다.


전체 코드는 https://github.com/bwcho75/facerecognition/blob/master/2.%2BFace%2BRecognition%2BPrediction%2BTest.ipynb


다음은 설현 사진을 가지고 예측을 한 결과 이다.


 

이 코드는 학습된 모델을 기반으로 얼굴을 인식이 가능하기는 하지만 실제 운영 환경에 적용하기에는 부족하다. 파이썬 모델 코드를 그대로 옮겼기 때문에, 성능도 상대적으로 떨어지고, 실제 운영에서는 모델을 업그레이드 배포 할 수 있고, 여러 서버를 이용하여 스케일링도 지원해야 한다.

그래서 텐서플로우에서는 Tensorflow Serving 이라는 예측 서비스 엔진을 제공하고 구글 클라우에서는 Tensorflow Serving의 매니지드 서비스인, CloudML 서비스를 제공한다.

 

앞의 두 글이 로컬 환경에서 학습과 예측을 진행했는데, 다음 글에서는 상용 서비스에 올릴 수 있는 수준으로 학습과 예측을 할 수 있는 방법에 대해서 알아보도록 하겠다.

 

저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

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


앞서 MNIST 데이타를 이용한 필기체 숫자를 인식하는 모델을 컨볼루셔널 네트워크 (CNN)을 이용하여 만들었다. 이번에는 이 모델을 이용해서 필기체 숫자 이미지를 인식하는 코드를 만들어 보자


조금 더 테스트를 쉽게 하기 위해서, 파이썬 주피터 노트북내에서 HTML 을 이용하여 마우스로 숫자를 그릴 수 있도록 하고, 그려진 이미지를 어떤 숫자인지 인식하도록 만들어 보겠다.



모델 로딩

먼저 앞의 예제에서 학습을한 모델을 로딩해보도록 하자.

이 코드는 주피터 노트북에서 작성할때, 모델을 학습 시키는 코드 (http://bcho.tistory.com/1156) 와 별도의 새노트북에서 구현을 하도록 한다.


코드

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()


num_filters1 = 32


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

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


#  layer 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')


num_filters2 = 64


# layer 2

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')


# 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)


# prepare session

sess = tf.InteractiveSession()

sess.run(tf.global_variables_initializer())

saver = tf.train.Saver()

saver.restore(sess, '/Users/terrycho/anaconda/work/cnn_session')


print 'reload has been done'


그래프 구현

코드를 살펴보면, #prepare session 부분 전까지는 이전 코드에서의 그래프를 정의하는 부분과 동일하다. 이 코드는 우리가 만든 컨볼루셔널 네트워크를 복원하는 부분이다.


변수 데이타 로딩

그래프의 복원이 끝나면, 저장한 세션의 값을 다시 로딩해서 학습된 W와 b값들을 다시 로딩한다.


# prepare session

sess = tf.InteractiveSession()

sess.run(tf.global_variables_initializer())

saver = tf.train.Saver()

saver.restore(sess, '/Users/terrycho/anaconda/work/cnn_session')


이때 saver.restore 부분에서 앞의 예제에서 저장한 세션의 이름을 지정해준다.

HTML을 이용한 숫자 입력

그래프와 모델 복원이 끝났으면 이 모델을 이용하여, 숫자를 인식해본다.

테스트하기 편리하게 HTML로 마우스로 숫자를 그릴 수 있는 화면을 만들어보겠다.

주피터 노트북에서 새로운 Cell에 아래와 같은 내용을 입력한다.


코드

input_form = """

<table>

<td style="border-style: none;">

<div style="border: solid 2px #666; width: 143px; height: 144px;">

<canvas width="140" height="140"></canvas>

</div></td>

<td style="border-style: none;">

<button onclick="clear_value()">Clear</button>

</td>

</table>

"""


javascript = """

<script type="text/Javascript">

   var pixels = [];

   for (var i = 0; i < 28*28; i++) pixels[i] = 0

   var click = 0;


   var canvas = document.querySelector("canvas");

   canvas.addEventListener("mousemove", function(e){

       if (e.buttons == 1) {

           click = 1;

           canvas.getContext("2d").fillStyle = "rgb(0,0,0)";

           canvas.getContext("2d").fillRect(e.offsetX, e.offsetY, 8, 8);

           x = Math.floor(e.offsetY * 0.2)

           y = Math.floor(e.offsetX * 0.2) + 1

           for (var dy = 0; dy < 2; dy++){

               for (var dx = 0; dx < 2; dx++){

                   if ((x + dx < 28) && (y + dy < 28)){

                       pixels[(y+dy)+(x+dx)*28] = 1

                   }

               }

           }

       } else {

           if (click == 1) set_value()

           click = 0;

       }

   });

   

   function set_value(){

       var result = ""

       for (var i = 0; i < 28*28; i++) result += pixels[i] + ","

       var kernel = IPython.notebook.kernel;

       kernel.execute("image = [" + result + "]");

   }

   

   function clear_value(){

       canvas.getContext("2d").fillStyle = "rgb(255,255,255)";

       canvas.getContext("2d").fillRect(0, 0, 140, 140);

       for (var i = 0; i < 28*28; i++) pixels[i] = 0

   }

</script>

"""


다음 새로운 셀에서, 다음 코드를 입력하여, 앞서 코딩한 HTML 파일을 실행할 수 있도록 한다.


from IPython.display import HTML

HTML(input_form + javascript)


이제 앞에서 만든 두 셀을 실행시켜 보면 다음과 같이 HTML 기반으로 마우스를 이용하여 숫자를 입력할 수 있는 박스가 나오는것을 확인할 수 있다.



입력값 판정

앞의 HTML에서 그린 이미지는 앞의 코드의 set_value라는 함수에 의해서, image 라는 변수로 784 크기의 벡터에 저장된다. 이 값을 이용하여, 이 그림이 어떤 숫자인지를 앞서 만든 모델을 이용해서 예측을 해본다.


코드


p_val = sess.run(p, feed_dict={x:[image], keep_prob:1.0})


fig = plt.figure(figsize=(4,2))

pred = p_val[0]

subplot = fig.add_subplot(1,1,1)

subplot.set_xticks(range(10))

subplot.set_xlim(-0.5,9.5)

subplot.set_ylim(0,1)

subplot.bar(range(10), pred, align='center')

plt.show()

예측

예측을 하는 방법은 쉽다. 이미지 데이타가 image 라는 변수에 들어가 있기 때문에, 어떤 숫자인지에 대한 확률을 나타내는 p 의 값을 구하면 된다.


p_val = sess.run(p, feed_dict={x:[image], keep_prob:1.0})


를 이용하여 x에 image를 넣고, 그리고 dropout 비율을 0%로 하기 위해서 keep_prob를 1.0 (100%)로 한다. (예측이기 때문에 당연히 dropout은 필요하지 않다.)

이렇게 하면 이 이미지가 어떤 숫자인지에 대한 확률이 p에 저장된다.

그래프로 표현

그러면 이 p의 값을 찍어 보자


fig = plt.figure(figsize=(4,2))

pred = p_val[0]

subplot = fig.add_subplot(1,1,1)

subplot.set_xticks(range(10))

subplot.set_xlim(-0.5,9.5)

subplot.set_ylim(0,1)

subplot.bar(range(10), pred, align='center')

plt.show()


그래프를 이용하여 0~9 까지의 숫자 (가로축)일 확률을 0.0~1.0 까지 (세로축)으로 출력하게 된다.

다음은 위에서 입력한 숫자 “4”를 인식한 결과이다.



(보너스) 첫번째 컨볼루셔널 계층 결과 출력

컨볼루셔널 네트워크를 학습시키다 보면 종종 컨볼루셔널 계층을 통과하여 추출된 특징 이미지들이 어떤 모양을 가지고 있는지를 확인하고 싶을때가 있다. 그래서 각 필터를 통과한 값을 이미지로 출력하여 확인하고는 하는데, 여기서는 이렇게 각 필터를 통과하여 인식된 특징이 어떤 모양인지를 출력하는 방법을 소개한다.


아래는 우리가 만든 네트워크 중에서 첫번째 컨볼루셔널 필터를 통과한 결과 h_conv1과, 그리고 이 결과에 bias 값을 더하고 활성화 함수인 Relu를 적용한 결과를 출력하는 예제이다.


코드


conv1_vals, cutoff1_vals = sess.run(

   [h_conv1, h_conv1_cutoff], feed_dict={x:[image], keep_prob:1.0})


fig = plt.figure(figsize=(16,4))


for f in range(num_filters1):

   subplot = fig.add_subplot(4, 16, f+1)

   subplot.set_xticks([])

   subplot.set_yticks([])

   subplot.imshow(conv1_vals[0,:,:,f],

                  cmap=plt.cm.gray_r, interpolation='nearest')

plt.show()


x에 image를 입력하고, dropout을 없이 모든 네트워크를 통과하도록 keep_prob:1.0으로 주고, 첫번째 컨볼루셔널 필터를 통과한 값 h_conv1 과, 이 값에 bias와 Relu를 적용한 값 h_conv1_cutoff를 계산하였다.

conv1_vals, cutoff1_vals = sess.run(

   [h_conv1, h_conv1_cutoff], feed_dict={x:[image], keep_prob:1.0})


첫번째 필터는 총 32개로 구성되어 있기 때문에, 32개의 결과값을 imshow 함수를 이용하여 흑백으로 출력하였다.




다음은 bias와 Relu를 통과한 값인 h_conv_cutoff를 출력하는 예제이다. 위의 코드와 동일하며 subplot.imgshow에서 전달해주는 인자만 conv1_vals → cutoff1_vals로 변경되었다.


코드


fig = plt.figure(figsize=(16,4))


for f in range(num_filters1):

   subplot = fig.add_subplot(4, 16, f+1)

   subplot.set_xticks([])

   subplot.set_yticks([])

   subplot.imshow(cutoff1_vals[0,:,:,f],

                  cmap=plt.cm.gray_r, interpolation='nearest')

   

plt.show()


출력 결과는 다음과 같다



이제까지 컨볼루셔널 네트워크를 이용한 이미지 인식을 텐서플로우로 구현하는 방법을 MNIST(필기체 숫자 데이타)를 이용하여 구현하였다.


실제로 이미지를 인식하려면 전체적인 흐름은 같지만, 이미지를 전/후처리 해내야 하고 또한 한대의 머신이 아닌 여러대의 머신과 GPU와 같은 하드웨어 장비를 사용한다. 다음 글에서는 MNIST가 아니라 실제 칼라 이미지를 인식하는 방법에 대해서 데이타 전처리에서 부터 서비스까지 전체 과정에 대해서 설명하도록 하겠다.


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


저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

수포자를 위한 딥러닝


#2 - 선형회귀분석을 통한 머신러닝의 기본 개념 이해


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


Linear Regression을 통한 머신 러닝의 개념 이해

거리에 따른 택시 요금 문제

머신러닝이란 무엇일까? 개념 이해를 돕기 위해서 선형 회귀 (Linear Regression)이라는 머신러닝 모델을 보자. 먼저 선형 회귀 (Linear regression)이 무엇인지 부터 이해를 해야 하는데, 쉽게 설명하자면 결과값 (output value)이 있고 그 결과값을 결정할 것이라고 추정되는 입력값 (input value)과 결과 값의 연관관계를 찾는 것이고 이를 선형 관계를 통해 찾는 방법이 선형 회귀 (Linear regression)이다.

예를 들어서 설명해보자, 택시 요금을 예로 들어보자,택시 요금은 물론 막히냐 마냐에 따라 편차가 있지만, 대부분 거리에 비례해서 요금이 부과된다. 그래서 결과값 (요금)과 입력값 (거리)의 관계를 찾아야 한다.


거리별 요금을 그래프로 나타내보면 대략 다음과 같은 분포를 띄게 된다

원본 데이타의 거리를 x_data 그리고, 그 거리에서 측정된 택시 요금을 y_origin 이라고 하자.



가설 (Hypothesis) 정의

거리와 요금이 서로 비례하기 때문에, 거리(x_data)와 요금(y_data)간의 상관 관계는 다음과 같이 일차 방정식과 형태의 그래프를 그리게 된다고 가정하자. W (Weight)는 그래프의 각도, b는 bias를 뜻한다

y_data = Wx_data + b

이 일차 방정식 형태로 대충 1차원 그래프를 그려보자 같은 형태로 아래와 같이 그래프를 그려봤다.


그래프를 그려보니 그래프의 각이 안맞는것 같다. 그래프의 각도와 높이를 보정해보자


그래프를 보정했지만 또 안 맞는 것 같다. 그렇다면 최적의 그래프의 각도 W와, 높이 B는 어떻게 찾아야 하는 것일까?

코스트(비용) 함수

우리가 구하고자 하는 그래프는 실제 값에서 그래프의 값까지 차이가 가장 작은 값을 구하고자 하는 것이다. 아래 그림을 보자, 아래와 같이 y_data=Wx_data +b와 같은 그래프를 그렸다고 하자.


원래 값에서 우리가 예측한 값의 차이는 

(원래값과 계산된 값의 차이) = 측정값 - 그래프의 값

인데, 차이를 d라고 하자. 그리고 그래프에 의해서 계산된 값은 y_data라고 하면 택시 거리 x_data 에서 원래 측정된 값을 y_orgin라고 해서 수식으로 나타내면,

d = y_data - y_origin

이 된다. 이때 측정값은 여러개가 있기 때문에 n이라고 하면  n번째 측정한 택시비와 산식에 의해서 예측된 값의 차이는 dn이 된다.


dn = y_data_n - y_origin_n


즉 우리가 구하고자 하는 값은 dn의 합이 최소가 되는 W와 b의 값을 구하고자 하는 것이다.

다르게 설명하면 실제 측정한값과, 예측한 값의 차이가 최소가 되는 W와 b를 구하고자 하는 것이다.

dn은 위의 그래프에서 처럼 그래프 위에도 있을 수 있지만 (이경우 dn은 양수), 그래프 아래에도 있을 수 있기 때문에, (이경우 dn은 음수). 합을 구하면, 예측 선에서의 실측값 까지의 거리의 합이 되지 않기 때문에, dn에 대한 절대값을 사용한다고 하자.

그리고 n이 측정에 따라 여러개가 될 수 있기 때문에, 평균을 사용하자.


( ABS(d1)+ABS(d2)+ABS(d3)+.....+ABS(dn)) ) / n


즉 우리가 구하고자 하는 W와 b는 위의 함수의 값이 최소가 되는 값을 구하면 된다.

이렇게 측정된 값에서 연산된 값간의 차이를 연산하는 함수를 비용 함수 또는 영어로 코스트 함수 (Cost function이라고 한다.


사람이 일일이 계산할 수 없이니 컴퓨터를 이용해서 W=0.1,0.2,0.3,.... b=0.1,0.2,0.3,..... 식으로 넣어보고 이 코스트 함수가 가장 최소화되는 W와 b의 값을 찾을 수 있다.

옵티마이져 (Optimizer)

코스트 함수의 최소값을 찾는 알고리즘을 옵티마이져(Optimizer)라고 하는데, 상황에 따라 여러 종류의 옵티마이져를 사용할 수 있다. 여기서는 경사 하강법 (Gradient Descent) 라는 옵티마이져에 대해서 소개하도록 하겠다.


경사 하강법

그러면 W와 b를 구할때 W와 b를 어떤식으로 증가 또는 감소 시켜서 코스트 함수의 최소값을 가장 효율적으로 찾아낼 수 있을까? 위에서 언급한것 처럼 W를 0.0에서 부터 ). 0.1씩 증가시켜나가고 b도 같이 0.0에서 부터 1씩 증가 시켜 나갈까? 무한한 컴퓨팅 자원을 이용하면 되기는 하겠지만, 이렇게 무식하게 계산하지는 않는다.

코스트 함수를 최적화 시킬 수 있는 여러가지 방법이 있지만, Linear regression의 경우에는 경사 하강법 (그레이언트 디센트 : Gradient descent)라는 방식을 사용한다. 경사하강법에 대해서는 자세하게 알필요는 없고 ”대략 이런 개념을 사용하는 구나” 하는 정도만 알면 된다.


경사 하강법을 사용하기 위해서는 위의 코스트 함수를,측정값과 예측값의 절대값의 평균이 아니라 평균 제곱 오차라는 함수를 사용한다.

이 함수는 형식으로 정의되는데, 평균 제곱 오차 함수 (Mean square error function)이라고 한다.


Cost =  Sum( (y_data_n - y_origin_n) ^ 2) / n


풀어서 설명하면, n 번째의 원래데이타(y_origin_n)와 예측 데이타(y_data_n)의 차이를 제곱(^2)해서, 이 값을 n으로 나눈 평균 값이다.

즉 이 Cost가 최소가 되는 W와 b값을 구하면 된다.

편의상 W하나만을 가지고 설명해보자. 위의 그래프를 W와 b에 대한 상관 그래프로 그려보면 다음과 같은 함수 형태가 된다.


이 그래프에서 W에 대한 적정값에 대한 예측을 시작하는 점을 위의 그림에서 파란 점이라고 하면, 경사 하강법은 현재 W의 위치에 대해서, 경사가 아래로 되어 있는 부분으로 점을 움직이는 방법이다. 어느 방향으로 W를 움직이면 Cost 값이 작아지는지는 현재 W위치에서 비용 함수를 미분하면 된다. (고등학교 수학이 기억이 나지 않을 수 있겠지만 미분의 개념은 그래프에서 그 점에 대한 기울기를 구하는 것이다. )


이렇게, 경사를 따라서 아래로 내려가다 보면 Cost 함수가 최소화가 되는 W 값을 찾을 수 있다. 이렇게 경사를 따라서 하강 (내려가면서) 최소값을 찾는다고 하여 경사 하강법이라고 한다.  


학습

코스트 함수가 정의 되었으면 실제 데이타 x_data_n과 y_data_n을 넣어서 경사하강법에 의해서 코스트 함수가 최소가 되는 W와 b를 구한다. 이 작업은 W값을 변화시키면서 반복적으로 x_data_n로 계산을 하여, 실제 측정 데이타와 가설에 의해서 예측된 결과값에 대한 차이를 찾아내고 최적의 W와 b값을 찾아낸다.

예측

학습 과정에 의해서 최적의 W와 b를 찾았으면 이제, 이 값들을 이용해서 예측 해보자

학습에 의해서 찾아낸 W가 1600, b가 2000이라고 하면, 앞의 가설에서 정의한 함수는 Wx*b였기 때문에, 예측 함수는 


y = Wx +b

거리에 따른 택시비 = W*(거리) + b

거리에 따른 택시비 = 1600 * (거리) + 2000

이 되고, 이를 학습된 모델 이라고 한다.


이제 예측을 수행해보자, 거리가 10km일 때 택시비는 얼마일까? 공식에 따라

택시비 = 1600 * 10km + 2000

으로, 18000원이 된다.

머신 러닝의 순서

지금까지 택시 거리와 택시비에 대한 문제를 가지고 머신 러닝에 대한 기본 원리를 살펴보았다.

이를 요약해서 머신 러닝이란 것이 어떤 개념을 가지고 있는지 다시 정리해보자.


기본 개념은 데이타를 기반으로해서 어떤 가설 (공식)을 만들어 낸 다음, 그 가설에서 나온 값이 실제 측정값과의 차이(코스트 함수)가 최소한의 값을 가지도록 변수에 대한 값을 컴퓨터를 이용해서 찾은 후, 이 찾아진 값을 가지고 학습된 모델을 정의해서 예측을 수행 하는 것이다.  


학습 단계

즉 모델을 만들기 위해서, 실제 데이타를 수집하고, 이 수집된 데이타에서 어떤 특징(피쳐)를 가지고 예측을 할것인지 피쳐들을 정의한 다음에, 이 피쳐를 기반으로 예측을 한 가설을 정의하고, 이 가설을 기반으로 학습을 시킨다.


예측 단계

학습이 끝나면 모델 (함수)가 주어지고, 예측은 단순하게, 모델에 값을 넣으면, 학습된 모델에 의해서 결과값을 리턴해준다.


지금까지 Linear regression 분석을 통한 머신러닝의 원리에 대해서 간략하게 알아보았다. 다음 다음장에서는 이 모델을 어떻게 프로그래밍 언어를 이용하여 학습을 시키고 운영을 하는지에 대해서 알아보도록 하겠다.



Thanx to 

이글은 딥러닝 전문가 김홍회 박사님(Ayden Kim - https://www.facebook.com/Ayden.Kim )이 검수해주셨습니다. 감사합니다.


저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

2015년 개발 트랜드-조대협

IT 이야기/트렌드 | 2015.01.12 10:09 | Posted by 조대협

2015년 개발 트랜드


조대협입니다. 2015년 개발 트렌드에 대해서 간략하게 정리해봅니다. 여러 기술들을 보고 정리한 개인적인 생각이며, 앞으로 저도 집중하려고 하는 분야이기도 합니다.


애자일 및 협업 문화

애자일 과 수평 조직 기반의 개발 문화에 대한 현상은 올해에도 쭈욱 지속될 듯 합니다. 기존의 워터폴이나 경직된 조직 문화와 방법론으로는 현대의 빠른 서비스 개발을 따라갈 수 가 없져

애자일은 워낙 오래전 부터 언급되고 나온거라서 별도로 언급을 하지 않겠습니다만, 왜 이 부분을 2015년의 트랜드로 잡았느냐 하면, 국내 기업의 경우 애자일 프로세스만을 도입하는 것이 아니라, 조직의 구조나 문화 자체를 애자일 사상으로 옮겨가는 경우가 많이 보이기 때문입니다. 기존에 무늬만 애자일이었다면, 작년부터 올해까지는 애자일 문화를 적용하기 위한 직급을 없애고 직책(ROLE) 기반으로 일하기 위한 변화, 수평적 조직 구조, 그리고 스크럼 마스터와 프러덕트 오너등이 조직내에 점점 더 확실하게 자리 잡아 가는 것 같습니다.


MSA 아키텍쳐

작년 중반 부터 떠오르기 시작하더니 국내에도 많은 시스템들이 MSA 사상으로 구현되가고 있는 것들이 보입니다. 이제 시작 단계들로 보이는데, MSA를 적용을 하고 있는 조직들은 MSA가 가지고 있는 전통적인 문제들, 분산 트렌젝션에 대한 처리, 여러개의 API를 모아서 새로운 기능을 만들어내는 aggregation 개념들에서 많은 고민들을 하고 있는 것이 보입니다.

그리고 MSA를 개발하기 위한 개발환경을 셋팅하는데 많은 고민들을 하는데, MSA의 특성상 서버 컴포넌트가 많이 분산이 되고 폴리그랏(다양한 언어로 개발)현상이 조금씩 가속화 됨에 따라서, 이러한 복잡한 개발환경을 어떻게 개발자에게 전달할것인가가 새로운 키워드가 될 듯 합니다.

이에 대한 대안으로는 Docker등이 빠르게 떠오르고 있고, 사내/사외 개발용 클라우드를 구축 하는 움직임이 생기지 않을까 조심스럽게 점쳐 봅니다.

MSA를 적용함에 있어서 앞단에 api gateway (또는 proxy)역할을 하는 것들이 중요해지고 있는데, 현재는 대부분 직접 개발해서 사용하는 경우가 많습니다. 그 만큼 거기에 사용할 제대로된 제품이나 오픈소스가 없다는 것인데, (오픈소스는 현재 WSO2 api gateway, 상용 CA Layer7, 클라우드 기반 서비스 apigee) 아마 금년에는 이러한 needs 때문에 다양한 오픈소스가 나오지 않을까 조심스럽게 기대해봅니다. 2013년까지만 해도 API gateway 오픈 소스 제품들은 손에 꼽을 정도였는데, 작년말에 한번 만들어 볼까 하는 마음으로 살펴보니, 벌써 몇개의 오픈소스들이 시작되고 있더군요

그리고 MSA에 맞춰서, SpringBoot도 같이 올라가면서, 자바 진영의 개발 주류가 되지 않을까 생각해봅니다.


데이타 스트리밍 프로세스

빅데이타 영역은 하둡을 중심으로 어느정도 정리가 되었으나, 근래에 들어서 실시간 데이타 분석에 대한 니즈(needs)가 올라오면서 실시간 스트리밍 처리가 작년말부터 다시 주목 받는것 같습니다. 람다 아키텍쳐나 데이타레이크 아키텍쳐가 다시 언급되는 것도 같은 선상이라고 보는데, 금년에는 Storm,Spark 중심의 실시간 데이타 처리 기술이 다시금 부각되지 않을까 합니다.


머신 러닝의 보편화

머신 러닝은 수학 통계적인 지식이 있어야 접근할 수 있는 분야였지만, 근래에는 Apache Mahout등의 프레임웍으로, 주로 사용되는 머신 러닝 알고리즘 들은 대부분 프레임웍화 되어 있어서 접근이 매우 쉽습니다. 약간의 지식만으로도 머신러닝을 사용할 수 있다는 겁니다.

여기에, Microsoft Azure ML 서비스와, IBM의 왓슨 서비스들은 클라우드 기반으로 머신 러닝 알고리즘을 서비스하는데, 사용이 매우 쉬워서, 일반 개발자들도 쉽게 머신 러닝 알고리즘을 구현 및 운영 환경에 적용이 가능합니다.

다른 빅데이타 분석들도 이런 흐름을 따라가지 않을까 싶은데 제가 보는 관점에서는 ML쪽이 선두가 되서 서비스화되는 현상이 작년말 부터 시작되고, 금년에는 초기 활성화 단계에 들지 않을까 합니다.


폴리 그랏

작년에도 그랬지만, 금년에도 여러가지 프로그래밍 언어를 사용하는 폴리그랏 현상은 더욱 더 가속되지 않을까 합니다. Node.js등은 계속해서 약진할거 같고, Ruby,Groovy와 같은 기존의 스크립트 언어 뿐만 아니라 Google Go, MS가 이번에 Linux까지 자사의 프로그래밍 언어를 지원하겠다고 한 이마당에, 금년에 프로그래밍 언어의 흐름은 지켜볼만 합니다.


기타

자바스크립트의 약진, 자바스크립트 기반의 Pure 웹 클라이언트, 클라우드의 적용 가속화

이런것들은 워낙 뻔한 이야기이니 별도로 언급하지 않겠다. 다만 마지막으로 지켜볼것은 중국 IT 기술의 약진으로, 금년에 중국발 오픈소스나 기술들이 인터넷으로 조금씩 공개되지 않을까 기대해봅니다.


 

저작자 표시 비영리
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
 

티스토리 툴바