News & Events
[Tensorflow] 14. Tensorflow 시작하기 – Wide & Deep Learning
- 2019년 1월 9일
- Posted by: 인사이트캠퍼스
- Category: 금융/AI/IT 기사 블로그
이번에는 또 다른 모델인 선형 모델과 DNN 모델을 합쳐놓은 모델에 대한 튜토리얼 예제를 살펴보도록 하겠습니다.
우선 선형모델에 대하여 장점과 필요성에 대해서 문서에 잘 나와있으니 한번쯤 읽어보시길 바랍니다.
위에서 소개되어 있는 선형모델에 대하여 요약 정리하면,
선형 모델은 아주 간단하게 구현이 가능하고 학습하는 속도가 빠르기 때문에,
어떤 아이디어가 생기면 프로토타입형식으로 빠르고 간단하게 테스트를 해볼 수 있는 장점이 있습니다.
이렇게 한번 검증 절차를 거치면 상대적으로 복잡하고 긴 시간의 학습이 걸리는 모델에 들어가기 이전단계에서 충분히 가능성을 확보하고 진행을 할 수 있다는 장점이 있는 것입니다.
이러한 선형모델에서는 학습데이터에 대한 features가 중요하게 작용을 하는데,
이번 튜토리얼에서는 이러한 features를 어떻게 구분하고 다루는 것이 효과적인지에 대한 내용도 포함이 되어 있습니다.
그리고 이전에도 많이 보았던 Deep Neural Network와 혼용하여 사용할 수 있는 모델에 대해서 알아보게 됩니다.
[실행]
튜토리얼 소스는 링크를 통해서 다운로드 받을 수 있습니다. <소스 다운로드 받기>
먼저 소스를 실행을 해보고 결과를 살펴보겠습니다.
accuracy: 0.838585
accuracy/baseline_target_mean: 0.236226
accuracy/threshold_0.500000_mean: 0.838585
auc: 0.858263
global_step: 200
labels/actual_target_mean: 0.236226
labels/prediction_mean: 0.204424
loss: 0.385093
precision/positive_threshold_0.500000_mean: 0.764094
recall/positive_threshold_0.500000_mean: 0.458138
기본적으로 model_type 이 wide_n_deep 으로 되어 있기에 선형 모델과 뇌신경망 모델이 혼용된 형태로 동작을 하게 됩니다. 위 결과는 이에 대한 결과이고요.
만약 각각의 모델에서 결과를 확인해보고자 한다면, 소스상의 Flags 정의하는 부분에서 model_type을 wide 혹은 deep으로 변경하고 실행하면 됩니다.
[소스 분석]
가장 먼저 실행이 되면 train_and_eval() 함수가 수행이 됩니다.
이 함수는 제일 먼저 maybe_download() 함수를 호출하게 되는데요. 이 함수를 따라가보면 trainData와 testData를 다운로드하고 다운받은 csv 파일을 로딩하여 list 객체로 생성을 합니다.
이 튜토리얼에서 사용하게 되는 데이터는 인구통계에 따른 연봉수입에 대한 정보입니다. 인구정보에는 지역,성별,나이,종족,근무시간등의 정보가 있고 이러한 분들의 수입인 연봉에 대한 정보가 있는데 이 연봉을 구분하는 기준이 50K달러입니다. 50K달러가 넘으면 1의 값이 label이 되고 그보다 낮으면 0의 값이 Label이 되며, 이를 학습하여 인구정보에 대한 연봉을 예측하는 모델이 되겠습니다.
(https://archive.ics.uci.edu/ml/datasets/Census+Income)
def train_and_eval():
"""Train and evaluate the model."""
train_file_name, test_file_name = maybe_download()
df_train = pd.read_csv(
tf.gfile.Open(train_file_name),
names=COLUMNS,
skipinitialspace=True,
engine="python")
df_test = pd.read_csv(
tf.gfile.Open(test_file_name),
names=COLUMNS,
skipinitialspace=True,
skiprows=1,
engine="python")
그런후, 로딩한 데이터 중에서 income_bracket 컬럼의 연수입 데이터를 이용해서 50k보다 큰 데이터를 label로 사용하도록 합니다.
df_train[LABEL_COLUMN] = (
df_train["income_bracket"].apply(lambda x: ">50K" in x)).astype(int)
df_test[LABEL_COLUMN] = (
df_test["income_bracket"].apply(lambda x: ">50K" in x)).astype(int)
이제 모델이 생성되고 실행하면서 발생하는 데이터들을 로깅과 모니터링 하기 위한 임시 폴더 생성합니다.
model_dir = tempfile.mkdtemp() if not FLAGS.model_dir else FLAGS.model_dir
print("model directory = %s" % model_dir)
그런후에, build_estimator() 함수를 통해서 모델을 구성하고,
fit() 함수에서는 input_fn() 함수를 통해서 dt_train 데이터를 tensor로 변환하면서 불러와 데이터를 주입시켜 학습을 하고,
evaluate() 함수에서는 df_test 데이터를 input_fn() 함수를 통해서 tensor로 변환하면서 불러와서 평가를 하면 끝이납니다.
m = build_estimator(model_dir)
m.fit(input_fn=lambda: input_fn(df_train), steps=FLAGS.train_steps)
results = m.evaluate(input_fn=lambda: input_fn(df_test), steps=1)
위와 같이 함수로 생성을 해놓아서 간단하게 모델이 구성되고 학습 및 평가가 되는데,
build_estimator() 함수와 input_fn() 함수를 좀더 살펴보도록 하겠습니다.
먼저 build_estimator() 함수입니다.
이 함수에서는 features 의 컬럼 정보를 구분하여 생성하고, model_type에 따라서 수행하는데 사용될 estimator 를 3가지 중에서 선택되도록 하고 있습니다.
여기서 사용된 columns 에는 Sparse 와 Continuous base columns가 있는데,
이 둘의 큰 구분점은 해당 컬럼의 데이터값이 특성을 나타내는 구분자 역활(Sparse)을 하는지, 아니면 연속된 값들중에서 하나의 값을 나타내는지(Continuous)에 따라서 다른 방식으로 다루고 있습니다.
성별, 교육수준, 관계, 직업, 나라와 같은 그 사람을 구분지을 수 있는 데이터를 Sparse column으로 구분하고 이때 사용한 생성자가 sparse_column_with_keys()와 sparse_column_with_hash_bucket() 입니다. 이는 간단하게 성별과 같이 구분값이 명확하면 keys 옵션으로 직접 기입해주거나, 아니면 다양하게 구분이 되어 사이즈만 명시에 줄때 선별적으로 사용하면 됩니다.
그리고 이러한 구분자들을 정수값의 데이터로 매핑해주는 역활도 합니다.
반면에, 나이나 졸업점수, 대출금액, 근무시간 같은 연속적인 데이터중에서 하나의 값을 가지는 컬럼들은 Continuous columns으로 구분하고, 실수이므로 real_valued_column()으로 생성합니다.
그다음으로 bucketized_column() 으로 나이를 경계로 구분하여 나이대 별로 데이터를 학습하거나 뽑아보기 위해서 바운더리 정보를 사용할 수도 있습니다.
그리고 Wide columns은 1차적인 데이터 features를 혼용해서 또 다른 새로운 features를 생성하기 위하여 사용됩니다. 이를 위해서 crossed_column() 에 두가지 이상의 features를 대입하여 새로운 데이터를 생성하는 것을 볼 수 있습니다. 이것은 연관되어 있는 두가지 정보가 각각 분석되는 것을 방지하고 하나의 새로운 정보로서 학습이 되도록 하기 위하여 사용됩니다.
또, Deep columns는 Sparse columns 정보들을 높은 차원의 데이터를 변환하여 DNN에서 사용하도록 합니다. 이를 위해서 embedding_column()을 이용해서 8차원의 데이터로 정의하고 있습니다.
이렇게 특성에 따라서 Columns 들을 구분하여 Tensor로 변환하면서, 필요에 따라 새로운 Feature도 생성하거나 Embedding 할 수 있는 방법을 보여주고 있습니다.
그리고 estimator는 타입에 따라서 3가지 모델중에서 하나가 선택되어 수행이 됩니다. (LinearClassifier, DNNClassifier, DNNLinearCombinedClassifier)
def build_estimator(model_dir):
"""Build an estimator."""
# Sparse base columns.
gender = tf.contrib.layers.sparse_column_with_keys(column_name="gender",
keys=["female", "male"])
education = tf.contrib.layers.sparse_column_with_hash_bucket(
"education", hash_bucket_size=1000)
relationship = tf.contrib.layers.sparse_column_with_hash_bucket(
"relationship", hash_bucket_size=100)
workclass = tf.contrib.layers.sparse_column_with_hash_bucket(
"workclass", hash_bucket_size=100)
occupation = tf.contrib.layers.sparse_column_with_hash_bucket(
"occupation", hash_bucket_size=1000)
native_country = tf.contrib.layers.sparse_column_with_hash_bucket(
"native_country", hash_bucket_size=1000)
# Continuous base columns.
age = tf.contrib.layers.real_valued_column("age")
education_num = tf.contrib.layers.real_valued_column("education_num")
capital_gain = tf.contrib.layers.real_valued_column("capital_gain")
capital_loss = tf.contrib.layers.real_valued_column("capital_loss")
hours_per_week = tf.contrib.layers.real_valued_column("hours_per_week")
# Transformations.
age_buckets = tf.contrib.layers.bucketized_column(age,
boundaries=[
18, 25, 30, 35, 40, 45,
50, 55, 60, 65
])
# Wide columns and deep columns.
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(
[age_buckets, education, occupation],
hash_bucket_size=int(1e6)),
tf.contrib.layers.crossed_column([native_country, occupation],
hash_bucket_size=int(1e4))]
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,
]
if FLAGS.model_type == "wide":
m = tf.contrib.learn.LinearClassifier(model_dir=model_dir,
feature_columns=wide_columns)
elif FLAGS.model_type == "deep":
m = tf.contrib.learn.DNNClassifier(model_dir=model_dir,
feature_columns=deep_columns,
hidden_units=[100, 50])
else:
m = tf.contrib.learn.DNNLinearCombinedClassifier(
model_dir=model_dir,
linear_feature_columns=wide_columns,
dnn_feature_columns=deep_columns,
dnn_hidden_units=[100, 50])
return m
그리고 또 특이한 함수가 input_fn() 인데,
이 함수는 데이터의 컬럼별로 SparseTensor 혹은 constantTensor로 변환하여 생성하고 이들 데이터를 dictionary로 만들어 줍니다.
SparseTensor 가 되는 데이터들은 카테고리성의 데이터로 사람을 구분하는 정보들이 되어야 하며, constantTensor 가 되는 데이터들은 정수 값을 갖는 데이터들이 됩니다.
이렇게 생성된 feature_cols와 label 데이터를 return 해주는 함수입니다.
이 함수의 특이한 점 또 한가지는, 이 함수가 실행되는 시점이 그래프가 실행 되는 시점이 아니라 그래프가 구성되는 중간에 호출이 된다는 점입니다. 이를 잘 활용하면 데이터를 로딩하고 변환하는 시간을 더욱 단축할 수도 있을 것 같습니다.
def input_fn(df):
"""Input builder function."""
# Creates a dictionary mapping from each continuous feature column name (k) to
# the values of that column stored in a constant Tensor.
continuous_cols = {k: tf.constant(df[k].values) for k in CONTINUOUS_COLUMNS}
# Creates a dictionary mapping from each categorical feature column name (k)
# to the values of that column stored in a tf.SparseTensor.
categorical_cols = {k: tf.SparseTensor(
indices=[[i, 0] for i in range(df[k].size)],
values=df[k].values,
shape=[df[k].size, 1])
for k in CATEGORICAL_COLUMNS}
# Merges the two dictionaries into one.
feature_cols = dict(continuous_cols)
feature_cols.update(categorical_cols)
# Converts the label column into a constant Tensor.
label = tf.constant(df[LABEL_COLUMN].values)
# Returns the feature columns and the label.
return feature_cols, label
이렇게 하여 컬럼 데이터를 보다 효과적으로 관리하고 input data를 효과적으로 로딩하고 변환하는 것을 통해서 선형모델과 DNN모델을 사용하는 방법에 대해서 알아보았습니다.