1. modules import

import tensorflow as tf
from tensorflow.keras.datasets.boston_housing import load_data
from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.utils import plot_model

from sklearn.model_selection import train_test_split

import numpy as np
import matplotlib.pyplot as plt

 

 

2. 데이터 로드

  • 데이터의 수가 적기 때문에 테스트 데이터의 비율을 20%로 지정
  • 13개의 특성을 가짐
  • 각각의 특성이 모두 다른 스케일, 즉 단위가 모두 다름
    • 범죄율: 0~1 사이의 값
    • 방의 개수: 3~9 사이의 값
  • 정답 레이블은 주택 가격의 중간 가격($1000 단위)
tf.random.set_seed(111)
(x_train_full, y_train_full), (x_test, y_test) = load_data(path = 'boston_housing.npz',
                                                           test_split = 0.2,
                                                           seed = 111)
# 가장 첫번째 train 데이터의 독립변수들
print(x_train_full[0])

# 출력 결과
[2.8750e-02 2.8000e+01 1.5040e+01 0.0000e+00 4.6400e-01 6.2110e+00
 2.8900e+01 3.6659e+00 4.0000e+00 2.7000e+02 1.8200e+01 3.9633e+02
 6.2100e+00]
# 가장 첫번째 train 데이터의 종속변수
print(y_train_full[0])

# 출력 결과
25.0

 

 

3. 데이터 확인

print('학습 데이터: {}\t레이블: {}'.format(x_train_full.shape, y_train_full.shape))
print('테스트 데이터: {}\t레이블: {}'.format(x_test.shape, y_test.shape))

# 출력 결과
학습 데이터: (404, 13)	레이블: (404,)
테스트 데이터: (102, 13)	레이블: (102,)

 

 

4. 데이터 전처리

  • standardization
  • 특성의 단위가 모두 다르기 때문에 동일한 범위로 조정
# 정규화하기 위해 평균과 표준편차를 구한 후, 각 값에서 평균을 빼고 표준편차로 나눠줌
mean = np.mean(x_train_full, axis = 0)
std = np.std(x_train_full, axis = 0)

x_train_preprocessed = (x_train_full - mean) / std
x_test = (x_test - mean) / std

x_train, x_val, y_train, y_val = train_test_split(x_train_preprocessed, y_train_full, test_size = 0.3, random_state = 111)
print("학습 데이터: {}\t레이블: {}".format(x_train_full.shape, y_train_full.shape))
print("학습 데이터: {}\t레이블: {}".format(x_train.shape, y_train.shape))
print("검증 데이터: {}\t레이블: {}".format(x_val.shape, y_val.shape))
print("테스트 데이터: {}\t레이블: {}".format(x_test.shape, y_test.shape))

# 출력 결과
학습 데이터: (404, 13)	레이블: (404,)
학습 데이터: (282, 13)	레이블: (282,)
검증 데이터: (122, 13)	레이블: (122,)
테스트 데이터: (102, 13)	레이블: (102,)

 

 

5. 모델 구성

  • 학습 데이터가 매우 적은 경우에 모델의 깊이를 깊게 할수록 과대적합이 일어날 확률이 높음
model = Sequential([Dense(100, activation = 'relu', input_shape = (13, ), name = 'dense1'),
                    Dense(64, activation = 'relu', name = 'dense2'),
                    Dense(32, activation = 'relu', name = 'dense3'),
                    Dense(1, name = 'output')])

model.summary()

# 출력 결과
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense1 (Dense)              (None, 100)               1400      
                                                                 
 dense2 (Dense)              (None, 64)                6464      
                                                                 
 dense3 (Dense)              (None, 32)                2080      
                                                                 
 output (Dense)              (None, 1)                 33        
                                                                 
=================================================================
Total params: 9,977
Trainable params: 9,977
Non-trainable params: 0
_________________________________________________________________
plot_model(model)

 

 

6. 모델 컴파일

  • 회귀 문제에서는 주로 평균제곱오차(MSE)를 손실함수로,
    평균절대오차(MAE)를 평가지표로 많이 사용
model.compile(loss = 'mse',
              optimizer = Adam(learning_rate = 1e-2),
              metrics = ['mae'])

 

 

7. 모델 학습

history = model.fit(x_train, y_train, epochs = 300,
                    validation_data = (x_val, y_val))

 

 

8. 모델 평가

  • evaluate()
model.evaluate(x_test, y_test)

# 출력 결과
4/4 [==============================] - 0s 5ms/step - loss: 14.7238 - mae: 2.6094
[14.723812103271484, 2.609373092651367]
history_dict = history.history

loss = history_dict['loss']
val_loss = history_dict['val_loss']

epochs = range(1, len(loss) + 1)
fig = plt.figure(figsize= (12, 6))

ax1 = fig.add_subplot(1, 2, 1)
ax1.plot(epochs, loss, color = 'blue', label = 'train_loss')
ax1.plot(epochs, val_loss, color = 'red', label = 'val_loss')
ax1.set_title('Train and Validation Loss')
ax1.set_xlabel('Epochs')
ax1.set_ylabel('Loss')
ax1.grid()
ax1.legend()

mae = history_dict['mae']
val_mae = history_dict['val_mae']

ax2 = fig.add_subplot(1, 2, 2)
ax2.plot(epochs, mae, color = 'blue', label = 'train_mae')
ax2.plot(epochs, val_mae, color = 'red', label = 'val_mae')
ax2.set_title('Train and Validation MAE')
ax2.set_xlabel('Epochs')
ax2.set_ylabel('MAE')
ax2.grid()
ax2.legend()

 

 

9. K-Fold 교차 검증

  • 데이터셋의 크기가 매우 작은 경우에 [훈련, 검증, 테스트] 데이터로 나누게 되면 과소적합이 일어날 확률이 높음
  • 이를 해결하기 위해 K-Fold 교차 검증 실행

https://scikit-learn.org/stable/modules/cross_validation.html

 

 

10. 모델 재구성

  • K-Fold 교차검증을 위한 재구성
  • train 데이터를 5개로 나눔
from sklearn.model_selection import KFold
from tensorflow.keras.layers import Input
from tensorflow.keras.models import Model

tf.random.set_seed(111)
(x_train_full, y_train_full), (x_test, y_test) = load_data(path = 'boston_housing.npz',
                                                           test_split = 0.2,
                                                           seed = 111)

mean = np.mean(x_train_full, axis = 0)
std = np.std(x_train_full, axis = 0)

x_train_preprocessed = (x_train_full - mean) / std
x_test = (x_test - mean) / std

# 3개로 나누는 KFold 모델 생성
k = 3
kfold = KFold(n_splits = k, random_state = 111, shuffle = True)

# 모델 생성
def build_model():
    input = Input(shape = (13, ), name = 'input')
    hidden1 = Dense(100, activation = 'relu', input_shape = (13, ), name = 'dense1')(input)
    hidden2 = Dense(64, activation = 'relu', name = 'dense2')(hidden1)
    hidden3 = Dense(32, activation = 'relu', name = 'dense3')(hidden2)
    output = Dense(1, name = 'output')(hidden3)

    model = Model(inputs = [input], outputs = [output])

    model.compile(loss = 'mse',
                  optimizer = 'adam',
                  metrics = ['mae'])
    return model

# mae값을 저장할 리스트
mae_list = []

# 각 fold마다 학습 진행
for train_idx, val_idx in kfold.split(x_train):
    x_train_fold, x_val_fold = x_train[train_idx], x_train[val_idx]
    y_train_fold, y_val_fold = y_train_full[train_idx], y_train_full[val_idx]

    model = build_model()
    model.fit(x_train_fold, y_train_fold, epochs = 300,
              validation_data = (x_val_fold, y_val_fold))
    
    _, test_mae = model.evaluate(x_test, y_test)
    mae_list.append(test_mae)

print(mae_list)
print(np.mean(mae_list))

# 출력 결과
# 기준이 $1000이므로 $8000정도의 오차범위가 존재한다는 의미
[9.665495872497559, 8.393745422363281, 8.736763954162598]
8.932001749674479

+ Recent posts