]TensorFlow2[ Keras Model과 Callback 으로 비선형회귀

통계/Tensorflow2.0 예제

2020. 11. 28.

# !pip install seaborn
# !pip install tensorflow==2.2.0

Import the required modules

import numpy as np
import tensorflow as tf
from tensorflow import keras as ks
from keras.layers import Dense
from sklearn.model_selection import train_test_split

Define the training callbacks

  • 학습결과를 출력/분석하는 함수 작성
    • 손실율을 출력하는 on_epoch_end 메서드
    • 손실율 그래프를 그리는 on_train_end 메서드
  • 콜백기반
class CustomCallback(ks.callbacks.Callback):

    def __init__(self):
        self.losses = {}

    def on_epoch_end(self, epoch, logs):
        self.losses[epoch] = logs['loss']
        print('\n==========\n')
        print('Epoch:', epoch)
        print('Training Loss:', logs['loss'])
        print('Validation Loss:', logs['val_loss'])

    def on_train_end(self, logs):
        import matplotlib.pyplot as plt
        plt.plot(self.losses.keys(), self.losses.values())
        plt.xlabel('Training Epochs')
        plt.ylabel('Training Loss')
        plt.ylim([0, 2]) #수렴 여부를 관찰하기 위해 좁은 뷰 설정
        plt.show()

        self.losses = {}

Declare the model configurations

base_config = {
    'model_path': 'model_path',
    'model_version': '0', 
    'dataset_size': 100000,
    'test_ratio': 0.33,
    'activation': 'relu',
    'hiddens': [1],
    'epochs': 50,
    'batch_size': 1000,
    'loss_function': 'mean_squared_error',
    'optimizer': 'adam',
    'train_callbacks': [CustomCallback()]
}

from copy import copy

config_hidden_1 = copy(base_config)
config_hidden_1['model_version'] = '1'
config_hidden_1['epochs'] = 300
config_hidden_1['hiddens'] = [11, 1]

config_hidden_2 = copy(base_config)
config_hidden_2['model_version'] = '2'
config_hidden_2['hiddens'] = [33, 11, 1]

config_hidden_3 = copy(base_config)
config_hidden_3['model_version'] = '3'
config_hidden_3['hiddens'] = [99, 33, 11, 1]

config_hidden_4_narrowing = copy(base_config)
config_hidden_4_narrowing['model_version'] = '4'
config_hidden_4_narrowing['hiddens'] = [297, 99, 33, 11, 1]

config_hidden_4_expanding = copy(base_config)
config_hidden_4_expanding['model_version'] = '5'
config_hidden_4_expanding['hiddens'] = [11, 33, 99, 297, 1]

Define the cubic function

  • 데이터 샘플을 생성하는 함수 작성
def cubic_generator(a, b, c, d, e):
    def cubic(x):
        '''
        입력: 3차원 벡터
        출력: 입력 벡터에 대한 3차 함수 계산 값
        '''
        x1, x2, x3 = x[0], x[1], x[2]
        return a + b*x1 + c*x2**2 + d*x3**3 + e
    return cubic

cubic_function = cubic_generator(1, 3, 5, 10, 20)

Create the train/test dataset with the cubic function

xs = np.random.uniform(0, 1, (base_config['dataset_size'], 3))
ys = [cubic_function(x) for x in xs]
x_train, x_test, y_train, y_test = train_test_split(xs, ys, test_size=base_config['test_ratio'])

Define the training/evalutaion phase

  • KERAS NN으로 model을 구성하는 함수 작성
  • KERAS NN을 학습시키는 함수 작성
def train_and_eval(x_train, y_train, x_test, y_test, config):
    x_train = np.array(x_train, dtype='float64')
    y_train = np.array(y_train, dtype='float64')
    x_test = np.array(x_test, dtype='float64')
    y_test = np.array(y_test, dtype='float64')

    model = ks.models.Sequential()
    hiddens = config['hiddens']

    for i, hidden in enumerate(hiddens):
        if 1 == len(hiddens) or 0 == i:
            model.add(Dense(hidden, activation=config['activation'], input_shape=(3,)))
        elif i == len(hiddens)-1:
            model.add(Dense(hidden))
        else:
            model.add(Dense(hidden, activation=config['activation']))

    model.compile(loss=config['loss_function'], optimizer=config['optimizer'])
    print(model.summary())

    model.fit(x_train, y_train,
             batch_size=config['batch_size'],
             epochs=config['epochs'],
             verbose=0,
             validation_data=(x_test, y_test),
             callbacks=[CustomCallback()])

    return model
model = train_and_eval(x_train, y_train, x_test, y_test, config_hidden_1)
model = train_and_eval(x_train, y_train, x_test, y_test, config_hidden_2)
model = train_and_eval(x_train, y_train, x_test, y_test, config_hidden_3)
model = train_and_eval(x_train, y_train, x_test, y_test, config_hidden_4_narrowing)
model = train_and_eval(x_train, y_train, x_test, y_test, config_hidden_4_expanding)

Define the prediction function

  • 학습된 model에 임의의 입력변수를 입력으로 받아서, y값을 예측하는 함수 작성
def predict(model, x):
    x = np.array(x, dtype='float64')
    yh = model.predict(x)
    return yh
for x in x_test[1:5]:
    y_real = cubic_function(x)
    y_pred = predict(model, [x])
    print('Input:', x)
    print('Target:', y_real)
    print('Prediction:', y_pred.squeeze(), end='\n\n')

Summary

데이터 샘플 생성방법

  • cubic_genrator(a, b, c, d, e)

    :삼차함수를 정의한다. Python 데코레이터 패턴을 사용하여 함수의 계수 값들을 외부에서 설정받는다.

def cubic_generator(a, b, c, d, e):
    def cubic(x):
        '''
        입력: 3차원 벡터
        출력: 입력 벡터에 대한 3차 함수 계산 값
        '''
        x1, x2, x3 = x[0], x[1], x[2]
        return a + b*x1 + c*x2**2 + d*x3**3 + e
    return cubic
  • 데이터 랜덤 샘플

    : 3차원 벡터 원소는 균등분포에 따라 0~1 사이값을 랜덤으로 갖도록 했다. 이런 벡터를 dataset_size 갯수만큼 생성한다. 각 벡터에 대응되는 y값은 삼차함수를 호출하여 만든다.

    xs = np.random.uniform(0, 1, (base_config['dataset_size'], 3))
    ys = [cubic_function(x) for x in xs]

Epoch 수

  • 에폭 수 설정
    • 일반적으로 에폭 수가 증가할 수록 모든 모델의 손실율은 더욱 잘 감소하였다.
    • 모델이 깊지 않은 경우(1층) 에폭 수를 크게 잡아도(300) 손실 그래프의 기울기는 평평해지지 않았다
    • 모델이 깊을 때(4층), 초반 에폭에 모델이 이미 급격히 수렴되어 평평한 손실 그래프를 보였다
    • 학습 시간이 과하지 않도록 깊은 모델에 대해 적당한 에폭 수(50)를 쓰기로 했다

Hidden layer 수와 neuron 수

config_hidden_1['hiddens'] = [11, 1]

config_hidden_2['hiddens'] = [33, 11, 1]

config_hidden_3['hiddens'] = [99, 33, 11, 1]

config_hidden_4_narrowing['hiddens'] = [297, 99, 33, 11, 1]

config_hidden_4_expanding['hiddens'] = [11, 33, 99, 297, 1]
  • 히든 레이어 설정
    • 1, 2, 3, 4 깊이를 갖는 DNN 모델을 준비하였다. 삼차함수는 non-convex 하므로 히든 레이어를 깊게 쌓아 활성화하면 더 좋은 성능을 낼 것으로 기대했다.
    • 입력된 3차원 점의 내재된 의미를 모델이 파악할 수 있도록 고차원 공간으로 사상한다
    • 입력 맥락이 가장 잘 남아있는 Input Layer에서 최고차원으로 사상

Activation 함수 선정

  • sigmoid, tanh 함수 사용 시
    • 4층 짜리 모델이 수렴 되지 않고 1층 짜리 모델과 비슷한 성능을 내는 현상을 관찰하였다
  • relu 활성화함수 사용 시
    • Vanishing Gradient 문제를 해결하는 ReLU를 사용했다. 덕분에 4층짜리 모델의 학습이 진행된다.
    • 또 가장 빠른 수렴속도를 보였기에 ReLU를 활성함수로 채택하였다

The best model

config_hidden_4_narrowing

config_hidden_4_narrowing = {
    'model_path': 'model_path',
    'model_version': '4', 
    'dataset_size': 100000,
    'test_ratio': 0.33,
    'activation': 'relu',
    'hiddens': [297, 99, 33, 11, 1],
    'epochs': 50,
    'batch_size': 1000,
    'loss_function': 'mean_squared_error',
    'optimizer': 'adam',
    'train_callbacks': [CustomCallback()]
}
Epoch: 49
Training Loss: 0.004180582240223885
Validation Loss: 0.004152956418693066
  • Vanishing Gradient를 해결하는 ReLU 활성함수 사용
  • Non-convex 함수에 잘 fitting 하기 위해서 깊은 히든 레이어 사용
  • Input Layer에서부터 입력 확장하며 중요한 정보를 압축해 나가도록 함
  • 가중치 갱신 시 모멘텀을 이용하고, 가중치 별 학습률을 조정하는 Adam 옵티마이저 사용

'통계 > Tensorflow2.0 예제' 카테고리의 다른 글

]TensorFlow2[ Estimator를 사용한 비선형회귀  (0) 2020.11.28