Data Analysis Study

딥러닝 예제 : 회귀 (점수 예측)

Solbi Lee 2025. 7. 20. 11:17

1. 코드 개요 & 목적 

공부시간 (x) 한 개만으로 시험점수 (y)를 연속값 회귀(regression) 예측 

손실 함수로 MSE 사용 

입출력 모두 Z-score 정규화(표준화) 진행 -> 평균 0, 표준편차 1 

 

2. 이전 예제와 주요 차이점 

항목                               layer2.py (분류)                                                      layer3.py (회귀)

문제 유형 이진 분류 (합격/불합격) 회귀 (점수 예측)
레이블 형식 0 또는 1 연속값 (50, 60, … 90)
출력층 활성화 함수 sigmoid (0~1 확률) 없음 (선형 출력)
손실 함수 binary_crossentropy  mse (평균제곱오차)
정규화 방법 Min–Max (0∼1 스케일) Z‑score(x−평균)/표준편차(x-평균)/표준편차
입력 차원 2개 피처 (공부시간, 커피 수) 1개 피처 (공부시간)
평가 지표 accuracy (기본 설정에선 지표 없음→loss만)
추가 구성 요소 validation_split, EarlyStopping, LR 조정 등 간단한 구조, 별도 검증/콜백 없음

 

3. 코드 

# layer3.py
# 손실함수 - mse

from tensorflow.keras.models import Sequential # 케라스 모델 레이어
from tensorflow.keras.layers import Dense # 케라스 모델 레이어
import numpy as np # 수치 연산, 배열 처리

# 공부시간(입력), 시험점수(출력)
X = np.array([[1], [2], [3], [4], [5]]) # 5개 샘플, 1차원 피처
y_raw = np.array([50, 60, 70, 80, 90]) # 실제 점수

# 정규화 함수 (z-score)
# 데이터에서 데이터들의평균을 뺀값을 표준편차로 나눔
def normalize(data):
    return (data-np.mean(data)) / np.std(data)

# 정규화 적용 (X, y_raw를 각각 평균, 표준편차 기준으로 변환)
X = normalize(X)
y = normalize(y_raw)

# 모델 구성
model = Sequential()
model.add(Dense(8, input_dim=1, activation='relu')) # 뉴런 8개, relu 활성화
model.add(Dense(1)) # 하나의 선형 노드 -> 회귀 예측

# 모델 컴파일 MSE 손실, Adam 옵티마이저
# optimizer='adam': 적응적 학습률, loss='mse' : 회귀용 평균제곱오차
model.compile(optimizer='adam', loss='mse')

# 모델 학습 : 200 epoch, 로그 생략
model.fit(X, y, epochs=200, verbose=0)

# 예측 / 출력
# 예측 : 입력 3 -> 정규화 -> 예측 -> 역정규화
pred = model.predict(normalize(np.array([[3]])))
pred = pred * np.std(y_raw) + np.mean(y_raw) # 역정규화
print(f"공부 3시간 > 예측 점수 {pred[0][0]:.2f}")

 

1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 37ms/step
공부 3시간 > 예측 점수 72.53

=> 3시간 공부에 대해, 학습된 회귀 모델이 약 72.53점을 예측함 

원래 데이터 패턴 상으로는 3시간 공부 시 70점이지만(단순 선형이면 80점), 실제 모델은 72.5점으로 약간 다른 값을 반환했음 

 

4. 인사이트 

1. 단순 선형 대비 편차 

- 실제 관찰값 (70점)에 비해 +2.5점이 높고, 이론적 직선(80점)에 비해 -7.5점 낮은 위치 

- 표준화, 역정규화 과정에서 사용된 평균 표준편차, 그리고 학습된 가중치가 단순 선형 관계를 완벽히 재현하지 못했음을 뜻함 

 

2. 모델 용량과 활성화 함수 영향 

- 은닉층(relu)이 도입된 비선형 모델이지만, 실제 데이터는 완전한 선형 패턴 

- ReLU 특성상 음영 처리된 영역이 생겨, 작은 데이터셋에선 오히려 편향(bias) 발생 가능 

 

3. 데이터 샘플 수 부족 

- 총 5개 샘플로는 회귀선을 안정적으로 학습하기 어렵다. 

- 학습 데이터가 적으면 초기 가중치 설정과 학습률 변화에 모델 예측이 크게 흔들릴 수 있기 때문 

5. 개선 방향 

1. 모델 구조 단순화 

- 비선형층 없이 Dense(1, input_dim=1, activation='linear') 한 줄로만 구성 -> 순수 선형 회귀 

2. 데이터 확대 

- 더 많은 (공부시간, 점수) 페어를 수집하거나, 임의 노이즈를 추가한 합성 데이터로 학습 안정성 강화

3. 정규화 방식 비교 

- 현재 z-score 대신 MinMaxScaler 사용 실험 

- 두 방식 간 예측 성능, 안정성 차이 점검 

4. 하이퍼파라미터 튜닝 

- 학습률, epochs, 은닉 뉴런 수 등을 그리드 서치 혹은 수동 실험으로 최적 조합 탐색 

5. 평가 지표 확대 

MSE 외에 MAE, R2 Score 등을 함께 계산해서 오차 크기와 설명력을 다각도로 평가 

 

# layer3_improved.py
# 회귀 예제에 “검증 분리”, “EarlyStopping”, “MAE 지표” 추가

from tensorflow.keras.models import Sequential    # 케라스 순차 모델
from tensorflow.keras.layers import Dense         # 전결합(Dense) 레이어
from tensorflow.keras.callbacks import EarlyStopping  # 조기 종료 콜백
import numpy as np                                 # 수치 연산

# 1) 데이터 준비
X_raw = np.array([[1], [2], [3], [4], [5]])        # 공부시간
y_raw = np.array([50, 60, 70, 80, 90])             # 시험점수

# 2) Z‑score 정규화 함수
def normalize(data):
    return (data - np.mean(data)) / np.std(data)

# 3) 정규화 적용
X = normalize(X_raw)
y = normalize(y_raw)

# 4) 모델 구성
model = Sequential()
model.add(
    Dense(
        8,                 # 은닉 뉴런 8개
        input_dim=1,       # 입력 특성 1차원
        activation='relu'  # 비선형성 도입
    )
)
model.add(
    Dense(
        1                  # 선형 노드 1개 → 회귀 예측
        # activation='linear'  # 기본값이 선형이므로 생략 가능
    )
)

# 5) 모델 컴파일
#    - loss: MSE (평균제곱오차)
#    - metrics: MAE (평균절대오차) 추가 → 오차 크기 직관적으로 확인
model.compile(
    optimizer='adam',
    loss='mse',
    metrics=['mae']
)

# 6) EarlyStopping 설정
#    - val_loss(검증 손실) 기준으로 10회 연속 개선 없으면 중단
#    - restore_best_weights=True: 최적 검증 모델 가중치 복원
es = EarlyStopping(
    monitor='val_loss',
    patience=10,
    restore_best_weights=True
)

# 7) 모델 학습
#    - validation_split=0.2: 전체의 20%를 검증용으로 자동 분리
#    - callbacks=[es]: 조기 종료 적용
#    - verbose=1: epoch별 진행바 및 loss/mae 출력
history = model.fit(
    X, y,
    epochs=200,
    validation_split=0.2,
    callbacks=[es],
    verbose=1
)

# 8) 예측
#    - 입력 3시간도 정규화 → 예측 → 역정규화
test = np.array([[3]])
test_norm = normalize(test)
pred_norm = model.predict(test_norm)
pred = pred_norm * np.std(y_raw) + np.mean(y_raw)

print(f"\n공부 3시간 > 예측 점수 {pred[0][0]:.2f}")

# 9) 학습 결과 요약 (MAE 지표 포함)
final_train_mae = history.history['mae'][-1]
final_val_mae   = history.history['val_mae'][-1]
print(f"최종 Train MAE: {final_train_mae:.4f}")
print(f"최종 Val   MAE: {final_val_mae:.4f}")
공부 3시간 > 예측 점수 67.36
최종 Train MAE: 0.6054
최종 Val   MAE: 0.7915

현재 모델은 오히려 선형 패턴에 부적합하게 학습돼 평균 +-8~11점 오차가 나는 상태