[핸즈온 머신러닝] 11장. 심층 신경망 훈련하기 (2)
본 글은 [핸즈온 머신러닝 2판 (Hands-On Machine Learning with Scikit-Learn, keras & TensorFlow)] - (박해선 옮김) 책을 개인공부하기 위해 요약, 정리한 내용입니다.
전체 코드는 https://github.com/ingu627/handson-ml2에 기재했습니다.(원본 코드 fork)
뿐만 아니라 책에서 설명이 부족하거나 이해가 안 되는 것들은 외부 자료들을 토대로 메꿔 놓았습니다.
오타나 오류는 알려주시길 바라며, 도움이 되길 바랍니다.
11.3 고속 옵티마이저
- 지금까지는 훈련 속도를 높이는 네 가지 방법을 봄 (연결 가중치에 좋은 초기화 전략 적용하기, 좋은 활성화 함수 사용하기, 배치 정규화 사용하기, 사전훈련된 네트워크의 일부 재사용하기)
- 훈련 속도를 크게 높일 수 있는 또 다른 방법으로 표준적인 경사 하강법 옵티마이저 대신 더 빠른 옵티마이저를 사용할 수 있다.
- 모멘텀 최적화
- 네스테로프(Nesterov) 가속 경사(NAG)
- AdaGrad
- RMSProp
- Adam 최적화
- Nadam 최적화
11.3.1 모멘텀 최적화
- 표준적인 경사 하강법은 경사면을 따라 일정한 크기의 스텝으로 조금씩 내려 간다.
- 경사 하강법의 공식 : $\theta \leftarrow \theta-\eta\nabla_{\theta}J(\theta)$
- $\theta$ : 가중치
- $\eta$ : 학습률
- $J(\theta)$ : 가중치에 대한 비용 함수
- 이 식은 이전 그레이디언트가 얼마였는지 고려하지 않는다.
- 경사 하강법의 공식 : $\theta \leftarrow \theta-\eta\nabla_{\theta}J(\theta)$
- 모멘텀은 가중치 조정 과정을 추적하면서 변화 가속도를 조절한다.
- 일반 경사하강법보다 빠르게 전역 최소점에 도달
- 모멘텀의 이해는 볼링공이 매끈한 표면의 완만한 경사를 따라 굴러간다면 처음에는 느리게 출발하지만 종단속도 에 도달할 때까지는 빠르게 가속되는 것을 생각해본다.
- 모멘텀 최적화는 이전 그레이디언트가 얼마였는지를 상당히 중요하게 생각한다.
- 그레이디언트를 가속도로 사용한다.
- 일종의 마찰저항을 표현하고 모멘텀이 너무 커지는 것을 막기 위해 이 알고리즘에는 모멘텀이라는 새로운 하이퍼파라미터 $\beta$가 등장한다.
- 이 값은 0 (높은 마찰저항)과 1 (마찰저항 없음) 사이로 설정되어야 한다. (default : 0.9)
- 모멘텀 알고리즘
- $ m \leftarrow \beta m - \eta \nabla_{\theta} J(\theta)$
- $ \theta \leftarrow \theta + m $
이미지출처: 1
- 모멘텀 최적화는 골짜기를 따라 바닥(최적점)에 도달할 때까지 점점 더 빠르게 내려간다.
- 모멘텀 때문에 옵티마이저가 최적값에 안정되기 전까지 건너뛰었다가 다시 돌아오고, 다시 또 건너뛰는 식으로 여러 번 왔다 갔다 할 수 있다.
from tensorflow.keras.optimizers import SGD
optimizer = SGD(lr=0.001, momentum=0.9)
11.3.2 네스테로프 가속 경사
- 네스테로프 가속 경사 (NAG; Nesterov accelerated gradient)는 현재 위치가 $\theta$가 아니라 모멘텀의 방향으로 조금 앞선 $\theta + \beta m$에서 비용 함수의 그레이디언트를 계산한 것이다.
- 기본 모멘텀 최적화보다 일반적으로 훈련 속도 빠름
- 네스테로프 가속 경사 알고리즘
- $ m \leftarrow \beta m - \eta \nabla_{\theta}J(\theta + \beta m) $
- $ \theta \leftarrow \theta + m $
from tensorflow.keras.optimizers import SGD
optimizer = SGD(lr=0.001, momentum=0.9, nesterov=True)
11.3.3 AdaGrad
- 경사 하강법은 전역 최적점 방향으로 곧장 향하지 않고 가장 가파른 경사를 따라 빠르게 내려가기 시작해서 골짜기 아래로 느리게 이동한다.
AdaGrad
알고리즘은 가장 가파른 차원을 따라 그레이디언트 벡터의 스케일을 감소시켜 이 문제를 해결한다.- AdaGrad 알고리즘
- $ s \leftarrow s + \nabla_{\theta}J(\theta)\otimes \nabla_{\theta}J(\theta) $
- $ \theta \leftarrow \theta - \eta \nabla_{\theta}J(\theta)\oslash \sqrt{s + \varepsilon} $
- 이 알고리즘은 학습률을 감소시키지만 경사가 완만한 차원보다 가파른 차원에 대해 더 빠르게 감소된다. (= 적응적 학습률 (adaptive learning rate))
- 2차방정식에 대해 잘 작동함
- 전역 최적점 방향으로 더 곧장 가도록 갱신되는 데 도움이 된다.
- 하지만 단점으로 학습률이 너무 감소되어 전역 최적점에 도착하기 전에 알고리즘이 완전히 멈추므로 심층 신경망에는 사용하지 말아야 한다.
from tensorflow.keras.optimizers import Adagrad
optimizer = Adagrad(lr=0.001)
11.3.4 RMSProp
- AdaGrad 때 너무 빨리 느려져서 전역 최적점에 수렴하지 못하는 위험을
RMSProp
알고리즘으로 가장 최근 반복에서 비롯된 그레이디언트만 누적함으로써 이 문제를 해결했다.- 알고리즘의 첫 번째 단계에서 지수 감소를 사용한다.
- RMSProp 알고리즘
- $ s \leftarrow \beta s + (1 - \beta) \nabla_{\theta}J(\theta)\otimes \nabla_{\theta}J(\theta) $
- $ \theta \leftarrow \theta - \eta \nabla_{\theta}J(\theta)\oslash \sqrt{s + \varepsilon} $
from tensorflow.keras.optimizers import RMSProp
optimizer = RMSProp(lr=0.001, rho=0.9) # rho는 베타에 해당 (default = 0.9)
- 이 알고리즘은 Adam 최적화가 나오기 전까지 연구자들이 가장 선호하는 최적화 알고리즘이었다.
11.3.5 Adam과 Nadam 최적화
Adam
은 적응적 모멘트 추정 (adaptive moment estimation)을 의미- 모멘텀 최적화 (지난 그레이디언트의 지수 감소 평균을 따름) 와 RMSProp (지난 그레이디언트 제곱의 지수 감소된 평균을 따름) 를 합침
- Adam 알고리즘
- $ m \leftarrow \beta_1 m - (1 - \beta_1) \nabla_{\theta} J(\theta)$
- $ s \leftarrow \beta_2 s + (1 - \beta_2) \nabla_{\theta}J(\theta)\otimes \nabla_{\theta}J(\theta) $
- $ \hat m \leftarrow \frac {m}{1-\beta_1^t} $
- $ \hat s \leftarrow \frac {s}{1-\beta_2^t} $
- $ \theta \leftarrow \theta + \eta\hat m \oslash \sqrt{\hat s + \varepsilon} $
- $ \beta_1$ : 모멘텀 감쇠 하이퍼파라미터
- $ \beta_2$ : 스케일 감쇠 하이퍼파라미터
from tensorflow.keras.optimizers import Adam
optimizer = Adam(lr=0.001, beta_1=0.9, beta_2=0.999)
- 그 외
AdaMax
,Nadam
이 있다. - AdaMax 최적화
- Adam 알고리즘 개선. 하지만 경우에 따라 Adam 성능이 더 좋다.
from tensorflow.keras.optimizers import Adamax
optimizer = Adamax(lr=0.001, beta_1=0.9, beta_2=0.999)
- Nadam 최적화
- Adam + Nesterov
- 일반적으로 Adam보다 성능 좋지만 경우에 따라 RMSProp이 더 좋기도함
from tensorflow.keras.optimizers import Adamax
optimizer = Adamax(lr=0.001, beta_1=0.9, beta_2=0.999)
- 지금까지 논의한 모든 최적화 기법은 1차 편미분 (야코비얀)에만 의존한다.
모든 옵티마이저를 표로 정리
- 선택한 옵티마이저의 성능이 만족스럽지 않을 경우 기본
Nesterov
가속 경사 사용 추천 *
=나쁨,**
=보통,***
=좋음
클래스 | 수렴 속도 | 수렴 품질 |
---|---|---|
SGD | * |
*** |
SGD(momentum=…) | ** |
*** |
SGD(momentum=…, nesterov=True) | ** |
*** |
Adagrad | *** |
* (너무 일찍 멈춤) |
RMSprop | *** |
** 또는 *** |
Adam | *** |
** 또는 *** |
Nadam | *** |
** 또는 *** |
AdaMax | *** |
** 또는 *** |
11.3.6 학습률 스케줄링
- 학습률 선택이 훈련의 성패를 가른다.
- 아래는 모델 훈련과정동안 학습률을 조정하는 기법들
- 보통 높은 학습률로 시작해서 학습속도가 느려질 경우 학습률을 작게 조정
거듭제곱 기반 스케줄링
- 거듭제곱 기반 스케줄링 (power scheduling) : 학습률을 반복 횟수 t에 대한 함수 $\eta(t) = \eta_0 / (1 + t/s)^c $로 지정.
- $t=k\cdot s$로 커지면 학습률이 $\frac {\eta_0}{k+1}$로 줄어듦
- 하이퍼파라미터
- $\eta_0$ : 초기 학습률
- $c$ : 거듭제곱수, 일반적으로 1로 지정
- $s$ : 스텝 횟수
from tensorflow.keras.optimizers import SGD
optimizer = SGD(lr=0.01, decay=1e-4)
지수 기반 스케줄링
- 지수 기반 스케줄링 (exponential scheduling) : $ \eta(t) = \eta_0 0.1^{t/s}$
- 학습률이 s 스텝마다 10배씩 점차 줄어든다.
def exponential_decay_fn(epoch): # 현재 에포크의 학습률을 받아 반환하는 함수 필요
return 0.01 * 0.1**(epoch / 20)
구간별 고정 스케줄링
- 구간별 고정 스케줄링 (piecewise constant scheduling) : 일정 횟수의 에포크 동안 일정한 학습률을 사용하고 그 다음 또 다른 횟수의 에포크 동안 작은 학습률을 사용하는 식이다.
- 학습률
def piecewise_constant_fn(epoch):
if epoch < 5:
return 0.01
elif epoch < 15:
return 0.005
else:
return 0.001
- 콜백함수 지정
from tensorflow.keras.callbacks import LearningRateScheduler
lr_scheduler = LearningRateScheduler(piecewise_constant_fn)
history = model.fit(X_train_scaled, y_train, epochs=n_epochs,
validation_data=(X_valid_scaled, y_valid),
callbakcs=[lr_scheduler])
성능 기반 스케줄링
- 성능 기반 스케줄링 (performance scheduling) : 매 $N$스텝마다 검증 오차를 측정하고 오차가 줄어들지 않으면 $\lambda$배만큼 학습률을 감소시킨다.
ReduceLROnPlateau
콜백 클래스 활용
from tensorflow.keras.callbacks import ReduceLROnPlateau
from tensorflow.keras.schedules import ExponentialDecay
from tensorflow.keras.optimizers import SGD
# 다음 콜백을 fit() 메서드에 전달하면 최상의 검증 손실이 다섯 번의 연속적인 에포크 동안 향상되지 안흥ㄹ 때마다 학습률에 0.5를 곱한다.
lr_scheduler = ReduceLROnPlateau(factor=0.5, patience=5)
s = 20 * len(X_train) // 32 # 20번 에포크에 담긴 전체 스텝 수 (배치 크기 = 32)
learning_rate = ExponentialDecay(0.01, s, 0.1)
optimizer = SGD(learning_rate=learning_rate)
1 사이클 스케줄링
- 1 사이클 스케줄링 (1cycle scheduling) : 학습률을 훈련 과정 중에 올리거나 내리도록 조정
- 훈련 전반부: 낮은 학습률 $\eta_0$에서 높은 학습률 $\eta_1$까지 선형적으로 높힘
- 훈련 후반부: 다시 선형적으로 $\eta_0$까지 낮춤
- 훈련 마지막 몇 번의 에포크: 학습률을 소수점 몇 째 짜리까지 선형적으로 낮춤
- 지수 기반 스케줄링, 성능 기반 스케줄링, 1사이클 스케줄링이 수렴 속도를 크게 높일 수 있다.
- 성능 기반과 지수 기반 모두 좋지만 지수 기반 스케줄링 선호
- 튜닝 쉽고, 높은 성능, 구현이 쉽기 때문이다.
11.4 규제를 사용해 과대적합 피하기
- 심층 신경망의 높은 자유도는 네트워크를 훈련 세트에 과대적합되기 쉽게 만들기 때문에 규제가 필요하다.
- 지금까지 살펴본 규제기법으로는
- 조기종료 기법 :
EarlyStopping
콜백을 사용하여 일정 에포크 동안 성능이 향상되지 않는 경우 자동 종료시키기 - 배치정규화 : 불안정한 그레이디언트 문제해결을 위해 사용하지만 규제용으로도 활용 가능 (가중치의 변화를 조절하는 역할)
- 조기종료 기법 :
11.4.1 $l_1$과 $l_2$ 규제
- 층을 선언할 때 규제 방식과 규제강도를 지정할 수 있다.
kernel_regularizer
옵션 사용
from tensorflow.keras.layers import Dense
from tensroflow.keras.regularizers import l1,l2, l1_l2
layer = Dense(100, activation='relu',
kernel_initializer='he_normal',
kernel_regularizer=l2(0.01))
- 일반적으로 동일한 매개변수 값을 반복하는 경우가 많다.
functools.partial()
함수를 사용하여 기본 매개변수 값을 사용하여 함수 호출을 감싼다.
from functools import partial
from tensroflow.keras.regularizers import l1,l2, l1_l2
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Flatten
RegularizedDense = partial(Dense,
activation='elu',
kernel_initializer='he_normal',
kernel_regularizer=l2(0.01))
model = Sequential([
Flatten(input_shape=[28,28]),
RegularizedDense(300),
RegularizedDense(100),
RegularizedDense(10, activation='softmax',
kernel_initializer='glorot_uniform',)
])
11.4.2 드롭아웃
이미지출처: 2
- 매 훈련 스텝에서 각 뉴런은 임시적으로 드롭아웃될 확률 $p$를 가진다. 즉, 이번 훈련 스텝에는 완전히 무시되지만 다음 스텝에는 활성화될 수 있다.
- 하이퍼파라미터 $p$를 드롭아웃 비율이라고 하고 보통 10%와 50% 사이를 지정한다.
- 순환 신경망: 20% - 30%
- 합성곱 신경망: 40% - 50%
- 순환신경망(15장): 20%-30%
- 하이퍼파라미터 $p$를 드롭아웃 비율이라고 하고 보통 10%와 50% 사이를 지정한다.
- 에포크 또는 스텝마다 서로 다른 고유한 모델이 훈련되는 효과를 발생시킴
- 훈련이 끝난 후에는 뉴런에 더는 드롭아웃을 적용하지 않는다.
- 케라스에서는
keras.layers.Dropout
층을 사용하여 드롭아웃을 구현한다.
- 모든 Dense 층 이전에 드롭아웃 비율 0.2 지원하기
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Flatten, Dropout, Dense
model = Sequential([
Flatten(input_shape=[28, 28]),
Dropout(rate=0.2),
Dense(300, activation='elu', kernel_initializer='he_normal'),
Dropout(rate=0.2),
Dense(100, activation='elu', kernel_initializer='he_normal'),
Dropout(rate=0.2),
Dense(10, activation='softmax')
])
- 훈련시에만 드롭아웃을 적용하기 때문에 훈련손실과 검증손실을 그대로 비교하면 안된다.
- 훈련 후 드롭아웃을 끄고 훈련손실을 재평가해야 한다.
알파 드롭아웃
- SELU 활성화함수를 사용하는 경우 알파(alpha) 드롭아웃을 사용 추천
- 입력과 평균의 표준편차를 유지시켜준다.
- 일반 드롭아웃은 자기 정규화 기능 방해
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Flatten, AlphaDropout, Dense
model = Sequential([
Flatten(input_shape=[28, 28]),
AlphaDropout(rate=0.2),
Dense(300, activation='selu', kernel_initializer='lecun_normal'),
AlphaDropout(rate=0.2),
Dense(100, activation='selu', kernel_initializer='lecun_normal'),
AlphaDropout(rate=0.2),
Dense(10, activation='softmax')
])
11.4.3 몬테 카를로 드롭아웃
- 훈련된 드롭아웃 모델을 재훈련하지 않으면서 성능을 향상시키는 기법
- 훈련된 모델의 예측기능을 이용한 결과를 스택으로 쌓은 후 평균값을 계산
import numpy as np
y_probas = np.stack([model(X_test_scaled, training=True)
for sample in range(100)])
y_proba = y_probas.mean(axis=0)
y_std = y_probas.std(axis=0)
# 해석
# 테스트 세트 샘플 수는 10,000개. 클래스 수는 10개. 따라서 [10000, 10] 모양의 행렬 100개
# [100, 10000, 10] 모양의 행렬이 y_probas에 저장됨
- 모델이 훈련하는 동안 다르게 작동하는 층을 가지고 있다면 앞에서와 같이 훈련 모드를 강제로 설정하면 안 된다.
- Dropout 층을 MCDropout 클래스로 바꿔준다.
from tensorflow.keras.layers import Dropout
class MCDropout(Dropout):
def call(self, inputs):
return super().call(inputs, training=True)
11.4.4 맥스-노름 규제
- 맥스-노름 규제는 각각의 뉴런에 대해 입력의 연결 가중치 $w$가 $|w |_2 \le r $이 되도록 제한한다.
- $r$ : 맥스-노름 하이퍼파라미터
- $| \cdot |_2$ : $l_2$ 노름
- 훈련 스텝이 끝나고 $|w |_2$를 계산하고 필요하면 $w$의 스케일을 조정한다. ($w \leftarrow \frac {r}{|w |_2}$)
- 케라스에서 맥스-노름 규제를 구현하려면 적절한 최댓값으로 지정한 max_norm()이 반환환 객체로 은닉층의 kernel_constraint 매개변수를 지정한다.
from tensorflow.keras.layers import Dense
from tensorflow.keras.constraints import max_norm
Dense(100, activation='elu', kernel_initializer='he_normal',
kernel_constraint=max_norm(1.))
11.5 요약 및 실용적인 가이드라인
- 기본 DNN 설정
하이퍼파라미터 | 기본값 |
---|---|
커널 초기화 | He 초기화 |
활성화 함수 | ELU |
정규화 | 얕은 신경일 경우 없음. 깊은 신경망이라면 배치 정규화 |
규제 | 조기 종료 (필요하면 $l_2$ 규제 추가) |
옵티마이저 | 모멘텀 최적화 (또는 RMSProp이나 Nadam) |
학습률 스케줄 | 1사이클 |
- 네트워크가 완전 연결 층을 쌓은 단순한 모델일 때는 자기 정규화 사용
하이퍼파라미터 | 기본값 |
---|---|
커널 초기화 | 르쿤 초기화 |
활성화 함수 | SELU |
정규화 | 없음 (자기 정규화) |
규제 | 필요하다면 알파 드롭아웃 |
옵티마이저 | 모멘텀 최적화 (또는 RMSProp이나 Nadam) |
학습률 스케줄 | 1 사이클 |
댓글남기기