Machine Learning/Advanced (hands on machine learning)

7. 모델훈련 - 경사하강법

jwjwvison 2021. 5. 18. 20:54

 경사 하강법(gradient descent, GD)는 여러 종류의 문제에서 최적의 해법을 찾을 수 있는 일반적인 최적화 알고리즘이다. 경사 하강법의 기본 아이디어는 비용 함수를 최소화하기 위해 반복해서 파라미터를 조정해가는 것이다.

 

 경사 하강법에서 중요한 파라미터는 스텝의 크기로, 학습률(learning rate)하이퍼 파라미터로 결정된다. 학습률이 너무 작으면 알고리즘이 수렴하기 위해 반복을 많이 진행해야 하므로 시간이 오래 걸린다.

 

 아래 그림은 경사 하강법의 두가지 문제점을 보여준다. 무작위 초기화 때문에 알고리즘이 왼쪽에서 시작하면 전역 최솟값(global minimum) 보다 덜 좋은 지역 최솟값(local minimum)에 수렴한다. 알고리즘이 오른쪽에서 시작하면 평탄한 지역을 지나기 위해 시간이 오래 걸리고 일찍 멈추게 되어 전역 최솟값에 도달하지 못한다.

 

 다행히 선형 회귀를 위한 MSE비용 함수는 곡선에서 어떤 두 점을 선택해 선을 그어도 곡선을 가로지르지 않는 볼록함수 이다. 

 사실 비용함수는 그릇 모양을 하고 있지만 특성들의 스케일이 매우 다르면 길쭉한 모양일 수 있다. 다음 그림은 특성 1과 특성 2의 스케일이 같은 훈련 세트와 특성 1이 특성 2보다 더 작은 훈련 세트(오른쪽)에 대한 경사 하강법을 보여준다.

 

<배치 경사 하강법>

경사 하강법을 구현하려면 각 모델 파라미터 세타에 대해 비용 함수의 그레이디언트를 계산해야 한다. 이를 편도함수(partial derivative)라고 한다.

 

 이 알고리즘을 간단히 구현해보겠다.

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
theta

 

 학습률 n을 바꾸면 어떻게 될까?

 반복 횟수는 어떻게 정할까? 반복 횟수를 아주 크게 지정하고 그레이디언트 벡터가 아주 작아지면, 즉 벡터의 노름이 어떤 값(허용오차)보다 작아지면 경사 하강법이 (거의) 최솟값에 도달한 것이므로 알고리즘을 중지하면 된다.

 

<확률적 경사 하강법-SGD>

 확률적 경사 하강법은 매 스텝에서 한 개의 샘플을 무작위로 선택하고 그 하나의 샘플에 대한 그레이디언트를 계산한다. 매 반복에서 다뤄야 할 데이터가 매우 적기 때문에 한 번에 하나의 샘플을 처리하면 알고리즘이 확실히 훨씬 빠르다. 또한 매 반복에서 하나의 샘플만 메모리에 있으면 되므로 매우 큰 훈련 세트도 훈련시킬 수 있다.

 

 반면 확률적이므로 이 알고리즘은 배치 경사 하강법보다 훨씬 불안정하다. 비용 함수가 최솟값에 다다를 때까지 부드럽게 감소하지 않고 위아래로 요동치며 평균적으로 감소한다. 시간이 지나면 최솟값에 매우 근접하겠지만 요동이 지속되면서 최솟값에 안착하지 못할 것이다. 알고리즘이 멈출 때 좋은 파라미터가 구해지겠지만 최적치는 아니다.

 

 이 딜레마를 해결하는 한 가지 방법은 학습률을 점진적으로 감소시키는 것이다. 시작할 때는 학습률을 크게 하고(수렴을 빠르게 하고 지역 최솟값에 빠지지 않게 한다), 점차 작게 줄여서 알고리즘이 전역 최솟값에 도달하게 한다. 매 반복에서 학습률을 결정하는 함수를 학습 스케줄(learning schedule)이라고 부른다.

 

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

 

 다음 코드는 간단한 학습 스케줄을 사용한 확률적 경사 하강법의 구현이다.

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

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):
    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
    

 

 배치 경사 하강법 코드가 전체 훈련 세트에 대해 1000번 반복하는 동안 이 코드는 훈련 세트에서 50번만 반복하고도 매우 좋은 값에 도달했다.

theta

 

 사이킷런에서 SGD 방식으로 선형 회귀를 사용하려면 기본값으로 제곱 오차 비용 함수를 최적화하는 SGDRegressor 클래스를 사용한다. 다음 코드는 최대 1000번 에포크 동안 실행된다(max_iter=1000). 또는 한 에포크에서 0.001보다 적게 손실이 줄어들 때까지 실행된다(tol=1e-3), 학습률(eta0=0.1)로 기본 학습 스케줄을 사용한다.

from sklearn.linear_model import SGDRegressor

sgd_reg=SGDRegressor(max_iter=1000,tol=1e-3,penalty=None,eta0=0.1)
sgd_reg.fit(X,y.ravel())

sgd_reg.intercept_,sgd_reg.coef_

 

 

<미니배치 경사 하강법>

미니배치라 부르는 작은 샘플 세트에 대해 그레이디언트를 계산한다. 확률적 경사 하강법에 비해 미니배치 경사 하강법의 주요 장점은 행렬 연산에 최적화된 하드웨어, 특히 GPU를 사용해서 얻는 성능 향상이다.

 특히 미니배치를 어느 정도 크게 하면 이 알고리즘은 파라미터 공간에서 SGD보다 덜 불규칙하게 움직인다.  결국 미니배치 경사 하강법이 SGD보다 최솟값에 더 가까이 도달하게 될 것이다. 

 

 다음 그림은 세 가지 경사 하강법 알고리즘이 훈련 과정 동안 파라미터 공간에서 움직인 경로이다. 모두 최솟값 근처에 도달했지만 배치 경사 하강법의 경로가 실제로 최솟값에서 멈춘 반면 확률적 경사 하강법과 미니배치 경사 하강법은 근처에서 맴돌고 있다. 그렇지만 배치 경사 하강법에는 매 스텝에서 많은 시간이 소요되고, 확률적 경사 하강법과 미니배치 하강법도 적절한 학습 스케줄을 사용하면 최솟값에 도달한다는 것을 잊으면 안된다.