기본적인 선형회귀(OLS)는 강력하지만 만능은 아닙니다. 변수가 너무 많아지면 모델이 데이터를 외워버리는 과적합(Overfitting)이 발생하고, 데이터가 직선이 아니거나 결과값이 '횟수(Count)'인 경우에는 잘 맞지 않습니다.
오늘은 이러한 문제를 해결하는 고급 회귀 기법 4가지(Ridge, Lasso, Polynomial, Poisson)와, 그 배경이 되는 편향-분산 트레이드오프를 정리해 보겠습니다.
1. 배경 지식: 편향(Bias)과 분산(Variance)
모델의 성능을 떨어뜨리는 두 가지 주범입니다.
1) 편향 오류 (Bias Error) = 과소적합 (Underfitting)
- 상황: 모델이 너무 단순해서 데이터의 패턴을 제대로 잡아내지 못하는 경우.
- 증상: 학습 데이터에서도 오차가 크고, 테스트 데이터에서도 오차가 큽니다.
2) 분산 오류 (Variance Error) = 과적합 (Overfitting)
- 상황: 모델이 너무 복잡해서 학습 데이터의 잡음(Noise)까지 외워버린 경우.
- 증상: 학습 데이터(In-sample)에서는 완벽에 가깝지만, 새로운 데이터(Out-of-sample)에서는 엉망이 됩니다.
목표: 편향과 분산의 합(Total Error)이 최소가 되는 '최적의 복잡도(Optimal Point)'를 찾는 것입니다.
2. 규제(Regularization) 회귀: Ridge & Lasso
변수가 많아져서 과적합이 의심될 때, 회귀계수($\beta$)의 크기를 억제하여 모델을 단순하게 만드는 방법입니다. (손실함수에 페널티 항을 추가)
| 구분 | Ridge 회귀 (L2) | Lasso 회귀 (L1) |
| 수식 | $\sum \epsilon^2 + \lambda \sum \beta^2$ | $\sum \epsilon^2 + \lambda \sum |
| 특징 | 계수를 0에 가깝게 줄이지만 0이 되지는 않음. | 계수를 완전히 0으로 만들 수 있음. |
| 용도 | 다중공선성 해결, 모든 변수의 영향력 축소 | 변수 선택(Feature Selection), 중요한 변수만 남김 |
| $\lambda$ (람다) | 규제 강도. 클수록 계수를 더 많이 억제함 (분산 감소, 편향 증가). | |
3. 데이터 특성에 맞춘 회귀: 다항식 & 푸아송
1) 다항식 회귀 (Polynomial Regression)
- 상황: $X$와 $Y$가 직선이 아니라 곡선 관계일 때.
- 방법: 독립변수 $X$를 제곱($X^2$), 세제곱($X^3$) 등으로 확장하여 모델링합니다.
- $Y = \beta_0 + \beta_1 X + \beta_2 X^2 + \epsilon$
2) 푸아송 회귀 (Poisson Regression)
- 상황: 종속변수 $Y$가 '발생 횟수(Count)'일 때. (음수가 나올 수 없고, 정수여야 함)
- 의료 예시: 환자의 입원 횟수, 하루 동안 발생한 발작 횟수.
- 특징: 데이터가 푸아송 분포를 따른다고 가정하며, 로그 연결 함수($Log(\lambda)$)를 사용합니다.
4. [실습] Python & R 코드 완벽 정리
Case 1: 과적합 방지 (Ridge & Lasso)
- 상황: 환자의 유전자 정보(변수 100개)로 질병 지수를 예측하는데, 데이터 수에 비해 변수가 너무 많아 과적합이 우려됨.
Python (sklearn)
import numpy as np
import pandas as pd
from sklearn.linear_model import Ridge, Lasso
from sklearn.preprocessing import StandardScaler
# 1. 가상 데이터 생성 (변수가 많은 상황)
np.random.seed(42)
n_samples, n_features = 50, 100
X = np.random.randn(n_samples, n_features)
# 실제로는 처음 5개 변수만 영향이 있고 나머지는 잡음(Noise)
y = 3 * X[:, 0] + 2 * X[:, 1] + np.random.randn(n_samples)
# 2. 데이터 스케일링 (규제 회귀에서는 필수!)
# 이유: 변수들의 단위가 다르면 규제(Penalty)가 불공평하게 적용되므로 표준화가 필요함.
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 3. Ridge 회귀 (L2 규제)
# alpha: 규제 강도 (강의 자료의 lambda에 해당)
ridge = Ridge(alpha=1.0)
ridge.fit(X_scaled, y)
# 4. Lasso 회귀 (L1 규제)
lasso = Lasso(alpha=0.1)
lasso.fit(X_scaled, y)
print("Ridge 첫 5개 계수:", ridge.coef_[:5]) # 0에 가깝지만 0은 아님
print("Lasso 첫 5개 계수:", lasso.coef_[:5]) # 중요하지 않은 변수는 0이 됨
print("Lasso에서 0이 된 변수 개수:", np.sum(lasso.coef_ == 0))
R (glmnet)
# install.packages("glmnet")
library(glmnet)
# 1. 데이터 생성
set.seed(42)
n <- 50; p <- 100
X <- matrix(rnorm(n*p), n, p)
y <- 3 * X[,1] + 2 * X[,2] + rnorm(n)
# glmnet은 자동으로 스케일링을 수행합니다.
# 2. Ridge 회귀 (alpha = 0)
# lambda: 규제 강도
ridge_model <- glmnet(X, y, alpha = 0, lambda = 0.1)
# 3. Lasso 회귀 (alpha = 1)
lasso_model <- glmnet(X, y, alpha = 1, lambda = 0.1)
# 결과 확인
print(coef(ridge_model)[1:6,]) # Ridge 계수
print(coef(lasso_model)[1:6,]) # Lasso 계수 (일부 0으로 변함)

- 해석: 변수 V1~V5의 계수($\beta$)가 모두 살아있습니다.
- 특징: V3, V4, V5 처럼 영향력이 작은 변수들도 0.08, 0.21 처럼 작은 값을 가지고 모델에 남아 있습니다. Ridge는 변수의 영향력을 '축소(Shrinkage)'시킬 뿐, 없애지는 않습니다.

- 해석: V4, V5의 계수가 정확히 0.0000000이 되었습니다.
- 특징: Lasso는 영향력이 적은 변수(V4, V5)를 과감하게 제거(Feature Selection)해버렸습니다. 남은 변수(V1, V2)에 더 집중하여 계수 값이 Ridge보다 더 크게 추정되었습니다.
Insight
- Ridge: "유전자가 100개인데, 미세하지만 모두 조금씩 질병에 영향을 줄 것 같다" $\rightarrow$ 변수를 모두 안고 감.
- Lasso: "유전자가 100개지만, 진짜 범인(Biomarker)은 2~3개일 것이다" $\rightarrow$ 나머지는 0으로 만들어 버림 (변수 선택 효과).
Case 2: 곡선 관계 (다항식 회귀)
- 상황: 약물 투여량($X$)에 따른 치료 효과($Y$)가 처음엔 급격히 오르다가 나중엔 완만해지는 곡선 형태임.
Python (PolynomialFeatures)
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
# 1. 데이터 생성 (곡선 형태)
X = np.array([1, 2, 3, 4, 5]).reshape(-1, 1)
y = np.array([2, 8, 18, 32, 50]) # y = 2 * x^2 형태
# 2. 다항 특성 생성 (X -> X, X^2)
# degree=2: 2차항까지 생성
poly = PolynomialFeatures(degree=2)
X_poly = poly.fit_transform(X)
# 3. 선형회귀 적용
# 변형된 X_poly를 넣으면 다항 회귀가 됨
model = LinearRegression()
model.fit(X_poly, y)
print("회귀계수:", model.coef_) # [0, 0, 2] -> 0*1 + 0*x + 2*x^2
R (poly)
# 1. 데이터 생성
x <- c(1, 2, 3, 4, 5)
y <- c(2, 8, 18, 32, 50)
df <- data.frame(x, y)
# 2. 다항 회귀 (I() 함수 또는 poly() 사용)
# I(x^2): x의 제곱항을 독립변수로 추가하라는 의미
model_poly <- lm(y ~ x + I(x^2), data = df)
summary(model_poly)

데이터가 $y = 2x^2$ 형태의 곡선을 띄고 있을 때, 다항식 회귀가 이를 어떻게 찾아내는지 보여줍니다.
- I(x^2) (2차항)의 계수: 2.000e+00 (즉, 2)입니다.
- 이 모델은 데이터의 규칙이 $2x^2$임을 정확하게 찾아냈습니다.
- x (1차항)와 Intercept (절편): e-15는 소수점 아래 0이 15개라는 뜻으로, 사실상 0입니다.
- Multiple R-squared: 1.
- 설명력이 100%입니다. 오차 없이 완벽하게 곡선을 그려냈다는 뜻입니다.
Insight 일반 선형회귀($y = ax+b$)를 썼다면 $R^2$가 낮고 엉망이었을 것입니다. 약물 농도에 따른 반응처럼 데이터가 급격히 변하는 구간이 있다면, 변수를 제곱($X^2$)하여 모델에 넣어보면. 숨겨진 패턴($R^2=1$)을 찾을 수도 있습니다.
Case 3: 횟수 데이터 (푸아송 회귀)
- 상황: 환자의 나이($X$)에 따른 연간 응급실 방문 횟수($Y$, Count) 예측.
Python (statsmodels)
import statsmodels.api as sm
# 1. 가상 데이터
age = np.array([20, 25, 30, 40, 50, 60, 70, 80])
# 나이가 많을수록 방문 횟수 증가 (Count Data)
visits = np.array([0, 0, 1, 1, 2, 3, 5, 8])
# 상수항 추가
X = sm.add_constant(age)
# 2. 푸아송 회귀 모델링
# GLM(Generalized Linear Model) 사용, family=Poisson 설정
model = sm.GLM(visits, X, family=sm.families.Poisson()).fit()
print(model.summary())
R (glm)
# 1. 데이터 생성
age <- c(20, 25, 30, 40, 50, 60, 70, 80)
visits <- c(0, 0, 1, 1, 2, 3, 5, 8)
df <- data.frame(age, visits)
# 2. 푸아송 회귀
# family = poisson 지정이 핵심
model_poisson <- glm(visits ~ age, family = poisson, data = df)
summary(model_poisson)

- age 계수 (0.05499): P-value가 0.0001로 매우 유의합니다.
- 해석 방법: 푸아송 회귀는 로그($Log$)를 씌운 값이므로, 해석할 때 지수($e$, exp)를 취해야 합니다.
- $e^{0.055} \approx 1.056$
- "나이가 1살 증가할 때마다, 병원 방문 횟수는 약 1.056배 (5.6%) 증가한다"고 해석합니다. (더하기가 아니라 곱하기 개념입니다!)

- Null deviance (22.08): 아무 변수도 안 썼을 때의 오차 (나쁨).
- Residual deviance (1.88): 나이(Age)를 넣었을 때의 오차 (좋음).
- 오차가 22에서 1.88로 획기적으로 줄어들었습니다. 나이가 병원 방문 횟수를 아주 잘 설명한다는 뜻입니다.
Insight
- 언제 쓰는가?: 입원 일수, 응급실 방문 횟수, 발작 횟수 등 "0, 1, 2..."로 떨어지는 카운트 데이터일 때.
- 주의: 만약 결과값에 음수가 있거나 소수점(1.5회)이 있다면 푸아송 회귀를 쓰면 안 됩니다.
'Study Note > 통계' 카테고리의 다른 글
| 숫자를 센다? 무조건 '푸아송 회귀'입니다. (feat. 의료 데이터 활용법) (0) | 2025.12.15 |
|---|---|
| 직선을 넘어 곡선으로, 그리고 모델의 성적표 AIC (0) | 2025.12.15 |
| 회귀모형의 진단: 잔차, 레버리지, 그리고 쿡의 거리 (0) | 2025.12.15 |
| 선형회귀모형의 진단과 선별 (T검정, F검정, AIC, VIF) (0) | 2025.12.15 |
| [의료 통계] 내 BMI가 1 오르면 혈압은 얼마나 오를까? (Python vs R 선형회귀 완벽 비교) (0) | 2025.12.12 |