[DEEP LEARNIG with Python] 04장. 신경망 시작하기: 분류와 회귀 - 3

ByungJik_Oh·2025년 4월 27일
post-thumbnail

📖 04장. 신경망 시작하기: 분류와 회귀

🗝️ 핵심내용

  • 실제 머신 러닝 워크플로의 첫 번째 예제
  • 벡터 데이터를 사용한 분류 문제 처리하기
  • 벡터 데이터를 사용한 연속적인 회귀 문제 처리하기

📑 주택 가격 예측: 회귀 문제

앞선 두 문제와 다르게 이번 문제는 보스턴 주택 가격 데이터셋을 사용하여 개별적인 레이블 대신 연속적인 실수 값을 예측하는 회귀(regression) 문제이다.

분류 알고리즘인 로지스틱 회귀(logistic regression) 알고리즘과 혼동하면 안된다!

보스턴 주택 가격 데이터셋

# 보스턴 주택 데이터셋 로드하기
from tensorflow.keras.datasets import boston_housing

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

print(train_data[0]) # [1.23247, 0 ... 21, 396.9, 18.72]
print(train_targets[0]) # 15.2

보스턴 주택 가격 데이터셋의 경우 입력 데이터에 있는 각 특성(feature)은 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

print(train_data[0]) # [-0.27224633, -0.48361547 ... 0.44807713, 0.8252202 ]

이때 입력 데이터에 있는 각 특성의 스케일이 어떤 값은 0과 1사이, 어떤 값은 1과 12사이의 값을 가지는 등 각 특성마다 스케일이 서로 다르다. 따라서 이렇게 상이한 스케일을 가진 값을 신경망에 그대로 주입하면 문제가 될 수 있기 때문에 각 특성별로 정규화 작업을 해주어야 한다.

여기선 대표적인 정규화 방법 중 표준화(stardization)을 사용할 것이다.

표준화(stardardization)
입력 데이터에 대한 각 특성의 평균을 빼고 표준편차로 나누는 방법을 가리키며, 이때 특성의 중앙이 0에 근사하고 표준 편차가 1인 데이터로 변환된다.

머신러닝 작업과정에서 절대로 테스트 데이터에서 계산한 어떤 값도 사용해서는 안되기 때문에 테스트 데이터를 정규화 할 때도 훈련 데이터에서 계산한 값을 사용하는 것을 명심해야 한다.

모델 구성

# 모델 정의하기
def build_model():
    model = keras.Sequential([
        layers.Dense(64, activation='relu'),
        layers.Dense(64, activation='relu'),
        layers.Dense(1)])
    model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
    return model

현재 우리가 가지고 있는 훈련 데이터의 수는 404개, 테스트 데이터의 수는 102개로 데이터가 적기 때문에 이 경우에는 과대적합이 더 쉽게 일어나므로 2개의 중간층만을 가지는 작은 모델을 사용할 것이다.

또한, 마지막 층에 활성화 함수가 없는데, 이는 전형적인 스칼라 회귀를 위한 구성으로 활성화 함수를 사용하게 되면 출력의 범위가 제한되기 때문이다. 예를 들어, sigmoid 함수의 경우 출력의 범위를 0에서 1 사이의 값을 예측하도록 제한한다. 따라서 이 모델의 경우 어떤 범위의 값이라도 자유롭게 학습될 수 있게 된다.

한가지 더 주목해야 할 점은 손실 함수로 mse 함수, 평균 제곱 오차(mean squared error) 함수를 사용하고, 모니터링을 위해 정확도를 사용하던 분류 모델과 달리 평균 절대 오차(mean absolute error) 값을 사용한다. 이는 이 문제에서 mae가 0.5라면 평균적으로 500달러 정도 차이가 난다는 의미이다.

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

이번 모델 또한 검증을 위해 훈련 데이터에서 데이터를 분리하여 모델 검증을 할건데, 이 문제의 경우 데이터 수가 적기 때문에 검증 세트도 매우 작아지게 된다.

이렇게 되면 검증 세트가 어떤 데이터로 구성되어 있냐에 따라 검증 점수가 크게 달라지게 된다. 이때 K-겹 교차 검증(K-fold cross-validation) 방법을 사용할 수 있다.

K-겹 교차 검증(K-fold cross-validation)
데이터를 K개의 분할로 나누고 K개의 모델을 각각 만들어 K - 1개의 분할에서 훈련하고 나머지 분할에서 평가하는 방법이다. 이때 모델의 검증 점수는 K개의 검증 점수의 평균이 된다.

# K-겹 검증하기
k = 4
num_val_samples = len(train_data) // k
num_epochs = 100
all_scores = []
for i in range(k):
    print(f'# {i}번째 폴드 처리 중')
    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()
    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)

print(all_scores) 
# [1.8217039108276367, 2.467625379562378, 2.494258403778076, 2.514719247817993]
print(np.mean(all_scores)) # 2.324576735496521

현재 100번의 에포크로 훈련한 결과 검증 점수의 평균은 약 2.3으로 평균적으로 2,300달러 정도 차이가 난다.

# 각 폴드의 검증 점수 저장하기
num_epochs = 500
all_mae_histories = []
for i in range(k):
    print(f'# {i}번째 폴드 처리 중')
    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)
    
# K-겹 검증 점수(MAE) 평균 기록하기
average_mae_history = [
    np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]

print(average_mae_history)
print(np.mean(average_mae_history))   

이번엔 모델을 향상 시키기 위해 500번의 에포크로 훈련을 진행하고 각 에포크마다 모델이 얼마나 개선되는지 그래프로 나타내보자.

# 검증 점수 그래프 그리기
plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()


현재 그래프의 범위가 너무 크므로 변화를 뚜렷하게 보기 위해 MAE 값이 비교적 큰 처음 10개의 에포크를 제외시키고 다시 그래프를 그려보자.

# 처음 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()

마찬가지로 약 150번의 에포크 이후 과대적합이 발생하여 모델의 성능이 점점 떨어지는 것을 볼 수 있다.

# 최종 모델 훈련하기
model = build_model()
model.fit(train_data, train_targets,
          epochs=150, batch_size=16, verbose=0)
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)

print(test_mae_score) # 2.1342145919799805

최적의 에포크로 다시 모델을 학습시킨 뒤 최종 점수를 출력해보면 약 2.1로 평균적으로 2,100달러 정도 차이가 나는 것을 볼 수 있다. 여전히 오차가 크지만, 처음 2,300달러보다 성능이 향상된 것을 볼 수 있다.

새로운 데이터에 대해 예측하기

# 새로운 데이터에 대해 예측하기
predictions = model.predict(test_data)
print(predictions[0]) # [7.6983833]
print(test_targets[0]) # 7.2

마지막으로 새로운 데이터에 대해 모델이 잘 예측하는지 확인해보자. 테스트 데이터의 첫번째 데이터에 대해 모델이 예측한 값은 약 7.7(7,700달러)이고, 정답은 7.2(7,200달러)로 약 400달러 정도 오차가 있는 것을 볼 수 있다.


🔗 출처

https://www.gilbut.co.kr/book/view?bookcode=BN003496


profile
精進 "정성을 기울여 노력하고 매진한다"

0개의 댓글