텐서플로우 하이레벨 API를 Estimator를 이용한 모델 정의 방법
텐서플로우 하이레벨 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로 구현해보도록 하겠다.