본문 바로가기
Book Review/[케라스 창시자에게 배우는 딥러닝] 리뷰

신경망을 이용한 회귀: 주택 가격 예측

by 3n952 2022. 12. 29.

분류 문제와 달리 이번에는 회귀 문제를 다뤄보도록 하겠습니다.

회귀 문제는 개별적인 레이블을 예측하는 것이 아닌 연속적인 값들을 예측하는 것을 의미합니다.

가령, '기상 데이터로 내일의 기온을 예측하기'가 이에 해당할 것입니다.


-보스턴 주택 가격 데이터 셋

보스턴 데이터 셋을 활용하여 주택 가격을 예측해 보겠습니다.

해당 데이터 셋은 1970년대 중반 보스턴 외곽 지역의 범죄율, 지방세율 등의 데이터 셋입니다.

데이터 샘플은 총 506개이며, 404개는 훈련 샘플, 나머지 102개는 테스트 샘플입니다.

보스턴 주택 데이터 셋도 케라스에서 간편하게 로드할 수 있습니다.

from tensorflow.keras.datasets import boston_housing

(train_data, train_target), (test_data, test_targets) = boston_housing.load_data()

입력 데이터샘플에는 각각 13개의 feature를 수치로 갖고 있습니다.

특성은 1인당 범죄율, 주택당 평균 방 개수, 고속도로 접근성 등이 있습니다.

 

타겟 데이터샘플에는 주택의 중간 가격을 갖고 있습니다.

천 달러 단위로 1만달러에서 5만달러 사이의 값을 가지고 있습니다.

 

각 특성의 값들의 스케일이 다르기 때문에 데이터의 스케일을 맞춰줘야합니다.

이를 위해서는 특성별로 정규화과정을 거치는 것입니다.

각 특성의 평균을 빼고 표준 편차로 나누는 것이 그것입니다.

이렇게 스케일링 과정을 통해야 주어진 데이터로 적합하게 예측이 가능해 질 것입니다. 

 

이에 대한 보다 자세한 설명은 밑의 링크를 확인해주세요.

https://sanmldl.tistory.com/10?category=976947

 

2. 데이터 전처리 with python (2)

이번 포스트에서는 머신러닝에서 올바른 결과 도출을 위해 데이터 전처리 하는 법을 예제 코드를 통해 알아보겠습니다. 그 중 표준 점수로 특성의 스케일을 변환하는 법에 대해 알아보도록 하

sanmldl.tistory.com

 

정규화를 직접 계산할 수 있지만 넘파이에서 간단하게 구현할 수 있습니다.

코드는 다음과 같습니다.

mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std
test_data -= mean
test_data /= std

 

-모델 구성

데이터가 준비 되었으니 이제 모델을 구성할 차례입니다.

나중에 훈련검증에 사용하기 위해 모델의 구성을 하나의 함수로 정의하여 사용하겠습니다.

코드는 다음과 같습니다.

from tensorflow import keras

def build_model():
    model = keras.Sequential([
        keras.layers.Dense(64, activation="relu"),
        keras.layers.Dense(64, activation="relu"),
        keras.layers.Dense(1)
    ])
    model.compile(optimizer="rmsprop", loss="mse", metrics=["mae"])
    return model

여기서 주목할 점은 마지막 층의 활성화함수가 없다는 것과 손실함수로 mse, 모니터링을 위해 mae값을 사용한다는 것입니다.

마지막 층에 활성화 함수가 없는 것은 회귀 문제에 관련 되있는 것으로 예측한 값의 범위를 한정시키지 않기 위함입니다.

출력 크기가 1인 것도 이와 관련있다고 말할 수 있습니다. 하나의 스칼라 값을 예측해야 하기 때문입니다.

또한 회귀에서 자주 사용되는 mse(평균 제곱 오차)를 사용했으며, mae(평균 절대 오차)로 예측값과 실제값의 차이의 절댓값을 확인함으로써 얼마나 예측과 실제값이 떨어져있는지를 파악하여 모델을 모니터링합니다.

 

-훈련검증

훈련 검증을 위해서는 훈련 데이터, 검증 데이터로 나눠줘야 합니다.

이전 포스트의 데이터와 달리 이번 데이터 샘플의 개수가 많지 않기 때문에 검증 데이터 셋도 작습니다.

이런 경우에는 어떤 훈련 데이터, 검증 데이터가 쓰이는 지에 따라 크게 검증 점수가 좌우 될 것입니다.

즉, 검증 세트의 분할에 대한 검증 점수의 분산(variance)이 높다는 것을 의미합니다.

 

이를 해결하기 위해서는 k-겹 교차 검증(k-fold cross-validation)을 사용해야 합니다.

k-겹 교차 검증은 다음과 같이 이뤄집니다.

데이터를 k개로 나누고(k fold) k개의 모델을 각각 만들어 k-1개의 분할에서 훈련, 나머지 1개의 분할에서 검증하는 방법입니다.

모델의 검증 점수는 k개의 검증 점수의 평균이 됩니다.

그림으로 보면 이해하기 쉽습니다.

5-fold cross-validation

k-겹 검증을 하면 데이터에 대해 골고루 검증이 이뤄지기 때문에 적합하게 검증이 이뤄집니다.

적합한 k개를 구하는 것에도 방법이 있지만 이번 포스트에서는 다루지 않겠습니다.

 

이번 훈련 검증을 위해서는 4-fold 검증을 해보도록 하겠습니다.

코드는 다음과 같습니다.

import numpy as np

k = 4
num_val_samples = len(train_data) // k
num_epochs = 100
all_scores = [] #각 폴드에 대한 검증 점수를 담을 리스트

for i in range(k):
    print(f"#{i+1}번째 폴드 처리중")
    #검증 데이터 준비: k번째 분할에 대한 데이터 
    val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples] 
    val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
    #훈련 데이터 준비: 검증 데이터를 제외한 나머지 데이터 전체
    partial_train_data = np.concatenate(
        [train_data[:i * num_val_samples],
         train_data[(i + 1) * num_val_samples:]],
        axis=0)
    partial_train_targets = np.concatenate(
        [train_targets[:i * num_val_samples],
         train_targets[(i + 1) * num_val_samples:]],
        axis=0)
    model = build_model()
    #verbose = 0 매개변수로 훈련과정 출력 생략
    model.fit(partial_train_data, partial_train_targets,
              epochs=num_epochs, batch_size=16, verbose=0)
    #모델 평가: 검증 데이터로 평가
    val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
    all_scores.append(val_mae)
    all_scores_mean = np.mean(all_scores)

마지막 all_scores_mean을 통해 각 폴드의 검증 점수의 평균을 구했습니다.

제가 실행한 코드의 경우 대략 2.3이 나오는데 평균적으로 예측과 실제의 주택 가격은 2,300달러 정도 차이가 난다는 것을 의미합니다.

 

각 폴드의 점수를 저장하여 에포크별로 검증 점수를 시각화하면 적합한 에포크 수를 정할 수 있을 것입니다.

에포크를 500으로 늘려서 확인해보겠습니다.

각 폴드의 점수를 저장하는 코드는 다음과 같습니다.

num_epochs = 500
all_mae_histories = []
for i in range(k):
    print(f"#{i+1}번째 폴드 처리중")
    val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
    val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
    partial_train_data = np.concatenate(
        [train_data[:i * num_val_samples],
         train_data[(i + 1) * num_val_samples:]],
        axis=0)
    partial_train_targets = np.concatenate(
        [train_targets[:i * num_val_samples],
         train_targets[(i + 1) * num_val_samples:]],
        axis=0)
    model = build_model()
    history = model.fit(partial_train_data, partial_train_targets,
                        validation_data=(val_data, val_targets),
                        epochs=num_epochs, batch_size=16, verbose=0)
    mae_history = history.history["val_mae"]
    all_mae_histories.append(mae_history)

 

그 다음 모든 폴드에 대해 에포크의 MAE 점수 평균을 계산합니다.

코드는 다음과 같습니다.

average_mae_history = [np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]

결과를 그래프로 시각화 하면 다음과 같습니다.

import matplotlib.pyplot as plt

plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.xlabel("Epochs")
plt.ylabel("Validation MAE")
plt.show()

epcch = 500

 

에포크의 범위가 매우 크다보니 초반 부분에서 급격하게 MAE가 떨어지고 나머지 부분에 대해서는 시각적으로 구분하기 어렵습니다.

따라서 초반 10개 에포크 동안의 부분을 제외한 나머지 부분을 시각화하여 분석해보고자 합니다.

코드는 다음과 같습니다.

truncated_mae_history = average_mae_history[10:]
plt.plot(range(1, len(truncated_mae_history) + 1), truncated_mae_history)
plt.xlabel("Epochs")
plt.ylabel("Validation MAE")
plt.show()

epoch: 10~500

그래프를 확인해보면 거의 에포크 120~140 구간에서 검증 MAE가 다시 증가하는 추세를 보입니다.

모델의 여러 매개변수의 튜닝이 끝나면 모든 훈련 데이터로 모델을 다시 훈련시키고 테스트 데이터로 성능을 확인해야합니다.

코드는 다음과 같습니다. 

model = build_model()
model.fit(train_data, train_targets,
          epochs=130, batch_size=16, verbose=0)
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)

결과 값을 보면 test_mae_score 값이 2.5109로 나타났습니다. 이 경우에는 검증 점수가 다시 높이진 것을 의미하므로

적절한 매개변수 값으로 튜닝된 것이 아닙니다.

에포크 값을 조금씩 수정하면서 최적의 손실 점수를 나타내는 값을 찾아야합니다.

 

-새로운 데이터 예측하기

지금까지 만든 모델은 회귀 모델입니다. 따라서 predict()메서드를 호출하면 1,000달러 단위의 스칼라 값이 나올 것입니다.

테스트 데이터 셋에 있는 첫번째 데이터에 대해 주택 값을 예측해보겠습니다.

코드는 다음과 같습니다.

predictions = model.predict(test_data)
predictions[0]

결과 값은 9.38288로 약 9400달러로 예상됩니다.

 

이번 포스트에서는 회귀 분류에 대한 문제를 다뤘습니다.

이번 포스트에서 기억해야 할 것은 데이터 스케일 조정, 손실함수로 mse, 평가 지표는 mae를 사용했다는 점입니다.

또한 적은 데이터에 대한 적합한 검증을 위한 k겹 교차검증을 사용하여 모델을 평가 했습니다.