이전 글을 통해 OLS가 무엇인지, 어떻게 식을 유도하는지 알아봤다. (글보기)

이번 글에서는 경사하강법에 대해 알아보되, OLS와 개념적으로 어떻게 다른지를 중점으로 살펴보겠다.

 

경사하강법(Gradient Descent)

 

Gradient Descent, 즉 경사하강법은 iterative 하게 손실 함수를 최소화 시키는 파라미터를 찾는 최적화 방식이다.

 

 

파랑색 그래프가 손실함수라고 할 때, 값이 최소화되는 지점을 찾기 위해 경사하강법을 사용한다.

함수의 기울기(경사)를 구하여 기울기의 반대 방향으로 내려가면서 최소점을 찾는다.

 

cost function $J(w, b)$가 있을 때,

 

편의를 위해 파라미터 b의 축은 생략

 

$$w := w - \alpha \frac{\partial J(w)}{\partial w}$$

 

$J(w)$의 경사(미분값)를 활용하여 파라미터 $w$를 반복적으로 업데이트 한다.

 

 

OLS는 미분값이 0인 지점을 찾아서 최소값을 한방에 계산하던데? 왜 그렇게 하지 않는 거지?

 

OLS에서 추정하고자 하는 선형함수와 그로부터 계산되는 잔차제곱합의 식에 비해,

신경망에서 정의되는 함수가 훨씬 비선형적이고 복잡하여 미분계수의 근을 구하기가 쉽지 않다.

한 마디로, 식이 너무 복잡해서 수학적으로 한방에 구하기가 어렵다!

미분계수를 계산하느니 iterative하게 최소점을 찾아가는 것이 더 효율적이다.

 

따라서 최소제곱법과 경사하강법의 차이를 단순하게 설명해보자면,

전자는 수학적으로 잔차제곱합이 최소가 되는 파라미터를 (한번에) 계산해 추정하는 반면

후자는 손실함수가 최소가 되는 파라미터를 구하기 위해 반복적으로 기울기를 구해가며 최적화하는 방식이다.

 

 

 

 

numpy를 활용해 OLS와 gradient descent를 구현해보자.

 

import numpy as np
import matplotlib.pyplot as plt

x_data = np.array([1,3,5,7,9])
y_data = np.array([0,10,50,70,100])

plt.scatter(x_data, y_data)
plt.plot(x_data, y_data)
plt.show()

 

아주 간단한 데이터를 생성해보았다. 데이터를 시각화해보면 아래와 같다.

 

 

OLS회귀분석을 통해 위 데이터를 설명할 수 있는 회귀식 $y = \beta_{0} + \beta_{1} x$ 를 추정하려고 한다.

이전 글에 따라 $\beta_{0}$와 $\beta_{1}$는 아래와 같이 정리된다.

 

$\beta_{0}$

 

$\beta_{1}$

 

class LinearRegression:
    def __init__(self, x_data, y_data):
        self.X = x_data
        self.Y = y_data

    def slope(self, X, Y):
        meanDifferenceX = X - np.mean(X)
        meanDifferenceY = Y - np.mean(Y)
        numerator = np.sum(np.multiply(meanDifferenceX, meanDifferenceY))
        denominator = np.sum(np.square(meanDifferenceX))
        b_1 = np.divide(numerator,denominator)
        return b_1

    def train(self):
        self.b_1 = round(self.slope(self.X, self.Y), 2)
        self.b_0 = round(np.mean(self.Y) - round(self.b_1,2) * np.mean(self.X),2)

    def predict(self, X):
        output = np.add(self.b_0, np.multiply(self.b_1, np.array(X)))
        return output

 

train 함수를 살펴보면 : slope 함수를 통해 $\beta_{1}$를 구하고, 그렇게 구한 $\beta_{1}$를 기반으로 $\beta_{0}$을 구한다.

 

위에서 생성한 데이터에 적합시켜본다면

lr = LinearRegression(x_data, y_data)
lr.train()
lr.predict(x_data) # 예측결과 return

 

추정한 회귀식과 실제 데이터를 비교해보자

 

y = [(lr.b_1*number + lr.b_0) for number in x_data]

plt.plot(x_data, y_data, color='r', label='real data')
plt.plot(x_data, y, color='g', label='predicted')

빨간선이 real, 초록선이 predicted

 

같은 데이터를 써서, 경사하강법으로 선형회귀식을 구해보는 건 어떨까?

 

먼저 경사하강법을 통해 최소화하려는 목적함수를 설정해야 한다. 가장 일반적으로 사용하는 MSE(Mean Squared Error)를 목적함수로 설정해본다.

 

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

 

MSE는 OLS에서 구했던 오차제곱합을 원소의 개수로 나눠 평균을 구한 값이다.

 

그리고 경사하강법을 클래스로 구현해본다.

 

class LRwithGradientDescent:
    
    def __init__(self):
        # Initialize parameters
        self.bias = 0
        
        # Create an attribute to log the loss
        self.loss = []
        
    def fit(self, X, y, alpha = 0.05, n_iterations = 10):
        # Get num observations and num features
        self.n = len(X)
        
        # initialize weight
        self.weights = 1
        
        # Iterate a number of times
        for _ in range(n_iterations):
            
            # Generate prediction
            y_hat = np.dot(X, self.weights) + self.bias
            
            # Calculate error
            error = y - y_hat
            
            # Calculate loss (mse)
            mse = np.square(error).mean()
            
            # Log the loss
            self.loss.append(mse)

            # Calculate gradients using partial derivatives
            gradient_wrt_weights = - (1 / self.n) * np.dot(X.T, error)
            gradient_wrt_bias = - (1 / self.n) * np.sum(error)            
                
            # Update parameters using gradients and alpha    
            self.weights = self.weights - alpha * gradient_wrt_weights
            self.bias = self.bias - alpha * gradient_wrt_bias  
    
    def predict(self, X):
        # Generate predictions using current weights and bias 
        return np.dot(X, self.weights) + self.bias

 

처음 생성한 데이터에 학습시켜본다

learning rate는 0.05, epoch수는 1000번으로 한다

 

model = LRwithGradientDescent()
model.fit(x_data, y_data, alpha = 0.05, n_iterations = 1000)
model.predict(x_data) # 예측 결과 return

 

loss가 줄어들며 잘 학습된 것을 볼 수 있다

plt.plot(model.loss)

 

경사하강법을 통해 추정한 선형회귀식을 시각화해봤다.

 

yy = [(model.weights*number + model.bias) for number in x_data]

plt.plot(x_data, y_data, color='r', label='real data')
plt.plot(x_data, yy, color='g', label='predicted')

 

 

거의 동일한 선형회귀식을 그린 것을 볼 수 있다.

 

복사했습니다!