14 분 소요

본 글은 [핸즈온 머신러닝 2판 (Hands-On Machine Learning with Scikit-Learn, keras & TensorFlow)] - (박해선 옮김) 책을 개인공부하기 위해 요약, 정리한 내용입니다.
전체 코드는 https://github.com/ingu627/handson-ml2에 기재했습니다.(원본 코드 fork)
뿐만 아니라 책에서 설명이 부족하거나 이해가 안 되는 것들은 외부 자료들을 토대로 메꿔 놓았습니다.
오타나 오류는 알려주시길 바라며, 도움이 되길 바랍니다.


11 심층 신경망 훈련하기

  • 심층신경망 : 수백 개의 뉴런으로 구성된 10개 이상의 층을 사용하는 신경망
    • ex. 고해상도 이미지에서 수백 종류의 물체 감지
  • 심층 신경망을 훈련할 때 발생하는 문제
    • 그레이디언트 소실 또는 그레이디언트 폭주 : 심층신경망의 아래쪽으로 갈수록 그레이디언트가 점점 더 작아지거나 커지는 현상
    • 대규모 신경망을 위한 훈련 데이터가 충분하지 않거나 레이블을 만드는 작업에 비용이 너무 많이 듦
    • 훈련이 극단적으로 느려질 수 있다.
    • 수백만 개의 파라미터를 가진 모델은 훈련 세트에 과대적합될 위험이 매우 크다.
      • 특히 훈련 샘플이 충분하지 않거나 잡음이 많은 경우


11.1 그레이디언트 소실과 폭주 문제

  • 역전파 알고리즘은 출력층에서 입력층으로 오차 그레이디언트를 전파하면서 진행된다. 알고리즘이 신경망의 모든 파라미터에 대한 오차 함수의 그레이디언트를 계산하면 경사 하강법 단계에서 이 그레이디언트를 사용하여 각 파라미터를 수정한다.
  • 그레이디언트 소실 또는 그레이디언트 폭주는 심층 신경망의 아래쪽으로 갈수록 그레이디언트가 점점 더 작아지거나 커지는 현상이다. 두 현상 모두 하위층을 훈련하기 매우 어렵게 만든다.

이미지출처: 1

  • 로지스틱 활성화 함수는 입력이 커지면 0이나 1로 수렴해서 기울기가 0에 매우 가까워 진다. 그래서 역전파가 될 때 사실상 신경망으로 전파할 그레이디언트가 거의 없고 조금 있는 그레이디언트는 최상위층에서부터 역전파가 진행되면서 점차 약해져서 실제로 아래쪽 층에는 아무것도 도달하지 않게 된다.


11.1.1 글로럿과 He 초기화

  • 층에 사용되는 활성화 함수의 종류에 따라 세 가지 초기화 방식 중 하나를 선택
    • 글로럿(Glorot) 초기화
    • 르쿤(LeCun) 초기화
    • 헤(He) 초기화
  • 예측을 할 때는 정방향으로, 그레이디언트를 역전파할 때는 역방향으로 양방향 신호가 적절하게 흘러야 한다.
  • 글로럿과 벤지오는 적절한 신호가 흐르기 위해서는 각 층의 출력에 대한 분산이 입력에 대한 분산과 같아야 한다고 주장했다. 그리고 역방향에서 층을 통과하기 전과 후의 그레이디언트 분산이 동일해야 한다.


  • 세이비어 초기화 or 글로럿 초기화 : $fan_{avg} = \frac{fan_{in} + fan_{out}}{2}$
    • $fan_{in}$(팬-인) : 층에 들어오는 입력 수
    • $fan_{out}$(팬-아웃) : 층에 들어오는 출력 수
    • 평균($\mu$)이 0이고 분산($\sigma^2$)이 $\sigma^2 = \frac {1}{fan_{avg}}$인 정규분포
    • $r=\sqrt {\frac {3}{fan_{avg}}}$일 때 $-r$과 $+r$ 사이의 균등분포


  • 르쿤 초기화 : 글로로 초기화 정의에서 $fan_{avg}$를 $fan_{in}$으로 바꿈


  • 헤 초기화
    • 정규분포 활용 초기화 : $\sigma^2 = \frac {2}{fan_{in}}$
    • 균등분포 활용 초기화 : $r = \sqrt {3\sigma^2}$


활성화 함수와 초기화 방식

초기화 전략 활성화 함수 $\sigma^2$ (정규분포) 정규분포 초기화 균등분포 초기화
글로럿 활성화 함수 없음. tanh, logistic, softmax $1/fan_{\rm avg}$ glorot_normal glorot_uniform (default)
He ReLU 함수와 그 변종들 $2/fan_{\rm in}$ he_normal he_uniform
르쿤 SELU $1/fan_{\rm in}$ lecun_normal lecun_uniform
  • 케라스는 기본적으로 균등분포의 글로럿 초기화를 사용한다. 다음과 같이 층을 만들 때 kernel_initializer="he_uniform"이나 kernel_initializer="he_normal"로 바꾸어 He 초기화를 사용할 수 있다.
from tensorflow.keras.layers import Dense

Dense(10, activation="relu", kernel_initializer="he_normal")
  • $fan_{in}$ 대신 $fan_{out}$ 기반의 균등분포 He 초기화를 사용할 때
    • Variance Scaling 사용
from tensorflow.keras.initializers import VarianceScaling
from tensorflow.keras.layers import Dense

he_avg_init = VarianceScaling(scale=2., mode='fan_avg',
                              distribution='uniform')
Dense(10, activation='sigmoid', kernel_initializer='he_avg_init')


11.1.2 수렴하지 않는 활성화 함수

  • 그전에는 대부분 시그모이드 활성화 함수가 최선의 선택일 것이라고 생각했다.
  • ReLU 함수는 특정 양숫값에 수렴하지 않는다는 큰 장점이 있다. (그리고 계산도 빠르다.)
    • 하지만 dying ReLU의 문제가 있다.
      • dying ReLU는 훈련하는 동안 일부 뉴런이 0 이외의 값을 출력하지 않는다는 의미
    • 가중치 합이 음수이면 ReLU 함수의 그레이디언트가 0이 되므로 경사 하강법이 더는 작동하지 않는다.


  • 이 문제를 해결하기 위해 LeakyReLU 함수를 사용한다.
    • $LeakyReLU_{\alpha}(z) = max(\alpha z, z) $
    • 하이퍼 파라미터 $\alpha$가 이 함수 새는 정도를 결정한다.
      • 새는 정도란 $z <0$ 일 때 이 함수의 기울기이며, 일반적으로 0.01로 설정한다. 이 작은 기울기가 LeakyReLU를 절대 죽지 않게 만들어 준다.
    • ReLU보다 좋은 성능 발휘 (default = $\alpha=0.1$)
      • $\alpha = 0.2$로 할 때 좀 더 성능이 좋아짐

이미지출처: 2

  • RReLU (randomized leaky ReLU) : 훈련하는 동안 주어진 범위에서 $\alpha$를 무작위로 선택하고 테스트시에는 평균을 사용
    • 과대적합을 줄이는 규제역할도 수행
  • PReLU (parametric leaky ReLU) : $\alpha$가 훈련하는 동안 학습 (하이퍼파라미터가 아니고 다른 모델 파라미터와 마찬가지로 역전파에 의해 변경된다.)
    • 대규모 이미지 데이터셋에서 ReLU보다 성능 좋음
    • 소규모 데이터셋에서는 과대적합 위험성 존재
  • ELU 활성화 함수 : 앞서 언급된 ReLU 변종들보다 성능이 좋은 것으로 보인다. (훈련 시간 줄어듦)
    • $z<0$ 일 때 음숫값이 들어오므로 활성화 함수의 평균 출력이 0에 더 가까워진다. 이는 그레이디언트 소실 문제를 완화해준다.
    • 하이퍼파라미터 $\alpha$는 z가 큰 음숫값일 때 ELU가 수렴할 값을 정의한다.
    • $z<0$이어도 그레이디언트가 0이 아니므로 죽은 뉴런을 만들지 않는다.
    • $\alpha = 1$이면 이 함수는 $z=0$에서 급격히 변동하지 않으므로 $z=0$을 포함해 모든 구간에서 매끄러워 경사 하강법의 속도를 높여준다.
    • 장단점
      • 수렴 속도가 빠르다.
      • (지수 함수를 사용하므로) ReLU나 그 변종들보다 계산이 느리다.

이미지출처: 3

image

  • SELU (Scaled ELU) 4
    • ELU 활성화 함수의 변종
    • 완전 연결 층만 쌓아서 신경망을 만들고 모든 은닉층이 SELU 활성화 함수를 사용하면 네트워크가 자기 정규화(self-normalized) 된다고 저자는 주장
    • 훈련하는 동안 각 층의 출력이 평균 0과 표준편차 1을 유지하는 경향(그래디언트 소실과 폭주 문제를 막아준다.)
    • 다른 활성화 함수보다 뛰어난 성능을 종종 보이지만 자기 정규화가 일어나기 위한 몇가지 조건이 존재

    1) 입력 특성이 반드시 표준화(평균 0, 표준편차 1)되어야 한다.

    2) 모든 은닉층의 가중치는 르쿤 정규분포 초기화로 초기화되어야 한다. 케라스에서는 kernel_initializer=’lecun_normal’로 설정

    3) 네트워크는 일렬로 쌓은 층으로 구성되어야 한다. 순환 신경망이나 스킵 연결과 같은 순차적이지 않은 구조에서 사용하면 자기 정규화되는 것을 보장하지 않는다. 4) 모든 층이 완전연결층이어야 한다.


활성화 함수 쓰는 방법

  • 일반적으로 SELU > ELU > LeakyReLU(그리고 변종들) > ReLU > tanh > sigmoid
  • 네트워크가 자기 정규화되지 못하는 구조라면 SELU 보단 ELU
  • 실행 속도가 중요하다면 LeakyReLU(하이퍼파라미터를 더 추가하고 싶지 않다면 케라스에서 사용하는 기본값 $\alpha$ 사용)
  • 시간과 컴퓨팅 파워가 충분하다면 교차 검증을 사용해 여러 활성화 함수를 평가
  • 신경망이 과대적합되었다면 RReLU
  • 훈련세트가 아주 크다면 PReLU
  • ReLU가 가장 널리 사용되는 활성화 함수이므로 많은 라이브러리와 하드웨어 가속기들이 ReLU에 특화되어 최적화.
    • 따라서 속도가 중요하다면 ReLU가 가장 좋은 선택


  • LeakyReLu 활성화 함수 사용하기 예제
from tensorflow.keras.layers import Dense, LeakyReLU
from tensorflow.keras.models import Sequential

model = Sequential([
    [...]
    Dense(10, kernel_initializer="he_normal"),
    LeakyReLU(alpha=0.2)
    [...]
])
  • PReLU 활성화 함수 사용하기 예제
    • PReLU층을 만들고 모델에서 적용하려는 층 뒤에 추가
from tensorflow.keras.layers import Flatten, Dense, PReLU
from tensorflow.keras.models import Sequential

model = Sequential([
    Flatten(input_shape=[28, 28]),
    Dense(300, kernel_initializer='he_normal'),
    PReLU(),
    Dense(100, kernel_initializer='he_normal'),
    PReLU(),
    Dense(10, activation='softmax')
])
  • SELU활성화 함수 사용하기 예제
    • 층을 만들때 activation=’selu’와 kernel_initializer=’lecun_normal’ 지정
from tensorflow.keras.layers import Flatten, Dense
from tensorflow.keras.models import Sequential

model = Sequential()
model.add(Flatten(input_shape=[28, 28]))
model.add(Dense(300, activation='selu', kernel_initializer='lecun_normal'))

for layer in range(99):
    model.add(Dense(100, activation='selu', kernel_initializer='lecun_normal'))
model.add(Dense(10, activation='softmax'))


11.1.3 배치 정규화

  • (ELU or ReLU 변종) + (He 초기화)는 훈련 초기단계의 소실/폭주 문제를 해결했지만 훈련 중 동일 문제 재발생 방지 보장하지 못한다.
  • 배치 정규화 (BN; batch normalization)는 그레이디언트 소실과 폭주 문제를 해결하기 위해 등장했다.
    • 이 기법은 각 층에서 활성화 함수를 통과하기 전이나 후에 모델에 연산을 하나 추가한다.
    • 이 연산은 단순하게 입력을 원점에 맞추고 정규화한 다음, 각 층에서 두 개의 새로운 파라미터로 결곽값의 스케일을 조정하고 이동시킨다.
      • 평균: 0으로 조정
      • 분산: 스케일 조정
    • 하나는 스케일 조정에, 다른 하나는 이동에 사용한다.
    • 많은 경우 신경망의 첫 번째 층으로 배치 정규화를 추가하면 훈련 세트를 표준화할 필요가 없다. 배치 정규화 층이 이런 역할을 대신한다.
    • 입력 데이터를 원점에 맞추고 정규화하려면 알고리즘은 평균과 표준편차를 추정해야 한다. 이를 위해 현재 미니배치에서 입력의 평균과 표준편차를 평가한다.

image
이미지출처5


배치 정규화 알고리즘

  1. $\mu_{\beta} = {1\over m_\beta} \sum_{i=1}^{m_{\beta}} x^i $
  2. $\sigma_{B}^2 = \frac{1}{m_{\beta}} \sum_{i=1}^{m_{\beta}} (x^{(i)}-\mu_B)^2$
  3. $\hat x^{(i)} = \frac{x^{(i)}-\mu_B}{\sqrt{\sigma_B^2 + \varepsilon}}$
  4. $z^{(i)}=\gamma\otimes\hat x^{(i)} + \beta$
  • 훈련하는 동안 배치 정규화는 입력을 정규화한 다음 스케일을 조정하고 이동시킨다.
  • 훈련이 끝난 후 전체 훈련 세트를 신경망에 통과시켜 배치 정규화 층의 각 입력에 대한 평균과 표준편차를 계산하는 것이다.
  • 대부분 배치 정규화 구현은 층의 입력 평균과 표준편차의 이동 평균을 사용해 훈련하는 동안 최종 통계를 추정한다.
  • 케라스의 BatchNormalization층은 이를 자동으로 수행한다.
  • 배치 정규화 층마다 네 개의 파라미터 벡터가 학습된다.
    • $\gamma$ (출력 스케일 벡터)와 $\beta$ ( 출력 이동 벡터)는 일반적인 역전파를 통해 학습된다.
    • $\mu$ (최종 입력 평균 벡터)와 $\sigma$ (최종 입력 표준편차 벡터)는 지수 이동 평균을 사용하여 추정된다.
      • $\mu$$\sigma$는 훈련하는 동안 추정되지만 훈련이 끝난 후에 사용된다.


  • 그레이디언트 소실/폭주 문제를 감소시켜서 하이퍼볼릭 탄젠트 또는 로지스틱 활성화 함수 사용 가능
  • 가중치 초기화에 덜 민감해짐
  • 배치 정규화는 규제와 같은 역할을 하여 다른 규제 기법의 필요성을 줄여준다.
  • 그러나 배치 정규화를 사용할 때 에포크마다 더 많은 시간이 걸리므로 훈련이 오히려 느려질 수 있다.


케라스로 배치 정규화 구현하기

  • 은닉층의 활성화 함수 전이나 후에 BatchNormalization 층을 추가하면 된다.
from tensorflow.keras.layers import Dense, Flatten, BatchNormalization
from tensorflow.keras.models import Sequential

model = Sequential([
    Flatten(input_shape=[28, 28]),
    BatchNormalization(),
    Dense(300, activation="elu", kernel_initializer="he_normal"),
    BatchNormalization(),
    Dense(100, activation="elu", kernel_initializer="he_normal"),
    BatchNormalization(),
    Dense(10, activation="softmax")
])
model.summary()

# Model: "sequential_1"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# flatten_1 (Flatten)          (None, 784)               0         
# _________________________________________________________________
# batch_normalization_1 (Batch (None, 784)               3136      
# _________________________________________________________________
# dense_1 (Dense)              (None, 300)               235500    
# _________________________________________________________________
# batch_normalization_2 (Batch (None, 300)               1200      
# _________________________________________________________________
# dense_2 (Dense)              (None, 100)               30100     
# _________________________________________________________________
# batch_normalization_3 (Batch (None, 100)               400       
# _________________________________________________________________
# dense_3 (Dense)              (None, 10)                1010      
# =================================================================
# Total params: 271,346
# Trainable params: 268,978
# Non-trainable params: 2,368
# _________________________________________________________________

  • 배치 정규화 층은 입력마다 네 개의 파라미터 $\gamma, \beta, \mu, \sigma$를 추가한다. (784 x 4 = 3,316개의 파라미터)
  • 마지막 두 개의 파라미터 $\mu, \sigma$는 이동 평균이다. 이 파라미터는 역전파로 학습되지 않기 때문에 케라스는 ‘Non-trainable’ 파라미터로 분류한다.


  • 활성화 함수 이전에 정규화 층 추가 방법 : 은닉층에서 활성화 함수를 지정하지 않고 활성화 함수를 정규화 층 뒤에 별도의 층으로 추가함
  • 또한 배치 정규화 층은 입력마다 이동 파라미터를 포함하기 때문에 이전 층에서 편향을 뺄 수 있다.(use_bias=False)
from tensorflow.keras.layers import Dense, Flatten, BatchNormalization, Activation
from tensorflow.keras.models import Sequential

model = Sequential([
    Flatten(input_shape=[28, 28]),
    BatchNormalization(),
    Dense(300, kernel_initializer='he_normal', use_bias=False),
    BatchNormalization(),
    Activation('elu'),
    Dense(100, kernel_initializer='he_normal', use_bias=False),
    BatchNormalization(),
    Activation('elu'),
    Dense(10, activation='softmax')
])
  • BatchNormalization 클래스는 조정할 하이퍼파라미터가 적다.
    • 하지만 가끔 momentum 매개변수를 변경해야 할 수 있다.
    • BatchNormalization 층이 지수 이동 평균을 업데이트할 때 이 하이퍼파라미터를 사용한다.
    • 새로운 값 $v$가 주어지면 다음 식을 사용해 이동 평균 $\hat v$를 업데이트한다.
    • $\hat v \leftarrow \hat v \times momentum + v \times (1- momentum)$


11.1.4 그레이디언트 클리핑

  • 그레이디언트 클리핑 (gradient clipping) : 그레이디언트 폭주 문제를 완화하는 방법으로 역전파될 때 일정 임곗값을 넘어서지 못하게 그레이디언트를 잘라내는 것
    • 순환신경망에서 배치정규화를 사용하지 못하는 경우 유용함
  • 케라스에서 그레이디언트 클리핑을 구현하려면 옵티마이저를 만들 때 clipvalueclipnorm 매개변수를 지정하면 된다.
  • 이 기능은 그레이디언트 벡터의 방향을 바꿀 수 있다.
optimizer = tensorflow.keras.SGD(clipvalue=1.0) # 손실의 모든 편미분 값을 -1.0에서 1.0으로 잘라낸다.
model.compile(loss='mse', optimizer=optimizer)


11.2 사전훈련된 층 재사용하기

  • 전이 학습 (Transfer Learning)은 특정 분야에서 학습된 신경망의 일부 능력을 유사하거나 전혀 새로운 분야에서 사용되는 신경망의 학습에 이용하는 것을 의미6
    • 이 방법은 훈련 속도를 크게 높일 뿐만 아니라 필요한 훈련 데이터도 크게 줄여 준다.

이미지 출처: 7

  • 비슷한 원본 모델의 하위 은닉층이 훨씬 유용함
  • 먼저 재사용하는 층을 모두 동결하고 훈련 및 성능 평가
    • 여기서 동결한다는 의미는 경사 하강법으로 가중치가 바뀌지 않도록 훈련되지 않는 가중치로 만든다.
  • 맨 위에 있는 한 두 개 은닉층의 동결을 해제하고 역전파를 통해 가중치를 조정
  • 성능이 좋아지지 않거나 훈련 데이터가 적을 경우 상위 은닉층 제거 후 남은 은닉층 동결하고 다시 훈련
  • 훈련 데이터가 많을 경우 은닉층을 제거하기보다는 다른 것으로 바꾸거나 더 많은 은닉층 추가
  • 위 과정 반복


11.2.1 케라스를 사용한 전이 학습

  • 먼저 모델 A를 도르하고 이 모델의 층을 기반으로 새로운 모델을 만든다.
  • 가정: model_A가 주어짐
    • 샌들과 셔츠를 제외한 8개의 클래스만 담겨 있는 패션 MNIST 활용
  • 목표: 셔츠와 샌들을 분류하는 이진분류기 model_B 훈련
  • 전이학습을 이용한 model_B_on_A 훈련
from tensorflow.keras.models import load_model, Sequential
from tensorflow.keras.layers import Dense

model_A = load_model("my_model_A.h5")
model_B_on_A = Sequential(model_A.layers[:-1])
model_B_on_A =(Dense(1, activation="sigmoid"))


  • clone_model() 메서드로 모델 A의 구조를 복제한 후 가중치를 복사한다. (clone_model() 메서드는 가중치를 복제하지 않는다.)
from tensorflow.keras.models import clone_model

model_A_clone = clone_model(model_A)
model_A_clone.set_weights(model_A.get_weights())
  • 새로운 출력층이 랜덤하게 초기화되어 있으므로 큰 오차를 만들 것이다. 이를 피하는 한 가지 방법은 처음 몇 번의 에포크 동안 재사용된 층을 동결하고 새로운 층에게 적절한 가중치를 학습할 시간을 주는 것이다.
  • 훈련이이를 위해 모든 층의 trainable 속성을 False로 지정하고 모델을 컴파일한다.
# 층을 동결하거나 동결을 해제한 후 반드시 모델을 컴파일해야 한다.
for layer in model_B_on_A.layers[:-1]:
    layer.trainable = False

model_B_on_A.compile(loss="binary_crossentropy", optimizer="sgd",
                     metrics=["accuracy"])
from tensorflow.keras.optimizers import SGD

history = model_B_on_A.fit(X_train_B, y_train_B, epochs=4,
                           validation_data=(X_valid_B, y_valid_B))

for layer in model_B_on_A.layers[:-1]:
    layer.trainable = True

optimizer = SGD(lr=1e-4)
model_B_on_A.compile(loss="binary_crossentropy", optimizer=optimizer,
                     metrics=["accuracy"])
history = model_B_on_A.fit(X_Train_B, y_train_B, epochs=16,
                           validation_data=(X_valid_B, y_valid_B))


  • 얕은 신경망 모델에서는 전이학습 성능이 좋지 않다.
  • 전이 학습은 조금 더 일반적인 특성을 감지하는 경향이 있는 심층 합성곱 신경망에서 잘 동작한다.


11.2.2 비지도 사전훈련

  • 비지도 사전훈련은 풀어야 할 문제가 복잡하고 재사용할 수 있는 비슷한 모델이 없으며 레이블된 훈련 데이터가 적고 그렇지 않은 데이터가 많을 때 사용 (볼츠만 머신 (RBM; restricted Boltzmann machine) 을 사용한. 그러나 오늘날엔 일반적으로 RBM보다 오토인코더나 GAN을 사용한다.)
  • 레이블되지 않은 훈련 데이터를 많이 모을 수 있다면 이를 사용하여 오토인코더나 생성적 적대 신경망과 같은 비지도 학습 모델을 훈련할 수 있다.
    • 오토인코더나 GAN 판별자의 하위층을 재사용하고 그 위에 새로운 작업에 맞는 출력층을 추가할 수 있다.
    • 그 다음 지도 학습으로 최종 네트워크를 세밀하게 튜닝한다.


11.2.3 보조 작업에서 사전훈련

  • 레이블된 훈련 데이터가 많지 않다면 레이블된 훈련 데이터를 쉽게 얻거나 생성할 수 있는 보조 작업에서 첫 번째 신경망을 훈련하는 것이다.
    • 이 신경망의 하위층을 실제 작업을 위해 재사용한다.
    • 첫 번째 신경망의 하위층은 두 번째 신경망에 재사용될 수 있는 특성 추출기를 학습하게 된다.

11.3 고속 옵티마이저

  • 지금까지는 훈련 속도를 높이는 네 가지 방법을 봄 (연결 가중치에 좋은 초기화 전략 적용하기, 좋은 활성화 함수 사용하기, 배치 정규화 사용하기, 사전훈련된 네트워크의 일부 재사용하기)
  • 훈련 속도를 크게 높일 수 있는 또 다른 방법으로 표준적인 경사 하강법 옵티마이저 대신 더 빠른 옵티마이저를 사용할 수 있다.
    • 모멘텀 최적화
    • 네스테로프(Nesterov) 가속 경사(NAG)
    • AdaGrad
    • RMSProp
    • Adam 최적화
    • Nadam 최적화


11.3.1 모멘텀 최적화

  • 표준적인 경사 하강법은 경사면을 따라 일정한 크기의 스텝으로 조금씩 내려 간다.
    • 경사 하강법의 공식 : $\theta \leftarrow \theta-\eta\nabla_{\theta}J(\theta)$
      • $\theta$ : 가중치
      • $\eta$ : 학습률
      • $J(\theta)$ : 가중치에 대한 비용 함수
    • 이 식은 이전 그레이디언트가 얼마였는지 고려하지 않는다.
  • 모멘텀은 가중치 조정 과정을 추적하면서 변화 가속도를 조절한다.
    • 일반 경사하강법보다 빠르게 전역 최소점에 도달
  • 모멘텀의 이해는 볼링공이 매끈한 표면의 완만한 경사를 따라 굴러간다면 처음에는 느리게 출발하지만 종단속도 에 도달할 때까지는 빠르게 가속되는 것을 생각해본다.
    • 모멘텀 최적화는 이전 그레이디언트가 얼마였는지를 상당히 중요하게 생각한다.
    • 그레이디언트를 가속도로 사용한다.
    • 일종의 마찰저항을 표현하고 모멘텀이 너무 커지는 것을 막기 위해 이 알고리즘에는 모멘텀이라는 새로운 하이퍼파라미터 $\beta$가 등장한다.
      • 이 값은 0 (높은 마찰저항)과 1 (마찰저항 없음) 사이로 설정되어야 한다. (default : 0.9)
  • 모멘텀 알고리즘
    1. $ m \leftarrow \beta m - \eta \nabla_{\theta} J(\theta)$
    2. $ \theta \leftarrow \theta + m $

image 이미지출처: 5

  • 모멘텀 최적화는 골짜기를 따라 바닥(최적점)에 도달할 때까지 점점 더 빠르게 내려간다.
  • 모멘텀 때문에 옵티마이저가 최적값에 안정되기 전까지 건너뛰었다가 다시 돌아오고, 다시 또 건너뛰는 식으로 여러 번 왔다 갔다 할 수 있다.
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$에서 비용 함수의 그레이디언트를 계산한 것이다.
    • 기본 모멘텀 최적화보다 일반적으로 훈련 속도 빠름
  • 네스테로프 가속 경사 알고리즘
    1. $ m \leftarrow \beta m - \eta \nabla_{\theta}J(\theta + \beta m) $
    2. $ \theta \leftarrow \theta + m $

image

from tensorflow.keras.optimizers import SGD

optimizer = SGD(lr=0.001, momentum=0.9, nesterov=True)


11.3.3 AdaGrad

  • 경사 하강법은 전역 최적점 방향으로 곧장 향하지 않고 가장 가파른 경사를 따라 빠르게 내려가기 시작해서 골짜기 아래로 느리게 이동한다.
  • AdaGrad 알고리즘은 가장 가파른 차원을 따라 그레이디언트 벡터의 스케일을 감소시켜 이 문제를 해결한다.
  • AdaGrad 알고리즘
    1. $ s \leftarrow s + \nabla_{\theta}J(\theta)\otimes \nabla_{\theta}J(\theta) $
    2. $ \theta \leftarrow \theta - \eta \nabla_{\theta}J(\theta)\oslash \sqrt{s + \varepsilon} $
  • 이 알고리즘은 학습률을 감소시키지만 경사가 완만한 차원보다 가파른 차원에 대해 더 빠르게 감소된다. (= 적응적 학습률 (adaptive learning rate))
    • 2차방정식에 대해 잘 작동함
    • 전역 최적점 방향으로 더 곧장 가도록 갱신되는 데 도움이 된다.
  • 하지만 단점으로 학습률이 너무 감소되어 전역 최적점에 도착하기 전에 알고리즘이 완전히 멈추므로 심층 신경망에는 사용하지 말아야 한다.

image

from tensorflow.keras.optimizers import Adagrad

optimizer = Adagrad(lr=0.001)


11.3.4 RMSProp

  • AdaGrad 때 너무 빨리 느려져서 전역 최적점에 수렴하지 못하는 위험을 RMSProp 알고리즘으로 가장 최근 반복에서 비롯된 그레이디언트만 누적함으로써 이 문제를 해결했다.
    • 알고리즘의 첫 번째 단계에서 지수 감소를 사용한다.
  • RMSProp 알고리즘
    1. $ s \leftarrow \beta s + (1 - \beta) \nabla_{\theta}J(\theta)\otimes \nabla_{\theta}J(\theta) $
    2. $ \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 알고리즘
    1. $ m \leftarrow \beta_1 m - (1 - \beta_1) \nabla_{\theta} J(\theta)$
    2. $ s \leftarrow \beta_2 s + (1 - \beta_2) \nabla_{\theta}J(\theta)\otimes \nabla_{\theta}J(\theta) $
    3. $ \hat m \leftarrow \frac {m}{1-\beta_1^t} $
    4. $ \hat s \leftarrow \frac {s}{1-\beta_2^t} $
    5. $ \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 드롭아웃

이미지출처: [^9]

  • 매 훈련 스텝에서 각 뉴런은 임시적으로 드롭아웃될 확률 $p$를 가진다. 즉, 이번 훈련 스텝에는 완전히 무시되지만 다음 스텝에는 활성화될 수 있다.
    • 하이퍼파라미터 $p$를 드롭아웃 비율이라고 하고 보통 10%와 50% 사이를 지정한다.
      • 순환 신경망: 20% - 30%
      • 합성곱 신경망: 40% - 50%
      • 순환신경망(15장): 20%-30%
  • 에포크 또는 스텝마다 서로 다른 고유한 모델이 훈련되는 효과를 발생시킴
  • 훈련이 끝난 후에는 뉴런에 더는 드롭아웃을 적용하지 않는다.
  • 케라스에서는 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 사이클


References

댓글남기기