본문 바로가기
데이터 과학 수학

머신러닝의 시작, 선형 회귀(Linear Regression)와 최소제곱법·정규방정식의 수학

by dexien 2026. 5. 4.

머신러닝을 처음 공부할 때 선형 회귀가 너무 단순해 보여서 건너뛰고 싶었습니다. 직선 하나 긋는 게 AI랑 무슨 상관인가 싶었습니다. 그런데 그 직선을 어떻게 구하는지 파고들다 보니 최소제곱법, 정규방정식, 편미분이 전부 연결되어 있었습니다. 선형 회귀(Linear Regression)를 제대로 이해하고 나서야 그 위에 쌓인 로지스틱 회귀, 신경망이 왜 그런 구조인지 납득이 됐습니다.

오늘은 데이터를 가장 잘 설명하는 직선을 찾는 수학적 과정을 실제 숫자로 따라가 보겠습니다. 정규방정식이 왜 현실에서 잘 안 쓰이는지, 경사하강법이 왜 대신 쓰이는지까지 연결해서 설명드릴게요.

선형 회귀(Linear Regression)와 최소제곱법·정규방정식의 수학
머신러닝의 시작, 선형 회귀(Linear Regression)와 최소제곱법·정규방정식의 수학

선형 회귀란 무엇인가

선형 회귀의 목표는 데이터를 가장 잘 설명하는 직선을 찾는 겁니다. 집의 면적으로 가격을 예측한다고 해봅니다. 데이터가 이렇게 주어졌을 때:

면적(x): 20, 30, 40, 50, 60
가격(y): 1000, 1400, 1800, 2300, 2600 (만 원)

이 패턴을 직선 하나로 표현: ŷ = w·x + b

w: 기울기(가중치) — 면적 1㎡당 가격 변화
b: 절편(bias) — 기준값

문제: 어떤 w와 b가 이 데이터를 가장 잘 설명하는가?

직선은 무수히 많이 그릴 수 있습니다. 그중에서 데이터에 가장 잘 맞는 직선을 고르는 기준이 필요합니다. 그 기준을 수학으로 정의한 게 최소제곱법입니다. 이 원리가 로지스틱 회귀, SVM, 신경망 각 레이어까지 이어집니다.

면적과 가격 데이터의 산점도와 회귀직선 그리고 잔차를 보여주는 선형 회귀 시각화
선형 회귀는 잔차제곱합(RSS)을 최소화하는 직선을 찾는 과정이다


최소제곱법 — 오차의 합을 최소화하라

모델 예측값 ŷ 와 실제값 y의 차이를 잔차(residual)라고 합니다. 잔차를 그냥 더하면 양수와 음수가 상쇄됩니다. 그래서 제곱해서 더합니다. 이게 잔차제곱합(RSS)입니다.

RSS(w, b) = Σ (yᵢ - ŷᵢ)²
= Σ (yᵢ - w·xᵢ - b)²

제곱을 쓰는 이유는 세 가지입니다.
양수·음수 잔차가 상쇄되지 않고,
큰 오차에 더 큰 벌점을 주고,
미분이 깔끔하게 풀립니다.

RSS를 최소화하려면 w, b 각각 편미분 = 0

∂RSS/∂b = 0 → b = ȳ - w·x̄
∂RSS/∂w = 0 → w = Σ(xᵢ - x̄)(yᵢ - ȳ) / Σ(xᵢ - x̄)²

실제 숫자로 계산해보겠습니다.

x = [20, 30, 40, 50, 60], x̄ = 40
y = [1000, 1400, 1800, 2300, 2600], ȳ = 1820

분자 Σ(xᵢ - x̄)(yᵢ - ȳ):
(20-40)(1000-1820) = (-20)(-820) = 16400
(30-40)(1400-1820) = (-10)(-420) = 4200
(40-40)(1800-1820) = 0
(50-40)(2300-1820) = (10)(480) = 4800
(60-40)(2600-1820) = (20)(780) = 15600
합계 = 41000

분모 Σ(xᵢ - x̄)²:
400 + 100 + 0 + 100 + 400 = 1000

w = 41000 / 1000 = 41
b = 1820 - 41 × 40 = 180

→ ŷ = 41x + 180

검증: 면적 45㎡ → ŷ = 41×45 + 180 = 2,025만 원

w=41이라는 숫자가 "면적 1㎡가 늘어날 때 평균 41만 원이 오른다"는 뜻입니다. 딥러닝 모델은 왜 그런 예측을 했는지 설명하기 어렵지만, 선형 회귀는 계수 하나하나가 의미를 가집니다. XAI 글에서 다룬 해석 가능성 문제를 선형 회귀는 구조적으로 해결하고 있습니다.


다변수 선형 회귀와 정규방정식

현실에서 입력 변수는 하나가 아닙니다. 집값을 예측할 때 면적만 보지 않고 방 개수, 층수, 건축연도까지 씁니다. 변수가 늘어나면 편미분을 하나씩 손으로 푸는 게 번거로워집니다. 행렬 표기를 쓰면 한 번에 해결됩니다.

X : (n × p) 행렬 — n개 데이터, p개 특성
y : (n × 1) 벡터 — 실제 정답값
w : (p × 1) 벡터 — 찾아야 할 가중치

예측값: ŷ = Xw
손실함수: RSS = ||y - Xw||²

RSS를 w에 대해 미분하고 0으로 놓으면:
∂RSS/∂w = -2Xᵀ(y - Xw) = 0
Xᵀy = XᵀXw

→ 정규방정식: w = (XᵀX)⁻¹ Xᵀy

(XᵀX)⁻¹Xᵀ = 무어-펜로즈 의사역행렬
→ 데이터 행렬 X로부터 최적 가중치를 한 번에 계산

NumPy로 직접 구현하면 이렇습니다.

import numpy as np

X_raw = np.array([
    [20, 1], [30, 2], [40, 2], [50, 3], [60, 3]
])
y = np.array([1000, 1400, 1800, 2300, 2600])

# 절편 항 추가 (1 열 추가)
X = np.hstack([X_raw, np.ones((5, 1))])

# 정규방정식 적용
w = np.linalg.inv(X.T @ X) @ X.T @ y

print(f"면적={w[0]:.2f}, 방개수={w[1]:.2f}, 절편={w[2]:.2f}")
# 출력: 면적=36.50, 방개수=58.33, 절편=148.33

# 면적 45㎡, 방 2개인 집 예측
pred = np.array([45, 2, 1]) @ w
print(f"예측: {pred:.0f}만 원") # → 약 1,980만 원

정규방정식은 반복 학습 없이 단 한 번에 최적 가중치를 구합니다. 학습률 같은 하이퍼파라미터도 필요 없습니다. 근데 이 편리함에는 함정이 있습니다.

선형 회귀 정규방정식 행렬 구조와 정규방정식 경사하강법 비교표
정규방정식은 최적 가중치를 한 번에 계산하지만 특성 수가 많아지면 역행렬 연산 비용이 급격히 증가한다


정규방정식의 한계 — 왜 경사하강법을 쓰는가

정규방정식의 핵심 연산은 (XᵀX)의 역행렬 계산입니다. 특성이 p개면 XᵀX는 (p × p) 행렬이고, 역행렬 계산 시간복잡도는 O(p³)입니다. 특성이 늘어날수록 연산량이 폭발합니다.

특성 수(p) 연산량 실용성
─────────────────────────────────────
100 100만 회 ✅ 빠름
1,000 10억 회 ⚠️ 느려짐
10,000 1조 회 ❌ 비실용
1억 (LLM) 10²⁴ 회 ❌ 불가능

추가 문제:
XᵀX가 역행렬 없는 경우 (특성 선형 종속 시)
데이터 수 < 특성 수일 때 성립 불가
전체 데이터를 메모리에 올려야 함

현대 딥러닝 모델은 파라미터가 수억~수천억 개입니다. 이 경우 정규방정식은 사실상 불가능합니다. 이 연재에서 앞서 다룬 경사하강법과 Adam 옵티마이저가 바로 이 한계를 극복하기 위해 존재합니다. 선형 회귀에서는 정규방정식이 편리하지만, 스케일이 커지면 경사하강법이 유일한 선택입니다.


RSS, MSE, RMSE — 평가 지표의 차이

선형 회귀를 공부하다 보면 RSS, MSE, RMSE, MAE가 다 나옵니다. 처음엔 다 비슷해 보여서 어느 걸 써야 할지 헷갈렸습니다.

RSS = Σ(yᵢ - ŷᵢ)² (잔차제곱합)
MSE = RSS / n (평균제곱오차)
RMSE = √MSE (제곱근평균제곱오차)
MAE = Σ|yᵢ - ŷᵢ| / n (평균절대오차)

RSS는 데이터 수에 따라 값이 달라집니다.
MSE는 데이터 수로 나눠서 데이터셋 간 비교가 가능합니다.
RMSE는 단위가 y와 같아서 해석이 직관적입니다.
MAE는 이상치에 덜 민감합니다.

앞서 계산한 ŷ = 41x + 180으로 검증:
실제: [1000, 1400, 1800, 2300, 2600]
예측: [1000, 1390, 1820, 2270, 2620]
잔차: [0, -10, 20, -30, 20]
RSS = 0 + 100 + 400 + 900 + 400 = 1800
MSE = 1800 / 5 = 360
RMSE = √360 ≈ 19만 원

RMSE가 19만 원이 나왔습니다. 집값 2,000만 원 기준으로 1% 오차이니 꽤 정확한 편입니다. 실무에서는 RMSE와 MAE를 함께 봅니다. RMSE가 MAE보다 훨씬 크다면 이상치가 있다는 신호입니다. 이전 글에서 다룬 MSE 손실 함수가 바로 이 RSS를 n으로 나눈 형태입니다.


선형 회귀의 가정과 현실

선형 회귀가 잘 작동하려면 몇 가지 가정이 필요합니다. 교과서에는 항상 나오는 내용인데, 직접 데이터를 다뤄봐야 얼마나 중요한지 체감됩니다.

첫째는 선형성입니다. x와 y 사이에 선형 관계가 있어야 합니다. 집값과 면적은 대략 선형이지만 주가와 시간은 비선형입니다. 이 경우 x², log(x) 같은 변환을 추가하는 다항 회귀로 넘어가야 합니다.

둘째는 독립성입니다. 각 데이터 포인트의 오차가 서로 독립이어야 합니다. 시계열 데이터처럼 이전 값이 다음 값에 영향을 주면 이 가정이 깨집니다. 앞서 다룬 LSTM이 필요한 이유입니다.

셋째는 등분산성입니다. 잔차의 분산이 x와 관계없이 일정해야 합니다. 소득이 낮을수록 지출 패턴이 예측 가능하지만 소득이 높아질수록 들쭉날쭉해지면 등분산성이 깨집니다.

넷째는 다중공선성 없음입니다. 면적과 방 개수처럼 특성 간에 강한 상관관계가 있으면 각 계수 해석이 불안정해집니다. XᵀX의 역행렬이 불안정해지는 것도 이 때문입니다.

현실에서 네 가지 가정이 모두 만족되는 경우는 드뭅니다. 가정이 심각하게 깨지면 L1·L2 규제를 추가한 릿지(Ridge), 라쏘(Lasso) 회귀로 넘어가야 합니다. 이 연재에서 다룬 L1·L2 규제가 바로 여기서 직접 연결됩니다.


scikit-learn으로 선형 회귀 구현하기

scikit-learn의 LinearRegression은 내부적으로 SVD(특이값 분해)를 활용한 최소제곱법을 씁니다. 정규방정식보다 수치적으로 안정적입니다. 이 연재에서 다룬 SVD가 여기서 실용적으로 쓰이는 지점입니다.

from sklearn.linear_model import LinearRegression, Ridge
from sklearn.metrics import mean_squared_error
import numpy as np

X = np.array([[20,1],[30,2],[40,2],[50,3],[60,3]])
y = np.array([1000, 1400, 1800, 2300, 2600])

# 기본 선형 회귀 (절편 자동 처리)
model = LinearRegression()
model.fit(X, y)
print(f"가중치: {model.coef_}")
print(f"절편: {model.intercept_:.2f}")

# 예측
pred = model.predict([[45, 2]])
print(f"예측: {pred[0]:.0f}만 원")

# RMSE
rmse = np.sqrt(mean_squared_error(y, model.predict(X)))
print(f"RMSE: {rmse:.2f}만 원")

# 릿지 회귀 (L2 규제 추가)
ridge = Ridge(alpha=1.0)
ridge.fit(X, y)
print(f"릿지 가중치: {ridge.coef_}")

fit_intercept=True가 기본값이라 NumPy로 직접 구현할 때처럼 1 열을 따로 추가할 필요가 없습니다. Ridge(alpha=1.0)에서 alpha가 클수록 가중치를 더 강하게 억제합니다. 이 연재에서 다룬 L2 규제의 λ가 여기서 alpha로 들어가 있습니다.

선형 회귀는 시작점입니다. 이걸 이해하면 로지스틱 회귀가 왜 선형 회귀에 시그모이드를 붙인 구조인지, SVM이 왜 마진을 최대화하는 선형 경계를 찾는지, 신경망 각 레이어가 왜 선형 변환과 활성화 함수의 조합인지가 자연스럽게 연결됩니다. 


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 블로그 이름