컴퓨터공학 300 주제 시리즈의 264번째 글이다. 전체 지도는 여기.

한 줄 요약

선형 회귀는 입력의 가중합으로 실수 값을 예측하고, 예측 오차 제곱의 평균(MSE)을 최소화하는 가중치를 찾는다. 모델·손실·최적화라는 머신러닝의 세 요소가 가장 단순한 모습으로 다 들어 있다.

왜 필요한가

선형 회귀는 오래되고 단순하다. 그래서 무시하기 쉽지만, 실무와 학습 양쪽에서 출발점이다.

  • 실무에서는 기준선(baseline)이다. 복잡한 모델을 만들기 전에 선형 회귀가 얼마나 하는지 보면, 복잡함이 값을 하는지 판단할 수 있다.
  • 해석이 쉽다. 가중치 하나가 “이 특징이 1 늘면 예측이 얼마나 변하는가”를 뜻한다.
  • 신경망의 마지막 층은 결국 선형 변환이다. 경사 하강법, 손실 함수, 정규화 같은 개념이 여기서 처음 등장하고 신경망까지 그대로 이어진다.

핵심 개념

모델

특징이 d 개인 입력 x 에 대해

ŷ = w0 + w1·x1 + w2·x2 + ... + wd·xd = Xw   (X 에 1 로 채운 열을 붙여 w0 를 흡수)

“선형”은 가중치에 대해 선형이라는 뜻이다. x² 이나 log x 같은 변환한 특징을 넣어도 가중치에 대해서는 여전히 선형이므로 같은 방법으로 푼다. 다항 회귀가 그렇다.

손실 — 최소제곱

MSE(w) = (1/n) · Σ (ŷi − yi)²

왜 제곱인가. 미분이 쉽고, 큰 오차를 더 세게 벌주며, 오차가 정규분포를 따른다고 가정하면 최소제곱 해가 최대우도 추정과 같아진다. 대신 이상치 하나에 크게 끌려간다. 이상치가 많으면 절댓값 오차나 Huber 손실을 쓴다.

풀이 1 — 정규방정식

MSE 는 w 에 대한 볼록한 2차 함수라 기울기가 0 인 점이 최솟값이다.

∇MSE = (2/n) · Xᵀ(Xw − y) = 0   ⟹   XᵀX w = Xᵀy   ⟹   w = (XᵀX)⁻¹ Xᵀy

특징 수 d 가 작으면 한 번에 정확한 답이 나온다. 다만 역행렬을 직접 구하는 것은 수치적으로 불안정할 수 있어서 실제로는 QR 분해나 SVD 기반의 최소제곱 해법(numpy.linalg.lstsq)을 쓴다. 특징끼리 강하게 상관되어 XᵀX 가 특이 행렬에 가까우면 해가 크게 흔들리는데, 이것이 다중공선성 문제이고 릿지 정규화로 완화한다.

풀이 2 — 경사 하강법

w ← w − η · ∇MSE(w)

η 는 학습률이다. 기울기의 반대 방향으로 조금씩 내려간다. 데이터가 크거나 특징이 아주 많으면 정규방정식보다 낫고, 무엇보다 닫힌 해가 없는 모델(신경망)에도 그대로 쓸 수 있다는 점이 중요하다.

  정규방정식 경사 하강법
반복 없음 필요
학습률 필요 없음 골라야 함
비용 특징 수 d 에 대해 대략 d³ 반복당 n·d
일반화 선형 모델 전용 미분 가능한 모든 모델

학습률이 너무 크면 발산하고, 너무 작으면 한없이 느리다. 특징의 스케일이 크게 다르면 손실 곡면이 길쭉해져 수렴이 느려지므로 표준화(평균 0, 분산 1)를 먼저 한다.

평가 — R²

R² = 1 − Σ(ŷ − y)² / Σ(y − ȳ)²

“평균값 하나로 찍는 것보다 분산을 얼마나 더 설명하는가”다. 1 이면 완벽, 0 이면 평균과 같은 수준, 음수면 평균보다 못하다.

직접 해 보기

기울기 3, 절편 2 인 직선에 잡음을 섞은 데이터 50개를 만들고 두 방법으로 푼다.

import numpy as np
rng = np.random.default_rng(0)
x = rng.uniform(0, 10, 50)
y = 3.0 * x + 2.0 + rng.normal(0, 1.0, 50)        # 참값: 기울기 3, 절편 2
X = np.column_stack([np.ones_like(x), x])          # 절편용 1 열
# 1) 정규방정식(수치적으로는 lstsq 가 안전)
w_closed, *_ = np.linalg.lstsq(X, y, rcond=None)
# 2) 경사 하강법
w = np.zeros(2); lr = 0.01
for step in range(5000):
    grad = 2 / len(y) * X.T @ (X @ w - y)
    w -= lr * grad
mse = np.mean((X @ w - y) ** 2)
r2 = 1 - np.sum((X @ w - y)**2) / np.sum((y - y.mean())**2)
print("정규방정식 [b, w]:", w_closed.round(3))
print("경사하강   [b, w]:", w.round(3))
print(f"MSE={mse:.3f}  R^2={r2:.4f}")

실행 결과:

정규방정식 [b, w]: [1.757 3.052]
경사하강   [b, w]: [1.757 3.052]
MSE=0.993  R^2=0.9876

두 방법의 답이 소수 셋째 자리까지 같다. 참값(2, 3)과 약간 다른 것은 잡음 때문이다. 데이터 50개로 추정한 값에는 원래 오차가 있다. MSE 가 1 근처인 것도 우리가 넣은 잡음의 분산(1.0²)과 맞는다. 모델이 잡음 자체는 설명할 수 없다는 뜻이다.

학습률 lr 을 0.05 로 바꿔 보라. 이 데이터에서는 x 값이 0~10 이라 기울기가 커서 손실이 발산한다. 특징을 표준화한 뒤에 다시 해 보면 큰 학습률에서도 수렴한다.

현업에서는

  • 용량 계획: “동시 접속자 수와 CPU 사용률” 같은 관계는 선형 근사로 충분한 경우가 많다. 홈랩 클러스터에서도 요청 수 대비 메모리 사용량에 직선을 맞춰 보면 언제 노드를 늘려야 할지 대략 감이 온다. 단, 범위 밖으로 외삽하면 틀리기 쉽다.
  • A/B 테스트·계량 분석: 다른 변수의 영향을 통제한 효과 추정에 회귀를 쓴다. 이때는 예측보다 계수의 해석과 신뢰구간이 중요하다.
  • 기준선: Kaggle 이든 사내 과제든, 선형 모델을 먼저 돌려 두면 이후 모델이 얼마나 나은지 숫자로 말할 수 있다.

확인 문제

  1. ŷ = w0 + w1·x + w2·x² 는 선형 회귀인가?
  2. 정규방정식 w = (XᵀX)⁻¹Xᵀy 를 유도하는 출발점은 무엇인가?
  3. 경사 하강법이 발산할 때 가장 먼저 의심할 두 가지는?
  4. R² 이 음수라는 것은 무엇을 뜻하는가?

풀이

  1. 그렇다. 가중치에 대해 선형이므로 x 와 x² 을 특징으로 넣은 선형 회귀다.
  2. MSE 의 w 에 대한 기울기를 0 으로 놓는 것. MSE 가 볼록 함수라 그 점이 최솟값이다.
  3. 학습률이 너무 큰 것, 특징의 스케일이 표준화되지 않은 것.
  4. 모델의 예측이 단순히 평균값을 내는 것보다 오차가 크다는 뜻이다. 보통 평가 데이터에서 과적합된 모델이나 잘못된 모델에서 나온다.

더 읽을거리 (References)