728x90
반응형

선형 회귀 모델 - 경사 하강법(Gradient descent, GD)

 지난 포스트까지 정규방정식(Normal Equation)과 최소제곱법(Least Squares method)을 이용하여 선형 회귀(Linear Regression)의 모델 파라미터를 계산해보았다.

 정규방정식이나 최소제곱법을 이용하여 모델 파라미터를 계산하는 경우, 공식이 꽤나 단순하고 단 한 번만 연산을 해도 된다는 장점이 있지만, 칼럼의 양이 2배 늘어나는 경우 정규방정식은 계산 시간이 최대 약 8배까지 증가하며, 최소제곱법은 계산 시간이 최대 4배까지 증가한다는 단점이 있다고 하였다.

 때문에, 데이터의 피처(컬럼의 수)가 매우 많거나 데이터 자체가 큰 경우, 정규방정식이나 최소제곱법을 사용하여 선형 회귀의 모델 파라미터를 계산하는 것보다. 경사 하강법(Gradient descent)을 사용하여 계산하는 것이 보다 유리하다.

 이번 포스트에서는 경사 하강법에 대해 알아보고, 경사하강법을 사용하여 선형 회귀 모형으로 학습을 해보도록 하겠다.

 

 

 

 

 

1. 경사 하강법이란?

  • 앞서 우리는 선형 회귀 모델의 손실 함수인 MSE에 대해 알아보았다.

$$ MSE = \frac{1}{n}\sum_{i=1}^{n}(\hat{y_i} - y_i)^2 $$

  • MSE에서 $\hat{y_i} = \theta x + b$이므로($\theta$: 계수, $x$: 실제 값, $b$: 절편), 학습을 통해 알고자 하는 미지수인 모델 파라미터 $\theta$는 1개의 최적해를 갖는 이차 함수 형태인 것을 알 수 있다.
  • 때문에 손실 함수의 개형은 아래와 같다.

  • 선형 회귀 모델을 비롯한 머신러닝 알고리즘에서 최고의 모델 파라미터를 찾는 방법은 손실 함수(Loss function)을 최소로 만드는 점 $\alpha$를 찾는 것이다.
  • 정규방정식이나 최소제곱법은 우리가 찾고자 하는 $\alpha$를 한 번에 찾는 방법이고, 경사 하강법은 손실 함수의 랜덤 한 위치에서 기울기를 계산하고, 기울기가 0이 되는 방향으로 학습률(Learning rate)만큼 점진적으로 이동하여, 최적해 $alpha$를 찾는 방법이다.
  • 위 과정은 전역 최적해(Global minimub)를 찾기 위해 반복 수행되며, 반복 수행된다는 의미는 다양한 랜덤한 위치에서 경사 하강법이 실시된다는 의미다.
  • 보다 자세히 경사 하강법의 원리를 알고자 하는 경우, 이전 포스트인 "딥러닝-6.0. 최적화(1)-손실함수와 경사하강법"을 참고하기 바란다.

 

1.1 경사 하강법의 한계점

  • 경사 하강법의 태생적 문제로 인해 다음과 같은 한계점이 존재한다.
  1. 데이터가 많아질수록 계산량 증가: 학습 데이터의 양이 늘어나는 경우, 계산량이 매우 커져 학습 속도가 느려진다.
  2. 지역 최소해(Local minimum) 문제: 실제 손실 함수의 형태는 위 그래프처럼 매끈하지 않고, 울퉁불퉁하기 때문에 실제로 찾아야 하는 전역 최적해(Global minimum)가 아닌 지역 최소해(Local minimum)를 찾을 수 있다.
  3. Plateau 문제: 평평한 지역(Plateau)에 들어가서, 학습이 더 이상 진행되지 못하는 문제
  4. Zigzag 문제: 찾아야 하는 해가 많아질수록 차원이 복잡해져, 제대로 해를 찾지 못하는 문제

 

 

 

 

 

2. 경사 하강법의 공식 - 배치 경사 하강법(Batch Gradient Descent)


  • 앞서 봤던 MSE 공식을 이번에는 벡터 형태로 가지고 와보자.

$$ MSE(X, h_{\theta}) = MSE(\theta) = \frac{1}{n}\sum_{i=1}^{n}(\theta^Tx^{(i)} - y^{(i)})^2 $$

  • 위 식을 모델 파라미터 $\theta$에 대해 편미분 해보자.

$$\frac{\partial}{\partial \theta_j}MSE(\theta) = \frac{2}{n}\sum_{i=1}^{n}(\theta^T x^{(i)} - y^{(i)})x_{j}^{i}$$

  • 위 편미분 결과에 대하여, 모든 모델 파라미터 $\theta_j$의 성분에 대한 편미분으로 구성된 열벡터로 만들어보고, 이를 행렬식으로 바꿔보자.

$$\bigtriangledown_{\theta} MSE(\theta) =
\begin{pmatrix} \frac{\partial}{\partial \theta_0}MSE(\theta)
\\ \frac{\partial}{\partial \theta_1}MSE(\theta)
\\ \vdots 
\\ \frac{\partial}{\partial \theta_0}MSE(\theta)
\end{pmatrix} = \frac{2}{m}X^T(X\theta - y)$$

  • 경사 하강법 공식은 다음과 같다.

$$x_{i+1} = x_i - \eta \bigtriangledown f(x_i)$$

  • 위 경사 하강법 공식에서 손실함수 $\bigtriangledown f(x_i)$의 위치에, 위에서 구한 $MSE$의 편미분 행렬식인 $\bigtriangledown_{\theta} MSE(\theta)$을 넣어주고, 미지수$x$도 선형 회귀의 모델 파라미터인 $\theta$로 바꿔 선형 회귀 모델에 대한 경사 하강법 공식으로 만들어주자.

$$\theta_{i+1} = \theta_i - \eta \bigtriangledown MSE(\theta) = \theta_i - \frac{2}{m} \eta X^T(X\theta - y)$$


  • 위 공식은 매 경사 하강법 스텝에서 전체 훈련 세트 $X$에 대해 계산한다. 이를 배치 경사 하강법(Batch gradient descent)라 하며, 매 스텝에서 훈련 데이터 전체를 대상으로 최적해를 찾기 때문에 계산 시간도 길고, 소모되는 컴퓨터 자원의 양도 많다.
  • 때문에 매우 큰 데이터를 대상으로 할 때는 위 공식인 배치 경사 하강법을 사용하지 않는 것이 좋으며, 무작위 샘플 데이터 셋을 추출해, 그 샘플에 대해서만 경사 하강법을 실시하는 확률적 경사 하강법(Stochastic Gradient Descent, SGD)을 실시하는 것이 좋다.
  • 그러나, 경사 하강법은 특성 수에 민감하지 않기 때문에, 특성 수가 매우 많은 경우, 정규방정식이나 특잇값 분해(SVD)를 이용한 최소제곱법보다 경사 하강법을 사용하여 학습하는 것이 좋다.
  • 경사 하강법 공식의 $\eta$는 학습률(Learning rate)라 하며, 에타라고 발음한다. 경사 하강법은 현재(n)의 파라미터 $\theta$에서 MSE의 편미분 벡터에 학습률을 곱한 수치만큼 아래 방향으로 이동한다.

 

 

 

 

 

3. 넘파이 코드를 이용해서 배치 경사 하강법을 구현해보자.

  • 위에서 행렬식으로 만든 경사 하강법 함수는 다음과 같다.

$$\theta_{i+1} = \theta_i - \frac{2}{m} \eta X^T(X\theta - y)$$

  • 경사 하강법은 최적의 모델 파라미터를 찾기 위해 전체 데이터에 대하여 m번 반복하여 계산된다.
  • 이 반복되는 것을 학습 단위라고 하며, Epoch(에포크), Batch size(배치 사이즈), Iteration(이터레이션)에 대한 기본 개념을 알아야 한다.
  1. Epochs: 전체 데이터를 학습 시키는 횟수
  2. Batch size: 연산 한 번에 들어가는 데이터의 크기
  3. Iteration: 1 epoch를 마치는데 필요한 모델 파라미터 업데이터의 횟수

 

3.1. 사용할 데이터

  • 데이터는 이전 포스트에서 사용했던 컬럼이 1개인 데이터를 사용해보겠다.
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1234)

def f(x):
    return 3 * x + 4

X = 2 * np.random.rand(100, 1)
Y = f(X) + np.random.randn(100, 1)

# 상수항을 위해 모든 원소가 1인 컬럼을 추가함
X_b = np.c_[X, np.ones((100, 1))]
  • 생성한 데이터를 sklearn 라이브러리의 LinearRegression 모델을 사용하여, 학습시켜보겠다.
  • sklearn의 LinearRegression에 학습시킬 때는 상수항을 위해 모든 원소가 1인 컬럼을 추가하지 않아도 된다.
# sklearn 라이브러리의 선형 회귀 모형
from sklearn.linear_model import LinearRegression

LR_model = LinearRegression()
LR_model.fit(X, Y)
>>> LR_model.coef_, LR_model.intercept_
(array([[2.94132381]]), array([4.07630632]))
  • 숨겨진 패턴인 함수 $f(x) = 3x + 4$의 계수 3과 상수 4에 근사한 결과가 나왔다.

 

3.2. 배치 경사 하강법 코드

def Batch_GD(x, y, eta=0.01, iteration_N=1000):
    
    # 초기 무작위 파라미터
    theta = np.random.randn(x.shape[1], 1)

    for iteration in range(iteration_N):

        # MSE의 편미분 벡터
        gradients = (2/x.shape[0])* x.T.dot(x.dot(theta) - y)
        theta = theta - eta * gradients
        
    return theta
>>> Batch_GD(X_b, Y, 0.01, 1000)
array([[3.02789289],
       [3.97237607]])
  • 학습률 eta나 파라미터 갱신 횟수 iteration_N과 같은 분석가가 직접 설정하는 파라미터를 머신러닝에서는 하이퍼 파라미터라고 한다.
  • 학습률이 크면 클수록 빠르게 최적해에 수렴하나, 너무 크게 이동하므로 전역 최적해에 수렴하지 못할 수 있다.
  • 학습률을 보다 크게 학습시켜보자.
>>> Batch_GD(X_b, Y, 0.1, 1000)
array([[2.94132381],
       [4.07630632]])
  • 학습률이 0.1로 10배나 커지자, 보다 빠르게 수렴하여 최소제곱법(LSM)을 사용하여 회귀 모델의 최적의 파라미터를 계산하는 sklearn의 LinearRegression과 같은 결과가 나왔다.

 

 

 

 

 

4. 학습률과 Iteration에 따른 수렴 속도의 차이 시각화

  • 위에서 만든 Batch_GD 함수를 약간 수정하여, 갱신된 파라미터에 대한 예측값을 plot으로 시각화하는 함수를 만들어보자.
# 전체 시각화
def draw_eta(x, y, eta_, iteration_N_):
    
    plt.scatter(x, y)
    
    # 상수항을 위한 모든 원소가 1인 컬럼 추가
    x_b = np.c_[x, np.ones((X.shape[0], 1))]
    Batch_GD_Visual(x_b, y, eta=eta_, iteration_N=iteration_N_)
    
    # 제목
    plt.xlabel("X", fontsize = 15)
    plt.ylabel("Y", fontsize = 15, rotation = 0)
    plt.title(f"$\eta$={eta_}", fontsize = 20, pad=15)
    
    plt.xlim(0, 2)
    plt.ylim(0, 15)
    
    plt.show()



# 갱신되는 theta에 따른 경사 하강법 시각화
def Batch_GD_Visual(x, y, eta=0.01, iteration_N=1000):

    # 초기 무작위 파라미터
    theta = np.random.randn(x.shape[1], 1)

    for iteration in range(iteration_N):

        # MSE의 편미분 벡터
        gradients = (2/x.shape[0])* x.T.dot(x.dot(theta) - y)
        theta = theta - eta * gradients
        
        # 경사 하강법을 통해 바뀐 theta로 예측한 값
        new_Y = np.array([[0,1],[2,1]]).dot(theta)
        plt.plot([0,2], new_Y, c = "r", alpha = 0.2)
        
    return theta
  • draw_eta는 시각화 코드를 정리해놓은 함수다.
  • Batch_GD_Visual은 갱신되는 $\theta$에 따른 경사 하강법을 시각화하는 함수로, 옅은 빨간색 선을 그린다.

4.1. iteration은 50이고, 학습률이 변하는 경우

  • iteration 1000은 꽤 큰 값이므로, 작은 50으로 설정해놓고 학습률만 바꿔보자.
  • 학습률이 0.01인 경우
eta_ = 0.01
iteration_N_ = 50
draw_eta(X, Y, eta_, iteration_N_)

 

  • 학습률이 0.1인 경우
eta_ = 0.1
iteration_N_ = 50
draw_eta(X, Y, eta_, iteration_N_)

 

  • 학습률이 0.5인 경우
eta_ = 0.5
iteration_N_ = 50
draw_eta(X, Y, eta_, iteration_N_)

  • 위 결과를 보면, 학습률이 커지면 커질수록 빠르게 최적해를 찾아가는 것을 볼 수 있지만, $\eta=0.5$ 같이 지나치게 큰 경우, 모델 파라미터가 큰 폭으로 요동치는 것을 볼 수 있다.
  • 위에서 볼 수 있듯, 데이터에 맞는 적절한 학습률을 찾는 것은 매우 중요하다.

4.2. 학습률은 0.01이고, iteration이 변하는 경우

  • 학습률을 최적해에 비교적 늦게 수렴하는 0.01로 잡고, 파라미터 갱신 횟수인 iteration만 바꿔보자.
    (꽤 많은 머신러닝 알고리즘에서 Learning rate의 기본값을 0.01로 잡아놓는다.)
  • iteration이 10인 경우
eta_ = 0.01
iteration_N_ = 10
draw_eta(X, Y, eta_, iteration_N_)

  • iteration이 30인 경우
eta_ = 0.01
iteration_N_ = 30
draw_eta(X, Y, eta_, iteration_N_)

  • iteration이 100인 경우
eta_ = 0.01
iteration_N_ = 100
draw_eta(X, Y, eta_, iteration_N_)

  • 위 결과를 보면, 파라미터 갱신량도 필요치보다 작은 경우, 최적해를 찾기 전에 경사 하강이 끝나버리고, 필요치 보다 큰 경우, 최적해를 찾고 파라미터가 더 이상 변하지 않는 시간 낭비가 발생하는 것을 볼 수 있다.
  • 이에 대한 해결책으로는, iteration을 애초에 크게 잡은 후, 경사 하강법 알고리즘에 규제를 추가하여, 모델 파라미터의 변화량이 허용오차(tolerance) $\varepsilon$보다 작아지면 최적해에 도달한 것으로 판단하고 모델 파라미터 갱신을 중지시키는 것이다.

 

 

[참고 서적]

 

 

 

 이번 포스트에서는 배치 경사 하강법을 이용하여 선형 회귀 모형을 만들어보았다. 배치 경사 하강법은 데이터가 지나치게 큰 경우 계산 시간이 지나치게 오래 걸리는 정규방정식이나 최소제곱법보다 빠르게 최적해를 찾을 수 있다.

 그러나, 학습률, 파라미터 갱신 횟수(iteration)과 같은 하이퍼 파라미터를 제대로 잡지 않는다면, 학습 시간이 필요보다 길어지거나, 최적해에 수렴하지 못할 수 있으므로, 최적의 하이퍼 파라미터를 찾을 수 있어야 한다.

 배치 경사 하강법은 모든 데이터에 대한 편미분 벡터를 계산하게 되므로, 데이터의 크기가 이보다 더 커지는 경우, 학습 시간이 더 길어질 수 있다. 다음 포스트에서는 이 문제를 해결한 확률적 경사 하강법에 대해 알아보도록 하겠다.

728x90
반응형
728x90
반응형

선형 회귀의 구현 - 정규방정식과 최소제곱법

 지난 포스트에서 머신러닝에서 선형 회귀 모델의 기본적인 원리에 대해 알아보았다. 이번 포스트는 정규방정식을 넘파이 함수로 구현해보고, 정규방정식으로 찾아낸 최적의 모델 파라미터를 이용해서 새로운 데이터에 대해 예측을 해보자.

 

 

 

 

1. 선형성을 갖는 무작위 데이터를 만들어보자.

  • 임의의 선형 데이터를 만들어 정규방정식에 넣어보도록 하자.
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1234)

def f(x):
    return 3 * x + 4

X = 2 * np.random.rand(100, 1)
Y = f(X) + np.random.randn(100, 1)
  • np.random.rand(shape): 0~1 사이의 표준 정규 분포 난수를 shape의 형태대로 생성
  • np.random.randn(shape): 평균 0, 표준편차 1의 가우시안 표준 정규 분포 난수를 shape의 형태대로 생성
  • 0에서 1 사이의 난수 X에 2를 곱하여, 값의 범위를 더 넓게 하였다.
  • 숨겨진 패턴은 함수 $f(x) = 3x + 4$이며, $f(x)$에 $N(0,1)$인 가우시안 표준 정규 분포를 더해 노이즈를 주었다.
plt.scatter(X, Y)

plt.xlabel("X", fontsize=15)
plt.ylabel("Y", rotation=0, fontsize=15)
plt.show()

 

 

 

 

 

2. 넘파이 코드를 사용해서 정규방정식을 구현해보자.

  • 정규방정식 함수는 다음과 같다.

$$\hat{\theta} = (X^TX)^{-1}X^Ty$$

  • 위 정규방정식 함수를 넘파이 라이브러리의 함수를 사용해서 함수로 만들어보자.
# 정규방정식
def NormalEquation(X, Y):
    
    return np.linalg.inv(np.dot(X.T, X)).dot(X.T).dot(Y)
  • np.linalg.inv(): 역행렬
  • np.dot(mat1, mat2): 행렬 mat1과 mat2의 행렬 곱 연산을 한다.
  • mat1.dot(mat2): 행렬 mat1과 mat2의 행렬 곱 연산을 한다.

 

2.1. 배열 X와 Y를 넣어보자

>>> NormalEquation(X, Y)
array([[6.00122666]])
  • 위 데이터에서 숨겨진 패턴인 함수 $f(x)$는 $f(x) = 3x + 4$이므로, 3과 4가 출력되어야 하지만, 원소가 단 하나만 출력되었다.
  • 이는 함수 $f(x)$에 들어가는 배열 X에 절편에 대한 칼럼이 반영되지 않았기 때문이다.
  • 절편 4는 4*1이므로, 배열 X에 모든 원소가 1인 칼럼을 하나 추가해주고, 정규방정식을 수행해보도록 하자.

 

2.2. 절편에 대한 칼럼이 반영된 배열 X와 Y를 넣어보자.

# 절편에 대한 1로 구성된 벡터를 추가함.
>>> X_b = np.c_[X, np.ones((100, 1))]

# 정규방정식을 통해 최적의 계수를 찾아냄
>>> NormalEquation(X_b, Y)
array([[2.94132381],
       [4.07630632]])
  • np.c_[array1, array2]: array1과 array2를 컬럼 방향으로 붙인다(오른쪽에 붙인다)
  • 위 결과를 보면, 숨겨진 패턴인 함수 $f(x) = 3x + 4$의 계수인 3과 4와 아주 가까운 값이 도출된 것을 볼 수 있다.
  • 숨겨진 패턴의 계수 3과 4가 나오지 않은 이유는, 의도적으로 노이즈를 주었기 때문이며, 그 노이즈를 반영하였을 때, 숨겨진 패턴에 가장 가까운 모델 파라미터가 출력된 것이다.

 

2.3. 변수 $x$가 2개인 데이터에 대하여 정규방정식을 이용해 최적해를 구해보자.

  • 이번에는 숨겨진 패턴이 $h(x) = 3x_1 + 4x_2 + 5$인 함수에 대하여 구해보자.
  • 변수  $x$가 2개이므로, 컬럼의 수는 2개가 되어야 한다.
X1 = 2 * np.random.rand(100, 2)

def h(x):
    x1 = x[:,0]
    x2 = x[:,1]
    
    result = 3 * x1 + 4 * x2 + 5
    
    return result.reshape(100, 1)

Y1 = h(X1) + np.random.randn(100, 1)

# 절편을 위해 모든 원소가 1인 컬럼 추가
X1_b = np.c_[X1, np.ones((100, 1))]
>>> NormalEquation(X1_b, Y1)
array([[2.93017703],
       [4.19897028],
       [4.90893137]])
  • 각 계수의 값이 3, 4, 5에 근사하게 나왔으나, 실제 값과 어느 정도 거리가 있다.
  • 데이터의 양이 늘어날수록 실제 계수에 더 가까워진다.
X2 = 2 * np.random.rand(10000, 2)

def h(x):
    x1 = x[:,0]
    x2 = x[:,1]
    
    result = 3 * x1 + 4 * x2 + 5
    
    return result.reshape(10000, 1)

Y2 = h(X2) + np.random.randn(10000, 1)
X2_b = np.c_[X2, np.ones((10000, 1))]
>>> NormalEquation(X2_b, Y2)
array([[2.98528199],
       [4.01290386],
       [5.00091078]])

 

 

 

 

 

3. 찾아낸 모델 파라미터를 이용해서 예측을 해보자.

  • 정규방정식을 통해 계산해 낸 선형 회귀 모델의 파라미터 $\hat{\theta}$를 사용해서 새로운 데이터가 들어갔을 때, 그 결과를 예측해보자.
def predict_linear_R(X, theta):
    
    R_n = X.shape[0]
    
    try:
        C_n = X.shape[1]
    except:
        C_n = 1
    
    X_Reshape = X.reshape(R_n, C_n)
    X_concat_1 = np.c_[X_Reshape, np.ones((R_n,1))]
    
    return X_concat_1.dot(theta)

 

3.1. 독립변수가 1개인 경우

  • 처음 만들었던 칼럼이 1개 있는 데이터의 최적 모델 파라미터에 대하여, 새로운 데이터를 넣어 새로운 데이터에 대한 예측 값을 뽑아보자.
  • 새로운 데이터는 [0, 1, 2]로 구성된 벡터이다.
def f(x):
    return 3 * x + 4

X = 2 * np.random.rand(100, 1)
Y = f(X) + np.random.randn(100, 1)

X_b = np.c_[X, np.ones((100, 1))]
best_theta = NormalEquation(X_b, Y)
>>> best_theta
array([[2.94132381],
       [4.07630632]])
# 새로운 데이터
>>> new_X = np.array([0, 1, 2]).reshape((3,1))

# 새로운 데이터를 학습된 선형 회귀 모형에 넣어 예측된 결과
>>> pred_LR = predict_linear_R(new_X, best_theta)
>>> pred_LR
array([[4.07630632],
       [7.01763013],
       [9.95895394]])
  • 기존에 그린 산점도 위에 예측된 결과를 그려보자.
plt.scatter(X, Y, label="real")
plt.plot(new_X, pred_LR.reshape(new_X.shape), color = "red", label="predict")

plt.xlabel("X", fontsize=15)
plt.ylabel("Y", rotation=0, fontsize=15)
plt.legend(loc="lower right")
plt.show()

 

3.2. 독립변수가 2개인 경우

X1 = 2 * np.random.rand(100, 2)

def h(x):
    x1 = x[:,0]
    x2 = x[:,1]
    
    result = 3 * x1 + 4 * x2 + 5
    
    return result.reshape(100, 1)

Y1 = h(X1) + np.random.randn(100, 1)
X1_b = np.c_[X1, np.ones((100, 1))]
best_theta1 = NormalEquation(X1_b, Y1)
>>> best_theta1
array([[2.93017703],
       [4.19897028],
       [4.90893137]])
>>> new_X1 = np.array([[0, 1],[0.5, 1.5],[1, 2]])

>>> predict_linear_R(new_X1, best_theta1)
array([[ 9.10790166],
       [12.67247531],
       [16.23704897]])
  • 독립변수가 2개 이상인 경우, 독립 변수의 축 2개 + 종속변수의 축 1개로 3개 이상의 축이 생겨, 시각화가 어려워진다. 그러므로, 시각화는 생략하도록 하겠다.
  • 최적의 모델 파라미터만 찾아내면, 쉽게 회귀 모델을 사용해서, 예측값을 찾아낼 수 있다.

 

 

 

 

 

4. 사이킷런을 사용해서 선형 회귀를 실시해보자.

  • 사이킷런을 사용하면, 더 쉽게 위 과정을 진행할 수 있다.
from sklearn.linear_model import LinearRegression

lin_R = LinearRegression()
  • 사용하고자 하는 모델인 선형 회귀 모형을 객체로 만들었다.
  • 앞서 만든 데이터 X와 Y를 사용하여, 최적의 모델 파라미터를 찾아보자.
# 선형 회귀 모형 학습
>>> lin_R.fit(X, Y)

# 계수와 절편 출력
>>> lin_R.coef_, lin_R.intercept_
(array([[2.94132381]]), array([4.07630632]))

# Numpy로 만든 정규방정식의 결과
>>> best_theta
array([[2.94132381],
       [4.07630632]])
  • 모델.fit(array1, array2): 원하는 모델을 학습시킨다. array1은 훈련 데이터(Test data), array2는 레이블 데이터(Label data)다.
  • 선형회귀모델.intercept_, 선형회귀모델.coef_: sklearn을 사용하여, 선형 회귀 모형을 학습시키면, 계수와 절편의 값이 따로 출력된다. coef_는 계수이며, intercept_는 절편이다.
  • sklearn을 사용하여 계산된 결과와 numpy를 사용하여 만든 정규방정식의 결과가 동일한 것을 볼 수 있다.
  • 이번에는 학습된 모델 파라미터를 사용하여, 새로운 데이터가 들어갔을 때의 결과를 예측해보자.
# sklearn을 이용하여 예측한 결과
>>> lin_R.predict(new_X)
array([[4.07630632],
       [7.01763013],
       [9.95895394]])
       
# numpy 라이브러리를 사용하여 예측한 결과
>>> predict_linear_R(new_X, best_theta)
array([[4.07630632],
       [7.01763013],
       [9.95895394]])
  • 모델.predict(array): array에 모델 파라미터를 계산하여, 예측된 결과를 출력한다.

 

 

 

 

 

5. 정규방정식의 한계점

5.1. 의사역행렬(Pseudoinvese matrix)과 최소제곱법(LSM)

  • 앞서 우리가 학습하였던 정규방정식에는 큰 한계점이 존재한다.
  • 정규방정식 공식 $\hat{\theta} = (X^TX)^{-1}X^Ty$에는 역행렬이 포함되어 있는데, 역행렬은 항상 존재하는 것이 아니기 때문에 역행렬이 존재하지 않는다면, 정규방정식 공식은 작동하지 않을 수 있다.
  • 때문에 등장하는 것이 무어-펜로즈 역행렬(Moore-Penrose pseudoinverse matrix)이라고도 불리는 의사역행렬(Pseudoinverse matrix)을 사용하여 다음과 같은 공식으로 최적의 모델 파라미터를 찾아내게 된다.

$$ \hat{\theta} = X^{+}y$$

  • 의사역행렬은 특잇값 분해(Singular value decomposition, SVD)라는 표준 행렬 분해 기법을 통해 계산되며, 의사역행렬은 역행렬이 없는 행렬에 대해서도 계산을 할 수 있기 때문에 정규방정식의 한계점을 해결할 수 있으며, 계산 속도 또한 빠르다.
  • 선형 회귀 모델의 회귀 계수(모델 파라미터)를 추정할 때, 가장 많이 사용되는 기법인 최소제곱법(Least squares method, LSM)은 의사역행렬을 사용하여 해를 구하므로, sklearn의 선형 회귀 모델은 정규방정식이 아닌 최소제곱법을 기반으로 계산한다.
>>> np.linalg.lstsq(X_b, Y, rcond=1e-6)
(array([[2.94132381],
        [4.07630632]]),
 array([92.72583695]),
 2,
 array([14.98211623,  3.69398772]))
  • np.linalg.lstsq(array1, array2): array1, array2의 최소제곱해를 선형 행렬 방정식으로 반환한다.
  • 위 함수를 사용하면 총 4개의 결과가 반환되는데, 여기서 맨 처음에 반환된 결과인 최소제곱해만 신경 쓰면 된다.
# 최소제곱해
>>> np.linalg.lstsq(X_b, Y, rcond=1e-6)[0]
array([[2.94132381],
       [4.07630632]])
       
       
# Numpy로 만든 정규방정식의 결과
>>> best_theta
array([[2.94132381],
       [4.07630632]])
  • 보시다시피 최소제곱법을 사용해서 구하는 최소제곱해와 정규방정식의 결과는 동일한 것을 알 수 있다.
  • 그러나, 최소제곱법은 의사역행렬을 통해 계산되기 때문에 역행렬이 존재하지 않는 대상에 대해서도 계산이 가능하다.

 

5.2. 정규방정식의 계산복잡도

  • 정규방정식은 역행렬 계산, 행렬 곱 등 다양한 행렬 연산이 들어가기 때문에 계산 복잡도(Computation complexity)가 매우 크다.
  • 정규방정식을 사용할 때, 특성의 수가 2배 늘어나면, 계산 시간이 약 5.3배에서 8배로 증가하며, 특잇값 분해(SVD)를 통해 의사역행렬을 구한다 할지라도 특성의 수가 2배 늘어나는 경우, 계산 시간이 약 4배 증가하게 된다.
  • 즉, 칼럼의 수가 늘어나면 늘어날수록 계산 시간이 기하급수적으로 늘어나기 때문에, 칼럼이 많은 데이터를 정규방정식이나 최소제곱법으로 구하는 경우, 시간이 매우 오래 걸리게 된다.
    (행의 수가 늘어나는 경우, 늘어난 샘플 수만큼 선형적으로 소모 시간이 증가하기 때문에 행의 수는 큰 영향을 안 준다.)
  • 만약, 칼럼의 양이나 데이터 자체가 매우 많은 경우, 계산량이 지나치게 많아, 메모리를 넘어설 수 있기 때문에, 정규방정식이나 최소제곱법을 사용하지 못할 수도 있다.

 

 

[참고]

numpy.org/doc/stable/reference/generated/numpy.linalg.lstsq.html

 

numpy.linalg.lstsq — NumPy v1.20 Manual

Cut-off ratio for small singular values of a. For the purposes of rank determination, singular values are treated as zero if they are smaller than rcond times the largest singular value of a. Changed in version 1.14.0: If not set, a FutureWarning is given.

numpy.org

blog.daum.net/jungjin1980/77

 

특이값 분해

출처 : http://darkpgmr.tistory.com/106 활용도 측면에서 선형대수학의 꽃이라 할 수 있는 특이값 분해(Singular Value Decomposition, SVD)에 대한 내용입니다. 보통은 복소수 공간을 포함하여 정의하는 것이..

blog.daum.net

 

 

 

 지금까지 정규방정식과 정규방정식의 한계점을 보완한 최소제곱법에 대해 간략히 알아보고, 코드화해 보았다. 최소제곱법은 선형 회귀 모형을 다룰 때, 굉장히 심도 있게 공부해야 하는 부분이므로, 기초 통계학 부분에서 보다 자세히 다루도록 하겠다.

 다음 포스트에서는 선형 회귀 모형을 정규방정식이나 최소제곱법이 아닌, 경사하강법을 사용해서 구하는 방법에 대해 알아보도록 하겠다.

728x90
반응형
728x90
반응형

선형 회귀(Linear Regression)

 통계학의 꽃이라고도 불리는 선형 회귀(Linear Regression)는 수많은 머신러닝 알고리즘의 기반이 되기도 하기 때문에, 반드시 그 원리를 이해해야하는 알고리즘 중 하나다.

 선형 회귀의 원리를 단순하게 말하자면, 관찰값으로부터 가장 거리가 짧은 데이터를 대표할 수 있는 선을 긋는 것이며, 그 선을 회귀식이라고 한다. 관찰값으로부터 가장 짧은 거리인지를 평가하는 방법은 최소제곱법(Least Square Method)이 주로 사용된다.

 

 

 

 

1. 회귀식


$$\hat{y} = \theta_0 + \theta_1x_1 + \theta_2x_2 + \cdots + \theta_nx_n \ \ \ \cdots①$$

$$\hat{y} = h_\theta(x) = \theta \cdot x   \ \ \ \cdots②$$


  • 회귀식은 기본적으로 해가 $n$개인 일차 연립방정식의 형태이며, 계수와 특성의 값의 곱의 합에 편향(Bias, 절편 - Intercept)을 더한 것이다.
  • $\theta_n$는 해당 관찰값의 계수로, 관찰값 $x_n$가 예측값에 미치는 영향을 보여준다.
  • 계수 $\theta$는 음과 양의 부호를 가질 수 있으며, 이를 통해 해당 특성(변수, 필드)가 종속변수(예측값)에 어떠한 영향을 주는지 볼 수 있다.

1.1. 식 ①의 설명

  • $\hat{y}$: 예측값으로, 종속변수(Dependent variable)이라 한다.
  • $n$: 특성의 수, 독립변수(Independent variable)의 수이다.
  • $x_i$: $i$번째 특성값으로, $i$번째 독립변수를 의미한다.
  • $\theta_j$: $j$번째 모델 파라미터로, 머신러닝 알고리즘 스스로가 학습을 통해 찾아내는 값이다.

1.2. 식 ②의 설명

  • 식 ①을 벡터 형태로 바꾼 것으로, $\theta$와 $x$가 열 벡터(Column vector) 라면, 예측은 $\hat{y} = \theta^Tx$가 된다.
  • 열 벡터란 하나의 열(Column)을 가진 2D 배열이며, $\theta^T$는 열벡터 $\theta$의 전치(Transpose)이다.
  • 두 열 벡터 $\theta$와 $x$의 길이는 서로 동일하므로, 전치 벡터와 길이가 동일한 벡터를 곱하면, 동일한 위치의 원소끼리 곱하고, 더하는 효과가 발생한다.
  • $\theta$: 편향 $\theta_0$와 계수 $\theta_1, \theta_2, \cdots, \theta_n$에 대한 모델 파라미터 벡터다.
  • $x$: $x_0, x_1, \cdots, x_n$까지의 데이터의 특성 벡터다. $x_0$는 편향과 곱해지는 값이므로 무조건 1이다.
  • $\theta \cdot x$: 벡터 $\theta$와 $x$의 점곱으로, $\theta_0x_0 + \theta_1x_1 + \theta_2x_2 + \cdots + \theta_nx_n$과 같다.
  • $h_\theta$: 모델 파라미터 $\theta$를 사용한 가설(Hypothesis) 함수다.

 

 

 

 

 

2. 손실함수 RMSE & MSE

  • 선형 회귀 모델의 학습은 훈련 데이터 셋(Train dataset)에 가장 잘 맞는 모델 파라미터($\theta$)를 찾아내는 것이다.
  • 이를 위해 모델이 훈련 데이터에 얼마나 잘 맞는지 측정해야한다.
  • 선형 회귀 모형은 평균 제곱근 오차(Root Mean Square Error, RMSE)를 사용하여, 모델의 성능을 평가하며, RMSE를 최소화하는 $\theta$를 찾아내는 것이 선형 회귀 모델의 학습 과정이다.
  • RMSE 공식은 다음과 같다.

$$RMSE = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(\hat{y_i} - y_i)^2}$$

$$ RMSE(X, h_{\theta}) = RMSE(\theta) = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(\theta^Tx^{(i)} - y^{(i)})^2} $$


  • 평균 제곱근 오차(RMSE)의 원리는 표준편차와 동일하다. 예측값과 실제 관측값의 편차 제곱의 합의 평균에 제곱근을 씌워 제곱의 합을 보정해주는 것이다.
  • 평균 제곱근 오차(RMSE)에 대하여 더 자세히 알고자하는 경우, 다음 포스트 "딥러닝-5.2. 손실함수(3)-평균제곱근오차(RMSE)"를 참고하길 바란다.
  • 예측값과 실제값의 편차의 평균에 가장 근사한 지표는 RMSE지만, 실제 선형 회귀 모델을 만들 때는 평균 제곱 오차(Mean Square Error, MSE)가 더 많이 사용된다.
  • MSE 공식은 다음과 같다.

$$ MSE = \frac{1}{n}\sum_{i=1}^{n}(\hat{y_i} - y_i)^2 $$

$$ MSE(X, h_{\theta}) = MSE(\theta) = \frac{1}{n}\sum_{i=1}^{n}(\theta^Tx^{(i)} - y^{(i)})^2 $$


  • RMSE보다 MSE를 사용하는 이유는 제곱근을 사용하지 않았기 때문에 실제 값보다 증폭된 결과가 도출되긴 하지만, 공식이 더 간단하며, 미분하기도 쉽다.
    어차피 MSE로 도출된 결과들을 비교하기 때문에 다른 모델과 비교할 때, 보다 엄격한 결과가 나오는 RMSE를 굳이 사용할 필요가 없다.
  • MSE에 대해 보다 자세히 알고자 하는 경우, 다음 포스트 "딥러닝-5.1. 손실함수(2)-평균제곱오차(MSE)"를 참고하기 바란다.
  • 선형 회귀 모델에서의 학습은 비용 함수(Cost function, 손실 함수 - Loss function)인 RMSE나 MSE를 최소로 만드는 $\theta$를 찾는 것이다.

 

 

 

 

 

3. 정규방정식(Normal equation)


$$\hat{\theta} = (X^TX)^{-1}X^Ty$$


  • $\hat{\theta}$: 비용 함수를 최소화하는 $\theta$값이다.
  • $y$: $y^(1)$부터 $y^{(m)}$까지 포함하는 타깃 벡터다.
  • 정규방정식(Normal equation, Ordinary least squares, linear least squrares)는 선형 회귀에서 모델 파라미터인 $\theta$를 예측하기 위해 사용하는 최적화 알고리즘이다.
  • 위 정규방정식은 MSE를 통해 유도 된다.

 

3.1. 정규방정식의 유도

  • MSE 공식은 다음과 같다. $MSE(\theta) = \frac{1}{n}\sum_{i=1}^{n}(\theta^Tx^{(i)} - y^{(i)})^2$ 
  • 위 공식을 보면, MSE 공식은 $\theta^T$에 대하여 이차함수의 개형을 갖는 것을 알 수 있다.
  • 그러므로, MSE을 $theta^T$에 대하여 편미분의 결과가 0이 나오게 하는 $\theta^T$의 값이 MSE를 최소로 만드는 값임을 알 수 있다.
  • 위 내용을 이용하여, 정규방정식을 유도해보자.

 

3.2. 정규방정식과 경사하강법의 차이점

  • 대표적인 최적화 알고리즘인 경사하강법(참고: "딥러닝-6.0. 최적화(1)-손실함수와 경사하강법")은 학습률(Learning rate)를 기반으로 점진적으로 최적해를 찾아간다.
  • MSE를 유도하여 만들어진 정규방정식은 행렬 연산을 통해 한 번에 최적해를 구한다.
  • 정규방정식은 행렬 연산을 통해 결과를 구하기 때문에 피처의 크기(Column * Row의 양)가 커지면 커질수록 계산 시간이 오래 걸린다.
  • 경사하강법은 계산이 일어나 기본적으로 소모되는 시간이 크긴 하지만, 아무리 피처의 크기가 크더라도 일정 시간 안에 최적해를 찾아낼 수 있다.
  • 즉, 피처의 크기가 지나치게 크다면 선형 회귀에서도 경사하강법을 사용하는 것이 좋으며, 피처의 크기가 적당한 수준이라면, 정규방정식을 사용하도록 하자.

 

 

[참고]

 

 

 

 다음 포스트에서는 파이썬 넘파이(Numpy) 함수만을 사용해서 선형 회귀 모델을 구현해보고, 사이킷런을 사용해서 선형 회귀 모델을 사용해보도록 하자.

728x90
반응형

+ Recent posts