8 분 소요

본 글은 [핸즈온 머신러닝 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 보조 작업에서 사전훈련

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





References

댓글남기기