본문 바로가기

파이썬 머신러닝 완벽가이드

5.1 회귀와 경사하강법

728x90

그동안 머신러닝의 중요한 축인 분류에 대하여 학습하느라 

다들 너무 수고 많으셨습니다. 

이번 시간부터는 머신러닝의 또 다른 중요한 축인

'회귀'에 대해 배워보도록 하겠습니다.

집중해서 잘 따라와주세요~

해당 내용은 '파이썬 머신러닝 완벽 가이드'를 정리한 내용입니다.


1. 회귀

먼저 회귀의 정의에 대해 살펴보겠습니다. 회귀랑 통계학에서 사용되기 시작한 말로, 여러 개의 독립변수와 한 개의 종속변수 간의 상관관계를 모델링한 기법을 통치합니다. 예를 들어 아파트 가격이라는 종속 변수와 아파트 위치, 방의 개수, 방의 크기 등 독립 변수 간의 상관관계를 나타낸 것이 회귀입니다. 이를 선형 회귀로 표현하면 Y = W1*X1 + W2*X2 + W3*X3 W1*X1와 같이 표현할 수 있습니다.

독립 변수와 종속 변수 간의 상관관계를 나타내기 때문에 회귀의 중요한 포인트는 독립변수와 종속변수구나 생각할 수도 있습니다. 하지만 회귀에서 정말로 중요한 것은 회귀 계수입니다. 독립변수는 데이터의 피처, 종속 변수는 결정 값으로 단지 상수에 불가합니다. 회귀에서 학습을 통해 찾고자 하는 것은 최적의 회귀 계수입니다. 따라서 회귀 계수의 특징에 따라 회귀 유형이 분류됩니다.

여러 가지 회귀 중 가장 많이 사용되는 회귀 유형은 선형 회귀입니다. 선형 회귀는 규제 방법에 따라 다양하게 나뉩니다. 각각의 선형회귀는 뒤에서 차근차근 다루도록 하겠습니다.

2. 단순 선형회귀

단순 선형 회귀를 통해 선형 회귀에 대해 더욱 자세히 이해해보도록 하겠습니다. 주택 가격이 방의 크기에 따라서만 결정된다고 가정할 때 이를 다음과 같이 선형의 상관관계로 표현할 수 있습니다.

이때 실제 값과 선형 회귀 모델의 차이를 오류 값이라 합니다. 즉, 잔차입니다. 최적의 선형 회귀 모델을 만든다는 것은 바로 잔차를 최소로 만드는 최적의 회귀 계수를 찾는다는 의미입니다. 우리는 오류 값의 합을 구하기 위해 오류값에 절대값을 취해 더하거나(Mean Absolute Error) 오류값의 제곱을 구해서 더하는 방식(RSS, Residual Sum of Square)을 택합니다. 

오류 값을 최소화하는 것. 즉, RSS를 최소화하는 회귀계수를 찾는 것이 머신러닝의 핵심입니다. RSS는 다음과 같이 표현됩니다.

위의 RSS 식을 비용 함수(손실 함수)라고 부릅니다. 회귀 알고리즘은 데이터를 학습하면서 비용 함수가 반환하는 오류 값을 감소하여 가장 작은 오류 값을 반환합니다.

3. 경사 하강법(Gradient Descent)

다차원의 비용 함수를 일일이 계산하여 최소의 오류 값을 찾는 것은 불가능합니다. 예를 들어 독립변수가 100개 이상인 경우 이를 방정식으로 풀기란 매우 어렵습니다. 따라서 이러한 문제를 해결하기 위해 고안한 방법이 바로 경사 하강법입니다. 경사 하강법이란, ‘점진적으로’ 반복적인 계산을 통해 W 파라미터 값을 업데이트하면서 오류 값이 최소가 되는 W 파라미터를 구하는 방식입니다. 계속해서 최소가 되는 방향으로 찾아가며 업데이트한다는 것이 매우 무식한 방법처럼 들릴 수 있지만 가장 빠르면서, 확실한 방법입니다. 경사 하강법은 반복적으로 비용 함수를 반환하며 방향성을 가지고 회귀계수를 보정해 나갑니다. 

그럼 어떻게 방향성을 가질 수 있을까요? 바로 '미분'을 통해서 가능합니다. 예를 들어 비용 함수가 포물선이라면 우리는 현재 위치의 미분 값을 구해 방향을 구할 수 있습니다. 아래 그림처럼 말이죠 ㅎㅎ

 그럼 경사 하강법을 위해 비용 함수를 직접 미분해보고 이를 파이썬 코드로 표현해보겠습니다. 경사하강법의 필요성과 방향성은 미분으로 결정된다는 사실만 명확하게 인지하면 되기 때문에 아래 코드는 참고로 알아두시면 좋을 것 같습니다.

4. 경사하강법 - 수식

비용함수를 미분하기 위해서는 '편미분'을 사용하면 됩니다. 편미분 결과는 아래와 같습니다.(과정은 단순하기 때문에 직접 해보시면 좋을 것 같습니다.)

W0, W1에 대한 편미분 값을 통해 계속해서 W0, W1을 업데이트해가면서 비용 함수의 최솟값을 구하는 것입니다.

  1. M 임의의 값으로 설정하고 첫 비용 함수의 값을 계산합니다.
  2. W0, W1을 업데이트 한 뒤 비용 함숫값을 계산합니다.
  3. 비용 함수가 감소하지 않을 때까지 2번을 계속 반복합니다.

5. 경사 하강법 - 파이썬

경사 하강법을 파이썬 코드로 구현해보도록 하겠습니다. y=4x + 6에 근사할 수 있도록 임의의 100개의 데이터 세트를 만들고 최적의 선형 회귀 계수를 찾아보겠습니다. 

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

np.random.seed(0)
# y = 4X + 6 식을 근사(w1=4, w0=6). random 값은 Noise를 위해 만듬
X = 2 * np.random.rand(100,1)
y = 6 +4 * X+ np.random.randn(100,1)

# X, y 데이터 셋 scatter plot으로 시각화
plt.scatter(X, y)

 

다음과 같이 랜덤 하게 데이터가 흩뿌려졌습니다. 이제 경사 하강법을 구현해보겠습니다.

 

# w1 과 w0 를 업데이트 할 w1_update, w0_update를 반환. 
def get_weight_updates(w1, w0, X, y, learning_rate=0.01):
    N = len(y)
    # 먼저 w1_update, w0_update를 각각 w1, w0의 shape와 동일한 크기를 가진 0 값으로 초기화
    w1_update = np.zeros_like(w1)
    w0_update = np.zeros_like(w0)
    # 예측 배열 계산하고 예측과 실제 값의 차이 계산
    y_pred = np.dot(X, w1.T) + w0
    diff = y-y_pred
         
    # w0_update를 dot 행렬 연산으로 구하기 위해 모두 1값을 가진 행렬 생성 
    w0_factors = np.ones((N,1))

    # w1과 w0을 업데이트할 w1_update와 w0_update 계산
    w1_update = -(2/N)*learning_rate*(np.dot(X.T, diff))
    w0_update = -(2/N)*learning_rate*(np.dot(w0_factors.T, diff))    
    
    return w1_update, w0_update

 

# 입력 인자 iters로 주어진 횟수만큼 반복적으로 w1과 w0를 업데이트 적용함. 
def gradient_descent_steps(X, y, iters=10000):
    # w0와 w1을 모두 0으로 초기화. 
    w0 = np.zeros((1,1))
    w1 = np.zeros((1,1))
    
    # 인자로 주어진 iters 만큼 반복적으로 get_weight_updates() 호출하여 w1, w0 업데이트 수행. 
    for ind in range(iters):
        w1_update, w0_update = get_weight_updates(w1, w0, X, y, learning_rate=0.01)
        w1 = w1 - w1_update
        w0 = w0 - w0_update
              
    return w1, w0

 

def get_cost(y, y_pred):
    N = len(y) 
    cost = np.sum(np.square(y - y_pred))/N
    return cost

w1, w0 = gradient_descent_steps(X, y, iters=1000)
print("w1:{0:.3f} w0:{1:.3f}".format(w1[0,0], w0[0,0]))
y_pred = w1[0,0] * X + w0
print('Gradient Descent Total Cost:{0:.4f}'.format(get_cost(y, y_pred)))

 

w1:4.022 w0:6.162
Gradient Descent Total Cost:0.9935

 

plt.scatter(X, y)
plt.plot(X,y_pred)

 

위와 같이 경사 하강법을 구현하면 문제가 생깁니다. 반복횟수가 너무 크면 해당 반복횟수를 끝내고 업데이트할 값을 구하기 때문에 속도가 매우 느리다는 단점이 있습니다. 따라서 실전에서는 대부분 일부 데이터만 이용해 업데이트할 w 값을 구하여 업데이트하는 확률적 경사하강법을 사용합니다. 

 

확률적 경사하강법 구현 코드는 아래와 같습니다.

 

def stochastic_gradient_descent_steps(X, y, batch_size=10, iters=1000):
    w0 = np.zeros((1,1))
    w1 = np.zeros((1,1))
    prev_cost = 100000
    iter_index =0
    
    for ind in range(iters):
        np.random.seed(ind)
        # 전체 X, y 데이터에서 랜덤하게 batch_size만큼 데이터 추출하여 sample_X, sample_y로 저장
        stochastic_random_index = np.random.permutation(X.shape[0])
        sample_X = X[stochastic_random_index[0:batch_size]]
        sample_y = y[stochastic_random_index[0:batch_size]]
        # 랜덤하게 batch_size만큼 추출된 데이터 기반으로 w1_update, w0_update 계산 후 업데이트
        w1_update, w0_update = get_weight_updates(w1, w0, sample_X, sample_y, learning_rate=0.01)
        w1 = w1 - w1_update
        w0 = w0 - w0_update
    
    return w1, w0

 

w1, w0 = stochastic_gradient_descent_steps(X, y, iters=1000)
print("w1:",round(w1[0,0],3),"w0:",round(w0[0,0],3))
y_pred = w1[0,0] * X + w0
print('Stochastic Gradient Descent Total Cost:{0:.4f}'.format(get_cost(y, y_pred)))

 

w1: 4.028 w0: 6.156
Stochastic Gradient Descent Total Cost:0.9937

 

확률적 경사 하강법의 결과를 보면 모든 데이터를 다 확인 후 회귀계수를 구하는 것과 큰 차이가 없음을 알 수 있습니다. 대신 속도는 훨씬 빠른다는 장점이 있죠!


이번 시간에는 회귀의 개념과 회귀에서 매우 매우 중요한

경사 하강법에 대해서 다뤄봤습니다. 경사하강법 수식과 파이썬 구현은

지금 이해가 가지 않더라도 괜찮으니 참고만 하시면 됩니다.

다음 시간에는 사이킷런 회귀 라이브러리와 이를 통해

보스턴 주택 가격 데이트 세트에 적용해보도록 하겠습니다.

반응형