Google ML Bootcamp 2022/Coursera mission

Improving Deep Neural Networks - 1 week

피준 2022. 7. 10. 15:26

Train / Dev / Test sets

NN 구현에 대해서는 배웠다
이 강의에선 하이퍼 파라미터 튜닝, 데이터 설정과 같은 실용적 측면을 배울 것이다

신경망을 학습시킬 때, 레이어는 몇 개로 하고, 히든 유닛은 몇 개로 하고, 학습률.. 활성 함수 등 정할게 많다

가장 적절한 하이퍼 파라미터를 예측하는 건 거의 불가능하다

따라서 반복 또 반복해야한다

빠르게 반복하기 위해 반복이 효율적이어야 한다

그렇기에 데이터 셋을 train/dev/test set으로 나누는 것은 중요하다

각각의 의미는 다음과 같다

  • train set : 말 그래도 모델을 학습시키는 데이터 셋
  • dev set : 모델을 검증하고, 파라미터를 튜닝하기 위한 데이터 셋
  • train set : 학습과 검증이 완료된 모델의 성능을 평가하기 위한 데이터 셋

학습은 오로지 train set을 이용해서만 이루어져야 한다

데이터 셋 분리의 국룰은 70/30 분할이었다

혹은 60/20/20으로 검증 세트를 추가하기도 하였다 (train/dev/test)

데이터의 수가 많아짐에 따라 dev/test 셋의 비율을 줄이기도 하였다

극단적으로 98/1/1 or 99.5/0.4/0.1과 같은 경우도 있다

bias 한 문제가 있어도 괜찮다면 test set 없이 dev set만 있어도 괜찮다

Bias / Variance

왼쪽은 너무 맞지 않고, 오른쪽은 너무 복잡해 가운데 정도가 적당해 보인다

1번 그림의 왼쪽이 bias가 높고 under fitting 되었다고 하며,

3번으로 갈수록 variance가 높고 overfitting이 되었다고 한다

train set에 너무 맞춰져, train set error에 비해 dev set error가 높을 경우 모델에 과적합되어 variance가 높다고 한다

train set error와 dev set error가 모두 비슷하게 높으면, 과소 적합이며 bias가 높다고 한다

또한 train set error도 높지만, dev set error가 더 크면 high baias면서 high variance인 경우이다

두 에러 모두 낮아야 bias와 variance가 낮으며 잘 학습이 진행되었다고 말할 수 있다

Basic Recipe for Machine Learning

마치 레시피 처럼 ML에는 아래와 같은 방법론이 있다
 
만약 bias가 높다면?
->모델 크기를 키운다(깊이를 크게 만들고, 유닛 수를 늘리고 다른 알고리즘 사용 등),학습을 오래 한다, 다른 형태의 네트워크 형태를 사용한다(직선이 아닌 곡선 등)
다음으로 variance가 높은지 살펴본다
->더 많은 데이터를 얻는다, 정규화를 시도한다, 더 적절한 신경망 아키텍쳐를 찾는다
 
기술이 많이 발전 되지 않았을 때에는 bias/ variance trade off 라는 둘 중 하나를 포기해야만 하는 문제가 존재했다
그러나 최근에는 데이터를 늘리거나 네트워크를 수정하여 둘 모두를 얻을 수 있다

 

 

Regularization

overfitting 되었다고 의심 가는 경우, 정규화를 가장 먼저 시도해야 한다

로지스틱 회귀를 사용하는 경우를 예시로 알아보자

로지스틱 회귀의 목적은 비용 함수 J를 최소화하는 것이다

\({L_2}\) 정규화를 통해 w의 노름에 상수를 곱해 더해준 식을 보자

아래 식에서 추가된 항이 \(L_2\) norm이다

\(\mathcal{J}(w,b) = {1\over m}\sum_{i=1}^{m}\mathcal{L}(\hat{y^{(i)}},y^{(i)}) + {\lambda \over m}\left\|w \right\|^{2} \)

b에 대한 항은 없고 w에 대한 항만 있는데

이건 w는 b에 비해 차원이 높아 high variance 문제가 더 잘 발생하기 때문이다

\({L_2}\)가 있으니 물론 다른 정규화 상수도 존재한다

\({L_2}\) regulation : \(\left\|w \right\|_2^{2}=\sum_{j=1}^{n_x} w_j^2=w^Tw\)
\({L_1}\) regulation : \(\sum_{j=1}^{n_x} \left|w_j \right|=\left\|w \right\|_1\)

\(\lambda\)는 정규화 상수로 하이퍼 파라미터이다

NN에선 다음과 같은 식을 가진다

\(\mathcal{J}(w^{[1]},b^{[1]}, \dots ,w^{[l]},b^{[l]}) = {1\over m}\sum_{i=1}^{m}\mathcal{L}(\hat{y^{(i)}},y^{(i)}) + {\lambda \over m}\sum_{l=1}^{L}\left\|w^{[l]} \right\|^{2} \)

노름은 Frobenius norm이라 하며 아래와 같은 식을 가진다

\(\left\|w^{[l]} \right\|^2_F = \sum_{i=1}^{n^{[l]}} \sum_{j=1}^{n^{[l-1]}}(w_{ij}^{[l]})^2    \)
\(w\) shape : \((n^{[l]},n^{[l-1]})\)

계산은 dw에 노름에 해당하는 값을 더해주면 된다

Why Regularization Reduces Overfitting?

그래서 정규화가 왜 과적합 문제 해결에 도움이 되는 걸까

정규화는 비용 함수 J에 w에 대한 항을 추가한 것이다

즉 w가 커질수록 비용 함수가 크므로, w가 작아지는 방향으로 유도한다

각각 유닛의 영향이 작아지므로 마치 모델이 작아지는 것과 같은 효과가 있다

즉 모델의 복잡도가 줄어들어 variance를 줄인다

Dropout Regularization

L2와 같이 또 다른 테크닉인 dropout에 대해 알아보자

dropout은 확률적으로 히든 유닛을 제거하는 방법이다

왼쪽의 overfitting 된 모델에 dropout을 적용했다

구현 방법을 알아보자

L=3인 모델을 구현하는 방법을 예시로 살펴보자

d3 = np.random.rand(a3.shape[0],a3.shape[1]) < keep_prob
a3 = np.multiple(a3,d3)
a3 /= keep_prob

keep_prob은 숫자로 numpy masking을 하는 느낌이다

마지막 줄은 원소가 0이 됨에 따라 발생하는 z 감소를 막기 위한 목적이다

Inverted dropout이라 부르며 z의 기댓값을 유지시켜주는 기술이다

 

마지막 test time을 진행을 할 때는 drop out을 하지 않는다

테스트 결과가 진행 시마다 차이가 나면 별로가 때문이다

단지 노이즈만 더 추가되어 별로다

Understanding Dropout

앞서 모델의 복잡도를 줄여, 정규화시킨다는 직관을 배웠다

다른 방식으로 drop out을 이해해 보자

하나의 feature에 의존 불가능하게 만들어 w를 퍼지게 만든다

why?

학습을 하며, feature에 의한 영향력이 0이 되기에, 특정 노드가 강한 의존성을 주지 못한다

레이어마다 keep_prob 값이 달라질 수 있다

가장 큰 가중치 행렬인 \(w^{[2]}, (7\times 7)\)에 가장 작인 keep_prob을 주어 영향력을 낮추자

입력층에도 dropout이 사용 가능하나 잘 안 쓰고, 쓰더라도 큰 값을 사용한다

 

컴퓨터 비전에선 입력 크기가 아아주 크며, 데이터가 충분하지 않아 dropout을 흔하게 사용한다

dropout은 무작위성을 가져 J가 잘 정의되지 않아 디버깅이 어렵다

따라서 dropout을 하지 않고 J가 감소하는지 확인한다

Other Regularization Methods

L2와 dropout 말고도 정규화 방법은 많다

  • Data augmentation
    data set의 크기를 늘리는 것도 정규화시키는 방법 중 하나이다
    데이터를 얻는 데엔 비용이 많이 들 수도 있기에 이런저런 변형시키기도 유용하다

확대하고, 쭈글쭈글하게 만들고, 회전시키고~

  • Early stopping

반복에 따른 train/dev set에서의 J

반복을 하며 과적합이 이루어짐에 따라 dev set은 극값을 가진다

early stop은 적당히 학습하다 극값에서 멈추는 것이다

그러나 이 방법은 J를 최적화시키는 것 / over fit을 막는 것 두 가지 일을 동시에 진행하게 된다

완전히 별개인 두 개의 일을 동시에 뭉뚱그려 진행한다

그렇기에 완전 최적의 조건이 아닐 수도 있다

->그냥 L2를 사용하여 해결 가능

근데 여러 람다에 대해 실행해야 하니 오래 걸릴 수도..

 

 

Normalizing Inputs

NN에서 입력을 정규화하면 속도를 빠르게 만들 수 있다

평균을 0으로, 분산을 1로 만들어 준다

각각 평균을 빼고, 분산으로 나누어 줌으로써 할 수 있다

그냥, 평균을 0으로, 분산을 1로 한 경우 데이터 분포

train/dev/test 셋은 같은 정규화를 해줘야 한다

즉 train set의 평균과 분산으로 정규화를 해줘야 한다

그래서 이걸 왜 해주는 걸까

비용함수 J의 그래프이다

경사 하강법을 하며, 왼쪽의 경우 중심까지 찾아가는데 많은 시간이 걸린다

그러나 정규화를 해주면 바로바로 중앙으로 가기에 속도가 더 빠르다

scale차이가 큰 경우, scale을 통일시켜 주면 빠르구나~ 생각하자

Vanishing / Exploding Gradients

NN을 학습시키는 과정에선 이런저런 문제가 발생할 수 있다

Vanishing/Exploding은 언더/오버플로우 문제처럼 보였다

신경망이 매우 매우 깊어질 경우, 기울기가 매우 커지거나 작아질 수 있다

w=2라고 하면 깊이 100이면 2^100, 반대로 w=0.1이면 10^-100인 느낌..

기울기가 매우 작으면 기울기 하강이 느려 학습에 매우 매우 오랜 시간이 걸린다

부분적인 해결 책이 있는데 기울기를 신중하게 초기화하는 것이다

Weight Initialization for Deep Networks

기울기 초기화를 어떻게 "잘" 할 수 있을까
하나의 뉴런을 초기화 하는걸 보며 알아보자
l 번 레이어에 있는 어떤 노드

\(Z=wX\) 이므로 \(n^[l-1]\)이 커질수록, \(w\)의 크기는 작아야 할 것 같다
그래야 sum의 크기가 대충 비슷해진까...
w의 분산을\(1\over {n^[l-1]}\)로 만드는 건 좋은 방법이라 한다
물론 이것도 마법이니 넘어가자(레이어를 통과해도 분산을 유지시키고 싶음. 모든 레이어에서 아웃풋(Z=WX+b)의 분산을 1로 고정하려고 하는데, input layer를 분산이 1인 분포로 normalization 했으니 W의 분산은 1/n이 되어야 한다는 뜻)

아무튼 아래와 같이 구현 가능하다

\(w^{[l]} = np.random.randn(shape) \times np.sqrt({1\over{n^{[l-1]}}})\)

활성 함수로 렐루를 쓰면 분산이 2/n인 게 더 잘 작동한다

tanh는 1/n이 좋다고 한다

Numerical Approximation of Gradients

엡실론-델타 논법 내용이다

근사 미분 값을 계산하며 한쪽 미분이 아닌 양측 미분을 시행하면 오차가 줄어든다

대체 이런 wjd확하지 않은건 뭘까

코세라의 자막은 자동 생성이 대부분인 것으로 아는데 위와 같은 자막은 대체 뭘까...

Gradient Checking

Gradient checking은 일종의 디버깅하는 방법이다

W, b를 \(\theta\)로, dW, db를 \(b\theta\)로 먼저 reshape 해주어야 한다

\(\mathcal{J}(w^{[1]},b^{[1]},\dots , w^{[l]},b^{[l]}) = \mathcal{J}(\theta)\)

근사 미분과 실제로 미분한 값을 비교한다

\(d\theta _{approx}^{[i]} = {\mathcal{J}(\theta _1,\dots ,\theta _i +\epsilon ,\dots)- \mathcal{J}(\theta_1,\dots ,\theta_i -\epsilon,\dots)\over{2\epsilon }}\)

아래 식을 이용하여 확인한다, 유클리디안 거리를 사용한다

\({\left\| d\theta_{approx}-d\theta \right\|_2\over \left\| d\theta_{approx}\right\|_2 + \left\|d\theta \right\|_2}\)

\(10^{-7}\) 정도면 잘 계산되었다 보고, \(10^{-3}\) 보다 큰 값이면 뭔가 잘못 계산되었다 보고 디버깅을 한다

Gradient Checking Implementation Notes

  • 시간이 좀 걸리기에 학습에 사용 말고 디버깅에만 사용하자
  • grad check에 실패하면 실패하기 시작한 부분을 찾아보자, 특정 부분에서 계속 실패한다면 아마 거기에 오류가 있겠지...
  • 정규화를 했다면 정규화 항도 당연히 고려해 줘야 한다
  • dropout처럼 랜덤이 적용되었다면 잘 작동하지 않을 수 있다 -> dropout 없이 식 맞는지 확인 후 dropout을 적용하자
  • 0 근처에서 잘못되었지만 잘 작동하는 것처럼 보일 수 있다 -> 학습을 더 시켜보고 다시 검사한다