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


Archive»


 
 

텐서플로우 하이레벨 API Estimator를 이용한 모델 정의 방법


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


텐서플로우의 하이레벨 API를 이용하기 위해서는 Estimator 를 사용하는데, Estimator 는 Predefined model 도 있지만, 직접 모델을 구현할 수 있다. 하이레벨 API와 Estimator에 대한 설명은 http://bcho.tistory.com/1195 글을 참고하기 바란다.


이 문서는 Custom Estimator를 이용하여 Estimator를 구현하는 방법에 대해서 설명하고 있으며, 대부분 https://www.tensorflow.org/extend/estimators 의 내용을 참고하여 작성하였다.

Custom Estimator

Estimator의 스켈레톤 코드는 다음과 같다. 모델을 정의하는 함수는 학습을 할 feature와, label을 입력 받고, 모델의 모드 (학습, 테스트, 예측) 모드를 인자로 받아서 모드에 따라서 모델을 다르게 정의할 수 있다. 예를 들어 학습의 경우 드롭 아웃을 사용하지만 테스트 모드에서는 드롭 아웃을 사용하지 않는다.

def model_fn(features, labels, mode, params):
  # Logic to do the following:
  # 1. Configure the model via TensorFlow operations
  # 2. Define the loss function for training/evaluation
  # 3. Define the training operation/optimizer
  # 4. Generate predictions
  # 5. Return predictions/loss/train_op/eval_metric_ops in EstimatorSpec object
  return EstimatorSpec(mode, predictions, loss, train_op, eval_metric_ops)

입력 인자에 대한 설명

그러면 각 인자를 구체적으로 살펴보자

  • features : input_fn을 통해서 입력되는 feature로 dict 형태가 된다.

  • labels : input_fn을 통해서 입력되는 label 값으로 텐서 형태이고, predict (예측) 모드 일 경우에는 비어 있게 된다.

  • mode : 모드는 모델의 모드로, tf.estimator.ModeKeys 중 하나를 사용하게 된다.

    • tf.estimator.ModeKeys.TRAIN : 학습 모드로 Estimator의 train()을 호출하였을 경우 사용되는 모드이다.

    • tf.estimator.ModeKeys.EVAL : 테스트 모드로, evaluate() 함수를 호출하였을 경우 사용되는 모드이다.

    • tf.estimator.ModeKeys.PREDICT : 예측모드로,  predict() 함수를 호출하였을 경우에 사용되는 모드이다.  

  • param : 추가적으로 입력할 수 있는 패러미터로, dict 포맷을 가지고 있으며, 하이퍼 패러미터등을 이 변수를 통해서 넘겨 받는다.

Estimator 에서 하는 일

Estimator 를 구현할때, Estimator 내의 내용은 모델을 설정하고, 모델의 그래프를 그린 다음에, 모델에 대한 loss 함수를 정의하고, Optimizer를 정의하여 loss 값의 최소값을 찾는다. 그리고 prediction 값을 계산한다.


Estimator의 리턴값

Estimator에서 리턴하는 값은 tf.estimator.EstimatorSpec 객체를 리턴하는데, 이 객체는 다음과 같은 값을 갖는다.

  • mode : Estimator가 수행한 모드. 보통 입력값으로 받은 모드 값이 그대로 리턴된다.

  • prediction (PREDICT 모드에서만 사용됨) : PREDICT 모드에서 예측을 수행하였을 경우, 예측된 값을 dict 형태로 리턴한다.

  • loss (EVAL 또는, TRAIN 모드에서 사용됨) : 학습과 테스트중에 loss 값을 리턴한다.

  • train_op (트레이닝 모드에서만 필요함) : 한 스텝의 학습을 수행하기 위해서 호출하는 함수를 리턴한다. 보통 옵티마이져의  minimize()와 같은 함수가 사용된다.
           optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
           train_op = optimizer.minimize(loss, global_step=global_step)
           return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)

  • eval_metrics_ops (optional) : EVAL (테스트) 모드에서 테스트를 위해서 사용된 인자들을 dict 형태로 리턴한다. tf.metrics에는 미리 정의된 일반적인 메트릭들이 정의되어 있는데, 예를 들어 accuracy 등이 이에 해당한다. 아래는 tf.metrics.accuracy를 이용하여 예측값 (predictions)과 라벨(labels)의 값을 계산하여, 메트릭으로 리턴하는 방법이다.

    eval_metric_ops = {
    "accuracy": tf.metrics.accuracy(labels, predictions) }

    만약 rmse를 evaluation metric으로 사용하고자 하면 다음과 같이 정의한다.
    eval_metric_ops = {
       "rmse": tf.metrics.root_mean_squared_error(
           tf.cast(labels, tf.float64), predictions)
    }

    만약에 별도의 메트릭을 정의하지 않으면, 디폴트로 loss 값만 EVAL 단계에서 계산되게 된다.

데이타 입력 처리

모델로의 데이타 입력은 Esitmator의 모델 함수로 입력되는 features 변수를 통해서 입력 된다.

features는 컬럼명으로된 키와, 컬럼 값으로 이루어진 dict 형태의 데이타 형으로, 뉴럴 네트워크 모델에 데이타를 입력하기 위해서는 이중에서 학습에 사용할 컬럼만을 추출하여, 입력 레이어에 넣어 줘야 한다.

이 features 에서 특정 컬럼만을 지정하여 추출한 후에, 그 컬럼의 값을 넣어주는 것은 tf.feature_column.input_layer 함수를 사용하면 된다.


예제를 보자

input_layer = tf.feature_column.input_layer(
 features=features, feature_columns=[age, height, weight])


위의 예제는 features 에서 age,height,weight 컬럼을 추출하여 input layer로 넣는 코드이다.

네트워크 정의

데이타를 읽었으면 이제 뉴럴네트워크를 구성해야 한다. 네트워크의 레이어는 tf.layers 로 간단하게 구현할 수 있다. tf.layer에는 풀링,드롭아웃,일반적인 뉴럴네트워크의 히든 레이어, 컨볼루셔널 네트워크들이 함수로 구현되어 있기 때문에 각 레이어를 하나의 함수로 간단하게 정의가 가능하다.


아래는 히든레이어를 구현하는 tf.layers.dense 함수이다.


tf.layers.dense( inputs, units, activation)


  • inputs는 앞의 레이어를 정의하고

  • units는 이 레이어에 크기를 정의하고

  • 마지막으로 activation은 sigmoid나,ReLu와 같은 Activation 함수를 정의한다.


다음 예제는 5개의 히든 레이어를 가지는 오토 인코더 네트워크를 정의한 예이다.

 input_layer = features['inputs'] # 784 pixels
   dense1 = tf.layers.dense(inputs=input_layer, units=256, activation=tf.nn.relu)
   dense2 = tf.layers.dense(inputs=dense1, units=128, activation=tf.nn.relu)
   dense3 = tf.layers.dense(inputs=dense2, units=16, activation=tf.nn.relu)
   dense4 = tf.layers.dense(inputs=dense3, units=128, activation=tf.nn.relu)
   dense5 = tf.layers.dense(inputs=dense4, units=256, activation=tf.nn.relu)
   output_layer = tf.layers.dense(inputs=dense5, units=784, activation=tf.nn.sigmoid)


5개의 히든 레이어는 각각 256,128,16,128,256 개의 노드를 가지고 있고, 각각 ReLu를 Activation 함수로 사용하였다.

그리고 마지막 output layer는 784개의 노드를 가지고 sigmoid 함수를 activation 함수로 사용하였다.

Loss 함수 정의

다음 모델에 대한 비용함수(loss/cost function)을 정의한다. 이 글을 읽을 수준이면 비용함수에 대해서 별도로 설명하지 않아도 되리라고 보는데, 비용함수는 예측값과 원래 라벨에 대한 차이의 합을 나타내는 것이 비용함수이다.


 # Connect the output layer to second hidden layer (no activation fn)

 output_layer = tf.layers.dense(second_hidden_layer, 1)
 # Reshape output layer to 1-dim Tensor to return predictions
 predictions = tf.reshape(output_layer, [-1])
 predictions_dict = {"ages": predictions}

 # Calculate loss using mean squared erro
 loss = tf.losses.mean_squared_error(labels, predictions)

코드를 보면, 최종 예측된 값은 predictions에 저장되고, 학습 데이타로 부터 받은 라벨 값은 labels에 저장된다. 이 차이를 계산할때, MSE (mean square error)를 사용하였다.

Training Op 정의

비용 함수가 적용되었으면, 이 비용함수의 값을 최적화 하는 것이 학습이기 때문에, 옵티마이저를 정의하고, 옵티마이저를 이용하여 비용함수의 최적화가 되도록 한다.

아래 코드는  Optimizer를 GradientDescentOptimizer로 정의하고, 이 옵티마이저를 이용하여 이용하여 loss 값을 최소화 하도록 하였다.

optimizer = tf.train.GradientDescentOptimizer(
   learning_rate=params["learning_rate"])

train_op = optimizer.minimize(
   loss=loss, global_step=tf.train.get_global_step())

전체 코드

그러면 위의 내용을 모두 합쳐서 model_fn으로 모아서 해보자.

def model_fn(features, labels, mode, params):
 """Model function for Estimator."""
 # Connect the first hidden layer to input layer
 # (features["x"]) with relu activation
 first_hidden_layer = tf.layers.dense(features["x"], 10, activation=tf.nn.relu)

 # Connect the second hidden layer to first hidden layer with relu
 second_hidden_layer = tf.layers.dense(
     first_hidden_layer, 10, activation=tf.nn.relu)

 # Connect the output layer to second hidden layer (no activation fn)
 output_layer = tf.layers.dense(second_hidden_layer, 1)


 # Reshape output layer to 1-dim Tensor to return predictions
 predictions = tf.reshape(output_layer, [-1])

 # Provide an estimator spec for `ModeKeys.PREDICT`.
 if mode == tf.estimator.ModeKeys.PREDICT:
   return tf.estimator.EstimatorSpec(
       mode=mode,
       predictions={"ages": predictions})

 # Calculate loss using mean squared error
 loss = tf.losses.mean_squared_error(labels, predictions)

 # Calculate root mean squared error as additional eval metric
 eval_metric_ops = {
     "rmse": tf.metrics.root_mean_squared_error(
         tf.cast(labels, tf.float64), predictions)
 }

 optimizer = tf.train.GradientDescentOptimizer(
  learning_rate=params["learning_rate"])

 train_op = optimizer.minimize(
     loss=loss, global_step=tf.train.get_global_step())

 # Provide an estimator spec for `ModeKeys.EVAL` and `ModeKeys.TRAIN` modes.

 return tf.estimator.EstimatorSpec(
     mode=mode,
     loss=loss,
     train_op=train_op,
     eval_metric_ops=eval_metric_ops)

데이타 입력

 first_hidden_layer = tf.layers.dense(features["x"], 10, activation=tf.nn.relu)

네트워크 정의

 # Connect the second hidden layer to first hidden layer with relu
 second_hidden_layer = tf.layers.dense(
     first_hidden_layer, 10, activation=tf.nn.relu)

 # Connect the output layer to second hidden layer (no activation fn)
 output_layer = tf.layers.dense(second_hidden_layer, 1)

first_hidden_layer의 입력값을 가지고 네트워크를 구성한다. 두번째 레이어는 first_hidden_layer를 입력값으로 하여, 10개의 노드를 가지고, ReLu를 activation 레이어로 가지도록 하였다.  

마지막 계층은 두번째 계층에서 나온 결과를 하나의 노드를 이용하여 합쳐서 activation 함수 없이 결과를 냈다.

 # Reshape output layer to 1-dim Tensor to return predictions
 predictions = tf.reshape(output_layer, [-1])

 # Provide an estimator spec for `ModeKeys.PREDICT`.
 if mode == tf.estimator.ModeKeys.PREDICT:
   return tf.estimator.EstimatorSpec(
       mode=mode,
       predictions={"ages": predictions})

예측 모드에서는 prediction 값을 리턴해야 하기 때문에, 먼저 예측값을 output_layer에서 나온 값으로, 행렬 차원을 변경하여 저장하고, 만약에 예측 모드 tf.estimator.ModeKeys.PREDICT일 경우 EstimatorSpec에 predction 값을 넣어서 리턴한다. 이때 dict 형태로 prediction 결과 이름을 age로 값을 predictions 값으로 채워서 리턴한다.

Loss 함수 정의

다음 비용 함수를 정의하고, 테스트 단계(EVAL)에서 사용할 evaluation metrics에 rmse를 테스트 기준으로 메트릭으로 정의한다.

 # Calculate loss using mean squared error
 loss = tf.losses.mean_squared_error(labels, predictions)

 # Calculate root mean squared error as additional eval metric
 eval_metric_ops = {
     "rmse": tf.metrics.root_mean_squared_error(
         tf.cast(labels, tf.float64), predictions)
 }

Training OP 정의

비용 함수를 정했으면, 비용 함수를 최적화 하기 위한 옵티마이져를 정의한다. 아래와 같이 GradientDescentOptimzer를 이용하여 loss 함수를 최적화 하도록 하였다.

 optimizer = tf.train.GradientDescentOptimizer(
  learning_rate=params["learning_rate"])

 train_op = optimizer.minimize(
     loss=loss, global_step=tf.train.get_global_step())

 # Provide an estimator spec for `ModeKeys.EVAL` and `ModeKeys.TRAIN` modes.

마지막으로, PREDICTION이 아니고, TRAIN,EVAL인 경우에는 EstimatorSpec을 다음과 같이 리턴한다.

Loss 함수와, Training Op를 정의하고 평가용 매트릭스를 정의하여 리턴한다.

 return tf.estimator.EstimatorSpec(
     mode=mode,
     loss=loss,
     train_op=train_op,
     eval_metric_ops=eval_metric_ops)

실행

그러면 완성된 Estimator를 사용해보자

train_input_fn = tf.estimator.inputs.numpy_input_fn(
   x={"x": np.array(training_set.data)},
   y=np.array(training_set.target),
   num_epochs=None,
   shuffle=True)

# Train

nn.train(input_fn=train_input_fn, steps=5000)

# Score accuracy

test_input_fn = tf.estimator.inputs.numpy_input_fn(
   x={"x": np.array(test_set.data)},
   y=np.array(test_set.target),
   num_epochs=1,
   shuffle=False)

ev = nn.evaluate(input_fn=test_input_fn)
print("Loss: %s" % ev["loss"])
print("Root Mean Squared Error: %s" % ev["rmse"])

각 코드를 보면

train_input_fn = tf.estimator.inputs.numpy_input_fn(
   x={"x": np.array(training_set.data)},
   y=np.array(training_set.target),
   num_epochs=None,
   shuffle=True)

를 이용하여 numpy 의 데이타로 input_fn 함수를 만들었다. training_set.data는 학습 데이타, training_set.target을 학습용 라벨로 설정하고, epoch는 무제한, 그리고 데이타는 셔플 하도록 하였다.

nn.train(input_fn=train_input_fn, steps=5000)

앞서 정의된 모델에 train_input_fn을 넣어서 총 5000 번 학습을 하도록 하였다.

학습이 끝난 모델을 테스트 해야 하는데, 같은 방법으로 test_input_fn을 정의하고

ev = nn.evaluate(input_fn=test_input_fn)

evaluate를 이용하여, 학습된 모델을 평가한다.

평가된 결과를 보기 위해서 loss 값과 rmse 값을 ev[‘loss’], ev[‘rmse’]로 출력하였다.

지금까지 Estimator를 만드는 방법에 대해서 알아보았다. 다음 글에서는 Auto Encoder 네트워크를 Estimator로 구현해보도록 하겠다.





텐서플로우 하이레벨 API

빅데이타/머신러닝 | 2017.09.06 14:57 | Posted by 조대협

텐서플로우 하이레벨 API에 대한 이해


머신러닝을 공부하고 구현하다 보니, 모델 개발은 새로운 모델이나 알고리즘을 개발하는 일 보다는, 기존의 알고리즘을 습득해서 내 데이타 모델에 맞도록 포팅하고, 학습 시키는 것이 주된 일이 되고, 오히려, 모델 보다는 데이타에 대한 이해와 전처리에 많은 시간이 소요되었다.


특히 여러번 실험을 하면서 패러미터를 조정하고 피쳐등을 조정하기 위해서는 많은 실험을 할 수 있어야 하는데, 이러기 위해서는 실험(학습)시간이 짧아야 한다. 이를 위해서는 모델 개발 보다 분산 러닝을 하기 위한 코드 변경 작업등이 많이 소요된다.


결론을 요약하자면, 실제로 알고리즘을 개발하는 데이타 과학자가 아니라, 머신러닝을 활용만 하는 프랙티셔너 입장이라면, 모델을 개발하는 것 보다는 있는 모델을 선택해서 쉽게 사용할 수 있는 방법을 찾으면 된다.

하이레벨 API

이런 관점에서 시작한 것이 머신러닝 하이레벨 API 이다. 복잡한 수식이 없이 마치 함수처럼 모델을 선택해서 사용하는 방법인데, 쉽게 이야기 하면, Hash table 알고리즘을 100% 이해하지 않더라도, hashtable 라이브러리를 가져다가 사용하면 되는것과 같은 원리이다.


머신러닝에서도 이미 이러한 하이레벨 API가 많이 제공되고 있는데, 파이썬 싸이킥 런(http://scikit-learn.org/) 이나 SparkML 등이 해당한다.

텐서플로우에도 같은 방식으로 하이레벨 API를 제공하는데, 텐서플로우 공식 SDK와 써드파티 오픈소스 라이브러리들이 있다.

그중에서 tf.contrib가 공식 텐서플로우의 하이레벨 API이며, 딥러닝 모델을 간단하게 만들 수 있는 Keras역시 얼마전에 텐서플로우 공식 하이레벨 API로 로 편입되었다.




텐서플로우에서는 Linear regression, SVM등 많이 쓰이는 일반적인 머신러닝 모델에서 부터 Deep Wide Network와 같은 딥 러닝 모델들을 Estimator 라는 형태로 제공하고 있다.

하이레벨 API를 쓰면 장점

그러면 이러한 하이레벨 API를 쓰면 장점이 무엇일까?

모델 개발이 쉽다

모델 개발이 매우 쉽다. 복잡한 모델을 손쉽게 개발할 수 있을뿐더러, 일부 모델들은 Out of box 형태로, 바로 라이브러리 식으로 불러서 사용만 하면 되기 때문에 모델 개발 시간이 줄어들고, 모델에 대한 기본적인 이해만 있더라도 쉽게 개발이 가능하다.

스케일링이 용이하다

큰 모델을 많은 데이타로 학습하기 위해서는 여러 머신에서 학습을 하는 분산 학습이 필요한데, 로우레벨 API를 이용할 경우 분산 학습을 개발하기가 쉽지 않다.  하이레벨 API를 이용할 경우 코드 변경 없이 싱글 머신에서 부터 GPU 그리고 분산 학습까지 손쉽게 지원이 되기 때문에, 실험 (학습/테스트) 시간을 많이 절약할 수 있다.


배포가 용이하다

모델을 학습 시킨 후 예측을 위해서 배포를 할 경우, 보통 모델을 *.pb 파일 형태로 Export 해야 하는데, 이 경우 학습에 사용된 그래프 말고 예측을 위한 그래프를 새로 그려야 하는 등 추가적인 작업이 필요하고 쉽지 않은데 반해 하이레벨 API의 경우, 코드 몇줄만으로도 손쉽게 예측 서비스를 위한 그래프를 Export할 수 있다.


텐서플로우 하이레벨 API

tf.layers

텐서플로우는 특히 딥러닝 (뉴럴네트워크)에 강점을 가지고 있는데, 딥네트워크의 각 계층을 설계 하기 위해서는 컨볼루셔널 필터, 풀링, 스트라이드,드롭 아웃 등 다양한 기법을 사용하게 된다. 이러한 것들을 복잡하게 구현하지 않고, 딥 네트워크를 손쉽게 만들 수 있게 각 레이어에 대한 구현을 함수식으로 제공한다.


다음 그림은 tf.layer로 컨볼루셔널 네트워크 (CNN)을 구현한 예제로 컨볼루셔널 레이어와, 맥스풀링, 드롭아웃, ReLu 엑티베이션 함수등을 사용하였다. 각 레이어는 tf.layers 라이브러리 하나씩으로 간단하게 구현되었다.


Estimator

일반적으로 머신러닝 개발은 다음과 같은 구조를 갖는다


개발한 모델에 Input,Labels 데이타를 넣은 후, 학습(Training), 테스트(Evaluation), 예측(Prediction)을 한후, 학습이 완료된 모델을 저장(Export)하여 배포한다.  거의 모든 모델 개발이 위의 구조를 반복하기 때문에, 이러한 구조를 추상화 해놓은 것이 Estimator 이다.


이 추상화를 통해서 Estimator에 데이타를 넣게 되면, Estimator는 Training, Evaluation, Prediction, Export를 위한 인터페이스를 제공한다. 텐서플로우 그래프 구축이나 세션 관리등은 모두 Estimator 안으로 추상화 한다.


Estimator는 직접 개발자가 모델을 직접 구현하여 Estimator를 개발할 수 도 있고 (Custom Estimator) 또는 이미 텐서플로우 tf.contrib.learn에 에 미리 모델들이 구현되어 있다. 딥네트워크 기반의 Classifier나 Regressor (DNNClassifieir, DNNRegressor), SVM, RNN, KMeans 등이 있기 때문에 간단하게 불러다 사용하기만 하면 된다.

Estimator 예제

Estimator 예제로 간단한 LinearRegression Estimator를 사용하는 예제를 보자

학습용 데이타

먼저 학습용 데이타와 라벨을 생성하였다.

import numpy as np

num_points = 300

vectors_set = []

for i in xrange(num_points):

 x = np.random.normal(5,5)+15

 y =  x*2+ (np.random.normal(0,3))*2

 vectors_set.append([x,y])

 

x_data = [v[0] for v in vectors_set ]

y_data = [v[1] for v in vectors_set ]


import matplotlib.pyplot as plt

plt.plot(x_data,y_data,'ro')

plt.ylim([0,100])

plt.xlim([5,35])

plt.xlabel('x')

plt.ylabel('y')

plt.legend()

plt.show()


데이타 분포는 아래와 같다.


모델 코드

데이타 리더

Estimator 를 사용하려면 데이타를 읽어서 Estimator 에 넣어주는 입력 함수를 구현해줘야 한다. 아래는  numpy 배열에서 데이타를 읽어서 리턴해주는 입력 함수이다.


input_fn_train = tf.estimator.inputs.numpy_input_fn(

   x = {"x":np.array(x_data[:200],dtype=np.float32)},

   y = np.array(y_data[:200],dtype=np.float32),

   num_epochs=100000,

   batch_size=50,

   shuffle=True

)


x_data 배열에서 0~200까지의 데이타를 학습용 데이타로 사용하였고, y_data 0~200을 라벨로 사용하였다. 한번에 50 개씩 리턴하도록 배치를 설정하였고, 100K epoch를 지원하고 데이타를 랜덤하게 리턴하도록 셔플 처리를 하였다.


input_fn_eval = tf.estimator.inputs.numpy_input_fn(

   x = {"x":np.array(x_data[200:300],dtype=np.float32)},

   y = np.array(y_data[200:300],dtype=np.float32),

   num_epochs=100000,

   batch_size=50,

   shuffle=True

)


input_fn_predict = tf.estimator.inputs.numpy_input_fn(

   x = {"x":np.array([15,20,25,30],dtype=np.float32)},

   num_epochs=1,

   shuffle=False

)


같은 방법으로 테스트용 데이타와 예측에 사용할 데이타 입력 함수를 같이 정의하였다.

모델 정의

column_x = tf.feature_column.numeric_column("x",dtype=tf.float32)

columns = [column_x]

읽어온 데이타에서, 어떤 컬럼을 학습에 사용할지, 그리고 그 컬럼의 데이타 타입 (연속형인지 분류형인지)를 정한다.  tf.feature_column.numeric_column("x",dtype=tf.float32) 는 컬럼 명 x를 학습 데이타로 사용하고 x는 연속형 변수로 지정하였다.

다음 columns에 피쳐로 사용할 컬럼 목록을 정한다.


LinearRegression Estimator를 정의하고, 여기에, column을 정해준다.  Optimizer나 Learning Rate등은 지정이 가능하다.

estimator = tf.contrib.learn.LinearRegressor(feature_columns=columns,optimizer="Adam")

학습과 예측

학습은 .fit 이라는 메서드를 사용하면 되고, 입력 함수와 학습 스텝을 정해주면 된다.

estimator.fit(input_fn = input_fn_train,steps=5000)

estimator.evaluate(input_fn = input_fn_eval,steps=10)

result = list(estimator.predict(input_fn = input_fn_predict))


마지막으로 예측은 predict 를 이용하면 된다.

x=15,20,25,30에 대해서 예측 결과는 다음과 같다.

[31.193062, 41.855644, 52.51823, 63.180817]



그래프와 비교해보면 유사 값이 나오는 것을 확인할 수 있다.



전체 코드 
https://github.com/bwcho75/tensorflowML/blob/master/HighLevel%20API%201.%20Linear%20Regression%20Estimator.ipynb






Wide and deep network 모델 활용하기

빅데이타/머신러닝 | 2017.07.20 17:12 | Posted by 조대협


Wide & deep model 알아보기

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

Wide & deep model

이글에 설명된 예제는 https://www.tensorflow.org/tutorials/wide_and_deep  문서에 있는 코드를 활용하였습니다. 음식 검색 키워드와 검색 결과를 학습 시킨 후에 이 결과를 기반으로 사용자에게 음식을 추천해주는 서비스가 있다고 하자.

Monetization and Wide model (기억과 와이드 모델)

로지스틱 회귀 모델을 이용하여 추천 알고리즘을 작성하여 학습을 시킨 경우, 학습 데이타를 기반으로 상세화된 예측 결과를 리턴해준다. 예를 들어 검색 키워드 (프라이드 치킨)으로 검색한 사용자가 (치킨과 와플)을 주문한 기록이 많았다면, 이 모델은 (프라이드 치킨)으로 검색한 사용자는 항상 (치킨과 와플)을 추천해주게 된다.  즉 예전에 기억된 값 (Memorization된 값)을 통해서 예측을 하는데, 이러한 모델을 와이드 모델이라고 한다.



<그림 와이드 모델 >

그러나 (프라이드 치킨)으로 검색한 사용자에게 같은 패스트 푸드 종류인 햄버거나 프렌치프라이등을 추천해도 잘 구매가 되지만 와이드 모델은 기존에 기억된 결과로만 추천을 하기 때문에 이러한 결과를 얻기가 어렵다.


Generalization and Deep model (일반화와 딥모델)

뉴럴네트워크 모델의 경우 프라이드 치킨을 햄버거, 프랜치 프라이등을 일반화 시켜서 패스트 푸드로 분류하여 프라이드 치킨으로 검색을 해도 이와 같은 종류의 햄버거를 추천해도 사용자가 택할 가능성이 높다.


<그림 딥 모델>


이러한 모델을 딥모델이라고 하는데, 딥 모델의 경우 문제점이, 너무 일반화가(under fitting)  되서 엉뚱한 결과가 나올 수 있다는 것인데, 예를 들어서 따뜻한 아메리카노를 검색했는데, 커피라는 일반화 범주에서 아이스 라떼를 추천해줄 수 있다는 것이다. 즉 커피라는 일반화 범주에서 라떼는 맞는 추천일 수 있지만, 따뜻한 음료를 원하는 사람에게 차가운 음료를 추천하는 지나친 일반화가 발생할 수 있다.


그래서 이런 문제를 해결하기 위해서 와이드 모델과 딥모델을 합친 “Wide & deep model”이라는 것을 구글이 개발하였고 이를 구글 플레이 스토어에 적용한 결과, 큰 효과를 얻었다고 한다. (https://arxiv.org/abs/1606.07792)


<그림 와이드 앤 딥모델 >


모델 사용 방법

이 모델이 텐서플로우에서 tf.contrib.learn 패키지에 라이브러리 형태로 공개가 되었다.

Classification 용은 tf.contrib.learn.DNNLinearCombinedClassifier

Regression 용은 tf.contrib.learn.DNNLinearCombinedRegressor

를 사용하면 된다.


이 라이브러리들은 텐서플로우의 Esimator API (https://www.tensorflow.org/extend/estimators)인데, 복잡한 알고리즘을 구현할 필요 없이 불러다 쓸 수 있는 하이레벨 API 이면서 학습에서 중요한 다음 두가지를 도와준다.

  • 분산러닝
    멀티 GPU나 멀티 머신에서 분산학습을 하려면 직접 텐서플로우 코드를 써서 작업 분산 및 취합 작업을 해줘야 하는데, Estimator API를 사용할 경우 Experiment API 를 통해서 Google CloudML 인프라 상에서 이런 작업을 자동으로 해준다.

  • 모델 EXPORT
    그리고 학습된 모델은 운영환경에서 예측용으로 사용할때, 모델을 Export 하여 Tensorflow Serving 과 같은 예측 엔진에 배포해야 하는데, 모델을 Export 하려면, 예측에 사용할 텐서플로우 그래프를 다시 그려주고 변수 값을 채워넣는 것에 대한 코드를 작성해야 하는데 (자세한 설명은 http://bcho.tistory.com/1183 문서 참조), 이 역시도 자동화를 해준다.


자 이제 머신러닝 모델은  있으니 여기에 데이타 즉 적절한 피쳐만 제대로 넣어서 학습을 시키면 되는데, 와이드 모델과 딥모델 각각 학습 하기 좋은 피쳐가 따로 있다.

와이드 모델 학습용 피쳐

와이드 모델에는 카테고리(분류)와 같은 비연속성을 가지는 데이타가 학습에 적절하다. 카테고리성 컬럼의 경우에는 다음과 같이 크게 두 가지가 있다.

Sparse based column

성별, 눈동자의 색깔과 같이 비연속성을 지니는 값으로 학습에 사용하려면 이를 벡터화를 해야 한다.

예를 들어 남자 = [1,0] 여자는 = [0,1] 식으로 또는 검정눈 = [1,0,0], 갈색눈 = [0,1,0], 푸른눈 = [0,0,1] 식으로 벡터화할 수 있다.

이때는 다음과 같이 sparse_column_with_keys라는 메서드를 써주면 위와 같은 방식으로 인코딩을 해준다.

gender = tf.contrib.layers.sparse_column_with_keys(column_name="gender", keys=["Female", "Male"])

만약에 나이와 같이 연속형 데이타라도 이를 10대,20대,30대와 같이 구간으로 나눠서 비연속성 분류 데이타로 바꾸고자 할 경우에는 다음과 같이 bucketized_column을 사용하면 된다.

age_buckets = tf.contrib.layers.bucketized_column(age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])

Crossed column

다음은 crossed column 이라는 피쳐인데, 예를 들어 교육 수준과, 직업이라는 피쳐가 있다고 하자. 이를 각각의 독립된 변수로 취급할 수 도 있지만, 교육수준과 직업에 상관 관계가 있다고 할때 이를 관계를 묶어서 피쳐로 사용할 수 있다. 예를 들어 대졸 사원의 연봉, 컴퓨터 프로그래머의 연봉과 같이 독립된 특징으로 보는것이 아니라 대졸 컴퓨터 프로그래머, 대학원졸 컴퓨터 프로그래머와 같은 상관 관계를 기반으로 피쳐를 사용할 수 있는데 이를 Crossed column이라고 한다. Cross column은 다음과 같이 crossed_colmn이라는 메서드를 이용해서 정의할 수 있다.

tf.contrib.layers.crossed_column([education, occupation], hash_bucket_size=int(1e4))

딥 모델 학습용 피쳐

딥 모델용 학습데이타는 연속성을 가지는 데이타가 적절하다.

Continuous column

Continuous column은 일반적인 연속형 데이타 변수이고 간단하게 real_valued_column 메서드를 정해서 다음과 같이 정의가 가능하다.

age = tf.contrib.layers.real_valued_column("age")

Embedding column

문장의 단어들을 학습 시키기 위해서 각 단어를 벡터로 표현하고자 할때 , 예를 들어 boy = [1,0,0,0..], girl=[0,1,0,...] 으로 단어 하나를 하나의 숫자로 1:1 맵핑을 시킬 수 있다. 그러나 이 경우 이 단어가 다른 단어와 어떤 상관 관계를 갖는지 표현이 불가능하다. 예를 들어 남자:소년=여자:?? 라는 관계식을 줬을때, 위의 방식으로는 단어간의 관계를 유추할 수 없기 때문에, ?? 를 찾아낼 수 없다. 즉 컴퓨터가 “단어가 다른 단어와 어떤 차이점과 공통점”을 가지는지 이해할 수가 없다는 단점이 존재한다.

이런 문제를 해결하기 위해서 단어를 다차원 공간에서 벡터로 표현하여 각 단어간의 관계를 표현할 수 있는 방법을 만들었다.

이와 같은 원리로 어떤 비연속된 카테고리 피쳐들을 숫자로 맵핑할때, 위의 boy,girl 과 같은 방식 (on_hot_encoding) 으로 의미없이 1:1 맵핑을 하는 것이 아니라, 각 카테고리들이 어떠한 연관 관계를 가질때 이 연관성을 표현하여 벡터값으로 변환하는 방법을 임베딩 (embedding)이라고 한다.


그래서 카테고리내의 값들이 서로 연관성을 가질때는 임베딩을 이용하여 벡터 값으로 변경을 한 후, 이 값을 딥모델에 넣어서 학습하면 좋은 결과를 얻을 수 있다. 카테고리화된 값을 임베딩하기 위해서는 아래와 같이 embedding_column이라는 메서드를 사용하면 된다.


tf.contrib.layers.embedding_column(education, dimension=8)

피쳐를 모델에 넣는 방법

위와 같은 방법으로 분리되고 변경된 피쳐는, Wide & deep model에서 각각 와이드 모델과, 딥모델로 주입되서 학습되게 된다.

아래와 같이 피쳐를 와이드 컬럼과 딥 컬럼으로 구별한 후에, 리스트에 넣는다.

wide_columns = [
 gender, native_country, education, occupation, workclass, relationship, age_buckets,
 tf.contrib.layers.crossed_column([education, occupation], hash_bucket_size=int(1e4)),
 tf.contrib.layers.crossed_column([native_country, occupation], hash_bucket_size=int(1e4)),
 tf.contrib.layers.crossed_column([age_buckets, education, occupation], hash_bucket_size=int(1e6))

deep_columns = [
 tf.contrib.layers.embedding_column(workclass, dimension=8),
 tf.contrib.layers.embedding_column(education, dimension=8),
 tf.contrib.layers.embedding_column(gender, dimension=8),
 tf.contrib.layers.embedding_column(relationship, dimension=8),
 tf.contrib.layers.embedding_column(native_country, dimension=8),
 tf.contrib.layers.embedding_column(occupation, dimension=8),
 age, education_num, capital_gain, capital_loss, hours_per_week]

다음 딥모델용 피쳐 리스트와 와이드 모델용 피쳐 리스트를 DNNLinearCombinedClassifier 에 각각 변수로 넣으면 된다. 이때 딥 모델은 뉴럴네트워크이기 때문에, 네트워크의 크기를 정해줘야 하는데 아래 코드에서는 각각 크기가 100인 히든 레이어와 50인 레이어 두개를 넣어서 구성하도록 하였다.

m = tf.contrib.learn.DNNLinearCombinedClassifier(
   model_dir=model_dir,
   linear_feature_columns=wide_columns,
   dnn_feature_columns=deep_columns,
   dnn_hidden_units=[100, 50])



지금 까지 아주 간단하게 나마 Wide & deep model에 대한 이론 적인 설명과 이에 대한 구현체인 DNNLinearCombinedRegressortf.contrib.learn.DNNLinearCombinedClassifier 에 대해서 알아보았다.  이 정도 개념만 있으면 실제 Wide & deep model 튜토리얼을 이해할 수 있으니, 다음은 직접 튜토리얼을 참고하기 바란다. https://www.tensorflow.org/tutorials/wide_and_deep


Reference


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


텐서플로우에서 array index를 문자열로 변환하는 방법


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


예전에, 얼굴 인식 모델을 만들때, 라벨 숫자로 하지 않고 사람 이름 문자열로 했다가 이 문자열의 배열 인덱스를 구하는 것을 구현하지 못해서 라벨을 다시 숫자로 데이타를 재생성한 적이 있었다. 텐서플로우에서 텐서는 파이썬의 일반 자료형이 아니기 때문에, 파이썬의 배열등을 사용하지 못해서 생기는 문제였는데, 포기하고 있다가 다른 코드를 보던중에, 이 부분을 해결해주는 코드를 찾아서, 정리해놓는다.


tf.contrib.lookup 에 이를 지원하기 위한 함수들이 정의되어 있다.

https://www.tensorflow.org/api_docs/python/tf/contrib/lookup


배열 인덱스로 문자열로 리턴하기


코드를 보자


import tensorflow as tf


table = tf.contrib.lookup.index_to_string_table_from_tensor(

    tf.constant(['Jessica','Jolie','Kidman','Sulyun'])

)


sess = tf.InteractiveSession()


# Initialize Table

tf.tables_initializer().run()


p1 = table.lookup(tf.to_int64(3))

print p1.eval()


p2 = table.lookup(tf.to_int64([0,2]))

print p2.eval()


tf.contrib.lookup.index_to_string_from_tensor() 메서드를 이용하여, index를 string으로 lookup 하기 위한 테이블을 생성한다. 이 때 테이블에 들어가는 배열은 tf.constant로 정의해서 전달한다.


다음 이렇게 정의된 테이블을 사용하기 위해서는 테이블을 초기화 해줘야 한다. 초기화는

tf.tables_initializer().run()


를 사용한다. 이렇게 초기화 된 테이블은 세션이 시작된 후에, table.lookup($배열의 인덱스)를 호출하면, 그 인덱스에 해당하는 문자열 배열값을 리턴한다.
다음은 실행 결과이다.

Sulyun
['Jessica' 'Kidman']


문자열로 배열 인덱스 구하기


반대로 문자열로 배열의 인덱스를 리턴할 수 도 있다. 함수는 tf.contrib.lookup.index_table_from_tensor()를 이용하여, 문자열이 들어간 배열을 tf.constant 형태로 넘기면 되고, 찾을때는 마찬가지로 lookup() 함수를 이용하면 된다.


import tensorflow as tf


table = tf.contrib.lookup.index_table_from_tensor(

    mapping = tf.constant(['Jessica','Jolie','Kidman','Sulyun'])

)


sess = tf.InteractiveSession()

tf.tables_initializer().run()


p1 = table.lookup(tf.constant('Kidman'))

print p1.eval()


p2 = table.lookup(tf.constant(['Jessica','Sulyun','Soho']))

print p2.eval()