Deep Learning/cs231n

cs231n Lecture 6 - Training Neural Networks I

준성(JunSeong) 2026. 6. 18. 20:54

강의: Stanford CS231n - Deep Learning for Computer Vision

챕터: Lecture 6 - Training Neural Networks I

영상: YouTube 바로가기

#cs231n #딥러닝 #활성화함수 #가중치초기화 #배치정규화 #BatchNormalization #ReLU #Xavier초기화

5강에서 CNN의 구조를 배웠으니, 이번엔 그 네트워크를 실제로 어떻게 잘 학습시키느냐의 문제다. 이론적으로 모델을 설계했다고 해서 바로 잘 돌아가지는 않는다. 활성화 함수 선택부터 가중치 초기화, 배치 정규화까지 - 이 선택들이 학습이 될지 안 될지를 가르는 경우가 많다. 6강은 그 실전 지식을 다룬다.

•   •   •

1. 활성화 함수 (Activation Functions)

뉴런 하나가 하는 일을 수식으로 쓰면 \(f\!\left(\sum_i w_i x_i + b\right)\) 이다. 가중합을 구하고 거기에 비선형 함수 \(f\)를 씌우는 구조다. \(f = Wx\) 만으로는 아무리 레이어를 쌓아도 결국 하나의 선형 변환이 되기 때문에, 각 레이어 사이에 비선형 함수를 끼워야 표현력이 생긴다.

뉴런 구조 다이어그램

▲ 생물학적 뉴런을 모델링한 인공 뉴런 구조. 입력 신호를 가중합한 뒤 활성화 함수 f를 거쳐 출력한다 (출처: cs231n Lecture 6)

Sigmoid

$$\sigma(x) = \frac{1}{1 + e^{-x}}$$

Sigmoid는 출력을 \([0, 1]\) 범위로 압축한다. 뉴런의 "발화율"로 해석하기 좋아서 초창기에 많이 쓰였는데, 지금은 hidden layer에서는 거의 안 쓴다. 문제가 세 가지다.

Sigmoid 3가지 문제

▲ Sigmoid 함수와 3가지 문제점 (출처: cs231n Lecture 6)

첫 번째, saturated neuron이 gradient를 죽인다. \(x\)가 양 끝으로 치우치면 sigmoid는 거의 평평해진다. 역전파 때 이 평평한 미분값이 곱해지다 보면, 레이어가 깊어질수록 gradient가 0에 수렴한다. 이게 vanishing gradient 문제다. 아래 다이어그램이 이 과정을 잘 보여준다.

Sigmoid gate backprop

▲ Sigmoid gate의 역전파. 포화 구간에서 dσ/dx가 0에 가까워져 upstream gradient가 거의 전달되지 않는다 (출처: cs231n Lecture 6)

두 번째, 출력이 zero-centered가 아니다. Sigmoid 출력은 항상 양수(0~1)다. 이걸 다음 레이어의 입력 \(x\)로 넣으면, \(x\)가 항상 양수이므로 weight에 대한 gradient가 항상 같은 부호를 갖게 된다. gradient 업데이트 방향이 아래 그림처럼 지그재그로만 이동 가능해서 비효율적이다.

zig-zag gradient

▲ 입력이 항상 양수일 때 gradient가 1, 3사분면 방향으로만 업데이트 가능해 지그재그 경로가 생긴다. 이게 zero-mean 데이터가 필요한 이유이기도 하다 (출처: cs231n Lecture 6)

세 번째, exp() 연산이 상대적으로 비싸다. 큰 문제는 아니지만, ReLU와 비교하면 차이가 난다.

tanh

tanh 함수

▲ tanh 함수. zero-centered이지만 양 끝 saturation 문제는 여전하다 (출처: cs231n Lecture 6)

tanh는 출력 범위가 \([-1, 1]\)이라 zero-centered 문제는 해결했다. sigmoid보다는 낫다. 하지만 양 끝에서 saturation이 일어나고 gradient가 죽는 건 똑같다. sigmoid보다 낫지만, ReLU 계열만큼은 아니다.

ReLU (Rectified Linear Unit)

ReLU 함수와 장단점

▲ ReLU의 장단점. 양수 구간에서 saturation이 없고 sigmoid/tanh 대비 약 6배 빠른 수렴을 보인다 (출처: cs231n Lecture 6)

현재 가장 많이 쓰이는 함수다. 양수 구간에서 saturation이 없고, 연산도 단순해서 sigmoid/tanh 대비 수렴 속도가 약 6배 빠르다고 알려져 있다. AlexNet 이후 딥러닝 대중화에 ReLU가 결정적인 역할을 했다.

문제는 Dead ReLU다. \(x \leq 0\) 구간에서 gradient가 아예 0이 된다. 한번 죽은 뉴런은 다시 살아나지 않는다. 학습률이 너무 크거나 초기화를 잘못하면 발생한다.

Dead ReLU 시각화

▲ Dead ReLU 시각화. 데이터 분포(DATA CLOUD) 밖에 놓인 뉴런(빨간선)은 어떤 입력이 들어와도 activate되지 않아 영구적으로 죽는다 (출처: cs231n Lecture 6)

Dead ReLU 방지 팁으로 bias를 0.01처럼 작은 양수로 초기화해서 초기에 뉴런이 activate될 확률을 높이는 방법을 쓰기도 한다. 효과가 항상 보장된 건 아니지만 나쁘진 않다.

Leaky ReLU / PReLU

Leaky ReLU와 PReLU

▲ Leaky ReLU와 PReLU. 음수 구간에도 기울기를 줘서 dead neuron을 막는다 (출처: cs231n Lecture 6)

Dead ReLU 문제를 해결하려고 음수 구간에 작은 기울기를 준 버전이다. \(f(x) = \max(0.01x,\; x)\). PReLU는 이 기울기 값 자체를 backprop으로 학습하는 변형이다. 잘 되는 경우도 있지만 항상 ReLU보다 낫다고 보장할 수는 없다.

ELU (Exponential Linear Unit)

ELU 함수

▲ ELU. 음수 구간이 부드럽게 수렴하며 평균 출력이 0에 더 가깝다 (출처: cs231n Lecture 6)

ReLU의 장점은 가져가면서, 음수 구간에서 부드럽게 0에 가까워지는 함수다. 출력의 평균이 0에 더 가까워지는 효과가 있다. 다만 exp() 연산이 들어가서 Leaky ReLU보다 계산이 더 무겁다.

Maxout

$$f(x) = \max(w_1^T x + b_1, \; w_2^T x + b_2)$$

ReLU와 Leaky ReLU를 일반화한 형태다. 두 선형 함수 중 큰 값을 취하므로 saturation도 없고 gradient가 죽지도 않는다. 단점은 파라미터 수가 두 배가 된다는 것이다.

6종 활성화함수 비교

▲ Sigmoid, tanh, ReLU, Leaky ReLU, Maxout, ELU 한눈에 비교 (출처: cs231n Lecture 6)

결론: 기본은 ReLU다. 학습률만 조심하면서 쓰다가 잘 안 되면 Leaky ReLU나 ELU를 시도해봐라. tanh도 나쁘진 않지만 ReLU 계열이 더 잘 된다. Sigmoid는 출력 레이어(확률 출력)가 아닌 이상 hidden layer에서는 쓰지 마라.
•   •   •

2. 데이터 전처리 (Data Preprocessing)

네트워크에 데이터를 넣기 전에 기본적인 전처리를 한다. 대표적인 방법은 두 가지 - 평균 빼기(zero-centering)와 정규화(normalization)다.

데이터 전처리

▲ 원본 데이터 → zero-centered → normalized. 코드는 각각 X -= np.mean(X, axis=0), X /= np.std(X, axis=0) (출처: cs231n Lecture 6)

Zero-centering은 데이터에서 평균을 빼는 거다. 앞에서 sigmoid의 두 번째 문제로 나온 zig-zag 문제가, 입력 데이터 자체가 all-positive일 때도 똑같이 발생한다. 모든 픽셀이 양수(0~255)면 첫 레이어에서 gradient가 항상 같은 부호를 갖게 된다. 평균을 빼서 음수와 양수를 골고루 분포시키면 이 문제를 피할 수 있다.

Normalization은 데이터를 동일한 스케일로 맞추는 거다. 이미지는 픽셀이 이미 0~255 범위 안에 있어서 스케일 차이가 크지 않다. 그래서 이미지 처리에서는 zero-centering만 하고 normalization은 보통 안 한다.

PCA와 Whitening

▲ PCA decorrelation과 Whitening 시각화. CNN에서는 거의 쓰지 않는다 (출처: cs231n Lecture 6)

PCA나 whitening 같은 더 복잡한 전처리도 있긴 한데, CNN을 쓸 때는 거의 안 쓴다. 실전에서는 학습 데이터의 평균을 구해서 빼는 것으로 충분하다. 채널별로 평균을 구하는 방식도 많이 쓴다.

•   •   •

3. 가중치 초기화 (Weight Initialization)

학습 전에 weight를 어떻게 초기화하느냐가 생각보다 중요하다. 잘못 초기화하면 학습이 아예 안 될 수도 있다.

왜 0으로 초기화하면 안 될까?

"중립적인 시작점"처럼 보여서 weight를 전부 0으로 초기화하고 싶을 수 있다. 그런데 이러면 같은 레이어의 모든 뉴런이 동일한 값을 출력하고, 동일한 gradient를 받아서, 동일하게 업데이트된다. 레이어가 아무리 넓어도 사실상 뉴런 하나짜리랑 다를 바가 없다. 이걸 대칭성 문제(symmetry problem)라고 한다.

작은 랜덤값으로 초기화하면?

그래서 처음엔 표준편차 0.01 정도의 가우시안 분포로 초기화하는 걸 많이 썼다. 얕은 네트워크에서는 괜찮은데, 레이어가 깊어지면 문제가 생긴다. 각 레이어를 거칠수록 activation 값이 점점 0에 수렴해버린다. gradient도 마찬가지로 0이 되어 학습이 멈춘다. 반대로 너무 큰 값으로 초기화하면 sigmoid/tanh에서 saturation이 일어난다.

초기화가 잘못됐을 때 아래와 같은 loss 곡선이 나타나기도 한다. 학습 초반에 loss가 한동안 꼼짝 않다가 뒤늦게 내려가는 패턴이다.

bad initialization loss

▲ 잘못된 초기화 시 나타나는 loss 곡선. 초반에 평평하게 유지되다가 뒤늦게 학습이 시작된다 (출처: cs231n Lecture 6)

Xavier 초기화

Glorot & Bengio (2010)에서 제안한 방법이다. 레이어를 거쳐도 activation의 분산이 유지되려면, weight의 분산을 입력 차원 수에 맞게 조정해야 한다는 아이디어다.

$$W \sim \mathcal{N}\!\left(0,\; \frac{1}{n_{\text{in}}}\right) \quad \text{또는} \quad W \sim \mathcal{U}\!\left(-\frac{1}{\sqrt{n_{\text{in}}}},\; \frac{1}{\sqrt{n_{\text{in}}}}\right)$$

입력 뉴런 수(\(n_{\text{in}}\))가 많을수록 각 weight를 더 작게 초기화해야 출력의 분산이 폭발하지 않는다. \(\frac{1}{\sqrt{n_{\text{in}}}}\)로 스케일을 맞추면 레이어를 거쳐도 분산이 유지된다.

Xavier는 tanh처럼 대칭적인 활성화 함수에서 잘 작동한다. 그런데 ReLU를 쓰면 음수 입력을 전부 0으로 날려버리니까 분산이 절반으로 줄어드는 효과가 생긴다. Xavier로는 부족하다.

He 초기화 (Kaiming 초기화)

ReLU에 맞게 Xavier를 보정한 버전이다. He et al. (2015)에서 제안했다. ReLU가 분산을 절반으로 줄이니까, 초기 분산을 두 배로 잡는 식이다.

$$W \sim \mathcal{N}\!\left(0,\; \frac{2}{n_{\text{in}}}\right)$$

지금 대부분의 딥러닝 프레임워크에서 ReLU 레이어의 기본 초기화 방식이 He 초기화다. ReLU를 쓴다면 He 초기화, tanh/sigmoid를 쓴다면 Xavier 초기화로 기억해두면 된다.

•   •   •

4. 배치 정규화 (Batch Normalization)

초기화를 조심스럽게 해야 한다는 게 번거롭다면, 아예 매 레이어마다 분포를 강제로 맞춰주는 방법이 있다. Batch Normalization이다. Ioffe & Szegedy (2015)가 제안했는데, 요즘 딥러닝 모델에는 거의 기본으로 들어간다.

아이디어는 간단하다. 각 레이어 출력을 다음 레이어로 넘기기 전에, 미니배치 단위로 정규화해서 분포를 맞춰준다. 그러면 초기화가 조금 이상해도 레이어를 거치면서 분포가 망가지지 않는다.

BN 수식 다이어그램

▲ N개 데이터, D차원 배치에서 차원별로 평균과 분산을 독립적으로 구해 정규화한다 (출처: cs231n Lecture 6)

수식

미니배치 \(\{x_1, \ldots, x_N\}\)에 대해 각 feature 차원 \(k\)별로 평균과 분산을 구한 뒤:

$$\mu_{\mathcal{B}} = \frac{1}{N}\sum_{i=1}^N x_i, \qquad \sigma_{\mathcal{B}}^2 = \frac{1}{N}\sum_{i=1}^N (x_i - \mu_{\mathcal{B}})^2$$
$$\hat{x}_i = \frac{x_i - \mu_{\mathcal{B}}}{\sqrt{\sigma_{\mathcal{B}}^2 + \epsilon}}, \qquad y_i = \gamma \hat{x}_i + \beta$$

\(\gamma, \beta\)는 학습 가능한 파라미터다. 정규화로 표현력이 너무 제한될 수 있으니, 네트워크가 필요하면 원래 분포로 복원할 수 있는 여지를 열어두는 거다. 이론적으로 \(\gamma = \sigma,\; \beta = \mu\)로 학습되면 정규화 전과 동일해진다.

BN 레이어 위치

▲ Batch Norm은 FC/Conv 레이어 직후, 활성화 함수(tanh/ReLU) 직전에 삽입한다 (출처: cs231n Lecture 6)

학습 vs 테스트 동작 차이

한 가지 꼭 알아야 할 점이 있다. 학습 때는 미니배치 단위로 평균과 분산을 구하지만, 테스트 때는 배치 개념이 없다. 배치 크기가 1일 수도 있다. 그래서 테스트 때는 학습 중에 누적해둔 이동 평균(running mean/variance)을 사용한다. PyTorch의 model.train() / model.eval()이 이 차이를 관리한다. 이걸 빠뜨리면 테스트 성능이 이상하게 나온다.

BN을 쓰면 학습률을 조금 크게 잡아도 안정적으로 학습된다. 초기화 민감도도 줄어들고, Dropout 없이도 어느 정도 정규화 효과가 있다. 학습 속도도 빨라진다. 단점이라면 train/eval 모드 구분을 잊으면 안 된다는 정도다.
•   •   •

5. 학습 과정 모니터링 (Babysitting the Learning Process)

학습을 시작하면 끝이 아니다. 학습 중에 계속 지켜봐야 한다. "Babysitting"이라는 표현이 딱 맞다 - 갓난아기처럼 끊임없이 확인해야 한다.

먼저 sanity check

학습 전에 몇 가지를 확인한다.

첫 번째, 초기 loss가 맞는지 본다. CIFAR-10(10클래스) Softmax라면 초기 loss가 \(-\ln(1/10) \approx 2.303\) 정도여야 한다. 이것보다 훨씬 크거나 작으면 뭔가 잘못된 거다.

두 번째, 소수의 데이터로 오버피팅시켜 본다. 학습 데이터 20개로 100% 정확도에 도달할 수 있어야 한다. 안 된다면 모델에 버그가 있다는 신호다.

loss 곡선 읽기

loss 곡선

▲ 학습률에 따른 loss 곡선 패턴. very high lr은 발산, low lr은 느린 수렴, good lr이 이상적이다 (출처: cs231n Lecture 6)

loss가 줄지 않고 평평하면 학습률이 너무 작거나 모델에 버그가 있는 거다. 발산하거나 NaN이 나오면 학습률이 너무 큰 거다. 초반에 한동안 평평하다가 내려가기 시작하는 패턴은 초기화 문제일 가능성이 높다.

train/val accuracy gap

train/val accuracy gap

▲ Training과 Validation accuracy의 gap. gap이 크면 오버피팅, gap이 없으면 underfitting 신호다 (출처: cs231n Lecture 6)

gap이 크면 오버피팅이니까 regularization을 강화해야 한다. 둘 다 낮으면 underfitting이니까 모델을 키우거나 학습을 더 해야 한다.

•   •   •

6. 하이퍼파라미터 최적화 (Hyperparameter Optimization)

학습률, regularization 강도, 네트워크 깊이/너비, 배치 크기... 이 값들을 어떻게 찾느냐의 문제다.

Grid Search vs Random Search

Grid vs Random Search

▲ Grid Search(왼쪽)는 중요한 파라미터 축에서 겨우 3개 값만 탐색하지만, Random Search(오른쪽)는 같은 횟수로 더 다양한 값을 탐색한다 (Bergstra & Bengio, 2012 / 출처: cs231n Lecture 6)

Grid Search는 각 파라미터를 일정 간격으로 탐색한다. 파라미터 수가 늘어날수록 탐색 공간이 기하급수적으로 커진다. 3개 파라미터를 각각 10개 값으로 탐색하면 1000번 학습이 필요하다. 비현실적이다.

cs231n에서 권장하는 건 Random Search다. 각 파라미터를 범위 안에서 랜덤하게 샘플링한다. 위 그림에서 보듯, 어떤 파라미터가 중요한지 모르는 상황에서 Random Layout이 중요한 파라미터 축 방향으로 더 다양한 값을 탐색하게 된다.

Log scale 탐색

학습률이나 regularization 강도는 log scale로 탐색해야 한다. \([0.001, 0.01, 0.1]\)에서 0.001~0.01 사이가 중요할 때, linear scale로 탐색하면 이 구간을 제대로 못 본다. \(10^{\text{Uniform}(-4, 0)}\) 형태로 log scale 랜덤 샘플링을 하면 전 범위를 고르게 탐색한다.

Coarse-to-Fine 전략

처음엔 넓은 범위로 대략 탐색하고, 좋은 결과가 나온 구간을 찾으면 그 주변을 더 세밀하게 탐색한다. 전체 학습을 다 돌리면 너무 오래 걸리니까, 처음엔 몇 에폭만 돌려보고 유망한 조합만 더 학습시키는 식으로 진행한다.

•   •   •

7. 핵심 요약

주제 핵심 선택 이유
활성화 함수 ReLU (기본) 빠른 수렴, 계산 효율, saturation 없음
데이터 전처리 Zero-centering (평균 빼기) 이미지는 normalization까지는 보통 불필요
Weight 초기화 He init (ReLU), Xavier init (tanh) 레이어 깊어져도 activation 분산 유지
Batch Normalization 사용 권장 학습 안정화, 초기화 민감도 감소, 빠른 수렴
하이퍼파라미터 탐색 Random Search + log scale Grid Search보다 효율적, 중요 파라미터 탐색에 유리
참고
· cs231n Lecture 6 강의 영상
· cs231n 공식 노트 - Neural Networks Part 2
· Glorot & Bengio (2010), Understanding the difficulty of training deep feedforward neural networks
· He et al. (2015), Delving deep into rectifiers
· Ioffe & Szegedy (2015), Batch Normalization: Accelerating Deep Network Training
다음 포스팅: cs231n Lecture 7 - Training Neural Networks II
(Fancier Optimization, Regularization, Transfer Learning)