[머신러닝 순한맛] 그레디언트 소실(Vanishing) / 폭주(Exploding)이란?
어제보다 나은 사람이 되기

걱정보단 실행을, 그러나 계획적으로

Box World 자세히보기

AI/Hands-On Machine Learning 2판

[머신러닝 순한맛] 그레디언트 소실(Vanishing) / 폭주(Exploding)이란?

Box형 2021. 5. 2. 17:45
반응형

잘못된 전략이라도 제대로 실행만 하면 반드시 성공할 수 있다.

반대로 뛰어난 전략이라도 제대로 실행하지 못하면 반드시 실패한다.

- 선 마이크로시스템즈 CEO, 스콧 맥닐리 -

 Deep Neural Network, 줄여서 DNN이라고 부르는 심층 신경망은 그 구조에 따라 수백 개에서 수 만개에 이르는 노드들이 엮어있는 구조를 지닙니다. 구조가 복잡한 것만큼 이것을 훈련하는 것 또한 쉽지 않습니다. 예를 들어

  • 신경망의 아래쪽으로 갈 수록 그레디언트가 작아지거나 커지는 그레디언트 소실/폭주(Gradient Vanishing/Exploding) 문제가 발생할 수 있습니다.
  • 훈련을 위한 데이터가 충분하지 않거나 데이터에 레이블을 붙이는데 많은 Cost가 들어갈 수 있습니다.
  • 과대적합(Overfitting)의 위험이 있습니다.

 이번 포스팅에서는 우리가 신경망을 훈련시키면서 발생할 수 있는 다양한 문제들에 대해 알아보고, 이들을 어떻게 해결할 수 있는지에 대해 알아보겠습니다.

 

 

 

 

 


그레디언트 소실/폭주

 신경망을 훈련시킨다는 것은 Back Propagation 알고리즘으로 Output Layer에서 Input Layer로 loss에 대한 그레디언트를 전파하고, 경사 하강법을 통해 이 그레디언트로 loss가 가장 적도록 파라미터를 수정하는 것을 의미합니다.

 그런데 알고리즘이 하위층으로 진행될수록 그레디언트가 더 작아지는 경우가 있는데, 이것을 그레디언트 소실이라고 합니다. 반대로 그레디언트가 비정상적으로 커지는 문제를 그레디언트 폭주라고 합니다. 이러한 불안정한 그레디언트는 Layer마다 상이한 학습 속도를 유발할 수 있기 때문에 좋지 않은 현상입니다.

 이런 부정적인 현상이 오랫동안 관측되었지만 그 원인이 명확하게 규명되지 않았습니다. 그러다 2010년의 한 논문에 의해 우리가 많이 사용하는 logistic sigmoid 활성화 함수와 당시 가장 인기 있던 가중치 초기화 방법(표준정규분포를 이용한 초기화)의 조합 때문이라는 것이 밝혀졌습니다.

  이 활성화 함수와 초기화 방식을 조합하여 사용했을 때, 신경망의 각 Layer에서 출력의 분산이 입력의 분산보다 더 크다는 것이 밝혀졌습니다. 이로 인해 신경망의 더 깊은 층으로 들어갈 수록 분산이 계속 커져 양 옆으로 퍼지면서 활성화 함수가 0이나 1로 수렴하게 되는 것이었습니다.

 게다가 logistic 함수의 평균이 0.5라는 사실 때문에 더 나빠졌습니다 (하이퍼볼릭 탄젠트 함수는 평균이 0이라 그나마 나았습니다) 다시 돌아와서 로지스틱 함수의 이러한 수렴이 문제가 되는 이유는 0이나 1로 수렴시 기울기가 0에 가깝기 때문에 깊어질수록 훈련을 위한 아무런 정보도 도달하게 되지 않게 되는 것입니다.


글로럿과 He 초기화

  결국 우리가 지향하고자 하는 이상적인 상황은 예측 시엔 정방향으로, 훈련 시엔 역방향으로 양방향 신호가 적절하게 흘러가야 하는데, 이때 신호가 죽거나 폭주 혹은 소멸하지 않는 것입니다.

 이에 대해 한 논문에선 이렇게 적절한 신호가 흐르기 위해서는 정방향의 경우, 각 층의 출력에 대한 분산이 입력에 대한 분산과 같아야하고. 역방향에선 층을 통과하기 전과 후의 그레디언트 분산이 동일해야 한다고 주장합니다.

 근본적으로 Layer의 Input과 Output의 연결 개수(fan in과 fan out)이 같지 않다면 위 두 가지를 보장할 수 없습니다. 그러나 저자들은 실전에서 매우 잘 작동한다고 입증된 대안을 제안하였는데, 바로 각 Layer들을 연결하는 가중치를 다음과 같이 무작위로 초기화하는 것입니다. 이를 글로럿 초기화(Glorot initialization)이라고 합니다.

  여기서 $fan_{avg}$를 $fan_{in}$으록 바꾸면 르쿤 초기화가 됩니다. 어쨌든 글로럿 초기화는 훈련 속도를 상당히 높이는데 기여하였습니다. 다음 표는 다른 논문들에서 로지스틱외에 다른 활성화함수를 사용하여 제안한 다양한 초기화 전략들입니다. 보다시피 분산의 스케일링이나 $fan_{avg}$이나 $fan_{in}$을 쓰는 것이 다릅니다. He 초기화나 SELU에 대한 내용은 이따 다시 설명하겠습니다.

 케라스는 기본적으로 균등분포의 글로럿 초기화를 사용합니다. 여기서 kernel_inializer = "he_uniform"이나 kernel_inializer = "he_normal"로 바꾸어 He 초기화를 사용할 수 있습니다.

keras.layers.Dense(10, activation="relu", kernel_initializer="he_normal")

$fan_{in}$ 대신 $fan_{out}$ 기반의 균등분포 He 초기화를 사용하고 싶다면 Variance Scaling을 다음과 같이 사용하면 됩니다.

init = keras.initializers.VarianceScaling(scale=2., mode='fan_avg',
                                          distribution='uniform')
keras.layers.Dense(10, activation="relu", kernel_initializer=init)

 

 

 

 

 


수렴하지 않는 활성화 함수

 결국 우린 활성화 함수를 잘 선택해야 그레디언트 소실이나 폭주로 이어지지 않을 수 있습니다. 기존에 logistic 활성화 함수가 최선일거라고 생각하였지만, 특정 양수값에 수렴하지 않고 계산도 빠른 Relu 함수가 있었습니다.

 하지만 Relu도 완벽하지 않습니다. 왜냐하면 큰 학습률(learning rate)를 사용 시 신경망 속 노드들의 절반이 죽어 있는 경우가 있었기 때문입니다.(노드가 0이외의 값을 출력하지 않을때 죽었다고 합니다)

 Relu함수 이용시 노드가 죽는 이유는 모든 Input data에 대한 가중치의 합이 음수가 되면 아래 그래프에서 볼 수 있듯이그레디언트가 0이기 때문에 경사 하강법이 더 이상 작동하지 않게 됩니다.

 이러한 문제 해결을 위해 LeakyReLU와 같은 변종을 사용합니다.

 하이퍼 파라미터 $α$는 이 함수가 새는 정도를 결정합니다. 즉 샌다는 것은 음수 영역에서의 함수의 기울기를 의미하며 보통 $0.01$로 설정합니다. 바로 이 작은 기울기가 LeakyRelu가 절대 죽지 않게 해주는 것입니다. 실제로 성능도 훨씬 좋습니다. 다음은 LeakyRelu 사용법입니다. 적용하려는 층 뒤에 붙여주면 됩니다.

model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    keras.layers.Dense(300, kernel_initializer="he_normal"),
    keras.layers.LeakyReLU(),
    keras.layers.Dense(100, kernel_initializer="he_normal"),
    keras.layers.LeakyReLU(),
    keras.layers.Dense(10, activation="softmax")
])

 이외에 $α$를 무작위로 선택하여 테스트 시에는 $α$들의 평균을 사용하는 RRelu$α$가 훈련하는 동안 학습되는 PReLU가 있습니다. 마지막으로 볼 활성화 함수는 다른 모든 ReLU 변종들의 성능을 앞서는 ELU입니다.

 이 함수는 몇가지를 제외하고 ReLU와 유사한 특성을 지닙니다.

  • $z < 0$일때 음수값을 내보내므로, 활성화함수의 평균 출력이 0에 가까워지고 이는 그레디언트 소실 문제를 완화해줍니다. 여기서의 알파값은 ELU가 수렴할 값을 정의하는데 보통 1로 설정합니다.
  • $z<0$이어도 그레디언트가 0이 아니기 때문에 죽은 뉴런을 만들지 않습니다.
  • 알파가 1일 때, $z=0$에서 급격한 변동이 일어나지 않는 즉 모든 구간에서 매끄럽기때문에 최적화의 속도를 높이는데 기여합니다.

 ELU의 주요한 단점은 계산이 느리다는 것입니다. 물론 수렴 속도가 빨라 상쇄가 되지만, 테스트의 경우엔 ReLU를 이용한 네트워크보단 느릴 것입니다.

 ELU의 변종인 SELU를 살펴보겠습니다. 저자들은 네트워크가 Fully-Connected한 상태에서 활성화함수로 SELU를 사용하면 자기 정규화(각 층의 출력의 평균이 0이고, 표준편차가 1이 유지됨)된다는 것을 보였습니다. 다음은 자기 정규화를 위한 몇가지 조건입니다.

  • Input은 반드시 표준화(평균 0, 표준편차 1)이어야 합니다.
  • hidden layer의 가중치는 르쿤 정규분포로 초기화되어 있어야 합니다.
  • 순환 신경망이나, skip connection과 같은 순차적이지 않은 구조를 사용시 자기 정규화를 보장하지 않습니다.

 다음은 일반적으로 우선순위시 되는 활성화 함수입니다.

  • 네트워크가 자기 정규화 되지 못한다면 SELU보단 ELU
  • 실행 속도가 중요하다면 LeakyReLU
  • 신경망이 Overfitting되었다면 RReLU
  • 그러나 대부분의 라이브러리와 하드웨어 가속기들은 ReLU에 특화되어있으므로, 속도를 위한다면 Relu

 

 

 

 

 


배치 정규화(Batch Normalization)

 우리가 앞서 살펴본 초기화 방법들은 훈련 초기 단계에서 그레디언트 소실이나 폭주를 방지하는 방법입니다. 하지만 훈련 도중에 이러한 현상들이 다시 발생하지 않으리라는 보장은 없습니다. 이에 따라 제시된 해결책이 배치 정규화입니다.

 이 기법은 활성화 함수 통과 전이나 후에 입력을 정규화한 후, 결과값의 스케일을 조정하고 이동시킵니다. 대부분의 경우 신경망의 첫번째 층에 배치 정규화를 추가하면 training set을 표준화할 필요가 없습니다.

 우리가 고등학교때 배웠지만, input data를 정규화하기 위해서는 평균과 표준편차가 필요하기 때문에 이를 추정해야합니다. 다음은 이 추정을 위해 mini-batch에서 Input의 입력과 표준편차를 추정하는 알고리즘입니다.

 

 하지만 테스트 시엔 데이터가 하나씩 들어오기 때문에, 전체 test data에 대한 평균과 표준편차를 계산할 방법이 없습니다. 이에 한가지 방법은 훈련이 끝난 후 test data가 포함된 전체 training set을 신경망에 통과시켜 각 input에 대한 평균과 표준편차를 계산하여 이를 예측에 활용하는 것입니다.

 이러한 배치 정규화는 그레디언트 소실 / 폭주 문제 해결이나 학습 속도 향상에 크게 기여하였습니다. 그러나 배치 정규화는 층마다 추가되는 계산으로 인하여 복잡도와 실행 시간을 높입니다.

 다행히 훈련이 끝난 후에 이전 층과 배치 정규화 층을 합쳐 실행 속도 저하를 피합니다. 즉 이전 층의 가중치를 바꾸어 바로 스케일이 조정되고 이동된 출력을 만듭니다. 다음은 이를 케라스로 구현한 코드입니다.

model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28]),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(300, activation="relu"),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(100, activation="relu"),
    keras.layers.BatchNormalization(),
    keras.layers.Dense(10, activation="softmax")
])

그레디언트 클리핑

 그레디언트 문제 그 중에서도 폭주 문제를 완화하는 인기 있는 방법은 역전파 시 일정 임곗값을 넘어가지 않도록 그레디언트를 잘라내는 것입니다. 이를 그레디언트 클리핑이라고 합니다. 

 이 방법은 앞서 우리가 배치 정규화를 적용하기 어렵다고 한 순환 신경망같은 구조에서 사용합니다. 다음은 이를 keras에서 구현한 것입니다.

optimizer = keras.optimizers.SGD(clipvalue=1.0)

 이 옵티마이저는 그레디언트 벡터의 모든 원소를 $-1.0$에서 $1.0$ 사이로 클리핑합니다. 즉 loss에 대한 모든 편미분 값을 이 사이로 잘라내는 것입니다. 예를 들어 원래 그레디언트 벡터가 $[0.9, 100.0]$이었다면 이를 클리핑 시 $[0.9, 1.0]$가 되는 것입니다.


다음 포스팅에서는 신경망 학습에서 많이 활용되는 전이 학습에 대해 알아보겠습니다. 오늘도 읽어주셔서 감사합니다. 행복한 하루 보내세요 :)

반응형