본문 바로가기
  • 조금 느려도, 꾸준히
Artificial Intelligence/Machine Learning

핸즈온 머신러닝(5) - 규제가 있는 선형 모델

by chan 2020. 1. 24.
반응형

선형 회귀 모델에서는 보통 모델의 가중치를 제한하여 규제를 가한다. 가중치를 제한하는 방법에 따라 릿지 회귀, 라쏘 회귀, 엘라스틱넷으로 구분할 수 있다.

 

# 릿지 회귀 (티호노프 규제)

규제항 $\alpha \sum_{i=1}^{n} \theta_i^2$ 이 비용 함수에 추가된 선형 회귀 버전이다.

 

이로 인해 학습 알고리즘을 데이터에 맞추는 과정에서 모델의 가중치가 가능한 작게 유지되도록 한다. 

규제항은 훈련하는 동안에만 비용 함수에 추가되며 모델의 훈련이 끝나면 모델의 성능을 규제가 없는 성능 지표로 평가한다. 릿지 회귀의 비용 함수는 다음과 같다.

$$ J(\theta) = MSE(\theta) + \alpha \frac{1}{2}\sum_{i=1}^{n} \theta_i^2$$

 

하이퍼파라미터 $\alpha$ 는 모델을 얼마나 많이 규제할지 조절하며, 편향(bias) $\theta_0$ 는 규제되지 않는다. (시그마에서 $i$=1 부터 시작함) 

 

릿지 회귀는 입력 특성의 스케일에 민감하기 때문에 수행하기 전에 데이터의 스케일을 맞추는 것이 중요하다.

 

다음은 선형 데이터에 서로 다른 $\alpha$ 를 사용해 릿지 모델을 훈련시킨 결과이다. 

from sklearn.linear_model import Ridge
import numpy as np
np.random.seed(42)
m = 20
X = 3 * np.random.rand(m, 1)
y = 1 + 0.5 * X + np.random.randn(m, 1) / 1.5
X_new = np.linspace(0, 3, 100).reshape(100, 1)

 

from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler

def plot_model(model_class, polynomial, alphas, **model_kargs):
    for alpha, style in zip(alphas, ("b-", "g--", "r:")):
        # alpha = 0 이면 선형 회귀 모델로 훈련
        model = model_class(alpha, **model_kargs) if alpha > 0 else LinearRegression()
        # polynomial 옵션 파이프라인 추가
        if polynomial:
            model = Pipeline([
                    # 데이터 확장, 10차원 (degree = 10)
                    ("poly_features", PolynomialFeatures(degree=10, include_bias=False)),
                    ("std_scaler", StandardScaler()), # 스케일 조정
                    ("regul_reg", model),
                ])
        model.fit(X, y)
        y_new_regul = model.predict(X_new)
        # 그래프 선 굵기 지정
        lw = 2 if alpha > 0 else 1
        plt.plot(X_new, y_new_regul, style, linewidth=lw, label=r"$\alpha = {}$".format(alpha))
    plt.plot(X, y, "b.", linewidth=3)
    plt.legend(loc="upper left", fontsize=15)
    plt.xlabel("$x_1$", fontsize=18)
    plt.axis([0, 3, 0, 4])

plt.figure(figsize=(8,4))
plt.subplot(121)
# 기본 데이터셋으로 릿지 모델 예측 
plot_model(Ridge, polynomial=False, alphas=(0, 10, 100), random_state=42)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.subplot(122)
# 확장된 데이터셋으로 릿지 모델 예측
plot_model(Ridge, polynomial=True, alphas=(0, 10**-5, 1), random_state=42)

plt.show()

plt.show()

 

위 그래프를 보면, 왼쪽 그래프는 기본 데이터셋으로 선형적인 릿지 모델의 예측 결과이고, 오른쪽 그래프는 PolynomialFeatures를 사용해 데이터를 10차원으로 확장하고 StandardScaler를 사용해 스케일을 조정한 데이터셋에 릿지 다항 회귀 모델을 적용한 결과이다.

 

$\alpha$ 를 증가시킬수록 왼쪽 그래프에서는 직선의 기울기가 감소하며 오른쪽 그래프에서는 곡선의 모양이 직선에 가까워지는 것을 볼 수 있으며, bias 는 커지는 것을 볼 수 있다.

 

즉 $\alpha$ 를 크게 하면 그에 영향을 받는 가중치들의 변화량이 줄어들고 영향을 받지 않는 bias는 커지게 되므로 모델은 분산이 줄면서 편향이 커지게 된다.

 

릿지 회귀를 계산하기 위해 정규방정식과 경사 하강법을 사용할 수도 있다. 다음 수식은 릿지 회귀의 정규방정식을 나타낸 것이다.

$$ \hat{\theta} = (X^{T}\cdot X + \alpha A)^{-1} \cdot X^{T} \cdot y $$

 

$A$ 는 bias에 해당하는 맨 좌측 상단의 원소가 0 인 $(n+1) \times (n+1)$ 의 단위 행렬 이다.

 

 

사이킷런에서 정규방정식을 사용해 릿지 회귀를 적용해보면 다음과 같다.

 

from sklearn.linear_model import Ridge
# 규제 강도 1, 숄레스키 분해 방법을 사용
ridge_reg = Ridge(alpha=1, solver="cholesky", random_state=42)
ridge_reg.fit(X, y)
ridge_reg.predict([[1.5]])
# array([[1.55071465]])

* 숄레스키 분해란 안드레 루이 숄레스키가 발견한 행렬 분해이며 어떤 행렬 $A$ 가 대칭이고 양의 정부호 행렬일 경우 $A = L \cdot L^T $ 로 분해된다. 이때 $L$ 은 하삼각 행렬이며 이를 이용해 정규방정식 $(X^T \cdot X + \alpha A) \cdot \hat{\theta} = (L \cdot L^T) \cdot \hat{\theta} = X^T \cdot y $ 로 바꾸어 $\hat{\theta}$ 를 구할 수 있다. 숄레스키 분해의 장점은 성능이며 Ridge의 solver 매개변수 기본값은 희소행렬이나 특이행렬이 아닐 경우 cholesky 가 된다 (원래는 auto).

 

다음은 확률적 경사 하강법을 사용했을 때이다.

 

from sklearn.linear_model import SGDRegressor
# 확률적 경사 하강 모델 SGD , 규제 파라미터 penalty = "12"
sgd_reg = SGDRegressor(max_iter=50, penalty="l2", tol=1e-3, random_state=42)
sgd_reg.fit(X, y.ravel())
sgd_reg.predict([[1.5]])
# array([1.47012588])

이때 penalty 매개변수는 사용할 규제를 지정하는데, "12" 는 SGD가 비용 함수에 가중치 벡터의 $l_2$ 노름의 제곱을 2로 나눈 규제항을 추가하게 만든다. ( 즉 $\frac{1}{2} \sum_{i=1}^{n} \theta_i^2 $ 이며 릿지 회귀와 같음)

 

 

 

 

# 라쏘 회귀

라쏘(Least Absolute Shrinkage and Selection Operator, Lasso) 회귀는 릿지 회귀처럼 비용 함수에 규제항을 더하지만 $l_2$ 노름의 제곱을 2로 나눈 것 대신 가중치 벡터의 $l_1$ 노름을 사용한다.

 

$$ J(\theta) = MSE(\theta) + \alpha \sum_{i=1}^{n} |\theta_i| $$

 

 

다음은 릿지 모델에서와 똑같은 과정으로 라쏘 모델을 사용한 결과이다.

 

from sklearn.linear_model import Lasso

plt.figure(figsize=(8,4))
plt.subplot(121)
plot_model(Lasso, polynomial=False, alphas=(0, 0.1, 1), random_state=42)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.subplot(122)
plot_model(Lasso, polynomial=True, alphas=(0, 10**-7, 1), tol=1, random_state=42)

plt.show()

 

 

라쏘 회귀는 덜 중요한 특성의 가중치를 완전히 제거하려는 특성이 있다. 오른쪽 그래프에서 녹색 점선은 10차 다항 회귀 모델을 적용했음에도 불구하고 2차방정식의 모습으로 보이며 그것도 거의 선형적으로 나타난다. 즉 차수가 높은 다항 특성의 가중치가 모두 0이 되었다는 것이다.

 

즉 라쏘 회귀는 자동으로 특성 선택을 하여 희소 모델(sparse model) 을 만든다(0이 아닌 특성의 가중치가 적다).

 

 

라쏘의 비용 함수는 $\theta_i$ = 0 에서 미분 가능하지 않다( $\theta$ 를 미분하면 1이 되는데 실제 값 0과 불일치 하므로). 그래서 $\theta_i$ = 0 일 때 서브그래디언트 벡터 $g$ 를 사용하여 경사 하강법에 적용한다. 다음 식은 경사 하강법을 위해 라쏘 비용 함수에 사용하는 서브그래디언트 벡터 공식이다.

 

$$ g(\theta, J) = \nabla_\theta MSE(\theta) + \alpha \begin{pmatrix} sign(\theta_1) \\ sign(\theta_2) \\ \vdots \\ sign(\theta_n) \end{pmatrix}$$

 

이때 $sign(\theta_i) = -1(\theta_i < 0),\; 0(\theta_i = 0), \; +1(\theta_i > 0)$ 이다.

 

 

 

 

 

# 엘라스틱넷

엘라스틱넷은 릿지 회귀와 라쏘 회귀를 절충한 모델이다. 규제항은 릿지와 라쏘 회귀의 규제항을 단순히 더해서 사용하며 이때 혼합 비율 $r$ 로 혼합 정도를 조절한다. $r = 1$ 이면 라쏘 회귀와 같고, $r=0$ 이면 릿지 회귀와 같다.

 

$$ J(\theta) = MSE(\theta) + r\alpha \sum_{i=1}^{n} |\theta_i| + \frac{1-r}{2} \alpha \sum_{i=1}^{n} \theta_i^2$$

 

다음은 사이킷런의 ElasticNet 을 사용한 예제이다.

 

from sklearn.linear_model import ElasticNet
elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5) # r=0.5 Lasso, Ridge 중간
elastic_net.fit(X,y)
elastic_net.predict([[1.5]])
# array([1.54333232])

 

이렇게 릿지, 라쏘, 엘라스틱 회귀 모델을 알아보았는데, 언제 어떤 모델을 사용해야 하는가에 대해 알아두는 것은 중요하다. 일반적으로 릿지 모델이 기본으로 쓰이지만 실제로 쓰이는 특성이 몇 개 뿐이라고 의심될 경우 라쏘나 엘라스틱넷을 사용한다. 또한 특성 수가 훈련 샘플 수보다 많거나, 특성 몇 개가 강하게 연관되어 있을 경우 라쏘보다는 엘라스틱넷을 선호한다.

 

참고로 라쏘 모델은 특성 수가 샘플 수 보다 많으면 최대 샘플 수 만큼의 특성을 선택하며, 여러 특성이 강하게 연관되어 있으면 그 특성들 중 임의의 특성 하나만을 선택한다.

반응형

댓글