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도 대각선에 가까워진다.
최종 예측
시각화: 학습 곡선

시각화: 예측 vs 실제

해석
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 그래프에서 대각선 위/아래 분포를 관찰하면 확인할 수 있다.