AI 기초

GUIDE / 중급

선형 회귀: 예측 모델의 출발점

최소 제곱법, 경사 하강법, 다중 선형 회귀, Ridge/Lasso 정규화까지 선형 회귀의 수학적 원리를 scikit-learn과 PyTorch 코드로 완전 정복한다.

PALDYN Team7 MIN READ

지난 글에서 지도 학습의 개념을 살펴봤다. 이번에는 지도 학습의 가장 기본적인 알고리즘인 선형 회귀(Linear Regression)를 다룬다. 1800년대 Legendre와 Gauss가 천문학 문제를 풀기 위해 개발한 이 방법은, 200년이 지난 지금도 실무에서 가장 먼저 시도해야 할 베이스라인 모델이다. 단순하지만 해석 가능성이 높고, 신경망의 선형 레이어도 결국 이 연산이다.

선형 회귀란

입력 변수 X와 출력 변수 y 사이에 선형 관계가 있다고 가정하고, 그 관계를 나타내는 직선(또는 초평면)을 찾는 알고리즘이다.

단순 선형 회귀: ŷ = w·x + b
다중 선형 회귀: ŷ = w₁x₁ + w₂x₂ + ... + wₙxₙ + b
행렬 형태:      ŷ = Xw + b
  • w (가중치/계수): 각 특성이 y에 미치는 영향
  • b (편향/절편): x=0일 때의 y 값
  • ŷ: 예측값 (실제값 y와 구분)

목표: 예측값 ŷ와 실제값 y의 차이(잔차)를 최소화하는 w, b를 찾는다.

손실 함수와 최소 제곱법

선형 회귀의 손실 함수는 평균 제곱 오차(MSE)다.

import numpy as np
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split

# 회귀 데이터 생성: y = 3x₁ + 2x₂ + noise
X, y = make_regression(n_samples=200, n_features=2, noise=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# MSE 손실 = (1/n) Σ (ŷᵢ - yᵢ)²
def mse_loss(w, b, X, y):
    y_hat = X @ w + b
    return np.mean((y_hat - y) ** 2)

# 수동 그래디언트 계산
def compute_gradients(w, b, X, y):
    n = len(y)
    y_hat = X @ w + b
    residual = y_hat - y
    dw = (2/n) * X.T @ residual   # ∂MSE/∂w
    db = (2/n) * np.mean(residual) # ∂MSE/∂b
    return dw, db

MSE를 w에 대해 미분하고 0으로 놓으면 해석적 해(Closed-form Solution)를 구할 수 있다.

# 최소 제곱법 (Ordinary Least Squares, OLS)
# w* = (XᵀX)⁻¹ Xᵀy
# numpy를 이용한 직접 계산

X_aug = np.column_stack([X_train, np.ones(len(X_train))])  # 편향 추가
w_ols = np.linalg.lstsq(X_aug, y_train, rcond=None)[0]    # 안정적인 OLS
print(f"OLS 해: w={w_ols[:-1]}, b={w_ols[-1]:.3f}")

# 주의: (XᵀX)⁻¹은 특성이 많으면 계산 비쌈 (O(n³)) → 대규모에서는 GD 선호

scikit-learn으로 빠르게 구현

from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.preprocessing import StandardScaler
import numpy as np

# 특성 스케일링 (선형 회귀에 권장)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s  = scaler.transform(X_test)

# 1. 기본 선형 회귀
lr = LinearRegression()
lr.fit(X_train_s, y_train)
pred_lr = lr.predict(X_test_s)

print(f"Linear:  R²={r2_score(y_test, pred_lr):.4f}, RMSE={mean_squared_error(y_test, pred_lr)**0.5:.2f}")
print(f"계수: {lr.coef_}, 절편: {lr.intercept_:.3f}")

# 2. Ridge (L2 정규화): 다중공선성 문제 해결
ridge = Ridge(alpha=1.0)
ridge.fit(X_train_s, y_train)
pred_ridge = ridge.predict(X_test_s)
print(f"Ridge:   R²={r2_score(y_test, pred_ridge):.4f}")

# 3. Lasso (L1 정규화): 특성 선택 효과 (일부 계수 → 0)
lasso = Lasso(alpha=0.1, max_iter=5000)
lasso.fit(X_train_s, y_train)
pred_lasso = lasso.predict(X_test_s)
print(f"Lasso:   R²={r2_score(y_test, pred_lasso):.4f}")
print(f"0인 계수 수: {(lasso.coef_ == 0).sum()}")  # 특성 선택 확인

선형 회귀: 최소 제곱법과 경사 하강법

PyTorch로 처음부터 구현

신경망도 결국 선형 레이어들의 조합이다. PyTorch로 선형 회귀를 구현하면 신경망 학습의 기본 구조를 이해할 수 있다.

import torch
import torch.nn as nn
import torch.optim as optim

# 데이터를 텐서로 변환
X_t = torch.FloatTensor(X_train_s)
y_t = torch.FloatTensor(y_train).unsqueeze(1)

# 모델 정의: y = w₁x₁ + w₂x₂ + b
model = nn.Linear(in_features=2, out_features=1)
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 학습 루프
for epoch in range(1000):
    model.train()
    optimizer.zero_grad()
    
    pred = model(X_t)
    loss = criterion(pred, y_t)
    loss.backward()
    optimizer.step()
    
    if epoch % 200 == 0:
        print(f"Epoch {epoch:4d}: Loss={loss.item():.4f}")

# 학습된 파라미터 확인
print(f"\n학습된 가중치: {model.weight.data}")
print(f"학습된 편향: {model.bias.data}")

다항 회귀: 비선형 관계 처리

특성을 변환해 선형 모델로 비선형 관계를 학습할 수 있다.

from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline

# y ≈ x² + 2x + 1 같은 비선형 관계 모델링
poly_model = Pipeline([
    ('poly', PolynomialFeatures(degree=2, include_bias=False)),
    ('scaler', StandardScaler()),
    ('lr', LinearRegression())
])

# 단변수 데이터 예시
X_1d = np.array([1, 2, 3, 4, 5, 6, 7, 8]).reshape(-1, 1)
y_1d = np.array([1.2, 5.1, 9.8, 17.0, 25.1, 36.2, 49.0, 64.1])

poly_model.fit(X_1d, y_1d)
print(f"다항 회귀 R²: {poly_model.score(X_1d, y_1d):.4f}")  # ≈ 0.9998
# degree=2 특성: [x, x²] → 실제 관계 포착

평가 지표

from sklearn.metrics import mean_absolute_error

pred = model_eval(X_test_s)  # 임의 모델 예측

mse  = mean_squared_error(y_test, pred)
rmse = mse ** 0.5
mae  = mean_absolute_error(y_test, pred)
r2   = r2_score(y_test, pred)

print(f"MSE:  {mse:.2f}  (평균 제곱 오차)")
print(f"RMSE: {rmse:.2f}  (원래 단위와 같음 → 해석 용이)")
print(f"MAE:  {mae:.2f}  (이상치에 덜 민감)")
print(f"R²:   {r2:.4f}  (0~1, 높을수록 좋음)")
# R² = 1 - Σ(y-ŷ)² / Σ(y-ȳ)²
# "모델이 y 분산의 몇 %를 설명하는가"

선형 회귀 scikit-learn / PyTorch 구현

언제 선형 회귀를 쓸까

선형 회귀는 여전히 실무에서 첫 번째로 시도해야 할 알고리즘이다.

써야 할 때:

  • 빠른 베이스라인이 필요할 때
  • 계수의 해석이 중요할 때 (의학, 경제 연구)
  • 데이터가 선형 관계를 따를 때
  • 특성이 많고 샘플이 적을 때 (Ridge/Lasso)

다른 방법이 필요할 때:

  • 명확히 비선형 관계 → 랜덤 포레스트, XGBoost, 신경망
  • 이미지, 텍스트 → CNN, Transformer
  • 극도로 복잡한 패턴 → 딥러닝

선형 회귀가 잘 안 된다면, 그것 자체가 중요한 정보다. 데이터가 선형이 아니거나, 특성 엔지니어링이 필요하다는 신호다.


지난 글: 지도학습 vs 비지도학습: 머신러닝의 두 패러다임

다음 글: 로지스틱 회귀: 분류 문제의 첫 걸음


읽어주셔서 감사합니다. 😊

KEEP READING

이어 읽기

AI 기초2026.07.29

검증자 모델: 답을 고르는 눈을 따로 기른다

규칙 검증기, 결과 보상 모델(ORM), 과정 보상 모델(PRM), 자기 검증의 특성과 한계를 비교하고, 여러 후보 중 정답을 골라내는 검증 계층을 실무에서 어떻게 구성할지 코드와 함께 정리합니다.

중급16 MIN
AI 기초2026.07.29

사고 예산 제어: 얼마나 생각하게 할 것인가

사고 예산과 출력 상한의 관계, 난도에 따른 적응형 예산 배분, 예산 소진으로 답변이 잘리는 실패 모드와 그 방어책을 실무 코드와 운영 지표 중심으로 정리합니다.

중급14 MIN