모멘텀 최적화
표준적인 경사하강법은 경사가 완만할 때는 작은 스텝으로, 경사가 가파를 때는 큰 스텝으로 이동하지만, 속도 자체가 높아지지는 않는다.
반면에 모멘텀 최적화(momentum optimization)의 경우, gradient를 속도가 아닌 가속도로 사용한다. 즉, 이전 gradient가 얼마였는지를 중요하게 생각하여, 다음과 같이 가중치를 갱신한다 ($\bf{m}$ 은 모멘텀 벡터, $\beta$ 는 모멘텀).
- $\bf{m} \leftarrow \beta \bf{m} - \eta \nabla_\theta J(\bf{\theta})$
- $\bf{\theta} \leftarrow \bf{\theta} + \bf{m}$
$\beta$ 는 0과 1사이의 값으로 정해지며, 보통 0.9이다.
모멘텀 최적화는 골짜기를 따라 최적점에 도달할 때까지 점점 더 빠르게 내려가며, 지역 최적점을 건너 뛰는 데도 도움이 된다. 또한, 점점 빨라진다는 특성 덕분에 스케일이 매우 다른 입력이 들어왔을 때도 유용하다.
optimizer = tf.keras.optimizers.SGD(learning_rate=0.001, momentum=0.9)
네스테로프 가속 경사
네스테로프 가속 경사(Nesterov Accelerated Gradient, NAG)는 현재 위치 $\bf{\theta}$ 가 아니라 모멘텀의 방향으로 조금 앞선 $\bf{\theta} + \beta \bf{m}$ 에서 비용함수의 gradient를 계산한다.
- $\bf{m} \leftarrow \beta \bf{m} - \eta \nabla_\theta J(\bf{\theta} + \beta \bf{m})$
- $\bf{\theta} \leftarrow \bf{\theta} + \bf{m}$
일반적으로 모멘텀 벡터가 최적점을 가리킬 것이므로, 그 방향으로 조금 더 나아간 gradient를 사용하는 것이 약간 더 정확하다.
NAG는 기본 모멘텀 최적화보다 약간 더 빠르다.
optimizer = tf.keras.optimizers.SGD(learning_rate=0.001, momentum=0.9, nesterov=True)
AdaGrad
입력의 스케일이 다른 상황에서의 경사 하강법을 생각해보자. 알고리즘이 전역 최적점쪽으로의 방향을 일찍 제대로 잡는다면 더 좋을 것이다. AdaGrad는 가장 가파른 차원을 따라 gradient 벡터의 스케일을 감소시켜 이 문제를 해결한다.
- $\bf{s} \leftarrow \bf{s} + \nabla_\theta J(\bf{\theta}) \otimes \nabla_\theta J(\bf{\theta})$
- $\bf{\theta} \leftarrow \bf{\theta} - \eta \nabla_\theta J(\bf{\theta}) \oslash \sqrt{\bf{s} + \epsilon}$
1에서는 gradient의 제곱을 누적한다. 즉, 비용 함수가 i번째 차원을 따라 가파르다면 $s_i$ 는 반복이 진행되면서 점차 커질 것이다.
2에서는 경사 하강법과 거의 같은 과정이다. 다른 점이 있다면 $\sqrt{\bf{s} + \epsilon}$ 로 나누어 스케일을 조정한다는 것이다. ($\oslash$ 는 원소별 나눗셈을 의미한다.)
이 알고리즘은 학습률을 감소시키지만, 경사가 가파른 차원($\sqrt{\bf{s} + \epsilon}$ 가 클수록)에 대해 더 빠르게 감소한다. 이를 적응적 학습률(adaptive learning rate)이라고 한다. 학습률을 튜닝하는 부담이 준다는 것도 장점이다.
케라스에는 AdaGrad
optimizer가 있지만, 심층 신경망에는 추천하지 않는다.
RMSprop
Adagrad는 너무 빨리 느려져서 전역 최적에 도달하지 못할 수도 있다. RMSprop는 가장 최근 반복에서 비롯된 gradient만 누적해서 이 문제를 해결한다.
- $\bf{s} \leftarrow \rho \bf{s} + (1- \rho) \nabla_\theta J(\bf{\theta}) \otimes \nabla_\theta J(\bf{\theta})$
- $\bf{\theta} \leftarrow \bf{\theta} - \eta \nabla_\theta J(\bf{\theta}) \oslash \sqrt{\bf{s} + \epsilon}$
$\rho$ 는 보통 0.9로 설정한다.
optimizer = tf.keras.optimizers.RMSprop(learning_rate=0.001, rho=0.9)
Adam
Adam은 적응적 모멘트 추정(adaptive moment estimation)을 의미한다. 모멘텀 최적화와 RMSprop을 합친 것이라고 보면 된다.
- $\bf{m} \leftarrow \beta_1 \bf{m} - (1-\beta_1) \nabla_\theta J(\bf{\theta})$
- $\bf{s} \leftarrow \beta_2 \bf{s} + (1- \beta_2) \nabla_\theta J(\bf{\theta}) \otimes \nabla_\theta J(\bf{\theta})$
- $\hat{\bf{m}} \leftarrow \frac{\bf{m}}{1 - \beta_1^t}$
- $\hat{\bf{s}} \leftarrow \frac{\bf{s}}{1 - \beta_2^t}$
- $\bf{\theta} \leftarrow \bf{\theta} + \eta \hat{\bf{m}} \oslash \sqrt{\hat{\bf{s}} + \epsilon}$
$\beta_1$ 은 모멘텀 최적화의 $\beta$ (모멘텀 감쇠), $\beta_2$ 는 RMSprop의 $\rho$ (스케일 감쇠)에 해당한다. 보통 $\beta_1=0.9$, $\beta_2=0.999$ 로 초기화하는 경우가 많다.
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001, beta_1=0.9, beta_2=0.999)
AdaMax
Adam의 단계 5를 간단히 생각하면, $\bf{s}$ 의 제곱근, 즉 $l_2$ norm으로 파라미터 업데이트의 스케일을 낮춘다고 생각할 수 있다.
AdaMax는 Adam에서의 $l_2$ norm 대신 $l_{\infty}$ norm을 사용한다.
구체적으로는 단계 2를 $\bf{s} \leftarrow max(\beta_2 \bf{s}, \text{abs}{\nabla_\theta J(\bf{\theta})})$ 과 같이 바꾸며, 단계 4는 삭제한다. 그리고 단계 5에서는 제곱근을 사용하지 않고 $\bf{s}$ 에 비례하여 스케일을 낮춘다.
보통 AdaMax가 Adam보다 안정적이기는 하나, 일반적으로 성능은 Adam이 더 낫다.
Nadam
Nadam은 Adam에 NAG 기법을 더한 것이다.
AdamW
AdamW은 가중치 감쇠라는 기법을 통합한 Adam의 변형이다. 각 훈련 반복에서 가중치에 0.99와 같은 일정한 감쇠 계수를 곱하여 가중치를 줄이는 것이다.
Learning Rate Scheduling
훈련하는 동안 학습률을 감소시키면 최적점 주변에서 진동하지 않고 수렴할 수 있다. 이처럼 학습률을 조정하는 것을 학습 스케줄링이라고 한다.
학습 스케줄링에는 다양한 방법이 있다.
-
거듭제곱 기반 스케줄링 (Power Scheduling)
학습률을 반복 횟수 t에 대한 함수 $\eta(t) = \frac{\eta_0}{(1 + t/s)^c}$ 로 지정한다. c는 일반적으로 1이다. s는 스텝 수로, 하이퍼 파라미터이다. 학습률은 s번 스텝 뒤에 처음의 절반, 2s번 뒤에는 처음의 1/3이 된다. 즉, 처음에는 학습률이 빠르게 감소하고, 나중에는 느리게 감소한다.# power scheduling optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, weight_decay=1e-4)
-
지수 기반 스케줄링 (Exponential Scheduling)
학습률은 $\eta(t) = \eta_0 0.1^{t/s}$ 이다. s 스텝 이후에 10배씩 감소한다.# exponential scheduling 1 - basic def exponential_decay_fn(epoch): return 0.01 * 0.1 ** (epoch / 20) # exponential scheduling 2 - closure(initial lr and s are not hardcoded.) def exponential_decay(lr0, s): def exponential_decay_fn(epoch): return lr0 * 0.1 ** (epoch / s) return exponential_decay_fn exponential_decay_fn = exponential_decay(lr0=0.01, s=20) lr_scheduler = tf.keras.callbacks.LearningRateScheduler(exponential_decay_fn) history = model.fit(X_train, y_train, epochs=20, validation_data=(X_valid, y_valid), callbacks=[lr_scheduler]) # exponential scheduling 3 - use current lr def exponential_decay_fn(epoch, lr): return lr * 0.1 ** (1 / 20) # exponential scheduling 4 - use tf.keras.optimizers.schedules import math batch_size = 32 n_epochs = 25 n_steps = n_epochs * math.ceil(len(X_train) / batch_size) scheduled_learning_rate = tf.keras.optimizers.schedules.ExponentialDecay( initial_learning_rate=0.01, decay_steps=n_steps, decay_rate=0.1 ) optimizer = tf.keras.optimizers.SGD(scheduled_learning_rate)
-
구간별 고정 스케줄링 (Piecewise Constant Scheduling)
일정 epoch 동안은 일정한 학습률을 사용하고, 그 다음 일정 epoch 동안은 좀 더 작은 일정한 학습률을 사용하는 기법이다. 적절한 학습률과 epoch 횟수의 조합을 찾기 위해서는 추가적인 튜닝이 필요하다.# piecewise constant scheduling def piecewise_constant_fn(epoch): if epoch < 5: return 0.01 elif epoch < 15: return 0.005 else: return 0.001
-
성능 기반 스케줄링 (Performance Scheduling)
N 스텝마다 검증 오차를 측정하고, 오차가 줄어들지 않으면 $\lambda$ 배 만큼 학습률을 감소시킨다.# performance scheduling lr_scheduler = tf.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=5) history = model.fit(X_train, y_train, epochs=20, validation_data=(X_valid, y_valid), callbacks=[lr_scheduler])
-
1사이클 스케줄링 (1cycle Scheduling)
훈련 절반 동안 초기 학습률을 선형적으로 증가시킨다. 그리고 그 다음 절반 동안은 선형적으로 감소시켜 다시 초기 학습률에 도달하게 한다. 마지막 몇 번 epoch 동안에는 학습률을 소수점 몇 째자리까지 줄인다.
별도의 출처 표시가 있는 이미지를 제외한 모든 이미지는 강의자료에서 발췌하였음을 밝힙니다.
댓글남기기