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


Archive»


 
 


Classification & Clustering 모델 평가


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


클러스터링과 분류 모델에 대한 성능 평가 방법은 데이타에 라벨이 있는가 없는가에 따라서 방법이 나뉘어 진다. 사실 클러스터링은 라벨이 없는 데이타에 주로 사용을 하고, 라벨이 있는 경우에는 분류 모델을 사용한다. 클러스터링 모델에 대한 평가는 라벨이 없는 상태에서 클러스터의 응집도등을 평가하는데 대부분 그 정확도가 그리 높지 않기 때문에, 도메인 지식을 가지고 있는 전문가에 의한 휴리스틱한 방식의 평가 방식이 대부분이다.


분류 모델(Classification) 에 대한 모델 평가

라벨이 있는 경우에는 분류 모델에 대한 모델 평가 방법을 사용한다.

Confusion matrix

이진 분류 문제에서 암의 양성과 음성 데이타를 가지고 있는 데이타 가 있다고 하자


만약 모델의 정확도가 100%이면, 양성과 음성 데이타를 100% 잘 구분할것이다. 아래 그림과 같이, 양성으로 분 예측된 영역을 Positive prediction, 음성으로 분리된 영역을 Negative prediction 이라고 한다.


그런데 실제 세계에서는 정확도 100% 모델은 매우 드물고 실제로는 아래 그림과 같이 예측이 되는 경우가 많다.


양성과 음성 데이타가 각각 잘못되는 경우가 있다.

  • 양성인데, 양성으로 제대로 검출된것은 True Positive (TP)

  • 음성인데 음성으로 제대로 검출된것은 True Negative (TN)

  • 양성인데 음성으로 잘못 검출된것은 False Negative (FN)

  • 음성인데 양성으로 잘못 검출된것은 False Positive (FP)


라고 하고 그림으로 표현하면 다음과 같은 그림이 된다.


보통 이를 표로 표시하는데, 다음과 같이 표현이 된다.




P = TP + FN

N = FP + TN


그러면 이 지표를 가지고 무엇을 하느냐? 이 값을 기반으로 다음과 같은 지표들을 계산하여 모델 평가에 사용한다.

Accuracy

가장 대표적으로 사용되는 지표로 전체 데이타중에서, 제대로 분류된 데이타의 비율로


ACC = (TP + TN)  / (전체 데이타 수 = P + N)


모델이 얼마나 정확하게 분류를 하는지를 나타낸다.


Error Rate

Error Rate는 Accuracy 와 반대로, 전체 데이타 중에서 잘못 분류한 비율을 나타낸다


ERR = (FN+FP) / (전체 데이타수 = P+N)


Sensitivity (Recall or True positive Rate)

민감도라고도 하는데, Sensitive 또는  Recall이라고도 하는데, 원래 Positive 데이타 수에서 Positive로 분류된 수를 이야기 한다. 에를 들어 원본 데이타에 암 양성이 100개 있었는데, 모델에 있어서 90개가 분류되었으면, Sensitive Rate = 0.9 가된다.


SN = (TP) / P


모델이 얼마나 정확하게 Positive 값을 찾느냐를 나타낸다.

Recall (as opposed to precision) is not so much about answering questions correctly but more about answering all questions that have answer "true" with the answer "true". So if we simply always answer "true", we have 100% recall.


Precision

Precision (정밀성)은 Positive로 예측한 내용 중에, 실제 Positive의 비율을 뜻한다.


PREC = TP / (TP+FP)


Precision is about being precise. In common English, being precise means: if you give an answer, the answer will very likely be correct. So even if you answered only one question, and you answered this question correctly, you are 100% precise.


Specificity (True negative rate)

Specificity 값은 Negative 로 판단한것중에, 실제 Negative 값의 비율이다.


SP = TN / TN+FP


False Positive rate

원래는 Positive 값인데, 잘못해서 Negative로 판단한 비율로


FPR = FP / N


이 된다. 예를 들어 게임에서 어뷰징 사용자를 검출했을때 정확도도 중요하겠지만, FPR 값이 높으면, 정상 사용자를 비정상 사용자로 검출하는 경우가 많다는 의미가 된다. 어뷰징 사용자에 대해서는 계정 정지등 패널티를 주게 되는데, 모델이 아무리 어뷰징 사용자를 잘 찾아낸다 하더라도 FPR 값이 높게 되면, 정상적인 사용자를 어뷰징 사용자로 판단하여 선의의 사용자가 징계를 받게 되서, 전체적인 게임 충성도에 문제가 생길 수 있다. (어뷰징 사용자를 많이 찾아내는 것보다, 정상 사용자가 징계를 받게 되는 경우가 비지니스에 크리티컬 할때) 이런 경우에 FPR 값을 레퍼런스 할 수 있다.



그러면, Confusion Matrix를 통해서 계산된 결과를 가지고 모델을 어떻게 평가를 할까? 앞에서 나온 지표중에서 일반적으로 Accuracy 지표가 많이 사용되고, 그외에, ROC , Precision Recall Plot, F-Score 등이 많이 사용되는데 각각에 대해서 알아보자

ROC (Receiver Operating Characteristics)

ROC 그래프는 가로축을 FP Rate (Specificity) 값의 비율로 하고 세로축을 TP Rate (Sensitive) 로 하여 시각화 한 그래프이다.


  • Specificity = TN / TN+FP

  • Sensitive (Recall) = (TP) / P




보통 다음과 같은 그래프가 되고



(출처 : http://scikit-learn.org/stable/auto_examples/model_selection/plot_roc.html )


그래프가 위로 갈 수록 좋은 모델이고, 적어도 Y=X 그래프보다 위에 있어야 어느정도 쓸모 있는 모델로 볼 수 있다. 아래 그래프는 3개로 결과를 분류하는 모델에 대한 ROC 그래프 이다.


(출처 : http://scikit-learn.org/stable/auto_examples/model_selection/plot_roc.html )


ROC 그래프가 class 0, class 2, class 1 순서로 높은것을 볼 수 있다. 즉 이 모델은 class 0 을 제일 잘 분류하고 그 다음은 2,1 순서로 잘 분류 한다는 의미가 된다.

ROC는 그래프이기 때문에, 모델을 정확도를 하나의 숫자로 나타내기 어려워서 AUC (Area Under Curve) 라는 값을 사용하는데, ROC AUC값은 ROC 그래프의 면적이 된다. 최대값은 1이 된다. 위의 그래프를 보면 모델 0,2,1의 AUC값은 0.91, 0.79, 0.60 이 된다.

Precision Recall Plot

Precision Recall Plot (이하 PR 그래프)의 경우도 ROC 와 유사한데, 주로 데이타 라벨의 분포가 심하게 불균등 할때 사용한데, 예를 들어 이상 거래 검출 시나리오의 경우 정상 거래의 비율이 비정상 거래에 비해서 압도적으로 많기 때문에 (98%, 2%) 이런 경우에는 ROC 그래프보다 PR 그래프가 분석에 더 유리하다.


PR 그래프는 X 축을 Recall 값을, Y축을 Precision 값을 사용한다.


  • Sensitive (Recall) = (TP) / P

  • Precision = TP / (TP+FP)



다음은 이진 분류 (binary classification)의 PR 그래프의 예이다. 그래프가 위쪽으로 갈수록 정확도가 높은 모델이고, ROC와 마찬가지로 PR 그래프의 AUC (면적)값을 이용하여 모델의 정확도를 평가할 수 있다.



(출처 : http://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html)


그러면 모델이 쓸만한 모델인지 아닌지는 어떤 기준을 사용할까? ROC 그래프의 경우에는 Y=X 그래프를 기준으로 그래프 윗쪽에 있는 경우 쓸만한 모델로 판단을 했는데, PR 그래프의 경우 Base line이라는 것을 사용한다.


Base line = P / (P+N) 으로 정하는데, P는 데이타에서 Positive 레이블의 수, N 은 전체 데이타의 수이다. 예를 들어 암 데이타에서 암 양성이 300개 이고, 전체 데이타가 700이면 Base line은 300/(700+300) = 0.3 이 된다.  


위의 PR 그래프에 Base line을 적용하여 모델이 좋고 나쁜 영역을 판단하는 그림이다.

아래 그림은 두 모델을 비교한 PR 그래프인데, 두 모델 다 베이스라인을 넘어서 쓸만한 모델이기는 하지만, 모델 A가 B모델보다 확연하게 위에 위치하고 있기 때문에, A 모델이 좋다고 이야기할 수 있다.


(출처 : https://classeval.wordpress.com/introduction/introduction-to-the-precision-recall-plot/)

F-Score

모델의 성능을 하나의 수로 표현할때, ROC나 PR 그래프의 AUC를 사용하면 되지만, AUC를 계산하려면 여러 Throughput에 대해서 Precision, Recall, Specificity 값을 측정해야 한다.

그렇다면 Throughput을 이미 알고 있거나 또는 다양한 Throughput에 대해서 어떤 Throughput이 좋은지를 하나의 수로 모델의 성능을 평가하려면 어떻게 해야할까? 이를 위해서 사용하는 것이 F-Score 라는 값이 있다.


When measuring how well you're doing, it's often useful to have a single number to describe your performance

When measuring how well you're doing, it's often useful to have a single number to describe your performance. We could define that number to be, for instance, the mean of your precision and your recall. This is exactly what the F1-score is.

https://www.quora.com/What-is-an-intuitive-explanation-of-F-score

F Score에 대한 계산은 다음 공식을 이용한다. 큰 의미상으로 보자면 Precision과 Recall에 대한 평균인데, 그냥 평균을 내면, 값의 외곡 현상이 생기기 때문에, 가중치를 주는 평균이라고 이해하면 된다.


특히 β가 1인 경우 (즉 F1)를 F1 Score라고 하고, 모델의 성능 평가 지표로 많이 사용한다.


참고 문서


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를 이용하여 연예인 얼굴을 학습 시키고 인식하는 모델을 개발하고 학습 및 예측 하는 방법에 대해서 알아보도록 하겠다.



텐서플로우에서 checkpoint와 saved model의 차이와


모델을 export할때 그래프를 다시 그리는 이유


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


Check point vs Saved model


텐서플로우 튜토리얼들을 보면 모델을 저장하고 리스토어 하는데, check point를 사용하도록 가이드하고 있다.

그런데, Tensorflow Serving이나 CloudML등에 학습된 모델을 올려서 inference를 하고자 할때는 check point 파일을 사용하지 않고, 별도로 모델을 Saved model로 export하여 사용한다. 그렇다면 check-point와 saved model의 차이가 무엇일까?


check-point를 학습을 하다가 학습 내용을 중간에 저장하고 나중에 학습을 연달아서 하기 위한 용도로 check point에 의해서 저장되는 값을 모델 그래프의 변수 (Variable)만이 저장된다. 모델의 그래프 자체는 저장되지 않는다. 그래서 check-point를 리스토어하는 예제를 보면, 다시 그래프를 코드로 그 정의한 후에, check-point에 저장된 데이타를 리스토어 하는 것을 볼 수 있다.


Saved model은 inference를 위해서 모델을 저장하는 것으로, check-point와는 다르게 변수뿐만 아니라 모델의 그래프도 같이 저장한다. Tensorflow serving이나 cloud ml 등에서 inference를 위해서는 당연히 변수뿐 아니라 모델의 그래프도 필요하기 때문에 이를 같이 넘기는 것이다.

모델을 Export 할때, 그래프를 다시 그리는 이유는?


다음은 모델을 export 하는 코드의 예제인데, 코드를 보면 모델을 위한 그래프를 다시 정의 하는 것을 볼 수 있다.


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

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

   prediction = build_model(images, keep_prob=1.0)


   # Define API inputs/outputs object

   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


   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


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


   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)

   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 할때 그래프를 다시 그려서 저장하는 이유는, training용 그래프와 inference용 그래프가 다르기 때문이다.

training 그래프는, 중간 중간 test (evaluation) 에 사용되는 코드가 들어간다. 즉 test 코드가 들어간다.

또는 training 그래프는 dropout 계층이 있지만, inference에는 dropout 계층이 필요 없기 때문에 그래프가 달라진다.

데이타를 피딩하는 경우에도 training 에서는 속도를 위해서 placeholder를 없애고 바로 Queue runner에서 데이타를 읽어서 모델에 읽도록할 수 도 있지만, inference에는 queue runner를 통해서 데이타를 읽는 것이 아니라 예측을 할 값을 입력 받아야 하기 때문에, 이 경우에는  placeholder가 필요하다.


이런 이유로 training 그래프와 inference 그래프가 달라지는데, 텐서플로우 1.0 버전 이후에서 부터는 model export를 SavedModel을 이용하여 저장하도록 가이드하는데, 구현 복잡도가 다소 높고 아직까지 가이드가 부족하다. 이를 단순화 하기 위해서 Experiment 클래스를 이용하면 자동으로 training 그래프와 inference graph를 분리해주기 때문에, 코드가 단순화 될 수 있기 때문에 모델을 만들때 처음 부터 Experiment 클래스를 사용하기를 권장한다.