본문 바로가기
딥러닝/CS182

CS182 - [Lecture 7] Getting Neural Nets to Train

by sb99 2022. 7. 23.

이번 강의에서는 모델을 더 잘 학습시키기 위한 방법에 대해 배운다.

 

현재까지 배운 내용들을 모델에 적용시켰음에도 불구하고 모델의 성능이 좋지 않을 수 있다.

학습을 더 잘 시키기 위해서는 많은 '트릭'들이 요구되며, 

우측 상단에 표시된 방법들에 대해 배울 예정이다.

 

Part 1

Batch Normalization

 

모델의 학습을 어렵게 하는 요소로, 먼저 input과 관련된 요소를 살펴보자.

만약 two dimension(두 개의 features이라 생각해도 될 것 같다)을 가지는 input이 있다고 가정하자.

이때 우측 그래프와 같이  \(x_{1}, x_{2}\)의 magnitude 차이가 크다면 모델이 적절한 학습을 하기 어렵다.

 

좌측 상단에 표시된 \( \frac {dL}{dW}\)의 경우, 

Magnitude가 상대적으로 큰 dimension(그래프에서의 \(x_{1}\))의 영향을 많이 받을 것이기 때문이다.

 

이렇듯 큰 변수에 편향되어 학습이 이루어지게 되면 

우측 하단의 그림과 같이 global optima를 찾아가는 과정이 지그재그로 이루어진다.

(즉, 수렴이 잘 되지 않는다.)

 

 

상황에 따라 dimension의 크기 편향에 따른 모델 학습 저해를 걱정하지 않아도 될 수 있다.

이미지 데이터의 경우 각 픽셀들은 0에서 255의 값을 가지며,

NLP와 같은 discrete inputs은 원-핫 인코딩을 통해 동일한 scale을 갖는다.

(물론 이러한 경우에도 각 dimension의 scale을 동일하게 유지하는 과정은

 다른 이유에 의해서라도 모델의 학습에 도움이 될 수 있다.)

 

하지만 기상 예측과 같이 input의 각 dimension들이 다른 scale을 갖는 경우에는

모델의 학습을 방해하는 분명한 요소가 될 것이다.

 

 

그렇다면 dimension에 따른 magnitude의 차이를 줄일 수 있는 방법은 무엇이 있을까?

우리는 각각의 dimension이 동일한 평균값과 분산을 갖도록 하는 standardization을 사용할 수 있다.

 

Standardization을 통해 각 dimension의 평균을 0으로, 분산을 1로 설정해주며

긴 타원 모양의 파라미터 분포를 완벽한 원 형태와 가깝게 만들 수 있다.

 

 

그렇다면 우리의 모델에 standardization을 어떻게 적용할 수 있을까?

가장 기초적인 아이디어에서부터 접근해보자.

 

모델의 non-linearity를 부여하기 위해 activation function을 사용하게 되는데,

Activation function의 결과에 standarization을 적용할 수 있을 것이다.

각각의 input에 대해 standarization을 적용한다면 위와 같은 수식이 된다.

(각 dimension의 평균과 분산을 구하고, 하나의 값에 평균을 뺀 뒤 분산을 나누는 방식)

 

하지만 모든 input에 대해 이러한 계산을 실행한다면 엄청난 계산량이 요구된다.

 

 

계산량을 효과적으로 줄이기 위한 방법으로 batch normalization이 있다.

Gradient descent를 활용한 parameters update에 대해 공부할 때도 언급했듯이,

우리는 계산량을 줄이기 위한 방법으로 mini-batch training을 활용한다.

이와 마찬가지로 normalization에 min-batch 방식을 도입한 것이 batch normalization이다.

 

변화된 점은, 하나의 input에 대해 standarization을 실행하는 것이 아니라,

하나의 mini-batch에 따라 standarization을 실행하게 된다.

 

 

하지만 실제로 우리가 사용하는 batch normalization 방식은 이전과 다소 다르다.

우측 하단에서 확인할 수 있듯, 우리는 학습 가능한 변수인 \( \gamma, \beta \)를 설정한다.

 

Input의 모든 dimension에 대해 scaling을 적용하는 것은 모델의 학습에 도움이 될 수 있지만,

과연 평균을 0으로, 분산을 1로 하는 분포로 설정하는 것이 

모든 모델에 있어서 학습이 도움이 되는지는 생각해 볼 필요가 있다.

 

이때 \( \gamma, \beta \)는 모델의 학습이 잘 되는 방향으로서 학습되어

Normalization 이후에 생성되는 분포를 모델에 맞게 조절해준다.

 

 

우리는 현재까지 activation function 이후에 batch normalization을 적용하는 것을 예시로서 활용했다.

하지만 실제로는 이 순서를 반대로 적용해도 된다고 하며,

Batch normalization 이후에 activaiton function을 적용하는 것이 논문에서 제안한 순서라고 한다.

하지만 이 두 방법 모두 잘 작동하기 때문에 특별히 합의된 것은 없다.

 

 

Batch norm을 적용할 때에는 몇 가지 고려 사항이 존재한다.

1. Learning rate를 더 큰 값으로 설정할 수 있다.

2. Train 속도가 더욱 빨라진다.

3. Regularization을 활용할 필요가 적어진다.

 

Batch normalization은 데이터를 모델이 잘 학습될 수 있는 분포로 변형하기 때문에

Gradient vanishing / exploding 문제를 해결해 주는데,

이로써 더욱 빠른 학습이 가능해지고 regularization의 필요성이 감소한다.

(참고: https://hyen4110.tistory.com/20)

 

 

Part 2

Weight Initialization

 

해당 파트에서는 모델의 파라미터들을 초기화하는 방법에 대해 배운다.

 

우리는 모델의 파라미터가 적절한 값을 가짐으로 인해서 gradient를 활용한 학습이 잘 이루어지길 바란다.

Gradient를 계산하기 위해 chain rule을 적용함에 따라 많은 값들을 곱하게 되는데,

이 값들이 1에 가깝도록 하여 모델이 잘 학습될 수 있도록 하는 것이 목표이다.

 

 

Weight를 초기화 하는 방법 중 가장 먼저 떠올릴 수 있는 것은 Gaussian 분포를 활용하는 것이다.

평균이 0이고 분산이 매우 작은 Gaussian 분포를 활용하여 weight를 초기화하면

하단의 그래프에서 확인할 수 있듯, layers가 깊어질수록 mean과 std가 0에 가까워진다.

 

이는 1보다 작은 값이 layer를 거칠 때마다 곱해짐에 따라 값이 0에 수렴해가기 때문이다.

이러한 현상이 문제가 되는 이유는 back propagation에서 gradient를 계산할 때,

값이 0에 매우 가까우므로 gradient 또한 0에 매우 가까워져

weight의 업데이트가 거의 일어나지 않기 때문이다.

 

 

조금 더 복잡한 초기화 방법에 대해 생각해보자.

우선 layer의 weight가 평균이 0, 분산이 \( \sigma^{2}_{W}\)를 가지며,

Bias는 0에 매우 근접한 값이라고 가정하자.

마찬가지로 input의 평균은 0, 분산은 \( \sigma^{2}_{a}\)라 가정하자.

 

이때의 각 layer를 거쳤을 때의 값인 \( E [z^{2}_{i}]\)는 \(D_{a}\sigma^{2}_{W}\sigma^{2}_{a}\)가 된다.

\(D_{a}\sigma^{2}_{W}\)가 1보다 크거나 작다면, 값이 layer를 지날수록 발산하거나 0에 수렴하기 때문에,

\(\sigma^{2}_{W}\)를 \(\frac{1}{D_{a}}\)로 설정해볼 수 있을 것이다.

즉, weight의 분산을 input의 dimension의 역수로 설정하는 것을 고려해 볼만 하다.

 

 

우리는 이를 'Xavier initialization'이라 부른다.

그 결과, 하단의 그래프와 같이 gaussian 분포를 활용했을 때보다는 좋은 분포를 보이고 있다.

하지만 여전히 layer를 지날 때마다 표준편차가 감소하고 있으며, 평균값이 매우 작음을 고려했을 때

Layers가 더욱 깊어진다면 gradient vanishing 문제가 발생할 수 있을 것이다.

 

 

그렇다면 std가 layers가 깊어짐에 따라 지속적으로 줄어드는 이유는 무엇일까?

이는 우리가 linear layer 이후에 추가하는 non-linearity function인 activation function 때문이다.

 

예를 들어 가장 흔히 사용하는 ReLU에 대해 생각해보자.

ReLU는 음수 값에 대해 0을 출력하게 되는데,

이에 따라 약 절반에 해당하는 0보다 작은 값들이 layer를 지남에 따라 제거된다.

즉, 절반 정도의 값들이 지속적으로 사라지기 때문에 variance가 감소하는 것이다.

 

 

이에 대한 간단한 해결책으로는 \(W_{ij}\)의 std 값을 증가시키는 방법이 있다.

위의 예에서는 \(\sqrt {2}\)를 곱해줌으로써 표준편차를 증가시켰다.

 

 

마지막으로 gradient clipping에 대해 알아보자.

실제 학습에 있어서 gradient가 optima 방향이 아닌 다른 방향으로 튀는 현상이 발생할 수 있다.

이는 딥러닝 학습에서 종종 일어나는데, 운이 좋지 않거나 어떤 값을 작은 값으로 나눔으로써 발생하곤 한다.

이러한 현상이 발생하면 더 이상 학습을 진행해도 모델의 성능이 개선되지 않는다.

 

 

이를 해결하는 방법이 gradient clipping이다.

말 그대로 기울기를 자르는 것을 의미하며 기울기가 임계값을 넘지 않도록 제한한다.

이때 방향은 유지한 채 적은 크기만큼 파라미터를 조절하기 때문에

모델이 보다 안정적으로 학습될 수 있도록 해준다.

 

 

Part 3

Ensembles & Dropout

 

아무리 모델을 잘 학습하더라도 모델이 잘못 예측한 값이 존재하기 마련이다.

모델의 잘못된 예측을 보정하는 방법으로 ensembles이 있다.

 

Ensembles은 여러 모델의 결괏값을 종합하여 하나의 결과를 도출하는 것을 말한다.

 

 

Ensembles에 대해 자세히 알아보자.

 

Ensembles에서는 여러 개의 모델을 사용한다고 했는데, 각각의 모델은 어떤 데이터 셋을 학습할까?

하나의 큰 데이터 셋에 대해 N개의 데이터를 중복을 허용하여 independent 하게 뽑고,

이렇게 생성된 N개의 데이터로 이루어진 데이터 셋들을 각각의 모델에 학습시킨다.

 

이렇듯 하나의 데이터 셋으로부터 여러 개의 데이터 셋을 생성하는 것을 resampling이라 한다.

 

 

예시를 통해 자세히 알아보자.

 

D라는 하나의 큰 데이터 셋이 있을 때 두 개의 모델을 학습시킨다고 가정하자.

이를 위해 두 개의 데이터 셋이 필요하며,

각각의 데이터 셋은 중복을 허용한 independent 추출을 통해 생성되어 모델에 학습된다.

 

 

그렇다면 이렇게 학습된 모델들은 어떠한 방식으로 결과를 종합하고 최종 결과를 출력할까?

가장 간단한 방법으로는

 1. 각 모델들이 출력한 확률 값들의 평균을 취한뒤 가장 높은 확률 값을 가진 클래스를 출력하는 방법

 2. 각 모델들이 출력한 클래스 값들을 종합하여 가장 많은 득표를 받은 클래스를 출력하는 방법

이 있다.

 

하지만 두 번째 방법의 경우, 각 모델이 가진 second best에 대한 정보를 잃어버리게 된다.

만약 wolf: 51%, dog: 49%라고 예측한 모델이 있다면,

이 모델이 가지고 있던 dog에 대한 예측값은 최종 결과에 반영되지 않게 된다.

따라서 첫 번째 방법이 두 번째 방법보다 일반적으로 좋다고 할 수 있다.

 

 

이제 ensembles를 실제로 적용시키는 것에 대해 생각해보자.

우리는 이전에 resampling을 통해 하위 데이터 셋을 생성하여 각각의 모델에 학습시킨다고 하였다.

 

하지만 neural network의 경우 이미 많은 randomness가 존재한다.

따라서 실제로는 resampling 없이도 ensembles을 통해 좋은 성능을 낼 수 있다.

 

 

Ensembles에 대해 알아볼 수록 이런 생각이 들 수 있다.

'Neural network 하나를 학습시키기에도 오랜 시간이 걸리는데, 

 여러 개의 모델에 대해 학습시키는 ensembles는 훨씬 많은 시간이 걸리지 않을까?'

 

이러한 문제를 해결하기 위해 우측 그림과 같은 형태의 ensembles 모델을 설계할 수 있다.

Convolutional layers는 공유한 채 마지막 부분의 fully connected layers에 대해서만

구분되어 있는 모델임을 확인할 수 있는데, 마치 여러 모델을 학습시켜 결과를 종합한 듯한 결과를 낼 수 있다.

 

 

더 빠른 ensembles 방법으로는 snapshot이 있다.

 

Snapshot은 여러 개의 모델을 생성하여 각각 training 하는 것이 아니라,

하나의 모델에 대해 학습시키면서 중간 중간의 parameters를 저장하고 

이렇게 저장된 각 parameters를 하나의 모델로서 간주하여

최종적으로 여러 모델들을 학습한 것 처럼 만드는 방법이다.

 

즉, 특정 epoch마다 모델의 parameters 값을 저장하다가 모델의 학습이 종료되면

저장된 parameters에 대한 test를 하고 결과를 종합함으로써 최종 결과를 도출한다.

(More detail: https://machinelearningmastery.com/snapshot-ensemble-deep-learning-neural-network/)

 

 

일반적으로 ensembles의 크기가 클 수록 좋은 효과를 갖지만, 그만큼 계산 비용이 많아진다.

그렇다면 큰 ensembles을 어떻게 효율적으로 설계할 수 있을까?

Dropout은 하나의 neural network가 많은 모델을 가지고 있는 것처럼 만들어 준다.

 

Dropout은 좌측에 있는 neural net에서 일부 노드의 활성화를 제한함으로써

새로운 network가 생성된 것처럼 만드는데,

이러한 과정을 반복함으로써 마치 여러 모델을 학습하는듯한 효과를 낸다.

 

 

Dropout에 대해 조금 더 자세히 알아보자.

 

Dropout이 효과적인 이유는 우측 그림에서 확인할 수 있다.

Neural network의 노드들은 다양한 representation을 학습하는데,

이들 중 일부를 랜덤하게 제외함으로써 특정 features만 과도하게 학습되는 것을 방지하고

다양한 features가 학습될 수 있도록 만들어준다.

 

따라서 dropout은 마치 여러 features를 가진 모델들이 여러 번 학습되는 것과 같은 효과를 가지며

거대한 ensemble 모델을 하나의 neural network 모델을 통해 생성할 수 있다.

 

 

Dropout을 사용함에 있어 주의해야 할 점은 모델 test에 있다.

Ensembles에서 학습한 여러 모델들의 결과를 마지막에 종합하는 것처럼,

Test에서는 dropout을 제거함으로써 모든 노드들이 test에 관여할 수 있도록 해야 한다.

 

하지만 만약 dropout의 probability를 0.5로 설정했다면 (반절의 units을 비활성화했다면)

Dropout을 제거했을 때와 제거하지 않았을 경우의 \(W^{i} a^{i}\)의 결과는 평균 2배 차이가 난다.

따라서 모델을 test 할 때에는 모든 weight를 2로 나눠야 한다. 즉, probability를 곱해준다.

(혹은 우측 상단의 코드처럼 train 할 때 weight에 probability를 나누어도 된다.)

 

 

 


References

'딥러닝 > CS182' 카테고리의 다른 글

CS182 - [Lecture 8] Computer Vision  (0) 2022.07.24
CS182 - [Lecture 6] Convolutional Networks  (0) 2022.07.10
CS182 - [Lecture 5] Backpropagation  (0) 2022.07.02
CS182 - [Lecture 4] Optimization  (0) 2022.06.29
CS182 - [Lecture 3] Error Analysis  (0) 2022.06.27

댓글