MLOps 프로젝트/도서 '케라스 창시자에게 배우는 딥러닝'

1부 3.6 주택 가격 분류: 회귀 문제

youjin86 2021. 8. 29. 04:54

1부 딥러닝의 기초

3장 신경망 시작하기


3.6 주택 가격 분류: 회귀 문제

 

보스턴 주택 가격 데이터셋

1970년 중반 보스턴 외곽 지역의 범죄율, 지방세율 등의 데이터가 주어졌을 때 주택 가격의 중간 값을 예측

입력 데이터에 있는 각 특성의 스케일이 서로 다름.

어떤 값은 0과 1사이의 비율을, 어떤 값은 1과 12사이의 값을 가지거나 1과 100 사이의 값을 가짐.

 

 

1. 데이터셋 로드

from keras.datasets import boston_housing

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

train_data, test_data : 13개의 수치 특성 (1인당 범죄율, 주택당 평균 방의 개수, 고속도로 접근성 등)

train_targets, test_gargets : 주택의 중간 가격으로 천 달러 단위 (일반적으로 1만 달러와 5만 달러 사이)

 

2.데이터 준비

상이한 스케일을 가진 값을 다룰 땐 대표적으로 특성별로 정규화를 함.

입력 데이터에 있는 각 특성(입력 데이터 행렬의 열)에 대해서 특성의 평균을 빼고 표준 편차로 나눔.

특성의 중앙이 0 근처에 맞추어지고 표준 편차가 1이 됨.

넘파이로 간단히 할 수 있음.

 

테스트 데이터에서 계산한 어떤 값도 사용하면 안됨.

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

test_data -= mean
test_data /= std

 

3. 신경망 모델 만들기

relu함수

입력 데이터가 벡터고 레이블은 스칼라(1 또는 0)과 같은 문제에 잘 작동하는 네트워크 종류는 relu 활성화 함수를 사용한 완전 연결층(Dense(16, activation='relu'))을 그냥 쌓은 것

 

Dense층에 전달한 매개변수(16)는 은닉 유닛(hidden unit)의 개수

하나의 은닉 유닛은 층이 나타내는 표현 공간에서 하나의 차원이 됨.

 

output = relu(dot(W,input) + b)

16개의 은닉 유닛이 있다는 것은 가중치 행렬 W의 크기가 (input_dimension, 16)이라는 뜻

입력 데이터와 W를 점곱하면 입력 데이터가 16차원으로 표현된 공간으로 투영됨.

그리고 편향 벡터 b를 더하고 relu연산을 적용

 

출처 : http://aidev.co.kr/deeplearning/6893

 

샘플 개수가 적기에 64개의 유닛을 가진 2개의 은닉 층으로 작은 네트워크 구성

일반적으로 훈련 데이터의 개수가 적을수록 과대적합이 더 쉽게 일어남.

 

이 네트워크의 마지막 층은 하나의 유닛을 갖고 활성화 함수가 없음.

(전형적인 스칼라 회귀(하나의 연속적인 값을 예측하는 회귀))를 위한 구성

 

손실함수를 mse함수(평균 제곱 오차(예측과 타깃 사이 거리의 제곱))를 사용

회귀 문제에서 많이 사용하는 함수

from keras import models
from keras import layers

def build_model():
    # 동일한 모델을 여러 번 생성할 것이므로 함수를 만들어 사용
    model = models.Sequential()
    model.add(layers.Dense(64, activation='relu',
                           input_shape=(train_data.shape[1],)))
    model.add(layers.Dense(64, activation='relu'))
    model.add(layers.Dense(1))
    model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
    return model

 

4. K-겹 검증을 사용한 훈련 검증

데이터를 K개의 분할로 나누고 K개의 모델을 각각 만들어 K-1개의 분할에서 훈련하고 나머지 분할에서 평가하는 방법

검증 세트를 다르게 함.

import numpy as np

k = 4
num_val_samples = len(train_data) // k
num_epochs = 100
all_scores = []
for i in range(k):
    print('처리중인 폴드 #', i)
    # 검증 데이터 준비: 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=1, verbose=0)
    # 검증 세트로 모델 평가
    val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
    all_scores.append(val_mae)

 

신경망을 좀 더 오래 500 에포크동안 훈련

각 에포크마다 모델이 얼마나 개선되는지 기록하기 위해 훈련 루프를 조금 수정해 에포크의 검증 점수를 로그에 저장

from keras import backend as K

# 메모리 해제
K.clear_session()

num_epochs = 500
all_mae_histories = []
for i in range(k):
    print('처리중인 폴드 #', i)
    # 검증 데이터 준비: 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 이므로 훈련 과정이 출력되지 않습니다)
    history = model.fit(partial_train_data, partial_train_targets,
                        validation_data=(val_data, val_targets),
                        epochs=num_epochs, batch_size=1, verbose=0)
    mae_history = history.history['val_mean_absolute_error']
    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()

결과

 

처음 10개의 데이터 포인트를 제외한 검증 점수 그리기

곡선의 다른 부분과 스케일이 많은 다른 첫 10개의 데이터 포인트를 제외

부드러운 곡선을 위해 각 포인트를 이전 포인트의 지수 이동 평균으로 대체

def smooth_curve(points, factor=0.9):
  smoothed_points = []
  for point in points:
    if smoothed_points:
      previous = smoothed_points[-1]
      smoothed_points.append(previous * factor + point * (1 - factor))
    else:
      smoothed_points.append(point)
  return smoothed_points

smooth_mae_history = smooth_curve(average_mae_history[10:])

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

결과

 

80번째 에포크 이후에 줄어드는 것이 멈춤. 80번 이후 과도하게 최적화된 과대 적합(overfitting)인 걸 확인할 수 있음.

 

따라서, 에포크를 80번으로 줄이고 다시 훈련하기

# 새롭게 컴파인된 모델
model = build_model()

# 전체 데이터로 훈련
model.fit(train_data, train_targets,
          epochs=80, batch_size=16, verbose=0)
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)

 

최종 결과 : 약 2.675달러의 차이가 보임.

>>> test_mae_score
2.675027286305147