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

핸즈온 머신러닝(5) - 선형 회귀와 경사 하강법 2

by chan 2020. 1. 23.
반응형

이전 포스트에서 경사 하강법 알고리즘을 간단히 구현해 보았다. 

코드를 다시 가져와보면

eta = 0.1
n_iterations = 1000
m = 100
theta = np.random.randn(2,1)

for iteration in range(n_iterations):
    gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y)
    theta = theta - eta * gradients

여기서 eta 변수, 즉 학습률  $\eta$ 을 나타내는 변수의 값이 0.1로 설정되어 있는데, 학습률 $\eta$ 를 바꿔보면 어떻게 될까? 임의로 다른 학습률을 사용하여 경사 하강법의 스텝 처음 10개를 그래프로 그려보면 다음과 같다.

 

theta_path_bgd = []
def plot_gradient_descent(theta, eta, theta_path=None):
    m = len(X_b)
    plt.plot(X, y, "b.")
    n_iterations = 1000
    for iteration in range(n_iterations):
        if iteration < 10:
            y_predict = X_new_b.dot(theta)
            style = "b-" if iteration > 0 else "r--"
            plt.plot(X_new, y_predict, style)
        gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y)
        theta = theta - eta * gradients
        if theta_path is not None:
            theta_path.append(theta)
    plt.xlabel("$x_1$", fontsize=18)
    plt.axis([0, 2, 0, 15])
    plt.title(r"$\eta = {}$".format(eta), fontsize=16)
    
np.random.seed(42)
theta = np.random.randn(2,1)  # random initialization

plt.figure(figsize=(10,4))
plt.subplot(131); plot_gradient_descent(theta, eta=0.01)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.subplot(132); plot_gradient_descent(theta, eta=0.1, theta_path=theta_path_bgd)
plt.subplot(133); plot_gradient_descent(theta, eta=0.4)

plt.show()

왼쪽은 학습률이 너무 낮아서 최적점에 도달하는데 시간이 매우 오래 걸릴 것이고, 오른쪽은 학습률이 너무 높아 결국엔 최적점에서 점점 멀어져 발산하게 된다.

 

따라서 적절한 학습률을 찾는것이 중요한데, 이떄 그리드 탐색을 사용한다. 그러나 그리드 탐색에서 수렴하는데 너무 오래 걸리는 모델을 막기 위해서는 반복 횟수를 제한해야 한다.

 

이때 반복 횟수를 아주 크게 지정하고 그래디언트 벡터의 노름이 허용오차 $\varepsilon$ 보다 작아지면 경사 하강법이 최솟값에 도달한 것으로 보고 알고리즘을 중지하는 방법이 있다.

 

 

 

 

# 확률적 경사 하강법

 

배치 경사 하강법은 매 스텝에서 전체 훈련 세트를 사용해 그래디언트를 계산하기 때문에 훈련 세트가 커지면 매우 느려지는 단점이 있다. 

 

이와 정반대로 확률적 경사 하강법(Stochastic Gradient Descent, SGD)은 매 스텝에서 딱 한개의 샘플을 무작위로 선택하고 그 하나의 샘플에 대한 그래디언트를 계산한다. 매 반복에서 매우 적은 데이터만 처리하기 때문에 알고리즘이 훨씬 빠르며 매우 큰 훈련 세트도 훈련시킬 수 있다.

 

반면 무작위적으로 그래디언트를 계산하기 때문에 배치 경사 하강법보다 훨씬 불안정하다. 비용함수가 부드럽게 감소하는 것이 아니라 위아래로 요동치면서 평균적으로 감소한다. 그렇기 때문에 비용 함수가 아주 불규칙적일 경우 알고리즘이 지역 최솟값을 건너뛸 수 있게 도와주기도 한다.

 

즉 무작위성은 지역 최솟값에서 탈출시켜 준다는 장점이 있지만 전역 최솟값에 다다르지 못하게 한다는 단점이 있다.

이 트레이드오프의 해결책 중 하나는 학습률을 점진적으로 감소시키는 것이 있다. 매 반복에서 학습률을 결정하는 함수를 학습 스케줄(learning schedule)이라고 한다.

 

학습률이 너무 빨리 줄어들면 지역 최솟값에 갇히거나 최솟값까지 가는 중간에 멈춰버릴 수도 있으며, 학습률이 너무 천천히 줄어들면 오랫동안 최솟값 주변을 맴돌거나 훈련을 너무 일찍 중지해서 지역 최솟값에 머무를 수도 있다.

 

다음은 간단한 학습 스케줄을 사용하여 확률적 경사 하강법을 구현한 코드와 훈련 스텝의 첫 20개를 출력한 그래프이다.

theta_path_sgd = []
m = len(X_b)
np.random.seed(42)

n_epochs = 50
t0, t1 = 5, 50  # 학습 스케줄 하이퍼파라미터 learning schedule hyperparameters

def learning_schedule(t):
    return t0 / (t + t1)

theta = np.random.randn(2,1)  # 무작위 초기화

for epoch in range(n_epochs):
    for i in range(m):
        if epoch == 0 and i < 20:                       
            y_predict = X_new_b.dot(theta)             
            style = "b-" if i > 0 else "r--"           
            plt.plot(X_new, y_predict, style)  #그래프 출력(처음 20번) 
        random_index = np.random.randint(m)    #무작위 샘플 추출
        xi = X_b[random_index:random_index+1]
        yi = y[random_index:random_index+1]
        gradients = 2 * xi.T.dot(xi.dot(theta) - yi)
        eta = learning_schedule(epoch * m + i)  #학습률 점진적 감소
        theta = theta - eta * gradients
        theta_path_sgd.append(theta)                   

plt.plot(X, y, "b.")                                    
plt.xlabel("$x_1$", fontsize=18)                       
plt.ylabel("$y$", rotation=0, fontsize=18)         
plt.axis([0, 2, 0, 15])                                 
plt.show()                 

 

 

사이킷런에서는 SGD방식으로 선형 회귀를 사용하기 위해 기본값으로 제곱 오차 비용 함수를 최적화하는 SGDRegressor 클래스를 사용한다. 다음 클래스는 $\eta$ = 0.1 로 기본 학습 스케줄을 사용해 에포크를 50번 수행한다. 

from sklearn.linear_model import SGDRegressor
sgd_reg = SGDRegressor(max_iter=50, penalty=None, eta0=0.1)
sgd_reg.fit(X, y.ravel())

sgd_reg.intercept_, sgd_reg.coef_
# (array([4.53370492]), array([2.6013874]))

 

* SGDRegressor 에서 학습 스케줄인 매개변수 learning_rate의 기본값은 'invscaling' 으로, 반복 횟수 t와 eta0, power_t (기본값 각각 0.01, 0.25)를 사용하여 다음 공식으로 학습률을 계산한다.

 

$$\eta^{(t)}=\frac{eta0}{t^{power\_t}}$$

 

* SGDClassifier의 learning_rate 기본값은 'optimal' 이며 다음 공식을 사용한다.

 

$$\eta^{(t)}=\frac{1}{alpha(t_0+t)}$$

 

 

 

 

 

 

# 미니배치 경사 하강법

 

미니배치 경사 하강법(Mini-batch Gradient Descent)는 각 스텝에서 미니배치라 부르는 임의의 작은 샘플 세트에 대해 그래디언트를 계산한다. 미니배치 경사 하강법은 행렬 연산에 최적화된 하드웨어(GPU 등)을 사용했을 때 성능 향상을 얻을 수 있다.

 

미니배치를 어느 정도 크게 하면 파라미터 공간에서 알고리즘은 SGD보다 덜 불규칙적이지만, 그만큼 지역 최솟값에서 빠져나오는 것은 힘들어질 것이다. 미니배치 경사 하강법 알고리즘은 SGD와 BGD의 절충적인 방법이라고 보면 된다.

 

 

 

선형 회귀를 위한 알고리즘들을 표로 비교해 보면 다음과 같다.

알고리즘 m이 클 때 외부 메모리 학습 지원 n이 클 때  하이퍼 파라미터 수  스케일 조정 필요 사이킷런
정규방정식 빠름 No 느림 0 No LinearRegression
배치 경사 하강 느림 No 빠름 2 Yes None
확률적 경사 하강 빠름 Yes 빠름 >=2 Yes SGDRegressor
미니배치 경사 하강 빠름 Yes 빠름 >=2 Yes None
반응형

댓글