콘텐츠로 이동

Lab 5. 고전 MLP 회귀

실습 실행: Google Colab에서 바로 실행

학습 목표

  • 고전 신경망(MLP)으로 \(y = x_1 \times x_2\) 회귀 문제를 학습한다
  • 입력과 출력 스케일링의 필요성을 이해한다
  • Lab 6(양자 회귀)과 비교하기 위한 고전 기준 모델(baseline)을 만든다

핵심 개념

문제 정의

홀수 수열 x₁ = 1, 3, 5, ..., 99와 짝수 수열 x₂ = 2, 4, 6, ..., 100의 곱 y = x₁ × x₂를 예측한다. y의 범위는 2 ~ 9,900이고, 이론적 총합은 169,150이다.

MLP 구조

입력(2) → Linear(2,3) → Tanh → Linear(3,1) → Sigmoid → 출력(1)

Sigmoid 출력은 [0, 1] 범위이므로, y를 [0, 1]로 스케일링한 뒤 학습하고, 예측 후 역스케일링으로 원래 범위를 복원한다.

평가 기준

  • Test MSE: 예측 오차의 크기
  • 총합: 예측값의 합이 이론 총합(169,150)에 가까운가
  • 예측 vs 실제 scatter: 45도 대각선에 가까울수록 좋음

데이터 생성

x1 = std_np.array([2*i + 1   for i in range(50)], dtype=float)  # 홀수
x2 = std_np.array([2*(i + 1) for i in range(50)], dtype=float)  # 짝수
y  = x1 * x2
THEORY_SUM = 169150

스케일링

입력 x는 [0, π]로, 출력 y는 [0, 1]로 변환한다. Lab 4에서 배운 것과 같은 이유이다. 양자 회로(Lab 6)와 동일한 스케일링을 사용해야 공정한 비교가 된다.

def scale_x(x):
    return (x - 1) / 99 * std_np.pi

Y_MIN, Y_MAX = 2.0, 9900.0

def scale_y(y_val):
    return (y_val - Y_MIN) / (Y_MAX - Y_MIN)

def unscale_y(y_scaled):
    return y_scaled * (Y_MAX - Y_MIN) + Y_MIN

x1_sc = scale_x(x1)
x2_sc = scale_x(x2)
y_sc  = scale_y(y)

Train / Test 분할

50개 중 35개를 train, 15개를 test로 셔플 분할한다. Lab 6에서도 동일한 분할을 사용한다.

모델 정의

class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(2, 3), nn.Tanh(),
            nn.Linear(3, 1), nn.Sigmoid()
        )
    def forward(self, x):
        return self.net(x).squeeze()

2 → 3 → 1 구조로 파라미터는 총 16개이다. Sigmoid 출력이 [0, 1]이므로, 스케일된 y와 직접 비교할 수 있다.

데이터 준비 및 학습

torch.manual_seed(42)
model     = MLP()
opt_mlp   = torch.optim.Adam(model.parameters(), lr=lr)
criterion = nn.MSELoss()
n_params  = sum(p.numel() for p in model.parameters())

학습 루프 (실시간 시각화)

실시간으로 학습 곡선(좌)과 예측 scatter(우)가 갱신된다. epoch가 진행되면서 scatter의 점들이 45도 대각선에 가까워진다.

최종 예측

시각화: 학습 곡선

MLP 학습 곡선

시각화: 예측 vs 실제

MLP 예측과 실제 비교

해석

MLP는 16개 파라미터로 y = x₁ × x₂를 학습한다. 이 결과가 Lab 6(양자 회귀)의 비교 기준이 된다.

항목 MLP (Lab 5) VQR (Lab 6)
파라미터 16 ?
총합 ? / 169,150 ? / 169,150
test MSE ? ?

Lab 6 실행 후 이 표를 채워서 비교한다.

과제 1: lr 비교

lr을 0.001과 0.1로 각각 실행하라. 학습 곡선의 수렴 속도와 최종 MSE를 비교하라.

# 학생 수정: lr = 0.001 → 재실행
# 다시: lr = 0.1 → 재실행
# 두 학습 곡선을 비교하라

과제 2: 은닉층 크기 변경

MLP의 은닉층을 3에서 10으로 변경하라. 파라미터 수가 어떻게 변하는가? 성능이 향상되는가?

class MLP_big(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(2, 10), nn.Tanh(),
            nn.Linear(10, 1), nn.Sigmoid()
        )
    def forward(self, x):
        return self.net(x).squeeze()

model_big = MLP_big()
n_params_big = sum(p.numel() for p in model_big.parameters())
print(f"  파라미터 수: {n_params} → {n_params_big}")

과제 3: 총합 의미

예측값의 총합이 이론 총합(169,150)에 가까운 것은 무엇을 의미하는가? 총합이 맞아도 개별 예측이 틀릴 수 있는가?

힌트: 총합은 "평균적으로 맞다"는 의미이다. 총합이 같아도 큰 값은 과소 예측, 작은 값은 과대 예측하는 경우가 있다. scatter 그래프에서 대각선 위/아래 분포를 관찰하면 확인할 수 있다.