본문 바로가기

카테고리 없음

[ML 머신러닝] Polynomial Regression & Regularization 다항식 회귀 분석과 정규화

 

 

본문에 들어가기에 앞서 우리말로 번역되는 정규화에는 Normalization과 Standardization 그리고 Regularization이 존재합니다. 이 세가지는 각기 다른 뜻을 가지고 있기에 머신러닝에서는 정규화라는 단어보다 영어를 주로 사용합니다. 따라서 이번 편에서는 정규화 대신 Regularization을 사용하겠습니다.

각 정규화들의 차이를 간단히 정리하자면,

Normalization값의 범위(scale)를 0~1 혹은 -1~1 사이의 값 등으로 바꾸는 것을 의미합니다. (Multiple Linear Regression편에서 다루었습니다.)

  • 학습 전에 scaling 하는 것으로 머신러닝에서 범위(scale)가 큰 값(feature)의 영향이 비대해지는 것을 미리 방지합니다.
  • 딥러닝에서 전체 최소값이 아닌 구간 최소값(Local Minima)에 빠질 위험이 감소합니다.
  • scikit-learn에서 MinMaxScaler 함수는 아래와 같은 수식을 사용합니다.

$$ \frac{x-x_{min}}{x_{max}-x_{min}} $$

Standardization

  • 값의 범위(scale)을 평균 0, 분산 1이 되도록 바꾸는 것을 의미합니다. (Multiple Linear Regression편에서 잠깐 등장했습니다.)
  • Normalization과 같이 학습전에 Scaling 하는 것으로 같은 의미를 갖습니다.
  • 정규분포를 표준정규분포로 변환하는 것과 같습니다.
    • Z-score(표준점수)
    • -1 ~ 1 사이에 68%가 있고, -2 ~ 2 사이에 95%가 있고, -3 ~ 3 사이에 99%가 있습니다.
    • -3 ~ 3의 범위를 벗어나면 outlier일 확률이 높습니다.
  • 일각에서는 정규화 대신, 표준화로 번역하기도 합니다.
  • scikit-learn에서 StandardScaler 함수는 아래와 같은 수식을 사용합니다.

$$ \frac{x-\mu}{\sigma} (\mu : 평균, \sigma : 표준편차)$$

 

Regularization

  • 가중치(weight)를 조정하는데, 규제(제약)을 거는 기법입니다.
  • 오버피팅(Overfitting)을 막기위해 사용합니다.
  • 일각에서는 정규화와 구분하기 위해, 규제화, 일반화로 번역하기도 합니다.
  • L1 regularization, L2 regularization 등의 종류가 있습니다.
    • L1 : 가중치의 절대값을 더해줍니다. 맨허튼 노름(Manhattan norm)을 더해준다고 생각할 수 있습니다.
      • $$ Loss =  \frac{1}{n} \sum_{i=1}^n(\mathbf{y} - \hat{\mathbf{y}})^2 + \frac{\lambda}{2} \left|w\right|$$ 
    • L2 : 가중치의 제곱한 값을 더해줍니다. 유클리드 노름(Euclidean norm)을 더해준다고 생각할 수 있습니다.
      • $$ Loss =  \frac{1}{n} \sum_{i=1}^n(\mathbf{y} - \hat{\mathbf{y}})^2 + \frac{\lambda}{2} w^2$$

 

와 같습니다. (헉헉... 쓰다보니 생각보다 길어졌네요...)

 

 

이번에 공부 할 다항식 회귀 분석(Polynomial Regression)은 데이터가 x,y 좌표에서 직선의 형태가 아닌 구부러진 형태로 되어있을 때, 적용해볼 만한 회귀 분석입니다. 그리고 Regularization은 가중치를 제약하면서 조정하는 기법입니다.

 

Step 1. A special case of multiple linear regression

 

먼저 4차 Polynomial function$ y=x^4+x^3-4x^2 $을 이용해서 데이터를 생성해봅니다. numpy의 random을 이용하여 noise를 추가해서 생성합니다.

from matplotlib import pyplot
from autograd import grad
from autograd import numpy

numpy.random.seed(0)    # fix seed for reproducibility
x = numpy.linspace(-3, 3, 20)
y = x**4 + x**3 - 4*x**2 + 8*numpy.random.normal(size=len(x))
pyplot.scatter(x, y);

이렇게 생성한 데이터는 곡선의 형태이기 때문에 직선으로 표현하는데에는 무리가 있습니다. 
그래서 다항함수(polynomial function)를 사용해서 표현합니다. 먼저 $d$ 차 곡선을 정의해보겠습니다. 
$$\hat{y} = w_0 + w_1x + w_2x^2 + ... + w_dx^d$$
$w$가 찾아야 할 계수들입니다. 즉, 주어진 $x, y$를 가장 잘 설명해줄 수 있는 $w$를 찾는 것이 목적입니다.
이제 우리는 위의 식을 조금 변형하여 Linear Regression 문제처럼 바꾸어 보겠습니다. $x_i = x^i$라고 정의를 하면 다음과 같이 나타낼 수 있습니다. 
$$\hat{y} = w_0 + w_1x_1 + w_2x_2 + ... + w_dx_d$$
이제 polynomial regression 문제가 multiple linear regression 문제와 동일해졌습니다. 이것도 행렬의 형태로 나타낼 수 있습니다. 

$$\hat{\mathbf{y}} = \begin{bmatrix}
\hat{y}^{(1)}  \\
\hat{y}^{(2)}\\
\vdots  \\
\hat{y}^{(N)}
\end{bmatrix} =
\begin{bmatrix}
x_0^{(1)} &  x_1^{(1)} & \cdots & x_d^{(1)} \\
x_0^{(2)} &  x_1^{(2)} & \cdots & x_d^{(2)} \\
\vdots & \vdots & \ddots & \vdots \\
x_0^{(N)} &  x_1^{(N)} & \cdots & x_d^{(N)}
\end{bmatrix} \begin{bmatrix}
w_0  \\
w_1\\
\vdots  \\
w_d 
\end{bmatrix} 
= \mathbf{X}\mathbf{w}$$

그럼 3차항까지 고려해서 코드로 작성합니다. 

degree = 3

def polynomial_features(x, degree):
    """ Generate polynomial features for x."""
    
    X = numpy.empty((len(x), degree+1))
    for i in range(degree+1):
        X[:,i] = x**i
    return X

X = polynomial_features(x, degree)

 

Step. 2 Scale the date, Train the model

 

scikit-learn 패키지 안에 있는 min_max_sclaer를 사용하여 입력 변수들을 정규화(Normalization) 시켜줍니다.

from sklearn.preprocessing import MinMaxScaler

min_max_scaler = MinMaxScaler()
X_scaled = min_max_scaler.fit_transform(X)
X_scaled[:,0] = 1   # the column for intercept

이제 학습을 진행합니다.

def linear_regression(params, X):
    '''
    The linear regression model in matrix form.
    Arguments:
      params: 1D array of weights for the linear model
      X     : 2D array of input values
    Returns:
      1D array of predicted values
    '''
    return numpy.dot(X, params)

def mse_loss(params, model, X, y):
    '''
    The mean squared error loss function.
    Arguments:
      params: 1D array of weights for the linear model
      model : function for the linear regression model
      X     : 2D array of input values
      y     : 1D array of predicted values
    Returns:
      float, mean squared error
    '''
    y_pred = model(params, X)
    return numpy.mean( numpy.sum((y-y_pred)**2) )

gradient = grad(mse_loss)
max_iter = 3000
alpha = 0.01
params = numpy.zeros(X_scaled.shape[1])
descent = numpy.ones(X_scaled.shape[1])
i = 0

from sklearn.metrics import mean_absolute_error

while numpy.linalg.norm(descent) > 0.01 and i < max_iter:
    descent = gradient(params, linear_regression, X_scaled, y)
    params = params - descent * alpha
    loss = mse_loss(params, linear_regression, X_scaled, y)
    mae = mean_absolute_error(y, X_scaled@params)
    if i%100 == 0:
        print("iteration {}, loss = {}, mae = {}".format(i, loss, mae))
    i += 1

이제 학습된 parameter들을 확인해봅니다.

print(params)

이러면 어떤 값인지 명확히 알 수가 없습니다. 원래 데이터와 함께 그래프로 표현해봅니다.

xgrid = numpy.linspace(x.min(), x.max(), 30)
Xgrid_poly_feat = polynomial_features(xgrid, degree)
Xgrid_scaled = min_max_scaler.transform(Xgrid_poly_feat)
Xgrid_scaled[:,0] = 1 
pyplot.scatter(x, y, c='r', label='true')
pyplot.plot(xgrid, Xgrid_scaled@params, label='predicted')
pyplot.legend();

Step. 3 Observe Underfitting & Overfitting

 

차수를 1부터 15까지 늘려보면서 지금 만들어진 3차원의 모델이 최선인지 확인해봅니다.

ipywidget 패키지를 통해서 어떻게 변화하는지 확인합니다.

from urllib.request import urlretrieve
URL = 'https://raw.githubusercontent.com/engineersCode/EngComp6_deeplearning/master/scripts/plot_helpers.py'
urlretrieve(URL, 'plot_helpers.py')
from plot_helpers import interact_polyreg

max_degree = 15
interact_polyreg(max_degree, x, y)

Step 3-1. Underfitting

결과 값의 차수를 1로 해봅니다. 그러면 그래프는 데이터와 가장 잘 맞는 직선을 그립니다.

하지만 직선은 너무 간단해서 데이터가 흩뿌려져 있을 때, 그 데이터를 잘 설명해주기 힘듭니다.

차수를 1로 바꾸면 모델은 선형으로 만들어지고 MAE가 12.123이 됩니다.

이것을 Underfitting 되었다고 합니다. Underfitting에서는 예측 값이 high bias, low variance의 성질을 가지게 됩니다.

Bias, variance의 개념을 이해하기 쉽게 예시를 들어서 보겠습니다.

Underfitting 되었다면, low variance, high bias이기 때문에 저 위의 4개의 과녁에서 왼쪽 아래의 과녁에 해당합니다.

예측한 값들이 한 곳에 몰려있어 low variance이지만, 정답에서 멀기 때문에 high bias입니다. 모델의 차수를 낮게 설정했다면, 그것은 계속해서 직선에 가까워지고, 그렇다면 에측 값들은 직선 근처에서만 나오기 때문에 low variance라고 할 수 있습니다. 그리고 직선은 흩뿌려진 데이터를 정확하게 예측 할 수 없기 때문에 high bias가 됩니다.

 

Step 3-2. Overfitting

이제 차수(degree)를 높게 설정해봅니다. Training error인 MAE 값이 점점 감소합니다. 그러면 degree가 제일 큰 15가 MAE값이 가장 작으니까 제일 잘 맞는 모델일까요? 트레이닝 데이터만 본다면 맞습니다. 하지만 다른 데이터가 들어간다면? 적합한 모델이 아닙니다. 차수(degree)를 높일수록 모델은 곡선을 이루면서 모든 training point를 지나갑니다. 이로 인해 새로운 데이터가 추가된다면, 그 데이터가 모델 위에 있을 확률은 매우 희박하다는 뜻입니다.

차수가 늘어나면서 거의 모든 데이터의 값을 맞추기 위해 모델이 변형됩니다.
차수가 15가 되면 거의 모든 데이터의 값에 맞추게 되고 Overfitting됩니다.

예를들어 차수가 15인 그래프에서 마지막 두점 사이에 값이 생긴다고 해봅시다. 기존의 데이터들과 매우 비슷한 데이터지만 모델은 매우 큰 값을 예측 할 것이고 큰 오류를 가지게 될 것입니다.

이것을 Overfitting이라고 부르고 high variance, low bias 이기 때문에 과녁 그림에서 오른쪽 위에 해당하게 됩니다.

Overfitting은 training 과정에서 작은 에러를 가지기 때문에 찾아내기 힘듭니다. Overfitting을 막기위해 Regularization을 사용합니다.

 

Step 4. Regularization

Regularization은 overfitting 을 막는 방법입니다. Cost function에서 새로운 식을 추가해서 복잡한 모델이 되는 것을 막아주게 됩니다. 먼저 기존 모델을 정의해봅니다. 
$$\hat{y} = w_0 + w_1x + w_2x^2 + ... + w_dx^d$$
차수를 가진 항들이 모델을 복잡하게 만듭니다. 그래서 그들의 계수값에 제한 조건을 추가합니다. 
보편적인 방법은 regularization term 인 $\lambda\sum_{j=1}^dw_j^2$를 추가하는 것 입니다. Mean-Squared Error(MSE)를 사용한 cost function에 추가하면 다음과 같습니다. 
$$L(\mathbf{w}) = \frac{1}{N} \lVert \mathbf{y} - \mathbf{Xw} \rVert^2 + \lambda \sum_{j=1}^d w_j^2$$
새롭게 만든 cost function은 계수들이 작은 값이 되는 것을 선호하게 됩니다. 왜냐하면 계수들이 커지게 되면 두번째 항이 커지게 돼서 cost 값이 늘어나게 되기 때문입니다. 
위 식에서 $\lambda$는 regularization parameter 입니다. 가중치(Weight)에 얼마나 큰 제약을 줄 것인가를 결정하게 됩니다. $\lambda$가 큰 값을 가질수록 계수들을 더 작게 할 수 있습니다. 
아래 코드에서는 $\lambda$값을 1로 설정하겠습니다.

def regularized_loss(params, model, X, y, _lambda=1.0):
    '''
    The mean squared error loss function with an L2 penalty.
    Arguments:
      params: 1D array of weights for the linear model
      model : function for the linear regression model
      X     : 2D array of input values
      y     : 1D array of predicted values
      _lambda: regularization parameter, default 1.0
    Returns:
      float, regularized mean squared error
    '''
    y_pred = model(params, X)
    return numpy.mean( numpy.sum((y-y_pred)**2) ) + _lambda * numpy.sum( params[1:]**2 )

gradient = grad(regularized_loss)
no_regularization_params = params.copy()
max_iter = 3000
alpha = 0.01
params = numpy.zeros(X_scaled.shape[1])
descent = numpy.ones(X_scaled.shape[1])
i = 0

from sklearn.metrics import mean_absolute_error

while numpy.linalg.norm(descent) > 0.01 and i < max_iter:
    descent = gradient(params, linear_regression, X_scaled, y)
    params = params - descent * alpha
    loss = mse_loss(params, linear_regression, X_scaled, y)
    mae = mean_absolute_error(y, X_scaled@params)
    if i%100 == 0:
        print("iteration {}, loss = {}, mae = {}".format(i, loss, mae))
    i += 1

이제 regularization을 추가한 것과 추가하지 않은 것을 비교해보겠습니다.

print("weights without regularization")
print(no_regularization_params)
print("weights with regularization")
print(params)

pyplot.scatter(x, y, c='r', label='true')
pyplot.plot(xgrid, Xgrid_scaled@no_regularization_params, label='w/o regularization')
pyplot.plot(xgrid, Xgrid_scaled@params, label='with regularization')
pyplot.legend();

더 큰 차수에서 Regularization을 비교해보겠습니다.

interact_polyreg(max_degree, x, y, regularized=True)

Degree가 커질수록 regularization의 중요성이 들어나게 됩니다. 이제 높은 차수에서도 예측 모델이 이리저리 움직이는 모습이 아닌 부드럽게 학습 데이터들을 지나가는 모습을 볼 수 있습니다. 이렇게 된다면 새롭게 들어오는 데이터도 잘 예측 할 수 있을 것입니다.