728x90
반응형

 이전 포스트에서는 Wide & Deep Learning 모델에 대한 간단한 설명과 함수형 API를 사용해서 기초적인 Wide & Deep Learning 모델을 만들어보았다. 이때는 완전히 동일한 Input 데이터를 바라보았으나, Wide & Deep Learning 모델은 다른 Input Layer를 만들어 Wide model과 Deep model에 따로 학습시킬 수 있다.

 

 

1. 다중 입력 Wide & Deep Learning

  • Wide & Deep Learning model의 특징은 Wide model에 전달되는 Feature와 Deep model에 전달되는 Feature를 다르게 할 수 있다는 것이다.
  • 즉, 입력되는 Feature에서 일부 특성은 Wide model로 나머지 특성은 Deep model로 전달할 수 있다.
    (각 모델로 전달되는 특성은 중복 가능하다).
  • Deep model은 입력된 모든 데이터를 MLP에 있는 모든 층에 통과시키다 보니, 간단한 패턴이 연속된 변환으로 인해 왜곡될 위험이 있다.
  • 때문에 Wide & Deep Learning model에서는 일반적으로 간단한 패턴이 있는 Column은 Wide model 쪽으로, 복잡한 패턴이 있는 Column은 Deep model 쪽으로 보낸다.
  • 간단한 패턴이 있어 Wide model로 보낸 Column 역시 Deep model에서 숨겨진 패턴을 찾는데 도움이 될 수 있으므로, Wide model로 Input 될 데이터의 일부 혹은 전체를 Deep model에 같이 넣기도 한다.
  • 이를 도식화해보면 다음과 같다.

  • 위 모델을 보면 Input Layer가 2개 존재하는 것을 볼 수 있는데, 이처럼 한 번에 2개 이상의 Input이 존재하여 동시에 여러 입력을 하는 것을 다중 입력 모델이라 한다.
  • 다중 입력 모델은 Keras의 함수형 API를 사용하면 쉽게 구현할 수 있다.

 

 

 

 

2. 데이터의 각 변수별 상세 정보 파악

  • 이전 포스팅에서 해당 모델을 Input Layer 1개로 넣었을 때, 이미 Accuracy 0.0034, Loss 0.0242로 꽤 괜찮은 결과가 나왔었다. 굳이 데이터를 나눠서 넣을 필요는 없으나, Keras API를 이용해서 다중 입력하는 방법을 학습하기 위해, 데이터 셋을 쪼개 보도록 하겠다.
  • 해당 포스트의 참고 논문(Heng-Tze Cheng et al.(2016))에서 예시로 든 단순한 패턴은 "설치한 앱"과 "열람한 앱"을  Cross-product transformation으로 생성한 희소 특징(Sparse feature)이다.
  • 그러나, 해당 데이터셋에는 그 대상이 될 수 있는 범주형 데이터가 따로 없으므로, 데이터 패턴을 보는 가장 기초적인 방법인 히스토그램과 독립변수와 종속변수 간의 산점도(상관관계)를 만들어, 패턴이 아주 단순한 경우는 Wide model로, 그렇지 않은 경우는 Deep model로 넣어보겠다.

 

# Import Module
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import callbacks
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.layers import (Input, Dense, Concatenate, concatenate)

 

2.1. 데이터 셋 정보 보기

  • sklearn에서 기본으로 제공하는 데이터에서는 아주 쉽게 메타 정보를 볼 수 있다.
>>> Rawdict = fetch_california_housing()
>>> print(Rawdict.DESCR)
.. _california_housing_dataset:

California Housing dataset
--------------------------

**Data Set Characteristics:**

    :Number of Instances: 20640

    :Number of Attributes: 8 numeric, predictive attributes and the target

    :Attribute Information:
        - MedInc        median income in block
        - HouseAge      median house age in block
        - AveRooms      average number of rooms
        - AveBedrms     average number of bedrooms
        - Population    block population
        - AveOccup      average house occupancy
        - Latitude      house block latitude
        - Longitude     house block longitude

    :Missing Attribute Values: None

This dataset was obtained from the StatLib repository.
http://lib.stat.cmu.edu/datasets/

The target variable is the median house value for California districts.

This dataset was derived from the 1990 U.S. census, using one row per census
block group. A block group is the smallest geographical unit for which the U.S.
Census Bureau publishes sample data (a block group typically has a population
of 600 to 3,000 people).

It can be downloaded/loaded using the
:func:`sklearn.datasets.fetch_california_housing` function.

.. topic:: References

    - Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,
      Statistics and Probability Letters, 33 (1997) 291-297
  • 캘리포니아 주택 데이터 셋의 각 Row인 객체는 블록이다.
  • MedInc: 소득의 중앙값
  • HouseAge: 건물 연령 중앙값
  • AveRooms: 평균 객실 수
  • AveBedrms: 평균 침실 수
  • Population: 인구수
  • AveOccup: 평균 주택 점유율
  • Latitude: 위도
  • Longitude: 경도
  • 종속변수(Target): MedHouseVal: 캘리포니아 지역의 주택 가격 중앙값

 

2.1. 변수별 히스토그램 보기

# 독립변수의 히스토그램 그리기
CaliFornia_DF = pd.DataFrame(Rawdict.data, columns=Rawdict.feature_names)
CaliFornia_DF.hist(figsize=(12,8), linewidth=3)
plt.show()

# 종속변수 히스토그램 그리기
plt.hist(Rawdict.target)
plt.show()

 

2.2 변수별 종속변수와의 산점도

fig, axes = plt.subplots(nrows=4, ncols=2, figsize = (12, 20))
ax = axes.ravel()

xlabels = Rawdict.feature_names
plt.suptitle("Predict Variable & MedHouseVal Scatter", y=0.93, fontsize=25)

for i in range(Rawdict.data.shape[1]):

    X = Rawdict.data[:,i]
    Y = Rawdict.target

    ax[i].scatter(X, Y, alpha = 0.025)
    ax[i].set_title(xlabels[i], fontsize=15, fontweight ="bold")

  • Row가 20,640으로 단순하게 산점도로 그리기엔 그 양이 너무 많아, alpha를 0.025로 주어, 데이터가 동일한 위치에 많이 존재하지 않는다면 연하게, 많이 겹칠수록 진하게 그려지도록 설정하였다.
  • 각 변수별 히스토그램과 독립변수 & 종속변수의 산점도를 볼 때, 단순한 패턴을 갖는 것은 AveRooms, AveBedrms, Population, AveOccup로 이들은 히스토그램에서도 데이터가 한쪽에 지나치게 모여있기 때문에 딱히 패턴이 존재하지 않는다. 그러므로, 이들을 Wide model에 넣겠다.
  • MedInc은 그 정도가 강하다고 할 수 있는 수준은 아니지만, 위 데이터 중 가장 뚜렷한 경향성을 가지고 있다. 이를 단순한 패턴으로 인식할지도 모르니, Wide model과 Deep model 양쪽에 넣어보자.

 

 

 

 

3. 데이터셋 쪼개기

  • 입력층이 2개이므로, Train dataset, Validation dataset, Test dataset 모두 2개로 쪼개져야 한다.
  • 출력층은 1개이므로, Label은 쪼개지 않아도 된다.
# 데이터를 쪼개기 좋게 변수의 순서를 바꾸자
CaliFornia_DF = CaliFornia_DF[["HouseAge", "Latitude", "Longitude", "MedInc",
                               "AveRooms", "AveBedrms", "Population", "AveOccup"]]

# train, validation, test set으로 쪼갠다.
X_train_all, X_test, y_train_all, y_test = train_test_split(CaliFornia_DF.values, Rawdict.target, test_size = 0.3)
X_train, X_valid, y_train, y_valid = train_test_split(X_train_all, y_train_all, test_size = 0.2)

# 정규화시킨다.
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_valid = scaler.transform(X_valid)
X_test = scaler.transform(X_test)

# 데이터 셋을 input layer의 수만큼 쪼갠다.
X_train_A, X_train_B = X_train[:, :4], X_train[:,3:]
X_valid_A, X_valid_B = X_valid[:, :4], X_valid[:,3:]
X_test_A, X_test_B = X_test[:, :4], X_test[:,3:]
>>> X_train_A.shape
(11558, 4)

>>> X_train_B.shape
(11558, 5)

 

 

 

 

4. 모델 생성하기

input_A = Input(shape=[4], name = "deep_input")
input_B = Input(shape=[5], name = "wide_input")
hidden1 = Dense(30, activation="relu", name = "hidden1")(input_A)
hidden2 = Dense(30, activation="relu", name = "hidden2")(hidden1)
concat = concatenate([input_B, hidden2], name = "concat")
output = Dense(1, name="output")(concat)
model = keras.Model(inputs=[input_A, input_B], outputs=[output])
  • 다중 입력 모델 생성 시, 주의사항은 층을 연결하는 부분인 concatenate layer다.
  • 이전 포스트에서는 Concatenate layer를 사용하였고, 이번 포스트에선 맨 앞이 소문자인 concatenate layer를 사용하였는데, 둘 사이의 차이는 inputs(At least 2)라는 파라미터 여부다.
  • 참고 1: tf.keras.layers.concatenate 공식 API 문서
  • 참고 2: tf.keras.layers.Concatenate 공식 API 문서
>>> model.summary()

Model: "model"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
deep_input (InputLayer)         [(None, 4)]          0                                            
__________________________________________________________________________________________________
hidden1 (Dense)                 (None, 30)           150         deep_input[0][0]                 
__________________________________________________________________________________________________
wide_input (InputLayer)         [(None, 5)]          0                                            
__________________________________________________________________________________________________
hidden2 (Dense)                 (None, 30)           930         hidden1[0][0]                    
__________________________________________________________________________________________________
concat (Concatenate)            (None, 35)           0           wide_input[0][0]                 
                                                                 hidden2[0][0]                    
__________________________________________________________________________________________________
output (Dense)                  (None, 1)            36          concat[0][0]                     
==================================================================================================
Total params: 1,116
Trainable params: 1,116
Non-trainable params: 0
__________________________________________________________________________________________________

 

 

 

 

5. 모델 학습 및 평가하기

# 모델 컴파일
model.compile(optimizer=Adam(learning_rate=0.005),
              loss = "msle",
              metrics=["accuracy"])

early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=30, restore_best_weights=True)

# 학습
history = model.fit((X_train_A, X_train_B), y_train,epochs=300,
                    validation_data=((X_valid_A, X_valid_B), y_valid),callbacks=[early_stop])
Epoch 1/300
362/362 [==============================] - 2s 5ms/step - loss: 0.1601 - accuracy: 0.0015 - val_loss: 0.0413 - val_accuracy: 0.0028
Epoch 2/300
362/362 [==============================] - 1s 1ms/step - loss: 0.0390 - accuracy: 0.0018 - val_loss: 0.0376 - val_accuracy: 0.0028
Epoch 3/300
362/362 [==============================] - 0s 1ms/step - loss: 0.0367 - accuracy: 0.0017 - val_loss: 0.0364 - val_accuracy: 0.0028
Epoch 4/300
362/362 [==============================] - 0s 1ms/step - loss: 0.0342 - accuracy: 0.0022 - val_loss: 0.0354 - val_accuracy: 0.0028

...

Epoch 277/300
362/362 [==============================] - 1s 1ms/step - loss: 0.0256 - accuracy: 0.0029 - val_loss: 0.0291 - val_accuracy: 0.0028
Epoch 278/300
362/362 [==============================] - 1s 1ms/step - loss: 0.0247 - accuracy: 0.0020 - val_loss: 0.0290 - val_accuracy: 0.0028
Epoch 279/300
362/362 [==============================] - 0s 1ms/step - loss: 0.0258 - accuracy: 0.0027 - val_loss: 0.0295 - val_accuracy: 0.0028
Epoch 280/300
362/362 [==============================] - 0s 1ms/step - loss: 0.0256 - accuracy: 0.0019 - val_loss: 0.0288 - val_accuracy: 0.0028
>>> model.evaluate((X_test_A, X_test_B), y_test)
194/194 [==============================] - 0s 753us/step - loss: 0.0300 - accuracy: 0.0040
[0.02998056262731552, 0.004037467762827873]
def Drawing_Scalars(history_name):
    
    history_DF = pd.DataFrame(history_name.history)
    # 그래프의 크기와 선의 굵기를 설정해주었다.
    history_DF.plot(figsize=(12, 8), linewidth=3)

    # 교차선을 그린다.
    plt.grid(True)

    plt.legend(loc = "upper right", fontsize =15)

    plt.title("Learning Curve", fontsize=30, pad = 30)
    plt.xlabel('Epoch', fontsize = 20, loc = 'center', labelpad = 20)
    plt.ylabel('Variable', fontsize = 20, rotation = 0, loc='center', labelpad = 40)

    # 위 테두리 제거
    ax=plt.gca()
    ax.spines["right"].set_visible(False) # 오른쪽 테두리 제거
    ax.spines["top"].set_visible(False) # 위 테두리 제거
    
    plt.show()
    
    
Drawing_Scalars(history)

  • 이전 포스트에서는 모든 데이터 셋을 Wide model과 Deep model에 넣었을 때는 Accuracy: 0.0034, Loss: 0.0242, epochs: 63에서 학습이 끝났으나, 이번 방법으로는 Accuracy: 0.0040, Loss: 0.0300, epochs: 280으로 기대에 미치는 성능이 나오진 않았다.
  • 학습에 대한 평가는 큰 차이가 없으나, 도리어 epochs가 63에서 280으로 학습 시간이 크게 늘어, 성능이 도리어 크게 떨어진 모습을 보여주었다.

 

 

 

 

6. 위 문제를 해결해보자.

  • 조기종료가 된 epochs가 63에서 280으로 증가했다는 소리는 주어진 데이터 셋이 이전보다 적합하지 않아, 학습하는데 시간이 오래 걸렸다는 소리다.
  • 그렇다면, 위에서 보았던 독립변수의 데이터 분포(히스토그램)와 독립변수와 종속변수의 상관관계(산점도)로 데이터 패턴의 단순한 정도를 본 방법이 틀린 것일까?
  • 앞서 우리가 학습하였던, "Tensorflow-4.0. 다층 퍼셉트론을 이용한 회귀모형 만들기"에서 회귀모형(Regression model)은 독립변수와 종속변수 간에 어떠한 경향성이 있다는 전제하에 독립변수가 변할 때, 종속변수가 변하고 이를 가장 잘 나타내는 계수를 찾는 것이 목적이라고 했다.
  • 딥러닝 역시 회귀모형의 원리와 매우 유사하게 돌아가므로, 이번에는 독립변수와 종속변수의 산점도(상관관계)에서 어떠한 경향성도 갖지 않는 "HouseAge", "AveRooms", "AveBedrms", "Population", "AveOccup", "Latitude", "Longitude" 모두를 Wide model에 넣고, 약한 선형 관계를 보이는 "MedInc"만 Deep model에 넣어보도록 하겠다.
# Dataset 생성
# 데이터를 쪼개기 좋게 변수의 순서를 바꾸자
CaliFornia_DF = CaliFornia_DF[["HouseAge", "Population", "Latitude", "Longitude", "MedInc",
                               "AveRooms", "AveBedrms", "AveOccup"]]

# train, validation, test set으로 쪼갠다.
X_train_all, X_test, y_train_all, y_test = train_test_split(CaliFornia_DF.values, Rawdict.target, test_size = 0.3)
X_train, X_valid, y_train, y_valid = train_test_split(X_train_all, y_train_all, test_size = 0.2)

# 정규화시킨다.
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_valid = scaler.transform(X_valid)
X_test = scaler.transform(X_test)

# 데이터 셋을 input layer의 수만큼 쪼갠다.
X_train_A, X_train_B = X_train[:, :1], X_train[:,1:]
X_valid_A, X_valid_B = X_valid[:, :1], X_valid[:,1:]
X_test_A, X_test_B = X_test[:, :1], X_test[:,1:]
# Model 
input_A = Input(shape=[1], name = "deep_input")
input_B = Input(shape=[7], name = "wide_input")
hidden1 = Dense(30, activation="relu", name = "hidden1")(input_A)
hidden2 = Dense(30, activation="relu", name = "hidden2")(hidden1)
concat = concatenate([input_B, hidden2], name = "concat")
output = Dense(1, name="output")(concat)
model = keras.Model(inputs=[input_A, input_B], outputs=[output])


# 모델 컴파일
model.compile(optimizer=Adam(learning_rate=0.005),
              loss = "msle",
              metrics=["accuracy"])

early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=30, restore_best_weights=True)

# 학습
history = model.fit((X_train_A, X_train_B), y_train,epochs=300,
                    validation_data=((X_valid_A, X_valid_B), y_valid),callbacks=[early_stop])
Epoch 1/300
362/362 [==============================] - 2s 5ms/step - loss: 0.2047 - accuracy: 0.0032 - val_loss: 0.0548 - val_accuracy: 0.0014
Epoch 2/300
362/362 [==============================] - 1s 1ms/step - loss: 0.0520 - accuracy: 0.0031 - val_loss: 0.0470 - val_accuracy: 0.0014
Epoch 3/300
362/362 [==============================] - 1s 2ms/step - loss: 0.0477 - accuracy: 0.0036 - val_loss: 0.0451 - val_accuracy: 0.0014
Epoch 4/300
362/362 [==============================] - 1s 2ms/step - loss: 0.0439 - accuracy: 0.0041 - val_loss: 0.0443 - val_accuracy: 0.0014

...

Epoch 46/300
362/362 [==============================] - 0s 1ms/step - loss: 0.0438 - accuracy: 0.0019 - val_loss: 0.0438 - val_accuracy: 0.0014
Epoch 47/300
362/362 [==============================] - 0s 1ms/step - loss: 0.0446 - accuracy: 0.0036 - val_loss: 0.0439 - val_accuracy: 0.0014
Epoch 48/300
362/362 [==============================] - 0s 1ms/step - loss: 0.0446 - accuracy: 0.0033 - val_loss: 0.0444 - val_accuracy: 0.0014
Epoch 49/300
362/362 [==============================] - 0s 1ms/step - loss: 0.0430 - accuracy: 0.0029 - val_loss: 0.0440 - val_accuracy: 0.0014
>>> model.evaluate((X_test_A, X_test_B), y_test)
194/194 [==============================] - 0s 966us/step - loss: 0.0468 - accuracy: 0.0024
[0.04679189994931221, 0.002422480611130595]

>>> Drawing_Scalars(history)

  • Deep model로는 1개의 변수만 집어넣었고, Wide model로 7개 변수를 넣었더니, Accuracy: 0.0024, Loss: 0.0468, epochs: 49(EarlyStopping의 patience인자가 30이므로, 실제 수렴은 epoch 19에서 한 것을 알 수 있다)로 수렴하였다.
  • 모든 변수를 Deep model과 Wide model로 보내는 경우, Accuracy: 0.0034, Loss: 0.0242, epochs: 63으로, 정확도가 미미한 수준으로 더 좋아졌고, 수렴 속도가 보다 빨라졌으나, 큰 차이가 있진 않다.
  • Deep Learning은 아주 많은 양의 데이터가 있을 때, 그 안에 우리가 인식하지 못하는 패턴을 찾아내는 것이므로, 위와 같은 밀집 특징(Dense Feature)에 대해서는 모든 데이터를 넣는 것을 추천한다.
  • 희소 특징(Sparse Feature)은 Deep model을 통과하며, 학습이 제대로 이루어지지 않을 수 있으므로, Wide model에는 희소 특징만 넣도록 하자.

 

 

 

 

7. 참고 - 일반적인 다중 입력 모델의 사용 예

  • 위 그림처럼 다중 입력은, 전혀 다른 데이터 셋 들로부터 하나의 결과를 도출해낼 수도 있다.
  • 예를 들어, 물건 판매 웹사이트 사용자들에 대해 패턴을 뽑고자 한다면, 사용자들의 정보는 Dense 모델로, 사용자의 제품 후기는 RNN 모델로, 사용자의 관심 있는 제품 사진들을 CNN 모델로 학습시키고 이를 하나로 합쳐 하나의 결괏값을 뽑아낼 수도 있다.

 

 

[참고 자료]

arxiv.org/abs/1606.07792

 

Wide & Deep Learning for Recommender Systems

Generalized linear models with nonlinear feature transformations are widely used for large-scale regression and classification problems with sparse inputs. Memorization of feature interactions through a wide set of cross-product feature transformations are

arxiv.org

ai.googleblog.com/2016/06/wide-deep-learning-better-together-with.html

 

Wide & Deep Learning: Better Together with TensorFlow

Posted by Heng-Tze Cheng, Senior Software Engineer, Google Research The human brain is a sophisticated learning machine, forming rules by me...

ai.googleblog.com

 

 

 지금까지 Wide & Deep Learning model을 Input Layer를 2개로 하여 다중 입력 모델로 만들어보았다. Keras API로 모델을 만드는 경우, 위처럼 쉬운 방법으로 다중 입력, 다중 출력 모델을 만들 수 있으므로, Keras로 모델을 만들 땐, 가능한 API로 만들도록 하자.

 지금까지 Wide & Deep Learning model의 개념을 어느 정도 알아보았으니, 다음 포스트에서는 좀 더 가볍게 해당 모델로 다중 입력, 다중 출력 모델을 만들어보도록 하겠다.

728x90
반응형

+ Recent posts