선형회귀의 핵심: 회귀계수와 규제(Ridge·Lasso·Elastic Net) 이해하기

Alchemist·5일 전

KT AIVLE

목록 보기
117/130

웹 서비스의 처리 시간이 무엇에 따라 달라지는지 알고 싶다고 하자. 요청 크기가 커질수록 시간이 오래 걸릴까? 동시에 접속한 사용자 수가 늘어나면 얼마나 느려질까?

선형회귀(Linear Regression)는 여러 입력값과 예측 대상 사이의 관계를 선형식으로 표현하는 가장 기본적인 머신러닝 방법이다. 복잡한 모델을 쓰기 전에 좋은 기준 모델을 만들고, 변수별 영향을 이해할 때 유용하다.

1. 직선 하나로 데이터를 설명한다는 것

독립변수 하나로 결과를 예측하는 단순 선형회귀는 다음 식으로 표현한다.

[
\hat{y} = wx + b
]

  • x: 입력값
  • w: 기울기, 가중치 또는 회귀계수
  • b: 절편 또는 편향
  • ŷ: 모델의 예측값

예를 들어 ŷ = 30 + 2x라면 x가 한 단위 늘 때 예측값이 2만큼 증가한다. 다만 이것이 x가 결과의 원인이라는 뜻은 아니다. 회귀계수는 다른 조건과 모델 가정에 따른 통계적 관계를 나타낸다.

입력 변수가 여러 개라면 다중 선형회귀가 된다.

[
\hat{y} = b + w_1x_1+w_2x_2+\cdots+w_px_p
]

이때 변수 개수만큼 회귀계수가 만들어진다.

2. 회귀선은 어떻게 결정될까?

모든 데이터가 정확히 한 직선 위에 놓이지는 않는다. 모델은 실제값과 예측값의 오차를 줄이는 방향으로 계수를 찾는다.

일반적인 최소제곱법(OLS)은 다음 잔차 제곱합(SSE)을 최소화한다.

[
SSE = \sum_{i=1}^{n}(y_i-\hat{y}_i)^2
]

평균을 낸 MSE를 최소화해도 같은 데이터에 대해서는 최적의 계수가 같다.

3. Scikit-learn으로 회귀계수 구하기

가상의 API 응답 시간 데이터를 만든다.

import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, r2_score
from sklearn.model_selection import train_test_split

rng = np.random.default_rng(42)
n = 350
payload_kb = rng.uniform(10, 200, n)
concurrent_users = rng.integers(5, 100, n)

latency_ms = (
    40
    + 0.7 * payload_kb
    + 1.1 * concurrent_users
    + rng.normal(0, 20, n)
)

data = pd.DataFrame({
    "payload_kb": payload_kb,
    "concurrent_users": concurrent_users,
    "latency_ms": latency_ms,
})

X = data[["payload_kb", "concurrent_users"]]
y = data["latency_ms"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42
)

model = LinearRegression()
model.fit(X_train, y_train)
pred = model.predict(X_test)

print("MAE:", round(mean_absolute_error(y_test, pred), 2))
print("R²:", round(r2_score(y_test, pred), 3))
print("절편:", model.intercept_)
print("계수:", dict(zip(X.columns, model.coef_)))

model.coef_는 각 입력변수의 계수를 열 순서에 맞는 배열로 반환하고, model.intercept_는 절편을 반환한다.

인공 데이터를 만들 때 요청 크기에 0.7, 동시 사용자 수에 1.1을 곱했으므로 학습한 계수도 대체로 이 근처에서 추정될 것이다. 단, 노이즈와 분할에 따라 정확히 같지는 않다.

4. 회귀계수는 어떻게 해석해야 할까?

payload_kb의 계수가 0.7이라면 다른 입력변수가 일정하다고 가정할 때 요청 크기가 1KB 늘어날 때 예측 응답 시간이 약 0.7ms 증가하는 관계로 해석한다.

주의할 점은 변수별 단위가 다르다는 것이다.

  • 요청 크기는 KB
  • 동시 사용자 수는 명
  • 응답 시간은 ms

이처럼 단위가 다르면 계수의 절댓값이 큰 변수라고 해서 무조건 더 중요하다고 말하기 어렵다. 표준화된 계수, 검증 성능 변화, 도메인 맥락을 함께 확인해야 한다.

5. 변수 수가 많으면 왜 문제가 될까?

변수가 많으면 모델은 더 많은 계수를 추정해야 한다. 일부 변수는 유용하지만 무관한 변수까지 넣으면 과적합 가능성이 커진다. 서로 비슷한 정보를 담은 변수들이 강하게 상관되어 있으면 계수 추정이 불안정해지는 다중공선성 문제도 생길 수 있다.

다만 "변수가 많으면 무조건 과적합"이라는 뜻은 아니다. 표본 수, 신호 강도, 규제, 모델 복잡도를 함께 고려해야 한다.

6. Ridge: 계수를 지나치게 크게 만들지 않기

Ridge 회귀는 최소제곱 손실에 계수 크기에 대한 L2 규제를 추가한다.

[
\text{Loss} = SSE + \alpha\sum_{j=1}^{p}w_j^2
]

alpha가 커질수록 큰 계수에 더 강한 불이익을 준다. 따라서 계수를 상대적으로 작게 만들어 복잡도를 제한하는 데 도움이 된다.

from sklearn.linear_model import Ridge
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

ridge = make_pipeline(StandardScaler(), Ridge(alpha=10.0))
ridge.fit(X_train, y_train)
print("Ridge MAE:", mean_absolute_error(y_test, ridge.predict(X_test)))

Ridge는 일반적으로 계수를 0에 가깝게 만들지만, Lasso처럼 정확히 0으로 만들지는 않는다.

7. Lasso: 필요 없는 계수를 0으로 만들기

Lasso 회귀는 L1 규제를 사용한다.

[
\text{Loss} = \frac{1}{2n}SSE + \alpha\sum_{j=1}^{p}|w_j|
]

Lasso는 일부 계수를 정확히 0으로 만들 수 있다. 이 성질 때문에 간단한 변수 선택 효과를 얻을 수 있다.

from sklearn.linear_model import Lasso

lasso = make_pipeline(
    StandardScaler(),
    Lasso(alpha=0.5, max_iter=10000, random_state=42),
)
lasso.fit(X_train, y_train)
print("Lasso MAE:", mean_absolute_error(y_test, lasso.predict(X_test)))

alpha가 너무 크면 중요한 계수까지 0에 가까워지면서 과소적합이 발생할 수 있다.

8. Elastic Net: L1과 L2를 함께 사용하기

Elastic Net은 Ridge의 L2 규제와 Lasso의 L1 규제를 함께 사용한다.

from sklearn.linear_model import ElasticNet

elastic = make_pipeline(
    StandardScaler(),
    ElasticNet(alpha=0.5, l1_ratio=0.5, max_iter=10000, random_state=42),
)
elastic.fit(X_train, y_train)
print("ElasticNet MAE:", mean_absolute_error(y_test, elastic.predict(X_test)))
  • alpha: 전체 규제 강도
  • l1_ratio: L1 규제의 상대적인 비율. 1에 가까울수록 Lasso 성격이 강해진다.

규제 모델에서 일반적으로 표준화가 중요한 이유는 변수 단위에 따라 계수 크기가 달라지기 때문이다. Pipeline을 사용하면 표준화 통계량을 학습 데이터에서만 계산할 수 있다.

9. 규제를 쓰면 무조건 좋은 모델이 될까?

그렇지는 않다. 변수 수가 적고 관계가 명확하다면 일반 선형회귀가 더 간단하고 좋은 선택일 수 있다. 규제도 데이터에 따라 과소적합을 만들 수 있다.

from sklearn.metrics import mean_absolute_error

candidates = {
    "LinearRegression": LinearRegression(),
    "Ridge": make_pipeline(StandardScaler(), Ridge(alpha=10)),
    "Lasso": make_pipeline(StandardScaler(), Lasso(alpha=0.5, max_iter=10000)),
    "ElasticNet": make_pipeline(
        StandardScaler(), ElasticNet(alpha=0.5, l1_ratio=0.5, max_iter=10000)
    ),
}

from sklearn.model_selection import cross_val_score

cv_scores = {}
for name, candidate in candidates.items():
    scores = cross_val_score(
        candidate,
        X_train,
        y_train,
        cv=5,
        scoring="neg_mean_absolute_error",
    )
    cv_scores[name] = -scores.mean()
    print(name, "CV MAE:", round(cv_scores[name], 2))

best_name = min(cv_scores, key=cv_scores.get)
best_model = candidates[best_name]
best_model.fit(X_train, y_train)

print("선택한 모델:", best_name)
print("최종 Test MAE:", mean_absolute_error(y_test, best_model.predict(X_test)))

위 예시는 학습 데이터 내부의 교차검증으로 알고리즘을 선택한 뒤 마지막에만 테스트 데이터로 평가한다. 실제 프로젝트에서는 Ridge, Lasso, Elastic Net의 alpha도 교차검증으로 조정해야 한다.

자주 헷갈리는 부분

  • coef_는 인과관계를 증명하지 않는다.
  • 단위가 다른 변수의 계수 크기를 그대로 비교하지 않는다.
  • 다중공선성은 예측 성능뿐 아니라 계수의 안정적인 해석을 어렵게 할 수 있다.
  • Ridge, Lasso, Elastic Net의 규제는 하이퍼파라미터를 통해 조절한다.
  • 변수 제거는 반드시 도메인 지식과 검증 성능을 함께 보며 결정해야 한다.

관련 공식 문서

핵심 정리

  • 선형회귀는 입력변수와 Target의 관계를 선형식으로 근사한다.
  • coef_는 각 입력변수의 계수, intercept_는 절편이다.
  • 최소제곱 선형회귀는 일반적으로 잔차 제곱합을 최소화한다.
  • 계수의 해석에는 단위와 다른 변수의 영향이 중요하다.
  • Ridge(L2)는 큰 계수에 불이익을 주어 복잡도를 제어한다.
  • Lasso(L1)는 일부 계수를 0으로 만들어 변수 선택 효과를 낼 수 있다.
  • Elastic Net은 L1과 L2를 함께 사용한다.
  • 규제 강도는 학습 성능이 아니라 교차검증 성능으로 선택하는 편이 좋다.
profile
html_programming_language

0개의 댓글