Skip to content
psymon-ai
Go back

2. 선형 회귀 I

2

Table of contents

Open Table of contents

들어가는 글

지난 글에서 지도 학습의 4단계 프레임워크를 다뤘다. 모델 형태를 정하고, 목표를 세우고, 학습하고, 예측한다. 이 단순한 네 단계가 현대 머신러닝의 뼈대라는 이야기였다. 이번 글에서는 그 프레임워크를 구체적으로 실현하는 첫 번째 모델을 다룬다. 바로 선형 회귀(Linear Regression)다.

선형 회귀는 y=ax+by = ax + b 형태로, 모든 모델 가운데 가장 단순하다. 그렇기에 출발점으로 삼기 좋다. 모델이 데이터로부터 무언가를 “학습한다”는 말이 수학적으로 정확히 무엇을 뜻하는지 또렷하게 드러나기 때문이다. 이 가장 얕은 수준에서 학습의 정체를 납득하지 못하면, 그 위에 쌓이는 신경망이나 트랜스포머의 작동을 설명할 언어가 부족해진다. 이번 편은 그 언어를 익히는 여정이다.

선형 회귀란 무엇인가

선형 회귀는 입력 변수 xx와 타겟 값 yy 사이에 선형 관계가 있다고 가정하고, 그 관계를 찾는 모델이다. 여기서 “선형 관계”란 yyxx에 대한 1차 함수, 즉 직선으로 표현할 수 있다는 뜻이다. 입력이 하나인 경우 다음과 같이 쓴다.

y=β0+β1x+ϵy = \beta_0 + \beta_1 x + \epsilon

각 기호가 뜻하는 바를 천천히 살펴보자.

β0\boldsymbol{\beta_0} (절편, Intercept)xx가 0일 때 yy의 기본값이다. 공부 시간이 0시간일 때 예상 점수 같은 값이다. 그래프에서는 직선이 y축과 만나는 지점이다.

β1\boldsymbol{\beta_1} (기울기, Coefficient)xx가 1 증가할 때 yy가 얼마나 변하는지를 나타낸다. 광고비를 1만원 늘렸을 때 매출이 몇 원 오르는지, 공부 시간을 1시간 늘렸을 때 점수가 몇 점 오르는지 같은 값이다. 모델이 학습으로 얻어내려는 핵심 정보다.

ϵ\boldsymbol{\epsilon} (오차항, Error Term)은 완벽한 선형 관계에서 벗어나는 노이즈다. 현실에서는 공부 시간이 같아도 시험 당일 컨디션, 문제 난이도, 커피를 얼마나 마셨는지에 따라 점수가 달라진다. 모델이 설명하지 못하는 이런 변동성을 ϵ\epsilon이 담당한다.

오차항 설명에서 느꼈겠지만, 현실 세계 문제 대부분은 비선형이다. 커피를 10잔 마셨다고 각성도가 10배가 되지는 않는다. 현실에는 포화, 한계효용, 임계점 같은 비선형 요소가 가득하다.

그런데도 선형 모델을 쓰는 까닭은 크게 두 가지다.

첫째, 수학적으로 다루기 쉽다. 미분하면 닫힌 형태의 해(Closed-form)가 나오고, 각 계수가 무엇을 뜻하는지 명료하게 해석된다. 최적화도 쉽고 해석도 쉽다.

둘째, 국소적으로는 선형인 경우가 많다. 복잡한 곡선도 충분히 좁은 구간을 잘라보면 직선에 가깝다. 지구 표면이 둥글지만 우리가 걷는 몇 미터 구간은 평평해 보이는 것과 같다. 미적분을 배웠다면 익숙할 텐데, 테일러 전개의 1차 근사가 강력한 도구로 쓰이는 까닭이 여기 있다. 현대의 깊은 신경망조차 근본을 파보면 선형 변환과 비선형 활성화 함수의 반복으로 이루어진다.

간단한 코드로 구현한 예제를 살펴보자. 100개 데이터 포인트가 있고 각각 하나의 xx 값과 하나의 yy 값을 가진다.

import numpy as np
# 데이터 생성: y = 3 + 2x + 노이즈
np.random.seed(42)
x = np.random.rand(100)
y = 3 + 2 * x + np.random.randn(100) * 0.5
print(f"x shape: {x.shape}")
print(f"y shape: {y.shape}")

우리는 정답이 β0=3\beta_0 = 3, β1=2\beta_1 = 2임을 안다. 하지만 모델은 이 규칙을 모른 채 y=3+2x+\boldsymbol{y = 3 + 2x + }노이즈 데이터만 보고 두 값을 추정해야 한다. 선형 모델이 과연 정답에 가까운 값을 스스로 찾아내는지가 이번 글의 검증 포인트다.

목표 함수 - 잔차 제곱합(RSS)

모델이 얼마나 잘 예측하는지 측정하려면 기준이 필요하다. “잘 예측한다”를 수치로 정의하지 않으면, 어떤 직선이 더 좋은지 비교할 방법이 없다. 선형 회귀에서 가장 많이 쓰는 기준은 잔차 제곱합(Residual Sum of Squares, RSS)이다.

RSS=i=1n(yiy^i)2RSS = \sum_{i=1}^{n} (y_i - \hat{y}_i)^2

  • y^\hat{y}(와이 햇)은 모델이 추정한 예측값(Predicted Value)을 뜻한다.

RSS의 동작은 단순하다.

  1. 각 데이터 포인트에서 실제 값 yiy_i와 예측 값 y^i\hat{y}_i의 차이(잔차, residual)를 구한다.
  2. 이를 제곱해서 모두 더한다.

이 값이 작을수록 모델이 데이터를 잘 설명한다는 뜻이다. 완벽한 모델이라면 모든 잔차가 0이 되어 RSS도 0이다. 현실에는 노이즈가 있으니 RSS가 완전히 0이 될 일은 거의 없다.

코드로 RSS를 직접 계산해 보자.

# 잔차 제곱합
def compute_rss(y_true, y_pred):
residuals = y_true - y_pred
return np.sum(residuals ** 2)
# 임의의 파라미터로 예측해보기
beta_0_guess = 2.5
beta_1_guess = 1.8
y_pred = beta_0_guess + beta_1_guess * x
rss = compute_rss(y, y_pred)
print(f"파라미터의 RSS: {rss:.4f}")
파라미터의 RSS: 55.3962

β0=2.5\beta_0 = 2.5, β1=1.8\beta_1 = 1.8. 정답인 β0=3\beta_0 = 3, β1=2\beta_1 = 2와 조금 다르다. 이 값으로 예측한 RSS는 55.3962다. 목표는 명확하다.

RSS를 최소화하는 β0\boldsymbol{\beta_0}β1\boldsymbol{\beta_1}을 찾아라.

최소 제곱법 - 미분으로 최적값 찾기

함수의 최솟값은 미분해서 0이 되는 지점에 있다. 고등학교에서 2차 함수의 꼭짓점을 미분으로 찾던 그 원리가 그대로 적용된다. RSS를 최소화하는 방법도 같은 발상이다.

다만 RSS는 함수의 변수가 두 개(β0\beta_0β1\beta_1)다. 변수가 둘일 때는 한 번에 하나씩 변수를 잡고 미분하는 편미분(partial derivative)을 쓴다. β0\beta_0으로 편미분할 때는 β1\beta_1을 상수로 취급하고, β1\beta_1로 편미분할 때는 β0\beta_0를 상수로 취급한다. 두 편미분 값을 각각 0으로 놓으면 두 미지수에 대한 두 방정식이 나온다. 이를 연립해 풀면 RSS가 최소가 되는 β^0\hat{\beta}_0, β^1\hat{\beta}_1을 구할 수 있다.

먼저 β0\beta_0로 편미분해 정리하면 다음 식이 나온다.

β^0=yˉβ^1xˉ\hat{\beta}_0 = \bar{y} - \hat{\beta}_1 \bar{x}

  • xˉ\bar{x}(엑스 바)와 yˉ\bar{y}(와이 바)는 각각 xxyy의 평균이다.

다음 β1\beta_1로 편미분해 정리하면 다음 식이 나온다.

β^1=i=1n(xixˉ)(yiyˉ)i=1n(xixˉ)2\hat{\beta}_1 = \cfrac{\sum_{i=1}^{n} (x_i - \bar{x})(y_i - \bar{y})}{\sum_{i=1}^{n} (x_i - \bar{x})^2}

두 식이 시사하는 바가 흥미롭다.

첫 식부터 살펴본다. 양변에 β^1xˉ\hat{\beta}_1 \bar{x}를 더하면 β^0+β^1xˉ=yˉ\hat{\beta}_0 + \hat{\beta}_1 \bar{x} = \bar{y}가 된다. 학습된 회귀 직선의 식이 y^=β^0+β^1x\hat{y} = \hat{\beta}_0 + \hat{\beta}_1 x이므로, x=xˉx = \bar{x}를 대입하면 y^=yˉ\hat{y} = \bar{y}다. 즉 학습된 회귀 직선은 데이터의 평균점 (xˉ,yˉ)\boldsymbol{(\bar{x}, \bar{y})}을 지난다. 선형 회귀 직선은 무작위 선이 아니다. 데이터의 무게중심을 통과하는 축이다.

두 번째 식은 얼핏 복잡해 보이지만 의미는 깔끔하다. 분자와 분모를 각각 nn으로 나누면 분자는 xxyy공분산 Cov(x,y)\text{Cov}(x, y), 분모는 xx분산 Var(x)\text{Var}(x)이다. 즉 기울기는 다음과 같이 쓸 수 있다.

β^1=Cov(x,y)Var(x)\hat{\beta}_1 = \cfrac{\text{Cov}(x, y)}{\text{Var}(x)}

직관적으로 풀어 보자. 공분산은 두 변수가 같은 방향으로 함께 움직이는 정도다. xx가 평균보다 클 때 yy도 같이 평균보다 크면 양수, 반대 방향이면 음수다. 분산은 x\boldsymbol{x} 자체가 얼마나 퍼져 있는지를 잰다.

분자(공분산)만 보면 “xxyy가 함께 변하는 절대량”을 알 뿐, 이게 큰 변화인지 작은 변화인지 판단할 수 없다. 그래서 분모(분산)로 나누어 “xx가 한 단위 움직일 때 yy는 평균적으로 얼마나 함께 움직이는가”라는 비율로 바꾼다. 회귀 계수가 뜻하는 바가 결국 이것이다.

이처럼 RSS를 미분해 0으로 놓고 직접 푸는 절차최소 제곱법(Least Squares Method)이라 부른다. 핵심은 RSS라는 목적 함수를 미분이라는 도구로 직접 풀어 최적 파라미터를 얻기다. 반복 최적화도 필요 없고, 초기값도 필요 없다. 수식에 데이터만 집어넣으면 답이 튀어나온다. 이런 경우를 닫힌 해(closed-form solution)라 부른다.

닫힌 해는 고마운 존재다. 최적화 과정에서 생길 수 있는 온갖 골치 아픈 문제(지역 최적, 학습률 조정, 수렴 실패 등)를 전부 우회한다. 안타깝게도 모델 대부분은 이런 사치를 누리지 못한다. 로지스틱 회귀만 해도 이미 닫힌 해가 없고, 신경망은 말할 것도 없다. 이후에는 경사 하강법 같은 반복 알고리즘에 기대야 한다. 닫힌 해의 깔끔함을 음미해 두자. 이런 경험을 다시 할 기회가 별로 없다.

이제 방금 유도한 두 식을 코드로 옮겨, β^0\hat{\beta}_0β^1\hat{\beta}_1이 정말 정답을 향해 가는지 확인해 보자.

# 최소 제곱법으로 beta_0, beta_1 추정
def least_squares(x, y):
x_mean = np.mean(x)
y_mean = np.mean(y)
# 기울기 계산
numerator = np.sum((x - x_mean) * (y - y_mean))
denominator = np.sum((x - x_mean) ** 2)
beta_1 = numerator / denominator
# 절편 계산
beta_0 = y_mean - beta_1 * x_mean
return beta_0, beta_1
beta_0_hat, beta_1_hat = least_squares(x, y)
print(f"추정된 beta_0: {beta_0_hat:.4f}")
print(f"추정된 beta_1: {beta_1_hat:.4f}")
# 최적 파라미터로 RSS 다시 계산
y_pred_optimal = beta_0_hat + beta_1_hat * x
rss_optimal = compute_rss(y, y_pred_optimal)
print(f"최적 RSS: {rss_optimal:.4f}")
추정된 beta_0: 3.1075
추정된 beta_1: 1.7701
최적 RSS: 20.1646

실행해 보면 추정한 β0\beta_0는 3에, β1\beta_1은 2에 아주 가까운 값이 나온다. 노이즈가 섞여 있으니 정확히 3, 2가 나올 수는 없지만, 그 근처를 맴도는 값이 나온다. 또한 RSS가 처음 추측한 값(55.40)에서 최적 값(20.16)으로 크게 줄어든 점도 확인할 수 있다. 미분이 실제로 더 좋은 직선을 찾아낸 것이다.

모델이 ‘얼마나 잘 맞는지’ 해석하기

RSS만으로는 모델 성능을 직관적으로 파악하기 어렵다. 두 가지 약점이 있다. 같은 모델이라도 데이터가 많아질수록 RSS는 비례해서 커진다. 1만 개 데이터의 RSS 1,000과 100개 데이터의 RSS 100을 같은 잣대로 비교할 수 없다는 뜻이다. yy의 단위에도 휘둘린다. 매출을 원 단위로 재면 RSS는 어마어마하게 커지고, 만원 단위로 바꾸면 1억 분의 1로 줄어든다. 모델의 좋고 나쁨은 그대로인데 숫자만 출렁이는 셈이다. 이런 차이는 해석을 어렵게 한다. 이를 해결할 지표 몇 가지를 알아보자.

RMSE (Root Mean Squared Error)

RMSE=RSSnRMSE = \sqrt{\cfrac{RSS}{n}}

RSS를 데이터 개수로 나눈 다음 루트를 씌운 값이다. 두 단계 모두 의미가 있다. nn으로 나누는 단계는 데이터 크기 의존성을 없애 평균을 만든다. 루트를 씌우는 단계는 제곱했던 단위를 원래 단위로 되돌린다. 결과적으로 “각 데이터 포인트에서 평균적으로 이 정도 틀린다”라는 해석이 가능하다. 예측이 시험 점수라면 “평균 5점 정도 틀린다”처럼 사람이 직관적으로 이해할 수 있는 단위가 된다.

결정 계수 R2\boldsymbol{R^2}

R2=1RSSTSSR^2 = 1 - \cfrac{RSS}{TSS}

여기서 TSS(Total Sum of Squares)(yiyˉ)2\sum(y_i - \bar{y})^2로, 타겟 값 자체의 총 변동량이다. 풀어 말하면 “모델 없이 그냥 평균값으로만 예측했을 때의 오차 총합”이다. 이 값이 모델이 넘어야 할 기준선이 된다.

R2R^2을 직관적으로 보면, RSS/TSSRSS/TSS는 “모델이 줄이지 못하고 남은 오차의 비율”이다. 1에서 빼주면 거꾸로 “모델이 설명한 변동량의 비율”이 된다. R2=0.75R^2 = 0.75면 이 모델이 yy 변동의 75%를 설명한다는 뜻이다. 1에 가까울수록 모델이 데이터를 잘 설명하고, 0에 가까우면 “평균값으로 예측하기와 별 차이 없다”는 뜻이다.

심지어 R2R^2이 음수가 나올 수도 있다. 이는 모델이 평균값 예측보다도 못하다는, 즉 모델을 만든 의미가 없다는 신호다.

# RMSE와 결정 계수 계산
def evaluate_model(y_true, y_pred):
n = len(y_true)
rss = np.sum((y_true - y_pred) ** 2)
tss = np.sum((y_true - np.mean(y_true)) ** 2)
rmse = np.sqrt(rss / n)
r_squared = 1 - rss / tss
return rmse, r_squared
rmse, r2 = evaluate_model(y, y_pred_optimal)
print(f"RMSE: {rmse:.4f}")
print(f"R^2: {r2:.4f}")
RMSE: 0.4491
R²: 0.5765

한 가지 주의할 점이 있다. R2R^2이 높다고 무조건 좋은 모델은 아니다. 피처를 마구 집어넣으면 R2R^2은 기계적으로 올라간다. 의미 있는 피처든 의미 없는 피처든 상관없이 올라간다. 그래서 피처 수에 패널티를 반영한 조정된 R2\boldsymbol{R^2}(Adjusted R²)이라는 지표가 따로 있다. 피처 개수에 비해 진짜 설명력이 얼마나 되는지를 측정한다. 이 주제는 다음 글에서 피처 선택을 다룰 때 본격적으로 다시 이야기한다.

해석할 때 조심해야 할 것들

선형 회귀의 매력 중 하나는 해석 가능성이다. “광고비를 1만원 늘리면 매출이 β1\beta_1만큼 오른다”라는 식의 명확한 해석이 가능하기에, 설명이 중요한 분야(의료, 정책, 금융)에서 많이 사용한다. 그러나 여기엔 빠지기 쉬운 함정이 몇 가지 있다.

함정 1: 상관관계 ≠ 인과관계

통계학 교과서 첫 장에 나오는 경고이지만, 실제 논문과 기사에서 가장 자주 위반되는 원칙이기도 하다. 회귀 계수 β\beta가 크고 통계적으로 유의미하다고 해서, 그 변수가 타겟의 원인이라고 단정할 수 없다.

고전적 예를 들어 보자. 어떤 연구자가 도시별 데이터를 분석했다.

교회 수가 많은 도시일수록 범죄 건수가 많다. (β1>0\beta_1 > 0, p < 0.001)

이 결과만 보면 “교회가 범죄를 유발한다”는 결론이 나올 것 같다. 교회를 없애면 범죄가 줄어들까? 당연히 아니다. 두 변수는 모두 “대도시”라는 숨겨진 변수의 영향을 받는다. 대도시일수록 사람이 많고, 사람이 많으면 교회도 많고 범죄도 많다. 이런 숨겨진 변수를 교란 변수(confounding variable)라 부른다.

이런 예는 끝이 없다. “아이스크림 판매량과 익사 사고 건수는 양의 상관관계가 있다”(숨은 변수: 여름). “해적 수가 줄어들수록 지구 기온이 올랐다”(숨은 변수: 산업화와 해적 소탕이 같은 시기에 진행된 우연한 공존). 회귀 모델은 상관관계를 찾는 도구이지, 인과관계를 증명하는 도구가 아니다.

인과관계를 증명하려면 무작위 대조 시험(RCT), 자연 실험, 혹은 인과 추론(causal inference)이라는 별도 분야의 도구가 필요하다. 이는 최근 LLM 연구에서도 중요한 주제다. “이 파인튜닝 기법이 왜 잘 작동하는가”를 설명할 때 단순 상관만 보고 결론 내리면, 다른 모델이나 데이터셋에서 재현되지 않는 경우가 부지기수다. 최근 NLP 논문이 ablation study(특정 구성 요소를 하나씩 제거해 보며 영향을 측정하는 실험)와 causal probing을 강조하는 배경이기도 하다.

함정 2: 변수 스케일의 영향

또 하나 흔한 실수는 계수 크기로 변수의 중요도를 단순 비교하기다. TV 광고비는 만 원 단위로 기록하고, 라디오 광고비는 천 원 단위로 기록했다고 하자. 이때 두 계수를 그대로 비교하면서 “TV 광고의 영향이 더 크다”거나 “라디오 광고의 영향이 더 크다”고 결론 내리면 안 된다. 애초에 입력 변수의 단위가 다르기 때문이다.

예를 들어 매출을 만 원 단위로 측정한다고 하자. TV 광고비가 1만 원 늘 때 매출이 2만 원 오른다면 βTV=2\beta_\text{TV} = 2다. 반면 라디오 광고비가 1천 원 늘 때 매출이 1천 원 오른다면, 매출 기준으로는 0.1만 원이므로 βradio=0.1\beta_\text{radio} = 0.1이다. 이 둘을 단순히 2와 0.1로 비교하면 TV 광고의 효과가 20배 커 보인다.

하지만 이것은 입력 단위가 다르기 때문에 생기는 착시다. TV는 “1만 원당 2만 원”이고, 라디오는 “1천 원당 1천 원”이다. 라디오 광고비도 만 원 단위로 맞추면 “1만 원당 1만 원”이 된다. 따라서 같은 1만 원 증가를 기준으로 보면 TV는 매출을 2만 원, 라디오는 매출을 1만 원 올리는 셈이다. 이처럼 계수는 반드시 변수의 단위와 함께 해석해야 한다.

이 문제를 줄이는 방법 중 하나가 표준화(standardization)다. 모든 피처를 평균 0, 표준편차 1로 맞춘 뒤 회귀를 돌리면 계수를 같은 스케일에서 비교할 수 있다. 표준화한 변수는 원래 단위가 사라지고 “평균에서 표준편차 몇 개만큼 떨어져 있는가”라는 공통의 기준을 갖기 때문이다. 이렇게 표준화한 변수로 얻은 계수를 표준화 계수(standardized coefficient) 혹은 표준화 베타 계수라고 부른다.

# 평균 0, 표준편차 1로 표준화
def standardize(x):
return (x - np.mean(x)) / np.std(x)
x_std = standardize(x)
y_std = standardize(y)
beta_0_std, beta_1_std = least_squares(x_std, y_std)
print(f"표준화 회귀 계수: {beta_1_std:.4f}")
표준화 회귀 계수: 0.7593

표준화한 회귀에서 β0\beta_0는 항상 0이 되고(두 변수 모두 평균이 0이기 때문), β1\beta_1의 값은 대략 두 변수 사이 상관계수에 가까워진다. 단위를 없앤 회귀가 결국 상관계수와 같은 정보를 준다는 뜻인데, 꽤 우아한 성질이다.

왜 제곱인가 - MLE의 관점

위에서 RSS(잔차 제곱합)를 구할 때 제곱을 사용했다. 왜 굳이 제곱인가. 절댓값을 써도 되지 않을까. 여기엔 몇 가지 이유가 있지만 가장 중요한 것은 그것이 확률론적으로 자연스러운 귀결이기 때문이다. 이제부터 RSS에서 왜 제곱을 쓰는지 MLE 관점에서 자세히 알아보자.

최대 우도 추정(Maximum Likelihood Estimation, MLE)은 통계학에서 가장 널리 쓰이는 파라미터 추정 방법 중 하나다. 아이디어는 이렇다.

관측한 데이터가 특정 확률 분포를 따른다고 가정할 때, 그 분포의 파라미터 값이 어떠해야 지금 관측된 데이터가 가장 그럴듯하게 나올 수 있는지를 찾는다.

여기서 “가장 그럴듯하다”를 수학적으로 말하면 우도(likelihood)가 최대라는 뜻이다. 우도는 관측된 데이터를 고정해두고, 파라미터 값에 따라 그 데이터가 얼마나 그럴듯한지를 나타내는 함수다. 이산 데이터에서는 관측된 데이터가 나올 확률로 볼 수 있고, 연속 데이터에서는 확률밀도로 해석한다.

쉽게 말해, 동전을 10번 던져서 앞면이 7번 나왔다고 하자. 이 동전의 앞면 확률 pp를 어떻게 추정할까. p=0.5p = 0.5, p=0.7p = 0.7, p=0.9p = 0.9 중에서 이 결과를 가장 잘 설명하는 값은 무엇일까. 직관적으로는 p=0.7p = 0.7일 것이다. 실제로 MLE를 적용하면 앞면이 나온 비율인 p=7/10=0.7p = 7/10 = 0.7이 나온다. 이처럼 MLE는 관측한 결과가 가장 자연스럽게 나오도록 만드는 파라미터를 찾는 절차다.

iid 가정과 우도의 곱

MLE를 적용하려면 필요한 가정이 있다. iid 가정(independent and identically distributed)이라 부르는 이 가정은 두 부분으로 나뉜다.

  • 독립(independent): 각 데이터 포인트가 서로 영향을 주지 않는다.
  • 동일 분포(identically distributed): 모든 데이터 포인트가 같은 분포에서 나왔다.

이제 전체 데이터의 우도를 구해보자. iid 가정에 따르면 각 데이터가 서로 독립적으로 관측되었다. 즉, 첫 번째 데이터가 관측되었다고 해서 두 번째 데이터가 나올 확률이 달라지지 않는다.

이 독립 가정 덕분에 전체 데이터의 우도는 각 데이터 우도의 곱으로 표현된다. 두 사건이 독립일 때 결합 확률이 곱으로 분해되는 것과 같은 원리다.

P(AB)=P(A)P(B)P(A \cap B) = P(A) \cdot P(B)

이를 데이터 전체에 적용하면 다음과 같다.

L(θ)=i=1np(yixi,θ)L(\theta) = \prod_{i=1}^{n} p(y_i \mid x_i, \theta)

여기서 L(θ)L(\theta)는 파라미터 θ\theta에 대한 우도 함수다. 각 항 p(yixi,θ)p(y_i \mid x_i, \theta)는 입력 xix_i와 파라미터 θ\theta가 주어졌을 때, 정답 yiy_i가 관측될 가능성을 의미한다. 전체 우도는 이 가능성을 모든 데이터에 대해 곱한 값이다.

하지만 곱셈 형태의 우도는 수학적으로 다루기 번거롭다. 데이터가 100개라면 100개의 확률값을 모두 곱해야 한다. 게다가 각 확률은 대부분 0과 1 사이의 작은 값이므로, 계속 곱하다 보면 값이 매우 작아진다. 실제 계산에서는 너무 작은 값이 0으로 처리되는 언더플로우(underflow) 문제가 생길 수 있다.

그래서 보통 우도에 로그를 취한 로그 우도(log-likelihood)를 사용한다.

logL(θ)=logi=1np(yixi,θ)\log L(\theta) = \log \prod_{i=1}^{n} p(y_i \mid x_i, \theta)

로그를 취하면 곱셈은 덧셈으로 바뀐다.

logL(θ)=i=1nlogp(yixi,θ)\log L(\theta) = \sum_{i=1}^{n} \log p(y_i \mid x_i, \theta)

이 변환이 가능한 이유는 로그 함수가 단조 증가 함수이기 때문이다. 어떤 θ\theta가 우도 L(θ)L(\theta)를 가장 크게 만든다면, 같은 θ\theta가 로그 우도 logL(θ)\log L(\theta)도 가장 크게 만든다. 즉, 우도를 최대화하는 문제와 로그 우도를 최대화하는 문제는 같은 답을 가진다.

결국 MLE에서는 곱으로 된 우도를 직접 다루기보다, 계산하기 쉬운 로그 우도를 최대화한다. 곱셈이 덧셈으로 바뀌기 때문에 수식도 단순해지고, 컴퓨터로 계산하기도 훨씬 안정적이다.

(θ)=i=1nlogp(yixi,θ)\ell(\theta) = \sum_{i=1}^{n} \log p(y_i | x_i, \theta)

이 프레임워크를 선형 회귀에 적용해 보자.

선형 회귀와 정규분포 가정

선형 회귀에서는 오차항 ϵ\epsilon평균 0, 분산 σ2\boldsymbol{\sigma^2}인 정규분포를 따른다고 가정한다.

ϵN(0,σ2)\epsilon \sim \mathcal{N}(0, \sigma^2)

각 기호의 의미는 다음과 같다.

  • ϵ\epsilon
    오차항이다. 실제값 yy와 모델이 설명하는 값 β0+β1x\beta_0 + \beta_1 x 사이의 차다.

  • \sim
    확률론에선 “분포를 따른다”는 뜻으로 쓴다. 왼쪽 ϵ\epsilon이 오른쪽 확률분포에서 나왔다고 이해하면 된다.

  • N\mathcal{N}
    정규분포(normal distribution)를 뜻한다. 가운데가 가장 높고 양쪽으로 갈수록 낮아지는 종 모양의 분포다.

  • 00
    정규분포의 평균이다. 오차의 평균이 0이라는 뜻이다. 즉, 모델이 계속 한쪽 방향으로만 틀리지는 않는다고 가정한다. 어떤 데이터에서는 실제값이 예측값보다 크고, 어떤 데이터에서는 작지만, 전체적으로 보면 그 차이가 0 근처에서 균형을 이룬다는 뜻이다.

  • σ2\sigma^2
    정규분포의 분산이다. 오차가 평균 0 주변에서 얼마나 넓게 퍼져 있는지를 나타낸다. σ2\sigma^2가 작으면 데이터가 회귀 직선 근처에 촘촘히 모여 있고, σ2\sigma^2가 크면 데이터가 회귀 직선 주변에 넓게 흩어져 있다.

이 가정이 왜 합리적일까. 핵심은 작은 요인의 합이다.

시험 점수를 예로 들어보자. 어떤 학생의 점수는 공부 시간만으로 완전히 결정되지 않는다. 컨디션, 전날 잠의 양, 시험 난이도, 찍은 문제가 맞았는지, 아침을 먹었는지 같은 수많은 작은 요인이 함께 영향을 준다. 각각의 요인은 점수를 조금 올리기도 하고, 조금 내리기도 한다.

이처럼 작은 무작위 요인이 독립적으로 더해지면, 그 합은 정규분포에 가까워지는 경향이 있다. 이것이 중심 극한 정리(Central Limit Theorem)의 핵심 아이디어다. 물론 현실의 모든 오차가 반드시 정규분포를 따르는 것은 아니다. 하지만 많은 작은 요인이 합쳐져 생긴 오차라면, 정규분포는 꽤 합리적 근사가 된다.

오차항이 정규분포를 따른다는 말은 곧, 주어진 x\boldsymbol{x}에 대해 y\boldsymbol{y}도 정규분포를 따른다는 뜻이 된다. 이 말이 성립하는 이유는 선형 회귀에서 yy를 다음처럼 보기 때문이다.

y=β0+β1x+ϵy = \beta_0 + \beta_1 x + \epsilon

여기서 β0+β1x\beta_0 + \beta_1 x는 모델이 예측한 값이고, ϵ\epsilon은 오차항이다. xx가 주어지면 β0+β1x\beta_0 + \beta_1 x는 하나의 고정값이 된다. 따라서 이 식에서 실제로 흔들리는 부분은 오차항 ϵ\epsilon뿐이다.

만약 오차항 ϵ\epsilon이 평균 0, 분산 σ2\sigma^2인 정규분포를 따른다면, yy는 고정된 예측값 β0+β1x\beta_0 + \beta_1 x에 정규분포 오차를 더한 값이 된다. 정규분포에 상수를 더하면 분포의 모양과 퍼짐은 그대로 유지되고, 중심만 그 상수만큼 이동한다. 따라서 yy는 평균이 β0+β1x\beta_0 + \beta_1 x이고 분산이 σ2\sigma^2인 정규분포를 따른다고 쓸 수 있다.

yxN(β0+β1x,σ2)y \mid x \sim \mathcal{N}(\beta_0 + \beta_1 x, \sigma^2)

xx를 알고 있을 때, yy는 평균이 β0+β1x\beta_0 + \beta_1 x이고 분산이 σ2\sigma^2인 정규분포에서 나온 값이다.

즉, 회귀 직선은 입력값 xx가 주어졌을 때 평균적 예측값을 알려준다. 실제 데이터는 그 직선을 중심으로 위아래로 흔들린다.

정규분포에서 RSS가 나오는 이유

이제 정규분포 가정이 최소제곱법과 어떻게 연결되는지 보자. 핵심은 정규분포의 확률 밀도 함수 안에 이미 제곱 오차가 들어 있다는 점이다.

정규분포 가정 아래에서, 주어진 xx에 대한 yy의 확률 밀도는 다음과 같다.

p(yx,β,σ2)=12πσ2exp((yβ0β1x)22σ2)p(y \mid x, \beta, \sigma^2) = \cfrac{1}{\sqrt{2\pi\sigma^2}} \exp\left(-\cfrac{(y - \beta_0 - \beta_1 x)^2}{2\sigma^2}\right)

복잡해 보이지만 지금 가장 중요한 부분은 지수 함수 안에 들어 있는 항이다.

(yβ0β1x)2(y - \beta_0 - \beta_1 x)^2

이 값은 실제값 yy와 모델 예측값 β0+β1x\beta_0 + \beta_1 x의 차이를 제곱한 것이다. 즉, 우리가 앞에서 본 제곱 오차가 정규분포 식 안에 이미 들어 있다.

직관적으로 보면 이렇다. 실제값 yy가 회귀 직선 β0+β1x\beta_0 + \beta_1 x에 가까우면 제곱 오차가 작아진다. 그러면 지수 함수 안의 음수 값도 작아지고, 그 데이터가 나올 확률 밀도는 커진다. 반대로 실제값이 회귀 직선에서 멀리 떨어져 있으면 제곱 오차가 커지고, 그 데이터가 나올 확률 밀도는 작아진다.

MLE는 관측된 데이터가 가장 그럴듯하게 나오도록 만드는 파라미터를 찾는 방법이다. 따라서 선형 회귀에서 MLE는 모든 데이터가 회귀 직선 주변에서 가장 그럴듯하게 관측되도록 하는 β0,β1\beta_0, \beta_1을 찾는다.

전체 데이터의 우도는 각 데이터의 확률 밀도를 모두 곱한 값이다.

L(β)=i=1np(yixi,β,σ2)L(\beta) = \prod_{i=1}^{n} p(y_i \mid x_i, \beta, \sigma^2)

하지만 곱셈 형태는 다루기 불편하다. 데이터가 많아질수록 작은 확률 밀도 값을 계속 곱해야 하므로 계산도 불안정해진다. 그래서 보통 로그를 취해 로그 우도(log-likelihood)로 바꾼다. 로그를 취하면 곱셈이 덧셈으로 바뀐다.

logL(β)=i=1nlogp(yixi,β,σ2)\log L(\beta) = \sum_{i=1}^{n} \log p(y_i \mid x_i, \beta, \sigma^2)

이제 정규분포 식에 로그를 취해보자. 12πσ2\cfrac{1}{\sqrt{2\pi\sigma^2}} 같은 앞부분은 β0,β1\beta_0, \beta_1에 따라 달라지지 않는 항이다. 우리가 찾고 싶은 것은 어떤 β0,β1\beta_0, \beta_1이 로그 우도를 가장 크게 만드는지이므로, 이런 상수항은 최적화 과정에서 핵심이 아니다.

결국 β\beta에 따라 달라지는 핵심 부분만 남기면 다음과 같이 정리된다.

(β)i=1n(yiβ0β1xi)2\ell(\beta) \propto -\sum_{i=1}^{n} (y_i - \beta_0 - \beta_1 x_i)^2

여기서 \propto는 “상수항을 제외하면 본질적으로 비례한다”는 뜻이다. 중요한 것은 식 앞에 마이너스가 붙어 있다는 점이다. 로그 우도를 최대화하려면 다음 값을 크게 만들어야 한다.

i=1n(yiβ0β1xi)2-\sum_{i=1}^{n} (y_i - \beta_0 - \beta_1 x_i)^2

그런데 앞에 마이너스가 붙어 있으므로, 이 값을 크게 만드는 일은 뒤의 제곱 오차 합을 작게 만드는 일과 같다. 즉, 로그 우도를 최대화하는 일은 다음 값을 최소화하는 일과 같다.

i=1n(yiβ0β1xi)2\sum_{i=1}^{n} (y_i - \beta_0 - \beta_1 x_i)^2

잠시만, 이 값은 우리가 바로 위에서 다룬 RSS(Residual Sum of Squares) 식과 정확히 같다.

따라서 다음 두 말은 같은 의미가 된다.

정규분포 오차를 가정하고 MLE로 파라미터를 찾는다.
RSS를 최소화하는 파라미터를 찾는다.

이게 중요한 이유는, 최소제곱법이 단순히 “제곱하면 미분하기 편해서” 나온 방법이 아니라는 점이다. 오차가 정규분포를 따른다고 가정하면, MLE 관점에서 자연스럽게 제곱 오차가 등장한다.

정리하면 다음과 같다.

오차가 정규분포를 따른다는 가정 아래에서, MLE로 파라미터를 추정하면 자연스럽게 RSS 최소화가 나온다. 따라서 최소제곱법은 MLE의 특수한 경우다.

이것이 RSS에서 제곱을 쓰는 가장 깊은 이유다. 단순히 계산이 편해서가 아니라, “오차가 정규분포를 따른다”는 확률적 가정과 정확히 연결되기 때문이다.

이 사실은 거꾸로 중요한 함의를 가진다. 오차가 정규분포가 아니라면 제곱 오차가 항상 최선은 아니다.

예를 들어 오차가 라플라스 분포처럼 가운데는 뾰족하고 양쪽 꼬리가 두꺼운 분포를 따른다면, MLE는 제곱 오차가 아니라 절댓값 오차를 최소화하는 방향으로 이어진다. 이것이 MAE와 연결된다. 또 데이터에 심한 이상치가 많다면 정규분포보다 꼬리가 두꺼운 t-분포를 가정하는 편이 더 나을 수 있다.

결국 손실 함수 선택은 단순한 취향이 아니다. 데이터 오차를 어떤 모양으로 가정하느냐에 대한 문제다. MSE를 쓴다는 것은 암묵적으로 “오차가 정규분포에 가깝다”고 보는 것이고, MAE를 쓴다는 것은 다른 형태의 오차 분포를 가정하는 것이다. 이것이 MLE 관점에서 얻을 수 있는 핵심 통찰이다.

NLL: 음의 로그 우도

실무에서는 보통 우도를 최대화하기보다 음의 로그 우도(negative log-likelihood, NLL)를 최소화하는 식으로 쓴다. 최적화 라이브러리 대부분이 최소화를 기본으로 가정하기 때문이다. 부호만 뒤집는 것이라 본질은 같다.

딥러닝 프레임워크에서 자주 보는 크로스 엔트로피 로스(Cross Entropy Loss)도 결국 분류 문제 NLL이다. 평균 제곱 오차도 회귀 문제 NLL이다. 손실 함수의 정체가 사실은 모두 같은 뿌리를 가졌다는 뜻이다. 이 용어를 한 번쯤 확실히 익혀 두면 이후 마주할 수많은 손실 함수의 정체가 한결 또렷해진다.

# 정규 분포 가정 하의 음의 로그 우도
def negative_log_likelihood(beta_0, beta_1, sigma, x, y):
n = len(y)
y_pred = beta_0 + beta_1 * x
residuals = y - y_pred
nll = (n / 2) * np.log(2 * np.pi * sigma**2) + \
(1 / (2 * sigma**2)) * np.sum(residuals**2)
return nll
sigma_hat = np.std(y - y_pred_optimal)
nll = negative_log_likelihood(beta_0_hat, beta_1_hat, sigma_hat, x, y)
print(f"최적 파라미터에서의 NLL: {nll:.4f}")
최적 파라미터에서의 NLL: 61.8318

RSS를 최소화한 결과와 NLL을 최소화한 결과가 일치한다는 점을 수치로 확인할 수 있다. 이 연결을 한 번 눈으로 확인해 두면, 이후 학습에서 “아, 이것도 MLE구나”라는 인식이 자연스레 따라온다.

행렬로 정리하기 - 정규 방정식의 우아함

지금까진 입력 변수가 하나뿐인 단순 선형 회귀를 다뤘다. 현실 문제는 대부분 여러 피처를 가진다. 집값을 예측하려면 평수, 역까지의 거리, 층수, 연식 등 여러 변수를 동시에 고려해야 한다.

피처가 pp개인 경우 모델은 다음과 같이 확장된다.

yi=β0+β1xi1+β2xi2++βpxip+ϵiy_i = \beta_0 + \beta_1 x_{i1} + \beta_2 x_{i2} + \cdots + \beta_p x_{ip} + \epsilon_i

피처가 많아질수록 식 하나가 길어지고, 데이터가 많아질수록 써야 할 식의 개수도 늘어난다. 이 문제를 해결하기 위해 행렬 표기법을 사용한다. 여러 개의 식을 행렬과 벡터의 연산 한 줄로 묶어 표현하는 방법이다.

y=Xβ+ϵ\mathbf{y} = \mathbf{X}\boldsymbol{\beta} + \boldsymbol{\epsilon}

이 식을 실제 모양으로 풀어 쓰면 다음과 같다.

[y1y2yn]=[1x11x12x1p1x21x22x2p1xn1xn2xnp][β0β1β2βp]+[ϵ1ϵ2ϵn]\begin{bmatrix} y_1 \\ y_2 \\ \vdots \\ y_n \end{bmatrix} = \begin{bmatrix} 1 & x_{11} & x_{12} & \cdots & x_{1p} \\ 1 & x_{21} & x_{22} & \cdots & x_{2p} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 1 & x_{n1} & x_{n2} & \cdots & x_{np} \end{bmatrix} \begin{bmatrix} \beta_0 \\ \beta_1 \\ \beta_2 \\ \vdots \\ \beta_p \end{bmatrix} + \begin{bmatrix} \epsilon_1 \\ \epsilon_2 \\ \vdots \\ \epsilon_n \end{bmatrix}

각 기호의 뜻은 다음과 같다.

  • y=[y1y2yn]\mathbf{y} = \begin{bmatrix} y_1 \\ y_2 \\ \vdots \\ y_n \end{bmatrix}
    모든 데이터 포인트의 타깃값을 세로로 쌓은 벡터다. 크기는 n×1n \times 1이다.

  • X=[1x11x12x1p1x21x22x2p1xn1xn2xnp]\mathbf{X} = \begin{bmatrix} 1 & x_{11} & x_{12} & \cdots & x_{1p} \\ 1 & x_{21} & x_{22} & \cdots & x_{2p} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 1 & x_{n1} & x_{n2} & \cdots & x_{np} \end{bmatrix}
    입력 행렬이다. 각 행은 하나의 데이터 포인트, 각 열은 하나의 피처를 나타낸다. 첫 번째 열을 1로 채우는 이유는 절편 β0\beta_0를 행렬 곱 안에 자연스럽게 포함하기 위해서다. 크기는 n×(p+1)n \times (p+1)이다.

  • β=[β0β1β2βp]\boldsymbol{\beta} = \begin{bmatrix} \beta_0 \\ \beta_1 \\ \beta_2 \\ \vdots \\ \beta_p \end{bmatrix}
    모델이 학습해야 할 계수 벡터다. 절편과 각 피처의 계수를 모두 담고 있다. 크기는 (p+1)×1(p+1) \times 1이다.

  • ϵ=[ϵ1ϵ2ϵn]\boldsymbol{\epsilon} = \begin{bmatrix} \epsilon_1 \\ \epsilon_2 \\ \vdots \\ \epsilon_n \end{bmatrix}
    각 데이터 포인트의 오차를 모아놓은 벡터다. 크기는 n×1n \times 1이다.

행렬 곱 Xβ\mathbf{X}\boldsymbol{\beta} 를 한 행만 펼쳐 보면 왜 이 표기가 편리한지 바로 보인다. 예를 들어 첫 번째 행은 다음과 같이 계산된다.

1β0+x11β1+x12β2++x1pβp1 \cdot \beta_0 + x_{11}\beta_1 + x_{12}\beta_2 + \cdots + x_{1p}\beta_p

즉, 첫 번째 데이터 포인트의 예측값이 된다. 마찬가지로 두 번째 행은 두 번째 데이터 포인트의 예측값, nn번째 행은 nn번째 데이터 포인트의 예측값이 된다. 결국 행렬 곱 한 번으로 모든 데이터의 예측값을 한꺼번에 계산하는 셈이다.

예를 들어 데이터가 3개이고 피처가 2개라면 식은 다음처럼 쓸 수 있다.

[y1y2y3]=[1x11x121x21x221x31x32][β0β1β2]+[ϵ1ϵ2ϵ3]\begin{bmatrix} y_1 \\ y_2 \\ y_3 \end{bmatrix} = \begin{bmatrix} 1 & x_{11} & x_{12} \\ 1 & x_{21} & x_{22} \\ 1 & x_{31} & x_{32} \end{bmatrix} \begin{bmatrix} \beta_0 \\ \beta_1 \\ \beta_2 \end{bmatrix} + \begin{bmatrix} \epsilon_1 \\ \epsilon_2 \\ \epsilon_3 \end{bmatrix}

첫 번째 행만 보면 다음 식과 정확히 같다.

y1=β0+β1x11+β2x12+ϵ1y_1 = \beta_0 + \beta_1 x_{11} + \beta_2 x_{12} + \epsilon_1

행렬 표기법은 복잡한 수식을 새로 만드는 것이 아니라, 같은 식들을 한 줄로 압축해서 쓰는 방법이다.

이 표기의 위력은 데이터 개수와 피처 개수에 관계없이 같은 형태의 수식으로 모델을 표현할 수 있다는 점이다. nn이 100이든 100만이든, pp가 3개든 100개든, 식의 모양은 그대로다. 이것이 선형대수가 머신러닝에서 사랑받는 까닭이다.

RSS 행렬식과 정규방정식

RSS를 행렬로 쓰면 다음과 같다.

RSS=(yXβ)T(yXβ)RSS = (\mathbf{y} - \mathbf{X}\boldsymbol{\beta})^T(\mathbf{y} - \mathbf{X}\boldsymbol{\beta})

하나씩 살펴보자, 먼저

yXβ\mathbf{y} - \mathbf{X}\boldsymbol{\beta}

잔차 벡터(residual vector)다. 즉, 실제값에서 예측값을 뺀 값들을 세로로 쌓아놓은 벡터다.

yXβ=[y1y2yn][y^1y^2y^n]=[y1y^1y2y^2yny^n]\mathbf{y} - \mathbf{X}\boldsymbol{\beta} = \begin{bmatrix} y_1 \\ y_2 \\ \vdots \\ y_n \end{bmatrix} - \begin{bmatrix} \hat{y}_1 \\ \hat{y}_2 \\ \vdots \\ \hat{y}_n \end{bmatrix} = \begin{bmatrix} y_1 - \hat{y}_1 \\ y_2 - \hat{y}_2 \\ \vdots \\ y_n - \hat{y}_n \end{bmatrix}

여기서 각 예측값은

y^i=β0+β1xi1++βpxip\hat{y}_i = \beta_0 + \beta_1 x_{i1} + \cdots + \beta_p x_{ip}

로 계산된다. 따라서 잔차 벡터의 각 원소는 “실제값 - 예측값”이다.

이제 이 벡터를 자기 자신과 내적해보자.

(yXβ)T(yXβ)=[y1y^1y2y^2yny^n][y1y^1y2y^2yny^n](\mathbf{y} - \mathbf{X}\boldsymbol{\beta})^T(\mathbf{y} - \mathbf{X}\boldsymbol{\beta}) = \begin{bmatrix} y_1 - \hat{y}_1 & y_2 - \hat{y}_2 & \cdots & y_n - \hat{y}_n \end{bmatrix} \begin{bmatrix} y_1 - \hat{y}_1 \\ y_2 - \hat{y}_2 \\ \vdots \\ y_n - \hat{y}_n \end{bmatrix}

행벡터와 열벡터를 곱하면 각 원소끼리 곱한 뒤 모두 더한 값이 된다. 그래서 결과는 다음과 같다.

RSS=(y1y^1)2+(y2y^2)2++(yny^n)2RSS = (y_1 - \hat{y}_1)^2 + (y_2 - \hat{y}_2)^2 + \cdots + (y_n - \hat{y}_n)^2

즉,

RSS=i=1n(yiy^i)2RSS = \sum_{i=1}^{n} (y_i - \hat{y}_i)^2

가 된다. 다시 말해, 행렬 형태의 RSS는 우리가 앞에서 본 잔차 제곱합을 더 압축해서 쓴 것에 불과하다. 표기만 바뀌었을 뿐 의미는 완전히 같다.

이제 목표는 이 RSS를 가장 작게 만드는 계수 벡터 β\boldsymbol{\beta}를 찾는 것이다. 이를 위해 RSS를 β\boldsymbol{\beta}에 대해 미분하고 0으로 놓는다. 전개 과정을 한 번 써보면 다음과 같다.

먼저 RSS를 전개하면

RSS=(yXβ)T(yXβ)=yTy2βTXTy+βTXTXβRSS = (\mathbf{y} - \mathbf{X}\boldsymbol{\beta})^T(\mathbf{y} - \mathbf{X}\boldsymbol{\beta}) = \mathbf{y}^T\mathbf{y} - 2\boldsymbol{\beta}^T\mathbf{X}^T\mathbf{y} + \boldsymbol{\beta}^T\mathbf{X}^T\mathbf{X}\boldsymbol{\beta}

가 된다.

여기서 yTy\mathbf{y}^T\mathbf{y}β\boldsymbol{\beta}와 무관한 상수항이다. 따라서 미분하면 사라진다. 나머지 항들을 β\boldsymbol{\beta}로 미분하면

RSSβ=2XTy+2XTXβ\cfrac{\partial RSS}{\partial \boldsymbol{\beta}} = -2\mathbf{X}^T\mathbf{y} + 2\mathbf{X}^T\mathbf{X}\boldsymbol{\beta}

를 얻는다.

최솟값에서는 기울기가 0이어야 하므로,

2XTy+2XTXβ=0-2\mathbf{X}^T\mathbf{y} + 2\mathbf{X}^T\mathbf{X}\boldsymbol{\beta} = 0

이 된다. 양변을 정리하면

XTXβ=XTy\mathbf{X}^T\mathbf{X}\boldsymbol{\beta} = \mathbf{X}^T\mathbf{y}

를 얻는다. 이것이 정규 방정식(normal equation)의 기본형이다.

이제 XTX\mathbf{X}^T\mathbf{X}가 역행렬을 가진다고 가정하면, 양변에 그 역행렬을 곱해

β^=(XTX)1XTy\hat{\boldsymbol{\beta}} = (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{y}

를 얻는다.

이 식의 의미는 분명하다. 입력 행렬 X\mathbf{X}와 타깃 벡터 y\mathbf{y}만 있으면, 모든 샘플과 모든 피처를 고려해 한 번에 RSS를 최소화하는 최적 계수를 계산할 수 있다는 뜻이다. 선형 회귀에서 따로 구했던 기울기와 절편 공식도 사실은 이 정규 방정식의 특수한 경우다.

즉, 우리가 앞에서 풀었던 단순 선형 회귀는 더 큰 일반식의 작은 예시였던 셈이다. 이 모든 것이 행렬 한 줄로 정리된다는 점에서, 정규 방정식은 정말 우아한 결과라고 할 만하다.

정규 방정식의 실무적 한계

정규 방정식은 깔끔한 수식이지만, 실무에선 실용적 문제가 세 가지 있다.

첫째, 역행렬 계산 비용이 크다. XTX\mathbf{X}^T\mathbf{X}의 크기는 (p+1)×(p+1)(p+1) \times (p+1)이고, 역행렬을 구하는 비용은 O(p3)O(p^3)에 비례한다. 피처가 10,000개만 되어도 역행렬 연산이 어마어마한 부담이 된다. 1만의 세제곱은 1조다.

둘째, XTX\mathbf{X}^T\mathbf{X}가 역행렬을 갖지 않거나, 역행렬이 매우 불안정할 수 있다.

0은 역수가 없다. 10\frac{1}{0}을 정의할 수 없기 때문이다. 행렬도 비슷하다. 모든 행렬이 역행렬을 갖는 것은 아니다. 역행렬이 없는 행렬을 특이 행렬(singular matrix)이라 부른다.

그렇다면 언제 이런 일이 생길까. 대표적으로 피처들 사이 정보가 완전히 겹칠 때다. 예를 들어 집값을 예측하는 모델에 다음 두 피처를 동시에 넣었다고 하자.

  • 평수
  • 제곱미터

평수와 제곱미터는 사실상 같은 정보를 다른 단위로 표현한 것이다. 하나를 알면 다른 하나를 거의 정확히 계산할 수 있다. 이런 식으로 어떤 피처를 다른 피처의 조합으로 표현할 수 있으면, 피처들 사이에 선형 종속성이 있다고 말한다.

이런 경우 모델에 문제가 생긴다. “평수의 효과”와 “제곱미터의 효과”를 따로 구분하기 어렵기 때문이다. 둘이 거의 같은 정보를 담고 있으니, 어느 쪽 계수에 얼마나 책임을 줄지 애매해진다. 그 결과 XTX\mathbf{X}^T\mathbf{X}가 특이 행렬이 되어 역행렬이 존재하지 않을 수 있다.

실무에서 완전한 특이 행렬보다 더 자주 마주치는 문제는 거의 특이 행렬(near-singular matrix)이다. 거의 특이 행렬이라는 말은, 역행렬이 아예 없는 것은 아니지만 역행렬 계산이 매우 불안정한 상태를 뜻한다.

예를 들어 두 피처가 완전히 같진 않지만 거의 같은 정보를 담고 있다고 하자. “평수”와 “실사용 면적”처럼 매우 강하게 상관된 변수가 함께 들어간 경우엔 역행렬은 존재할 수 있지만, 데이터가 조금만 바뀌어도 계산된 계수가 크게 흔들린다. 따라서 모델 예측은 어느 정도 유지되더라도, 개별 계수의 해석은 매우 불안정해진다.

셋째, 메모리 요구량이 크다. 정규 방정식은 기본적으로 전체 데이터 행렬 X\mathbf{X}를 바탕으로 XTX\mathbf{X}^T\mathbf{X}XTy\mathbf{X}^T\mathbf{y}를 계산한다. 작은 데이터에서는 문제가 없지만, 데이터가 매우 커지면 X\mathbf{X}를 한 번에 메모리에 올리는 것부터 부담된다. 물론 XTX\mathbf{X}^T\mathbf{X}XTy\mathbf{X}^T\mathbf{y}를 조금씩 누적해서 계산하는 방법도 있지만, 피처 수가 많아지면 XTX\mathbf{X}^T\mathbf{X} 자체의 크기도 커진다. 결국 데이터 크기와 피처 수가 모두 커질수록 정규 방정식은 계산과 메모리 양쪽에서 부담이 된다.

실무에서는 어떻게 푸는가

이런 까닭에 실무에서는 정규 방정식을 그대로 쓰기보다 상황에 맞는 다른 방법을 사용한다.

먼저, 같은 최소제곱 문제를 더 안정적으로 풀기 위한 선형대수 기법이 있다. 대표적 방법이 QR 분해SVD 분해다. 둘 다 핵심은 비슷하다. 역행렬을 직접 구하지 않고, 더 안정적인 방식으로 해를 구한다는 것이다.

QR 분해는 입력 행렬 X\mathbf{X}를 계산하기 좋은 형태로 나누어 최소제곱 문제를 푸는 방법이다. 정규 방정식처럼 XTX\mathbf{X}^T\mathbf{X}를 만든 뒤 역행렬을 구하는 대신, 원래의 X\mathbf{X}를 직접 분해해 해를 구한다. XTX\mathbf{X}^T\mathbf{X}를 만드는 과정은 수치 오차를 키울 수 있는데, QR 분해는 이 단계를 피하므로 더 안정적인 경우가 많다.

SVD 분해는 행렬의 구조를 더 깊이 들여다보는 방법이다. 행렬 안에 정보가 많이 담긴 방향과 거의 없는 방향을 분리해서 보여준다. 피처들 사이에 중복이 많거나 행렬이 거의 특이한 경우, 즉 역행렬 계산이 불안정한 경우에도 SVD는 약한 방향을 조심스럽게 처리해 비교적 안정적으로 해를 구할 수 있다. 역행렬이 없거나 불안정할 때 사용하는 의사역행렬(pseudo-inverse)도 SVD를 통해 계산할 수 있다.

다만 QR 분해와 SVD 분해의 자세한 원리는 수치선형대수의 영역이다. 이 글의 범위를 벗어나므로 여기서는 깊게 들어가지 않는다. 지금은 정규 방정식의 한계를 피하기 위해 역행렬을 직접 구하지 않는 더 안정적인 계산법들이 있다는 정도만 기억하고 넘어가자.

또 다른 방향은 경사 하강법(gradient descent) 같은 반복적 최적화 방법이다. 정규 방정식이나 QR/SVD가 선형대수적으로 해를 계산하는 방법이라면, 경사 하강법은 손실 함수가 줄어드는 방향으로 파라미터를 조금씩 고쳐 나가는 방법이다. 정답을 공식으로 한 번에 구하는 대신, 여러 번의 작은 업데이트를 통해 답에 가까워진다. 이 방식은 데이터를 작은 묶음으로 나누어 처리할 수 있어 대규모 데이터나 복잡한 모델에서 특히 중요하다. 더 깊은 내용은 이후 글에서 자세히 다룰 예정이다.

정리하면 이렇다. 정규 방정식은 선형 회귀 구조를 가장 깔끔하게 보여주는 아름다운 공식이다. 하지만 피처가 많아지거나 데이터가 커지면 계산 비용, 역행렬의 존재성과 수치 안정성, 메모리 문제가 생길 수 있다. 그래서 실제 머신러닝에서는 데이터 크기와 피처 간 관계에 따라 정규 방정식, QR/SVD 같은 안정적인 선형대수 방법, 경사 하강법 같은 반복적 최적화 방법을 적절히 선택한다.

나가는 글

여기까지 선형 회귀의 가장 기본 구조를 살펴봤다. 모델의 형태, 목적 함수(RSS), 최적화 방법(최소제곱법), MLE와의 연결, 행렬 표기, 정규 방정식의 의미까지. 이 정도면 선형 회귀의 수학적 토대는 꽤 단단히 만들어진 셈이다.

하지만 수학적 토대를 이해하는 것과, 현실 데이터를 다루는 일은 다른 문제다. 지금까진 설명을 위해 가장 단순한 상황을 가정했다. 입력 변수는 숫자였고, 관계는 직선에 가까웠으며, 모델에 넣을 피처도 이미 정해져 있었다. 실제 데이터는 대개 이보다 훨씬 복잡하다.

변수가 숫자가 아니라 범주형이면 어떻게 해야 할까? 두 변수가 따로 작용하는 것이 아니라 함께 작용한다면 어떻게 표현해야 할까? 관계가 직선이 아니라 곡선에 가깝다면 선형 회귀는 쓸 수 없을까? 피처가 수십 개, 수백 개로 늘어나면 어떤 변수를 모델에 포함해야 할까?

다음 글 선형 회귀 II에서는 이 질문들을 다룬다. 범주형 변수 처리, 상호작용 항, 다항 항, 피처 선택 알고리즘까지 살펴보며 선형 회귀를 실제 문제에 적용할 수 있는 형태로 확장해볼 것이다. 이론에서 실전으로 넘어가 보자.