CS231n Lecture 3 - Regularization and Optimization

LECTURE 글 목록
목차

핵심 한 줄 정리

Regularization은 모델이 training data에 너무 잘 맞는 것을 막아 test data에서 더 잘 동작하게 만드는 장치이고, optimization은 loss를 줄이는 방향으로 파라미터 WW를 찾는 과정이다.

이번 강의의 흐름은 loss function -> regularization -> gradient descent -> SGD -> momentum / RMSProp / Adam -> learning rate schedule로 이어진다.

반드시 기억할 개념

Loss function

모델이 얼마나 나쁜지를 수치로 나타낸 값이 loss이다.

데이터셋을 다음과 같이 두자.

(x1,y1),(x2,y2),,(xN,yN)(x_1,y_1), (x_2,y_2), \dots, (x_N,y_N)

각 샘플의 loss를 LiL_i라고 하면 전체 data loss는 보통 평균으로 계산한다.

Ldata=1Ni=1NLi(f(xi,W),yi)L_{\text{data}} = \frac{1}{N} \sum_{i=1}^{N} L_i(f(x_i,W), y_i)

여기서 f(xi,W)f(x_i,W)는 모델이 xix_i에 대해 만든 score이고, yiy_i는 정답 라벨이다.

Lecture 2에서 본 softmax loss도 이 LiL_i의 한 예이다.

Regularization

전체 loss는 data loss와 regularization term을 합친 형태로 쓴다.

L(W)=1Ni=1NLi(f(xi,W),yi)+λR(W)L(W) = \frac{1}{N} \sum_{i=1}^{N} L_i(f(x_i,W), y_i) + \lambda R(W)

R(W)R(W)는 regularization term이고, λ\lambda는 regularization strength이다.

  • λ=0\lambda=0: regularization을 사용하지 않는 경우이다.
  • λ\lambda가 커짐: training data에 너무 맞추는 것을 더 강하게 막는다.

Regularization의 핵심은 일부러 training data에서 조금 손해를 보더라도 test data에서 더 잘 동작하게 만드는 것이다.

규제는 데이터 손실만 최소화하는 게 아니라, 원하는 가중치 구조를 갖도록 모델을 유도하는 장치다.

강의에서는 이를 단순한 곡선 fitting 예시로 설명한다. 모든 training point를 완벽히 지나는 복잡한 함수보다, 몇 개의 점을 조금 놓치더라도 더 단순한 함수가 unseen data에 더 잘 맞을 수 있다는 직관이다.

L2 regularization

L2 regularization은 weight의 제곱합을 penalty로 사용한다.

R(W)=klWk,l2R(W) = \sum_k \sum_l W_{k,l}^2

L2는 weight 값들이 한쪽에 몰리기보다 여러 차원에 작게 퍼지는 것을 선호한다.

예를 들어 같은 score를 만드는 두 weight가 있다고 하자.

w1=[1,0,0,0]w_1 = [1,0,0,0] w2=[14,14,14,14]w_2 = \left[\frac14,\frac14,\frac14,\frac14\right]

L2 penalty는 다음과 같다.

R(w1)=12=1R(w_1)=1^2=1 R(w2)=4(14)2=14R(w_2) = 4 \cdot \left(\frac14\right)^2 = \frac14

따라서 L2 regularization은 w2w_2처럼 값이 더 spread out된 weight를 선호한다.

L1 regularization

L1 regularization은 weight의 절댓값 합을 penalty로 사용한다.

R(W)=klWk,lR(W) = \sum_k \sum_l |W_{k,l}|

L1은 필요 없는 weight를 00으로 보내는 방향이 강하다. 그래서 sparse한 weight를 만들기 쉽다.

위 예시에서는 L1 penalty가 같다.

R(w1)=1R(w_1)=1 R(w2)=414=1R(w_2) = 4 \cdot \frac14 = 1

즉, 이 특정 예시에서는 L1 관점에서 두 weight가 동등하다. 하지만 실제 최적화에서는 성능에 거의 영향을 주지 않는 값들이 있으면 L1이 그 값을 00으로 밀어 sparse한 해를 만들기 쉽다.

Optimization

Regularization까지 포함한 loss를 정의했다면, 다음 질문은 이것이다.

어떤 WW가 가장 좋은가?

Optimization은 loss를 최소화하는 WW를 찾는 과정이다.

W=argminWL(W)W^* = \arg\min_W L(W)

강의에서는 이를 loss landscape로 설명한다. 파라미터 WW의 값에 따라 위치가 정해지고, 그 위치에서의 높이가 loss라고 생각하면 된다.

우리가 원하는 것은 loss landscape에서 가장 낮은 지점으로 내려가는 것이다.

Gradient

1차원에서 derivative는 다음처럼 정의된다.

dfdx=limh0f(x+h)f(x)h\frac{df}{dx} = \lim_{h \to 0} \frac{f(x+h)-f(x)}{h}

여러 차원에서는 각 파라미터 방향에 대한 미분을 모은 gradient를 사용한다.

WL(W)\nabla_W L(W)

Gradient는 loss가 가장 빠르게 증가하는 방향을 가리킨다. 따라서 loss를 줄이려면 negative gradient 방향으로 움직여야 한다.

Numerical gradient와 analytic gradient

Numerical gradient는 작은 hh를 실제로 더해보면서 기울기를 근사하는 방식이다.

장점은 구현이 쉽다는 점이다. 하지만 느리고 근사값이라는 단점이 있다.

Analytic gradient는 미분 공식과 chain rule로 직접 gradient를 계산하는 방식이다.

장점은 빠르고 정확하다는 점이다. 하지만 직접 구현하면 실수할 수 있다.

그래서 실제 과제나 구현에서는 analytic gradient를 작성한 뒤, numerical gradient로 gradient check를 해서 구현이 맞는지 확인한다.

Gradient descent

Gradient descent는 현재 위치에서 gradient를 계산하고, negative gradient 방향으로 한 걸음 이동하는 방법이다.

WWαWL(W)W \leftarrow W - \alpha \nabla_W L(W)

여기서 α\alpha는 learning rate 또는 step size이다.

  • α\alpha가 너무 크면 loss landscape에서 튕겨 나갈 수 있다.
  • α\alpha가 너무 작으면 학습이 너무 느리다.

멈추는 기준은 보통 정해진 iteration 수를 채우거나, loss가 더 이상 충분히 줄어들지 않는 경우이다.

Stochastic Gradient Descent

전체 training set으로 gradient를 계산하면 데이터가 클수록 비용이 너무 크다.

SGD는 매번 전체 데이터를 쓰는 대신 mini-batch를 뽑아 gradient를 계산한다.

WWαWLB(W)W \leftarrow W - \alpha \nabla_W L_{\mathcal{B}}(W)

여기서 B\mathcal{B}는 mini-batch이다.

예를 들어 batch size가 256256이면, 매 update마다 training data 중 256256개만 사용해 gradient를 계산한다.

전체 training data를 한 번 모두 훑는 단위를 epoch이라고 한다.

SGD는 계산이 빠르지만 mini-batch를 사용하므로 update 방향에 noise가 생긴다.

SGD의 문제점

SGD에는 몇 가지 대표적인 문제가 있다.

  • Poor conditioning: 좁고 긴 valley에서는 steep한 방향으로 크게 흔들리고, 정작 가야 할 방향으로는 천천히 갈 수 있다.
  • Local minimum: gradient가 00인 지점에 갇힐 수 있다.
  • Saddle point: 어떤 방향으로는 내려갈 수 있지만 현재 지점의 gradient가 00이라 멈출 수 있다.
  • Noisy update: mini-batch마다 gradient가 달라 update 방향이 흔들릴 수 있다.

특히 고차원 neural network에서는 local minimum보다 saddle point가 더 자주 문제가 될 수 있다.

SGD + Hessian eigenvalues + ill-conditioned + narrow valley

Momentum

Momentum은 이전 gradient들의 방향을 어느 정도 기억해서 velocity를 만든다.

vρvαWL(W)v \leftarrow \rho v - \alpha \nabla_W L(W) WW+vW \leftarrow W + v

ρ\rho는 momentum coefficient이다.

Momentum은 공이 언덕을 굴러 내려가며 속도를 얻는 것처럼 생각할 수 있다.

  • 같은 방향으로 계속 gradient가 나오면 속도가 누적된다.
  • 지그재그로 흔들리는 방향은 서로 상쇄된다.
  • saddle point나 작은 local minimum을 지나칠 가능성이 커진다.

RMSProp

RMSProp은 gradient의 제곱에 대한 running average를 사용해 각 파라미터 방향의 step size를 조절한다.

cachedecaycache+(1decay)dW2cache \leftarrow decay \cdot cache + (1-decay) \cdot dW^2 WWαdWcache+ϵW \leftarrow W - \alpha \frac{dW}{\sqrt{cache}+\epsilon}

여기서 dWdWWW에 대한 gradient이고, ϵ\epsilon00으로 나누는 것을 막기 위한 작은 값이다.

RMSProp의 직관은 다음과 같다.

  • gradient가 계속 큰 방향: denominator가 커져 step이 작아진다.
  • gradient가 작은 방향: denominator가 작아져 상대적으로 더 크게 움직인다.

좁고 긴 valley에서 steep한 방향으로는 덜 움직이고, flat한 방향으로는 더 움직이게 해준다.

Adam intuition

Adam은 Momentum과 RMSProp을 합친 optimizer로 볼 수 있다.

첫 번째 moment는 momentum처럼 gradient의 running average를 저장한다.

mβ1m+(1β1)dWm \leftarrow \beta_1 m + (1-\beta_1) dW

두 번째 moment는 RMSProp처럼 squared gradient의 running average를 저장한다.

sβ2s+(1β2)dW2s \leftarrow \beta_2 s + (1-\beta_2) dW^2

Update는 대략 다음 형태이다.

WWαms+ϵW \leftarrow W - \alpha \frac{m}{\sqrt{s}+\epsilon}

실제 Adam에서는 초기 시점에 mmss00에서 시작하기 때문에 bias correction을 추가한다.

대표적인 기본값은 다음과 같다.

  • β1=0.9\beta_1 = 0.9
  • β2=0.999\beta_2 = 0.999
  • ϵ=108\epsilon = 10^{-8}

Adam 또는 AdamW는 새로운 문제에서 첫 번째로 시도하기 좋은 optimizer이다.

first_moment = 0
second_moment = 0

while True: 
    dW = compute_gradient(W)
    first_moment = beta1 * first_moment + (1 - beta1) * dW
    second_moment = beta2 * second_moment + (1 - beta2) * dW * dW
    W -= learning_rate * first_moment / (np.sqrt(second_moment ) + 1e-7)

Adam first moment vs Momentum

Adam의 첫 번째 moment는 Momentum과 비슷하게 과거 gradient 방향을 기억하지만, 목적이 조금 다르다.

Momentum의 velocity는 실제 update 방향에 가까운 누적량이다.

vρvαdWv \leftarrow \rho v - \alpha dW

같은 dWdW가 계속 나오면 정상상태에서 대략 다음과 같이 된다.

vα1ρdWv \rightarrow -\frac{\alpha}{1-\rho} dW

ρ=0.9\rho=0.9이면 과거 gradient가 크게 누적되어 관성 효과가 생긴다.

반면 Adam의 첫 번째 moment는 gradient의 exponential moving average이다.

mβ1m+(1β1)dWm \leftarrow \beta_1 m + (1-\beta_1)dW

같은 dWdW가 계속 나오면 첫 번째 moment는 dWdW가 된다.

mdWm \rightarrow dW

왜냐하면 가중치 합이 1이기 때문이다.

(1β1)(1+β1+β12+)=1(1-\beta_1)(1+\beta_1+\beta_1^2+\cdots)=1

따라서 Adam의 mm은 gradient를 크게 증폭하는 velocity라기보다, 최근 gradient들의 평균 방향을 추정하는 값이다.

Adam은 이후 이 값을 second moment의 square root로 나누어 update한다.

WWαm^s^+ϵW \leftarrow W - \alpha \frac{\hat{m}}{\sqrt{\hat{s}}+\epsilon}

Adam은 gradient 방향 정보를 사용하되, 각 parameter별 gradient scale로 나누어 update 크기를 조절한다.

  • m^\hat{m}: 최근 gradient의 평균 방향
  • s^\sqrt{\hat{s}}: 최근 gradient 크기의 평균적인 scale
m^s^+ϵ\frac{\hat{m}}{\sqrt{\hat{s}}+\epsilon}
  • Momentum: 과거 update 방향을 누적해서 관성 있는 velocity를 만든다.
  • Adam first moment: 최근 gradient의 평균 방향을 추정한다.
  • Adam second moment: squared gradient의 running average를 추정해 parameter별 step size를 조절한다.

Adam full form

초기값:

m0=0m_0 = 0 s0=0s_0 = 0

매 iteration에서 gradient를 계산한다.

gt=WL(Wt)g_t = \nabla_W L(W_t)

첫 번째 moment는 gradient의 exponential moving average이다.

mt=β1mt1+(1β1)gtm_t = \beta_1 m_{t-1} + (1-\beta_1)g_t

두 번째 moment는 squared gradient의 exponential moving average이다.

st=β2st1+(1β2)gt2s_t = \beta_2 s_{t-1} + (1-\beta_2)g_t^2

Adam은 m0=0m_0=0, s0=0s_0=0에서 시작하기 때문에 초반 추정값이 0 쪽으로 bias된다. 그래서 bias correction을 적용한다.

m^t=mt1β1t\hat{m}_t = \frac{m_t}{1-\beta_1^t} s^t=st1β2t\hat{s}_t = \frac{s_t}{1-\beta_2^t}

최종 update는 다음과 같다.

Wt+1=Wtαm^ts^t+ϵW_{t+1} = W_t - \alpha \frac{\hat{m}_t}{\sqrt{\hat{s}_t}+\epsilon}
first_moment = 0
second_moment = 0

for t in range(1, num_iterations):
    dW = compute_gradient(W)

    first_moment = beta1 * first_moment + (1 - beta1) * dW
    second_moment = beta2 * second_moment + (1 - beta2) * dW * dW

    first_unbias = first_moment / (1 - beta1 ** t)
    second_unbias = second_moment / (1 - beta2 ** t)

    W -= learning_rate * first_unbias / (np.sqrt(second_unbias) + eps)

AdamW와 weight decay

L2 regularization은 loss에 parameter 크기 penalty를 추가하는 방식이다.

L=Ldata(W)+λ2W22L = L_{data}(W) + \frac{\lambda}{2}\|W\|_2^2

12\frac{1}{2}를 붙이는 이유는 미분했을 때 계수를 깔끔하게 만들기 위해서이다.
여기서 WW는 input data가 아니라 optimizer가 업데이트하는 parameter를 의미한다.

그러면 gradient는 다음처럼 된다.

WL=dW+λW\nabla_W L = dW + \lambda W

여기서 dWdW는 data loss에 대한 gradient이다.

일반적인 Adam에서 L2 regularization을 gradient에 섞으면, Adam의 moment 계산에 λW\lambda W 항까지 같이 들어간다.

gdW+λWg \leftarrow dW + \lambda W mβ1m+(1β1)gm \leftarrow \beta_1 m + (1-\beta_1)g sβ2s+(1β2)g2s \leftarrow \beta_2 s + (1-\beta_2)g^2 WWαm^s^+ϵW \leftarrow W - \alpha \frac{\hat{m}}{\sqrt{\hat{s}}+\epsilon}

즉 regularization 항인 λW\lambda W가 first moment와 second moment 계산에 직접 섞인다.

반면 AdamW는 moment 계산에는 data loss gradient만 사용한다.

mβ1m+(1β1)dWm \leftarrow \beta_1 m + (1-\beta_1)dW sβ2s+(1β2)dW2s \leftarrow \beta_2 s + (1-\beta_2)dW^2

그리고 weight decay는 update 단계에서 별도로 적용한다.

WWαm^s^+ϵαλWW \leftarrow W - \alpha \frac{\hat{m}}{\sqrt{\hat{s}}+\epsilon} - \alpha \lambda W

같은 식을 다음처럼 볼 수도 있다.

W(1αλ)Wαm^s^+ϵW \leftarrow (1-\alpha \lambda)W - \alpha \frac{\hat{m}}{\sqrt{\hat{s}}+\epsilon}

따라서 AdamW는 다음 두 작업을 분리한다.

  • data loss gradient로 Adam의 moment를 계산한다.
  • weight decay는 parameter를 직접 줄이는 방식으로 따로 적용한다.

핵심 차이:

  • Adam + L2 regularization: λW\lambda Wmm, ss 계산에 섞인다.
  • AdamW: mm, ssdWdW만으로 계산하고, λW\lambda W는 별도 decay로 적용한다.

그래서 AdamW에서는 regularization이 momentum이나 squared gradient estimate에 직접 섞이지 않는다.

이는 λW\lambda W 가 Adam의 adaptive scaling을 타지 않게 하여, weight decay가 parameter를 일정 비율로 직접 줄이는 역할만 하도록 분리하기 위함이다.(decoupled weight decay)

Learning rate schedule

Learning rate는 고정된 값으로만 둘 필요가 없다.

학습이 진행되면서 learning rate를 바꾸는 방법을 learning rate schedule이라고 한다.

대표적인 방식은 다음과 같다.

  • Step decay: 특정 iteration마다 learning rate를 1/101/10 등으로 줄인다.

  • Cosine decay: cosine 곡선을 따라 learning rate를 부드럽게 줄인다.

    αt=12α0(1+cos(tπT))\alpha_t = \frac{1}{2}\alpha_0 \left(1 + \cos\left(\frac{t\pi}{T}\right)\right) α0:Initial learning rate\alpha_0 : \text{Initial learning rate} αt:Learning rate at epoch t\alpha_t : \text{Learning rate at epoch } t T:Total number of epochsT : \text{Total number of epochs}
  • Linear decay: 일정한 비율로 learning rate를 줄인다.

    αt=α0(1tT)\alpha_t = \alpha_0 \left(1-\frac{t}{T}\right)
  • Warmup: 초반에 learning rate를 작게 시작해서 점점 키운다.

Warmup은 특히 큰 모델을 학습할 때 자주 사용된다. 처음부터 큰 learning rate를 쓰면 학습이 불안정해질 수 있기 때문이다.

Batch size와 learning rate

강의에서는 경험적인 rule of thumb으로 linear scaling rule을 언급한다.

Batch size를 nn배 키우면 learning rate도 nn배 키우는 방식이다.

엄밀한 법칙이라기보다는 많은 문제에서 잘 동작하는 경험적 기준이다.

Second-order optimization

지금까지 다룬 방법들은 gradient를 사용하는 first-order optimization이다.

Second-order optimization은 Hessian, 즉 second derivative 정보를 사용한다.

Gradient가 1차 근사라면, Hessian은 2차 곡률 정보를 사용해 loss landscape를 더 잘 근사하려는 방식이다.

하지만 deep learning에서는 잘 쓰지 않는다.

이유는 Hessian이 너무 크기 때문이다. 파라미터가 수백만, 수십억 개가 되면 Hessian matrix를 계산하고 저장하는 것 자체가 거의 불가능하다.

그래서 큰 neural network에서는 보통 first-order method인 SGD, Momentum, Adam, AdamW를 사용한다.

과제에서 확인할 것

homework로 직접 언급된 부분은 gradient와 Adam 구현 쪽이다.

  • Softmax loss와 regularization term을 합친 전체 loss를 WW에 대해 미분할 수 있는가
  • Analytic gradient를 구현한 뒤 numerical gradient로 gradient check를 수행할 수 있는가
  • Adam의 naive implementation에서 초기 step이 커질 수 있는 이유와 bias correction이 이를 어떻게 보정하는지 확인할 것