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


Archive»


 
 

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



얼굴 인식 모델을 만들어보자 #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) 하는 방법에 대해서 알아보겠다.


참고 자료


텐서플로우에서 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 클래스를 사용하기를 권장한다.