9 분 소요

본 글은 2021년 4월에 강의한 스탠포드 대학의 “Convolutional Neural Networks for Visual Recognition” 2021년 강의를 듣고 정리한 내용입니다. 개인 공부 목적으로 작성되었으며, 설명이 맞지 않거나 글 오타가 있으면 알려주시길 바랍니다.

원본 링크 : cs231n.stanford.edu 한글 번역 링크 : aikorea.org - cs231n 강의 링크 : youtube - 2017 Spring (English)

Training Neural Networks

  • 이전 섹션들에서는 레이어를 어떻게 쌓을지, 어떤 손실함수를 써야 하는지 등 정적인 부분을 배웠는데, 이번 섹션부터는 동적인 부분을 알아본다.
  • 이 글에서 다루는 내용은 다음과 같다.
    • Improve your training error
      • (Fancier) Optimizers (SGD, Momentum, Nesterov, AdaGrad, RMSProp, Adam)
      • Learning rate schedules (Decay 기법 및 2차 최적화 L-BFGS)
    • Improve your test error
      • Regularization (Dropout, Data Augmentation, DropConnect, Mixup 등)
      • Choosing Hyperparameters (Random Search vs Grid Search)

(Fancier) Optimizers

  • 경사 하강법(Gradient Descent)에서는 가중치 $W$ 값에 대해서 그레이디언트의 반대 방향으로 step만큼 반복적으로 이동하여 손실함수가 최소가 되게 하는 가중치 $W$ 값을 찾는다.

# Vanilla Gradient Descent
while True:
    weights_grad = evaluate_gradient(loss_fun, data, weights)
    weights += - step_size * weights_grad
  • 즉, 미니 배치 안에서 데이터 손실을 계산하고, 그레이디언트의 반대 방향을 이용해서 파라미터(가중치) 벡터를 업데이트 시켜준다.
  • 하지만, SGD의 문제점들이 있다.

  • Q. 손실이 한 방향으로 빠르게 변하고 다른 방향으로 천천히 변화하면 어떻게 될까?

  • A. 완만한 기울기 축을 따라 진행이 지연되고, 가파른 축을 따라 진동 폭이 커진다.
    • 손실(loss)은 수직 방향의 가중치 변화에 더 민감하게 반응하여 지그재그 형태로 최적점을 찾아간다.
    • 하지만 수평 방향의 가중치는 느리게 변하기 때문에 전체 수렴 속도가 저하되는 단점이 있다.

  • 2번째 문제점으로 지역 최소점(local minima) 또는 안장 지점(saddle point)이 있다.
    • local minima는 그레이디언트가 0이 되어 중간에 멈춰버리는 것이고,
    • saddle point는 순간적으로 그레이디언트가 0에 가까운 지점에서 멈춰버리는 지점을 말한다.
    • 보통 고차원에서는 saddle point가 많이 발생한다.

  • 3번째 문제점으로, 그레이디언트는 각 미니배치에서 오는 것을 매번 업데이트하는데, 이때마다 정확하지 않고 노이즈가 발생할 수 있다는 것이다.

SGD + Momentum

  • SGD의 정의는 다음과 같다.
    • $x_{t+1}=x_t-\alpha \nabla f(x_t)$

      while True:
          dx = compute_gradient(x)
          x -= learning_rate * dx
      
  • 모멘텀(Momentum)은 SGD에 일종의 가속도를 주는 것이다. 즉, 그레이디언트가 0인 지점이 나와도 모멘텀으로 인해 계속 나아갈 수 있게 해준다.
  • SGD+Momentum 정의는 다음과 같다.
    • $v_{t+1}=\rho v_t+\nabla f(x_t)$
    • $x_{t+1}=x_t - \alpha v_{t+1}$
    • 즉, 그레이디언트(gradient) 방향으로 가는 게 아니라, 속도(velocity) 방향으로 나아가게 된다.

      vx = 0
      while True:
          dx = compute_gradient(x)
          vx = rho * vx + dx 
          x -= learning_rate * vx
          # vx = 속도(velocity)
          # rho = 마찰계수
          # 너무 빠르게 가지 않게 마찰을 주는 개념
          # 보통 rho를 0.9나 0.99로 준다.
      
  • SGD+Momentum의 다른 대체 공식도 있다.
    • $v_{t+1}=\rho v_t-\alpha \nabla f(x_t)$
    • $x_{t+1}=x_t+v_{t+1}$

      vx = 0
      while True:
          dx = compute_gradient(x)
          vx = rho * vx - learning_rate * dx
          x += vx
      

이미지출처 1

  • 위 그림의 파란색 선같이 부드럽고 빠르게 이동하는 것을 볼 수 있다.
  • local minima와 saddle point를 극복하여 계속 내려갈 수 있게 되었다.

Nesterov Momentum

  • 위 그림에서 빨간색은 gradient 방향을, 초록색은 velocity 방향을 나타내고, 초록색은 actual step 방향(둘의 가중평균)을 나타낸다.
  • Nesterov Momentum은 계산하는 순서를 바꾼 것이다.
    • 속도를 사용하여 업데이트 해야하는 지점까지 간다.
    • 그 지점에서 그레이디언트를 계산하고 속도와 혼합하여 실제 업데이트 방향을 파악한다.

  • 위 식에서 파란색 부분 $x_t+ \rho v_t$는 미리 속도 방향을 예측해서 그레이디언트를 구한다는 의미다.

AdaGrad

  • Adagrad(Adaptive Gradient)는 각 차원의 제곱합(sum of squares)에 기반한 그레이디언트의 요소별 스케일링을 추가한 것이다. 2
    • 즉, 이전 업데이트의 크기를 기준으로 각 매개 변수에 대한 학습 속도를 조정하는 알고리즘이다.
    • 업데이트 횟수가 적은 매개 변수에 대해 업데이트를 더 크게 확장
    • 더 많이 업데이트되는 매개 변수에 대해 업데이트를 더 작게 조정
  • 공식은 다음과 같다. 3
    • $h\leftarrow h+\frac{\partial L}{\partial W}\odot \frac{\partial L}{\partial W}$
    • $W\leftarrow W - \alpha \frac{1}{\sqrt{h}}\frac{\partial L}{\partial W}$
    • h에 이전 그레이디언트의 제곱들이 누적되어 더해지게 되는데, 매개변수를 업데이트할 때, $\frac{1}{\sqrt{h}}$를 통해 학습률을 조정한다.

      grad_squared = 0
      while True:
          dx = compute_gradient(x)
          grad_squared += dx * dx
          x -= learning_rate * dx / (np.sqrt(grad_squared) + 1e-7)
      

RMSProp

  • 하지만, AdaGrad는 non-convex일 때 saddle point에 걸려 학습이 멈출 수도 있다. 즉, 업데이트가 안되는 현상이 일어날 수 있다.
  • 또한, RMSProp는 극단적으로 학습률이 감소하는 것을 해결하기 위한 AdaGrad의 수정본이다.
    • 이 문제점을 해결하기 위해 나온 것이 RMSProp이다.

      grad_squared = 0
      while True:
          dx = compute_gradient(x)
          grad_squared += decay_rate * grad_squared + (1 - decay_rate) * dx * dx
          x -= learning_rate * dx / (np.sqrt(grad_squared) + 1e-7)
      
    • RMSProp는 지금까지의 모든 시간 단계에서 그레이디언트 제곱합을 누적 저장하는 대신 그레이디언트 제곱합에 대한 decayed moving average(지수 이동 평균)를 사용한다.
    • 보통, decay_rate는 0.9 또는 0.99를 사용한다.
    • 그리고 현재 그레이디언트 제곱에는 (1-decay_rate)를 곱해 이전 누적값에 가산한다.
  • 이를 통해 학습률이 극단적으로 0에 수렴하여 학습이 조기 정체되는 문제를 완화했다.

  • 다음은 소개됐던 방법들을 비교한 그림이다.

이미지출처 2

Adam

  • Adam은 모멘텀과 Ada 계열을 합친 것이다.
  • Adam은 first moment와 second moment를 이용해서 이전의 정보를 유지시킨다.
    • 코드는 다음과 같다.

      first_moment = 0
      second_moment = 0
      while True:
          dx = compute_gradient(x)
          first_moment = beta1 * first_moment + (1 - beta1) * dx # Momentum 부분
          second_moment = beta2 * second_moment + (1 - beta2) * dx *dx # AdaGrad/RMSProp 부분
          x -= learning_rate * first_moment / (np.sqrt(second_moment) + 1e-7) # AdaGrad/RMSProp 부분
      
    • 모멘텀(first_moment) 코드는 그레이디언트의 가중합이다.
    • Ada(second_moment) 코드는 그레이디언트의 제곱을 이용하는 방법이다.
    • 하지만, 초기 step에서 문제가 발생한다. 따라서 보정항을 추가한다.

      first_moment = 0
      second_moment = 0
      while True:
          dx = compute_gradient(x)
          first_moment = beta1 * first_moment + (1 - beta1) * dx # Momentum 부분
          second_moment = beta2 * second_moment + (1 - beta2) * dx *dx # AdaGrad/RMSProp 부분
          first_unbias = first_moment / (1 - beta1**t) # Bias correction
          second_unbias = second_moment / (1 - beta2**t)
          x -= learning_rate * first_unbias / (np.sqrt(second_unbias) + 1e-7) # AdaGrad/RMSProp 부분
      # beta1 = 0.9
      # beta2 = 0.999
      # learning_rate = 1e-3 or 5e-4
      
    • first moment와 second moment를 업데이트하고 현재 step에 맞는 편향 보정(bias correction)을 적용해 초기 단계에서 값이 과도하게 튀는 현상을 방지한다.

Summary

  • 옵티마이저(optimizer)는 딥러닝에서 신경망이 빠르고 정확하게 학습하는 것을 목표로 한다.
  • 다음은 optimizer의 전반적인 내용이다.

image

이미지출처 4

Learning rate schedules

  • 위에 소개된 옵티마이저들은 모두 학습률(learning rate)을 가지고 있다.
    • 학습률은 잠깐 뜸들여 방향을 찾는 것을 말한다.
    • 하지만, 학습률의 하이퍼파라미터 값을 찾는 건 쉽지 않다.
  • 시간의 경과에 따른 학습률 저하(learning rate decay)
    • step decay : 몇 에포크마다 감소시킨다.
    • exponential decay : $\alpha = \alpha_0 e^{-kt}$
    • 1/t decay : $\alpha = \alpha_0 /(1+kt)$

  • 방법으로 몇몇 고정된 지점에서 학습 속도를 줄여본다.
    • 예시로, ResNet 경우, 에포크 30, 60, 90마다 lr에 0.1씩 곱한다.
  • 위 그림에서 갑자기 뚝 떨어지는 이유는 lr을 변경했기 때문이다.

Learning Rate Decay

  • learning rate decay 경우, 처음에 학습률을 높게 설정하고 학습이 진행될수록 점점 낮추는 걸 말한다.
  • 다음은 각 함수에 대한 learning rate 그래프이다.

    • Cosine : $\alpha_t=\frac{1}{2}\alpha_0$ $(1+cos(t\pi /T))$
    • $\alpha_0$ : 초기 학습률
    • $\alpha_t$ : 에포크 t마다의 학습률
    • $T$ : 전체 에포크 수

    • Linear : $\alpha_t=\alpha_0(1-t/T)$

    • Inverse sqrt : $\alpha_t=\alpha_0 / \sqrt{t}$

Detail

  • 지금까지는 모두 1차 미분을 활용한 옵티마이저이다.

  • 여기에 second-order optimization을 사용하는 방법이 있다.
  • 이를 이용하면 minima에 잘 근접할 수 있다.
  • 이계도함수로 구성된 행렬인 Hessian matrix로 계산한다. (역행렬 이용)
    • 예를 들어 해시안 행렬의 고유값이
    • 모두 양수이면 local minima
    • 모두 음수이면 local maxima
    • 양수와 음수가 동시에 있으면 saddle point 이다. 5
  • second-order Taylor expansion
    • $J(\theta)\approx J(\theta_0)+$ $(\theta-\theta_0)^T\nabla_{\theta}J(\theta_0)+$ $\frac{1}{2}(\theta-\theta_0)^TH(\theta-\theta_0)$
  • 임계점을 해결함으로써 뉴런 매개변수 업데이트를 얻는다.
    • $\theta^*=\theta_0-H^{-1}\nabla_{\theta}J(\theta_0)$
  • BFGS : 헤시안을 직접 역행렬 계산하는 대신 랭크 1 업데이트를 통해 역헤시안을 점진적으로 근사한다.
  • L-BFGS(limited memory BFGS) : full inversed Hessian을 생성/저장하지 않고 최근 m개의 벡터만 보관한다.
    • 일반적으로 전체 배치, 결정론적 모드에서 잘 작동한다.
    • 하지만 미니배치 확률적 경사하강 설정에서는 노이즈로 인해 전이 효율이 떨어진다.

Optimizer & LR Summary

  • Momentum or RMSProp : ImageNet 등 대규모 비전 모델 학습에 널리 활용된다.
  • Adam은 대부분의 딥러닝 과제에서 안정적인 기본 베이스라인 옵티마이저로 권장된다.
    • 객체 탐지(Detection), 세그멘테이션(segmentation) 등에 쓸 때 효과적이다.
  • 하지만 full batch 학습 환경이라면 L-BFGS도 좋은 대안이 될 수 있다.
  • 지금까지 알아본 옵티마이저와 학습률은 training error를 줄이고, loss function을 최소화시키는 것이 목적이었다. 하지만 훈련 오차를 줄이는 것만으로는 실제 테스트 환경에서의 일반화 성능을 보장할 수 없다. 이제 모델의 테스트 에러를 개선하기 위한 규제(Regularization) 및 하이퍼파라미터 최적화 기법을 살펴본다.

Improve test error

  • 새로운 데이터가 들어오면 학습 데이터와 검증 데이터 사이의 격차가 크게 날 수 있는데 이 차이를 줄여야 한다.
    • 이 격차가 넓어질수록 과적합(overfitting)이 된다는 의미이다.
  • 손실함수를 최소화시키기 위해 더 나은 최적화(optimization) 알고리즘을 사용해보았다.
  • 하지만 테스트 시 성능을 올리기 위해서는 어떻게 해야 될까?

  • 한 가지 방법으로 Early Stopping이 있다.
    • 검증 데이터가 감소하거나 학습(train)데이터를 훈련시 시간이 너무 오래 걸릴 때 훈련중인 모델을 멈춘다.
  • 또 다른 방법으로 Model Ensembles이 있다.
    • 다수의 독립적인 모델을 학습시키고 이들의 평균을 이용하는 것이다.

Regularization

  • 단일 모델 성능을 향상시킬 수 있는 방법으로 규제(Regularization)이 있다.
  • 모델의 어떤 것을 추가하여 학습 데이터에 fit하는 것을 막아주는 역할을 한다.

  • 이전에 L1과 L2를 배웠지만 실제로는 잘 사용하지는 않는다.
  • 다음은 규제를 하기 위한 방법들이다.

Dropout

  • Dropout은 forward pass 과정에서 몇몇 뉴런을 0으로 만든다.
    • forward pass 마다 그 모양은 계속 바뀐다.
p = 0.5 # 활성화 확률

def train_step(X):

    # 3 레이어의 forward pass
    H1 = np.maximum(0, np.dot(W1, X) + b1)
    U1 = np.random.rand(*H1.shape) < p # 첫번째 dropout mask
    H1 *= U1 # drop
    H2 = np.maximum(0, np.dot(W2, H1) + b2)
    U2 = np.random.rand(*H2.shape) < p # 두번째 dropout mask
    H2 *= U2 # drop
    out = np.dot(W3, H2) + b3

    # backward pass : compute_gradient()
    # perform parameter update : step()
    # ...

  • 드롭아웃(Dropout)은 특징(feature) 간의 상호작용을 방지해준다.
    • 모델이 고양이를 예측할 때 다양한 특징들을 고르게 이용할 수 있게 해준다.
  • 따라서, 드롭아웃은 과적합을 막아주고 단일 모델로 매개변수를 공유하는 앙상블(ensemble) 효과도 가질 수 있다.
    • 각 binary mask는 하나의 모델이 된다.
  • Q. FC에서는 드롭아웃을 사용하지만, Conv layer에서는 드롭아웃이 왜 적용이 안 될까?
    • A. 컨볼루션 레이어의 모든 이미지 데이터들이 서로 정보를 공유하기 때문이다.

  • test time으로 넘어왔다.
  • 드롭아웃은 출력을 무작위로 만든다.

  • 테스트 시 드롭아웃 확률을 곱한다.

    def predict(X):
      # ensembled forward pass
      H1 = np.maximum(0, np.dot(W1, X) + b1) * p # scale the activation
      H2 = np.maximum(0, np.dot(W2, H1) + b2) * p # scale the activation
      out = np.dot(W3, H2) + b3
    
  • 테스트가 학습의 절반밖에 되지 않기 때문에 dropout probability(=p)를 출력에 곱한다.
  • 즉, 일부 노드를 무작위로 0으로 만들어주고, 테스트 때는 p를 곱해준다. 1

  • 대부분은 Inverted dropout를 사용한다.

    p = 0.5 # 활성화 확률
    
    def train_step(X):
    
        # 3 레이어의 forward pass
        H1 = np.maximum(0, np.dot(W1, X) + b1)
        U1 = np.random.rand(*H1.shape) / p # 첫번째 dropout mask
        H1 *= U1 # drop
        H2 = np.maximum(0, np.dot(W2, H1) + b2)
        U2 = np.random.rand(*H2.shape) / p # 두번째 dropout mask
        H2 *= U2 # drop
        out = np.dot(W3, H2) + b3
    
        # backward pass : compute_gradient()
        # perform parameter update : step()
        # ...
    
    def predict(X):
        # ensembled forward pass
        H1 = np.maximum(0, np.dot(W1, X) + b1) # no scaling necessary
        H2 = np.maximum(0, np.dot(W2, H1) + b2) 
        out = np.dot(W3, H2) + b3
    
  • 테스트 때 기존의 연산을 이용해 학습시간에 p로 나눠준다.

Regularization Summary

  • Training : 랜덤성을 추가해 과적합(overfitting)을 막는다.
    • $y=fw(x,z)$
    • 랜덤 미니 배치의 통계를 사용하여 정규화해준다.
  • Testing : 랜덤성을 평균화시켜 일반화(generalization) 효과를 준다.
    • $y=f(x)=E_z[f(x,z)]=$ $\int p(z)f(x,z)dz$
    • 정규화를 위해 고정 통계(fixed stats)를 사용한다.

Data Augmentation

DropConnect

  • DropConnect는 활성화가 아닌 가중치를 임의적으로 0을 만드는 걸 말한다.
    • 가중치를 0으로 만드는 건 training 때 그렇고, testing에는 모든 연결선을 사용한다.

Fractional Max Pooling

  • Fractional Max Pooling은 풀링이 될 지역을 랜덤하게 설정하여 sampling 하는 것이다.
    • Training : 랜덤화된 풀링 지역을 사용한다.
    • Testing : 몇몇 지역의 예측값들을 평균화한다.

Stochastic Depth

  • training 때는 일부 레이어를 제외하고 학습을 진행한다.
  • testing 때는 전체 네트워크를 사용한다.

Cutout / Random Crop

  • training 때는 이미지의 지역을 랜덤하게 설정해 0으로 만든다.
  • testing 때는 모든 이미지를 사용한다.
  • CIFAR 같은 작은 데이터셋은 잘 작동하지만, ImageNet 같은 큰 데이터셋에서는 덜 작동한다고 한다.

Mixup

  • training 때는 랜덤하게 이미지를 섞은(blend)것을 학습한다.
  • testing 때는 원본 이미지를 사용한다.

Choosing Hyperparameters

  • 하이퍼파라미터를 선택하는 건 총 6단계로 살펴봐야 한다.
  • 개요는 다음과 같다.
Step 1: Check initial loss
Step 2: Overfit a small sample
Step 3: Find LR that makes loss go down
  - good LR : 1e-1, 1e-2, 1e-3, 1e-4
Step 4: Coarse grid, train for ~1-5 epochs
  - good weight decay : 1e-4, 1e-5, 0
Step 5: Refine grid, train longer
Step 6: Look at loss and accuracy curves
Step 7: GOTO step 5

  • Grid search는 탐색할 하이퍼파라미터 값의 격자를 고정 설정하고 각 조합에 대해 모델을 학습하여 성능을 평가한다. 6
  • Random search는 하이퍼파라미터 정의 구간 내에서 무작위 조합을 샘플링하여 모델을 학습하고 점수를 측정한다. 6
    • 이렇게 하면 시도되는 매개 변수 조합의 수를 명시적으로 제어할 수 있다.
    • 검색 반복 횟수는 시간 또는 리소스에 따라 설정된다.
  • 위의 그림처럼 grid layout에서는 9개의 시도 중 3번의 개별 위치만 테스트한다. random layout에서는 9개의 모든 부분이 고유 값을 탐색한다.

한계 및 트레이드오프

신경망 최적화와 규제, 하이퍼파라미터 탐색 기법에는 다음과 같은 본질적 제약과 트레이드오프가 존재한다.7

  • 1차 옵티마이저의 하이퍼파라미터 민감도: Momentum, RMSProp, Adam 등 1차 도함수 기반 옵티마이저는 연산량이 $O(D)$로 가볍지만, 초기 학습률과 감쇠 계수($\beta_1, \beta_2$) 등 하이퍼파라미터 설정에 따라 수렴 속도와 최종 성능 격차가 크게 발생한다.
  • 2차 최적화(L-BFGS)의 확장성 한계: 헤시안 행렬을 근사하는 L-BFGS는 곡률 정보를 반영해 정확한 갱신 경로를 제공하지만, 메모리와 역행렬 근사 연산 비용이 크고 확률적 미니배치 샘플링의 노이즈가 존재하는 심층 신경망 환경에서는 안정적으로 작동하기 어렵다.
  • 규제 기법의 수렴 지연 트레이드오프: Dropout, DropConnect, Stochastic Depth, Mixup 등 정규화 기법은 과적합을 방지하고 일반화 성능을 향상시키지만, 네트워크의 유효 용량을 일시적으로 제한하고 신호 전달에 노이즈를 주입하므로 모델이 수렴하기까지 더 많은 훈련 에포크와 계산 시간이 요구된다.
  • 하이퍼파라미터 탐색 공간의 연산 비용: Random Search는 Grid Search 대비 비효율적인 중복 탐색을 줄여주지만, 매개변수 차원이 증가할수록 전역 최적점에 도달하기 위해 필요한 시도 횟수가 증가하므로 고비용 딥러닝 모델 훈련 시 충분한 컴퓨팅 자원이 뒷받침되어야 한다.

핵심 정리

  • SGD의 한계와 모멘텀(Momentum): 바닐라 SGD는 민감도 차이에 따른 지그재그 진동, Local Minima 및 Saddle Point 취약점이 존재하며, 모멘텀과 Nesterov 모멘텀은 관성(velocity)을 활용해 이를 극복한다.
  • 적응형 학습률(Adaptive LR): AdaGrad는 누적 그레이디언트 제곱을 고려해 매개변수별 업데이트 폭을 조절하며, RMSProp은 지수 이동 평균(EMA) 감쇠를 도입해 학습 정체 문제를 개선했다.
  • Adam 옵티마이저: Momentum(1차 모멘트)과 RMSProp(2차 모멘트)의 장점을 결합하고 편향 보정(Bias Correction)을 추가하여 대부분의 컴퓨터 비전 딥러닝 과제에서 가장 널리 권장되는 옵티마이저이다.
  • 학습률 스케줄링: Cosine, Step, Linear, Inverse Sqrt decay 등의 기법을 통해 후반부로 갈수록 학습률을 안정적으로 낮추어 최적해 수렴을 돕는다.
  • 과적합 방지와 규제(Regularization): Early Stopping과 앙상블 외에도 학습 시 임의성을 부여하고 테스트 시 평균화하는 Dropout, Inverted Dropout, DropConnect, Stochastic Depth, Cutout, Mixup 등이 효과적이다.
  • 하이퍼파라미터 탐색 전략: 차원의 저주와 중요 변수 탐색 효율성을 고려할 때, 정해진 격자를 순회하는 Grid Search보다 고유 값을 더 고르게 샘플링하는 Random Search가 탐색 효율성 측면에서 우수하다.

References

댓글남기기