콘텐츠로 이동

Ridge & Lasso Regression (정규화 선형 회귀)

개요

문제 정의

일반 선형 회귀(OLS)의 한계:

  • 다중공선성: 특성 간 상관이 높으면 계수 추정 불안정
  • 과적합: 특성이 많으면 훈련 데이터에 과적합
  • 고차원: n < d (샘플 < 특성)이면 해가 유일하지 않음

핵심 아이디어

손실 함수에 가중치 크기를 제한하는 정규화 항 추가:

Loss = MSE + lambda * Penalty(w)
방법 정규화 항 특징
Ridge (L2) lambda * ||w||_2^2 계수 축소
Lasso (L1) lambda * ||w||_1 희소 해 (특성 선택)
Elastic Net alpha * ||w||_1 + (1-alpha) * ||w||_2^2 L1 + L2 조합

알고리즘/수식

Ridge Regression (L2 정규화)

목적 함수:

L_ridge(w) = ||y - Xw||_2^2 + lambda * ||w||_2^2
           = sum(y_i - w^T*x_i)^2 + lambda * sum(w_j^2)

해석적 해:

w_ridge = (X^T*X + lambda*I)^(-1) * X^T*y
  • lambdaI 항이 X^TX의 대각 원소에 추가
  • 항상 역행렬 존재 (양의 정부호)
  • 다중공선성 문제 해결

기하학적 해석:

OLS 해를 원점 방향으로 축소 (shrinkage): - lambda -> 0: OLS 해에 수렴 - lambda -> inf: w -> 0 (모든 계수 0)

Lasso Regression (L1 정규화)

목적 함수:

L_lasso(w) = ||y - Xw||_2^2 + lambda * ||w||_1
           = sum(y_i - w^T*x_i)^2 + lambda * sum(|w_j|)

해석적 해 없음 -> 반복적 최적화 필요

희소 해 (Sparse Solution): - L1 페널티의 기하학적 특성으로 일부 계수가 정확히 0 - 자동 특성 선택 효과

Soft Thresholding (단변수):
w_j = sign(z_j) * max(|z_j| - lambda, 0)

Elastic Net

목적 함수:

L_elastic(w) = ||y - Xw||_2^2 + lambda * (alpha * ||w||_1 + (1-alpha) * ||w||_2^2)
  • alpha = 1: Lasso
  • alpha = 0: Ridge
  • 0 < alpha < 1: 둘의 조합

장점: - Lasso의 희소성 + Ridge의 안정성 - 상관된 특성 그룹을 함께 선택/제거 - Lasso의 "n개 특성만 선택" 제한 극복

비교: Ridge vs Lasso

          Ridge (L2)          |         Lasso (L1)
------------------------------|------------------------------
  제약 영역: 원 (구)           |   제약 영역: 다이아몬드
  모든 계수 축소               |   일부 계수 = 0 (희소)
  해석적 해 존재               |   반복 최적화 필요
  다중공선성에 강건            |   특성 선택 가능
  특성 선택 없음               |   그룹 중 하나만 선택
  미분 가능                    |   원점에서 미분 불가

정규화 경로 (Regularization Path)

lambda 변화에 따른 계수 변화:

lambda 증가
  |
  v
Ridge: 모든 계수가 점진적으로 0에 수렴
Lasso: 계수들이 순차적으로 0이 됨 (knot 발생)

시간 복잡도

방법 복잡도
Ridge (해석적) O(n*d^2 + d^3)
Lasso (좌표 하강) O(nditer)
Elastic Net O(nditer)

하이퍼파라미터 가이드

Ridge

파라미터 설명 권장 범위 기본값
alpha 정규화 강도 (lambda) 0.01 ~ 1000 1.0
solver 최적화 알고리즘 'auto', 'svd', 'cholesky', 'lsqr', 'saga' 'auto'
fit_intercept 절편 학습 True/False True

Lasso

파라미터 설명 권장 범위 기본값
alpha 정규화 강도 0.0001 ~ 10 1.0
max_iter 최대 반복 1000 ~ 10000 1000
tol 수렴 임계값 1e-4 ~ 1e-6 1e-4
selection 좌표 선택 'cyclic', 'random' 'cyclic'

Elastic Net

파라미터 설명 권장 범위 기본값
alpha 정규화 강도 0.0001 ~ 10 1.0
l1_ratio L1 비율 0 ~ 1 0.5

Python 코드 예시

기본 사용법

import numpy as np
import pandas as pd
from sklearn.linear_model import Ridge, Lasso, ElasticNet, LinearRegression
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, r2_score
import matplotlib.pyplot as plt

# 데이터 로드
data = fetch_california_housing()
X, y = data.data, data.target

# Train/test split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 스케일링 (정규화에 필수!)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 모델 비교
models = {
    'OLS': LinearRegression(),
    'Ridge': Ridge(alpha=1.0),
    'Lasso': Lasso(alpha=0.01),
    'ElasticNet': ElasticNet(alpha=0.01, l1_ratio=0.5)
}

print("=== Model Comparison ===")
print(f"{'Model':<15} {'R2':>8} {'RMSE':>8} {'Non-zero coef':>15}")
print("-" * 50)

for name, model in models.items():
    model.fit(X_train_scaled, y_train)
    y_pred = model.predict(X_test_scaled)

    r2 = r2_score(y_test, y_pred)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    n_nonzero = np.sum(model.coef_ != 0)

    print(f"{name:<15} {r2:>8.4f} {rmse:>8.4f} {n_nonzero:>15}")

정규화 경로 시각화

# Ridge 정규화 경로
alphas_ridge = np.logspace(-4, 4, 100)
coefs_ridge = []

for alpha in alphas_ridge:
    ridge = Ridge(alpha=alpha)
    ridge.fit(X_train_scaled, y_train)
    coefs_ridge.append(ridge.coef_)

coefs_ridge = np.array(coefs_ridge)

# Lasso 정규화 경로
alphas_lasso = np.logspace(-4, 1, 100)
coefs_lasso = []

for alpha in alphas_lasso:
    lasso = Lasso(alpha=alpha, max_iter=10000)
    lasso.fit(X_train_scaled, y_train)
    coefs_lasso.append(lasso.coef_)

coefs_lasso = np.array(coefs_lasso)

# 시각화
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

for i, name in enumerate(data.feature_names):
    axes[0].plot(alphas_ridge, coefs_ridge[:, i], label=name)
axes[0].set_xscale('log')
axes[0].set_xlabel('alpha')
axes[0].set_ylabel('Coefficients')
axes[0].set_title('Ridge Regularization Path')
axes[0].legend(loc='upper right', fontsize=8)

for i, name in enumerate(data.feature_names):
    axes[1].plot(alphas_lasso, coefs_lasso[:, i], label=name)
axes[1].set_xscale('log')
axes[1].set_xlabel('alpha')
axes[1].set_ylabel('Coefficients')
axes[1].set_title('Lasso Regularization Path')
axes[1].legend(loc='upper right', fontsize=8)

plt.tight_layout()
plt.savefig('regularization_path.png', dpi=150)
plt.show()

교차 검증으로 최적 alpha 찾기

from sklearn.linear_model import RidgeCV, LassoCV, ElasticNetCV

# RidgeCV
ridge_cv = RidgeCV(
    alphas=np.logspace(-4, 4, 50),
    cv=5
)
ridge_cv.fit(X_train_scaled, y_train)
print(f"Ridge best alpha: {ridge_cv.alpha_:.4f}")
print(f"Ridge CV R2: {ridge_cv.score(X_test_scaled, y_test):.4f}")

# LassoCV
lasso_cv = LassoCV(
    alphas=np.logspace(-4, 1, 50),
    cv=5,
    max_iter=10000
)
lasso_cv.fit(X_train_scaled, y_train)
print(f"\nLasso best alpha: {lasso_cv.alpha_:.4f}")
print(f"Lasso CV R2: {lasso_cv.score(X_test_scaled, y_test):.4f}")
print(f"Lasso selected features: {np.sum(lasso_cv.coef_ != 0)}/{len(lasso_cv.coef_)}")

# ElasticNetCV
enet_cv = ElasticNetCV(
    l1_ratio=[0.1, 0.5, 0.7, 0.9, 0.95, 0.99],
    alphas=np.logspace(-4, 1, 50),
    cv=5,
    max_iter=10000
)
enet_cv.fit(X_train_scaled, y_train)
print(f"\nElasticNet best alpha: {enet_cv.alpha_:.4f}")
print(f"ElasticNet best l1_ratio: {enet_cv.l1_ratio_:.2f}")
print(f"ElasticNet CV R2: {enet_cv.score(X_test_scaled, y_test):.4f}")

Lasso를 통한 특성 선택

# Lasso로 중요 특성 식별
lasso = Lasso(alpha=0.01, max_iter=10000)
lasso.fit(X_train_scaled, y_train)

feature_selection = pd.DataFrame({
    'feature': data.feature_names,
    'coefficient': lasso.coef_,
    'selected': lasso.coef_ != 0
}).sort_values('coefficient', key=abs, ascending=False)

print("\nLasso Feature Selection:")
print(feature_selection.to_string(index=False))

# 선택된 특성만으로 재학습
selected_features = feature_selection[feature_selection['selected']]['feature'].tolist()
selected_idx = [list(data.feature_names).index(f) for f in selected_features]

X_train_selected = X_train_scaled[:, selected_idx]
X_test_selected = X_test_scaled[:, selected_idx]

lr_selected = LinearRegression()
lr_selected.fit(X_train_selected, y_train)
y_pred_selected = lr_selected.predict(X_test_selected)

print(f"\nOLS with selected features:")
print(f"R2: {r2_score(y_test, y_pred_selected):.4f}")
print(f"Features used: {len(selected_features)}/{len(data.feature_names)}")

다중공선성 시뮬레이션

# 다중공선성 있는 데이터 생성
np.random.seed(42)
n = 200
x1 = np.random.randn(n)
x2 = x1 + np.random.randn(n) * 0.1  # x1과 높은 상관
x3 = np.random.randn(n)
y_sim = 2*x1 + 3*x3 + np.random.randn(n) * 0.5

X_sim = np.column_stack([x1, x2, x3])
feature_names_sim = ['x1', 'x2 (corr with x1)', 'x3']

# OLS vs Ridge
lr_sim = LinearRegression()
lr_sim.fit(X_sim, y_sim)

ridge_sim = Ridge(alpha=1.0)
ridge_sim.fit(X_sim, y_sim)

print("\n=== Multicollinearity Simulation ===")
print(f"Correlation between x1 and x2: {np.corrcoef(x1, x2)[0,1]:.4f}")
print(f"\n{'Feature':<20} {'OLS coef':>12} {'Ridge coef':>12}")
print("-" * 50)
for i, name in enumerate(feature_names_sim):
    print(f"{name:<20} {lr_sim.coef_[i]:>12.4f} {ridge_sim.coef_[i]:>12.4f}")

print("\nNote: Ridge는 상관된 특성에 계수를 분산시킴")

언제 쓰나?

Ridge

적합한 상황: - 다중공선성이 있을 때 - 모든 특성이 어느 정도 중요할 때 - 예측 성능 향상이 목적일 때 - 해석적 해가 필요할 때

Lasso

적합한 상황: - 특성 선택이 필요할 때 - 희소 모델이 필요할 때 (많은 특성 중 소수만 중요) - 해석 가능한 모델이 필요할 때 - 고차원 데이터 (d >> n)

Elastic Net

적합한 상황: - 상관된 특성 그룹이 있을 때 - Lasso가 불안정할 때 - Ridge와 Lasso 사이 균형이 필요할 때

장단점

방법 장점 단점
Ridge 안정적, 해석적 해, 다중공선성 해결 특성 선택 불가
Lasso 희소 해, 특성 선택, 해석 용이 불안정할 수 있음, 그룹 중 하나만 선택
Elastic Net Ridge + Lasso 장점 조합 추가 하이퍼파라미터 (l1_ratio)

관련 논문/참고

  • Hoerl, A.E., & Kennard, R.W. (1970). "Ridge regression: Biased estimation for nonorthogonal problems". Technometrics.
  • Tibshirani, R. (1996). "Regression shrinkage and selection via the lasso". Journal of the Royal Statistical Society.
  • Zou, H., & Hastie, T. (2005). "Regularization and variable selection via the elastic net". Journal of the Royal Statistical Society.
  • scikit-learn documentation: https://scikit-learn.org/stable/modules/linear_model.html