ResNet의 BatchNorm
— 깊은 네트워크는 왜 배치 정규화가 필요한가

Batch Normalization이 무엇을 하는지, 그리고 ResNet이 152층을 학습할 수 있었던 이유에서 BN이 차지하는 자리를 직접 실험하며 살펴봅니다.

ResNet 논문(2015)에는 이런 한 문장이 있습니다. "We adopt batch normalization (BN) right after each convolution and before activation." — 모든 conv 뒤, 활성화 함수 앞에 BN을 넣었다는 것입니다.

skip connection이 ResNet의 주인공이라면, BN은 조연이 아니라 공동 주연에 가깝습니다. BN 없이는 skip connection이 있어도 수십 층짜리 네트워크의 학습이 제대로 되지 않습니다. 이 페이지에서는 그 이유를 신호의 분포 관점에서 따라가 봅니다.

01배치 정규화란 무엇인가 — 이름 그대로 읽기

정규화(normalization)는 딥러닝 이전부터 쓰이던 오래된 기법입니다. 스케일이 제각각인 값들을 비교 가능한 공통 기준으로 옮기는 일이죠. 가장 흔한 방식이 평균을 빼고 표준편차로 나누는 표준화(z-score)입니다.

x̂ = x − μσ → 평균 0, 분산 1

키(170 근처)와 몸무게(65 근처)를 그대로 모델에 넣으면 숫자가 큰 특징이 학습을 지배합니다. 그래서 입력 데이터를 학습 전에 한 번 정규화하는 것은 머신러닝의 기본 전처리입니다.

배치 정규화(Batch Normalization, BN)는 이 표준화를 전처리 단계가 아니라 네트워크 안쪽, 층과 층 사이에서 반복하는 레이어입니다. 이름을 그대로 쪼개 읽으면 정의가 나옵니다.

입력 정규화 (전처리)배치 정규화 (BN)
적용 위치네트워크 입구에서 1회층 사이마다 반복
통계의 출처학습셋 전체 (학습 전 고정)현재 미니배치 (매 step 재계산)
학습 파라미터없음γ, β (채널마다 한 쌍)

그런데 왜 입구에서 한 번으로는 부족할까요? 입력을 아무리 깨끗하게 정규화해 줘도, 층을 하나 통과할 때마다 분포가 다시 망가지기 때문입니다. 다음 섹션에서 이 문제를 봅니다.

02문제 — 층을 지날수록 신호의 분포가 무너진다

신경망의 한 층은 대략 y = ReLU(Wx + b)입니다. 문제는 이 연산이 입력 분포의 평균과 분산을 보존하지 않는다는 점입니다. 가중치가 조금만 크면 출력의 분산은 입력보다 커지고, 조금만 작으면 작아집니다.

층이 하나라면 별일 아닙니다. 하지만 이 왜곡이 수십 층에 걸쳐 곱해지면 지수적으로 증폭됩니다. 분산이 층마다 1.5배씩만 커져도 30층 뒤에는 약 19만 배가 됩니다. 반대로 0.7배씩 줄면 30층 뒤에는 거의 0입니다.

He 초기화 같은 정교한 초기화는 학습 시작 시점의 분산을 맞춰 주지만, 학습이 진행되어 가중치가 변하기 시작하면 이 균형은 다시 깨집니다. 앞쪽 층이 업데이트될 때마다 뒤쪽 층이 받는 입력 분포가 계속 흔들리는 것이죠. BN 논문은 이 현상을 internal covariate shift라고 불렀습니다. (후속 연구(Santurkar et al., 2018)는 BN의 실제 효과가 loss landscape를 매끄럽게 만들어 큰 learning rate를 허용하는 것에 가깝다고 재해석했지만, "분포가 흔들리는 것을 층마다 다시 잡아준다"는 동작 자체는 그대로입니다.)

03BatchNorm의 동작 — 네 줄이면 끝난다

아이디어는 단순합니다. 미니배치 안에서, 채널(특징)별로 평균과 분산을 구해 정규화하고, 학습 가능한 두 파라미터로 다시 스케일·이동시킵니다.

μB = 1m Σ xi 배치 평균
σ²B = 1m Σ (xi − μB)² 배치 분산
x̂i = xi − μB√(σ²B + ε) 정규화 → 평균 0, 분산 1
yi = γ x̂i + β 스케일·이동 (학습 파라미터)

마지막 줄이 중요합니다. 정규화만 하면 네트워크의 표현력이 제한될 수 있으니(예: 항상 평균 0을 강제하면 ReLU 앞에서 절반이 잘려나감), γ(감마)와 β(베타)라는 학습 가능한 파라미터로 "네트워크가 원한다면" 어떤 평균·분산이든 다시 만들 수 있게 열어 둡니다. 핵심은 분포를 고정하는 것이 아니라, 분포를 네트워크가 직접 제어할 수 있는 두 개의 손잡이(γ, β)로 넘겨주는 것입니다.

conv 뒤에 붙는 BN(2D)은 채널 하나당 (배치 × 높이 × 너비) 전체에서 통계를 냅니다. 즉 채널 수만큼의 (γ, β) 쌍을 가집니다. ResNet-50의 BN 파라미터가 그토록 적은 이유입니다.

DEMO 1분포를 직접 밀고 당겨 보기

왼쪽(붉은색)이 어떤 층에 들어온 입력 분포, 오른쪽(푸른색)이 BN을 통과한 뒤의 분포입니다. 입력의 평균·표준편차를 아무리 흔들어도 출력은 γ·β가 정한 자리에 고정되는 것을 확인해 보세요.

입력 평균 입력 표준편차
γ (스케일) β (이동)
BN 입력 x BN 출력 y = γx̂ + β

입력을 어디로 끌고 가든 출력 분포는 평균 β, 표준편차 γ 근처에 정확히 놓입니다. 다음 층 입장에서는 "앞 층이 뭘 하든 내 입력은 항상 예측 가능한 범위에 있다"는 보장이 생기는 셈입니다. 이 보장이 층마다 반복되면, 아무리 깊어도 신호가 폭발하거나 소멸하지 않습니다.

04역전파 — gradient는 BN을 어떻게 통과하는가

γ와 β가 "학습 가능한 파라미터"라고 했는데, 실제로 어떻게 업데이트될까요? 특별한 메커니즘은 없습니다. conv의 W와 똑같이 역전파로 gradient를 받아 optimizer가 업데이트합니다. 다만 BN 레이어 안에서 gradient가 세 갈래로 갈라진다는 점이 재미있는 부분입니다.

FLOW순전파 → 역전파, 단계별로 따라가기

왼쪽 열이 순전파(위 → 아래), 오른쪽 열이 역전파(아래 → 위)입니다. 다음 ▶ 버튼으로 gradient가 흘러가는 순서를 한 단계씩 따라가 보세요.

순전파 ↓
역전파 ↑
입력 x
∂L/∂x가 앞 층의 출력 gradient가 되어 같은 과정이 첫 층까지 반복
↓
↑
conv 3×3 (W)
∂L/∂W 계산 W 업데이트
bias 방향 gradient는 뒤의 BN이 평균을 빼며 소거 → bias=False
↓
↑
BatchNorm (γ, β)
∂L/∂y 도착 — BN 안에서 세 갈래로 갈라짐
∂L/∂γ = Σ ∂L/∂y · x̂ γ 업데이트
∂L/∂β = Σ ∂L/∂y β 업데이트
∂L/∂x̂ = ∂L/∂y · γ → ∂L/∂x × 1/σ — conv 쪽으로 계속
↓
↑
ReLU
순전파 때 음수여서 0이 됐던 위치는 gradient도 0으로 차단, 나머지는 그대로 통과
↓
↑
다음 층들 ⋯
뒤쪽 층들을 하나씩 거슬러 gradient가 올라옴
↓
↑
Loss
∂L/∂output — 역전파의 시작점

따라가 보면 세 가지가 보입니다.

엄밀히는 μB와 σ²B 자체도 x의 함수이므로 ∂L/∂x에는 배치 통계를 거쳐 돌아오는 보정항이 두 개 더 붙습니다. 오토그래드가 알아서 계산해 주는 부분이라, 여기서는 큰 흐름만 봤습니다.

05ResNet 블록 안에서 BN의 자리

ResNet의 basic block에서 BN은 모든 conv 바로 뒤, ReLU 앞에 들어갑니다. 그리고 두 번째 BN 출력에 skip connection이 더해진 뒤 마지막 ReLU를 통과합니다.

원본 ResNet (post-activation, 2015)

conv 3×3
↓
BatchNorm
↓
ReLU
↓
conv 3×3
↓
BatchNorm
↓
⊕  + skip
↓
ReLU

개선판 pre-activation (2016)

BatchNorm
↓
ReLU
↓
conv 3×3
↓
BatchNorm
↓
ReLU
↓
conv 3×3
↓
⊕  + skip

왜 이 순서일까요?

덤: 마지막 BN의 γ를 0으로 초기화하는 트릭

각 residual block의 마지막 BN의 γ를 0으로 초기화하면, 학습 초기에 블록 전체가 y = 0·(...) + x = x, 즉 완전한 항등함수에서 출발합니다. 네트워크가 "얕은 네트워크에서 시작해 점점 깊어지는" 효과를 얻어 학습이 더 안정됩니다. γ·β가 분포의 손잡이라는 관점이 있어야 이해되는 트릭입니다. (Goyal et al. 2017, "Accurate, Large Minibatch SGD")

06실험 — BN이 있을 때와 없을 때, 신호는 어떻게 전파되는가

말로만 하면 심심하니, 브라우저에서 직접 30층짜리 네트워크에 신호를 흘려 봅시다. 아래 데모는 지금 이 자리에서 무작위 가중치(He 초기화 기반)로 30층 네트워크를 만들고, 실제로 forward pass를 계산해 각 층 activation의 표준편차를 그립니다.

DEMO 230층 네트워크의 신호 전파 시뮬레이션

세로축은 각 층 출력의 표준편차(로그 스케일), 가로축은 층 번호입니다. 가중치 크기를 조절하고 BN과 skip connection을 켜고 꺼 보세요. 회색 점선(std = 1)이 "건강한" 신호의 기준선입니다.

가중치 초기화 배율 BatchNorm skip connection
BN 없음 BN 있음 기준선 std = 1

직접 돌려 보면 이런 것들이 보입니다.

이것이 ResNet 논문에서 skip connection과 BN이 함께 쓰인 이유입니다. skip connection은 gradient가 지나갈 고속도로를 놓고, BN은 그 고속도로를 달리는 신호의 크기를 층마다 다시 규격에 맞춥니다. 둘 중 하나만으로는 매우 깊은 네트워크를 학습시키기 어렵습니다.

07학습할 때와 추론할 때의 BN은 다르다

BN에는 함정이 하나 있습니다. 배치 통계 μB, σ²B는 배치가 있어야 계산할 수 있다는 점입니다. 추론 시에는 이미지 한 장이 들어올 수도 있고, 같은 입력에 항상 같은 출력이 나와야 합니다.

그래서 BN은 두 가지 모드로 동작합니다.

학습 (train)추론 (eval)
사용하는 통계현재 미니배치의 μB, σ²B학습 중 누적한 이동평균 μ̂, σ̂²
출력의 성질같은 입력도 배치 구성에 따라 출력이 달라짐 (약한 규제 효과)결정적 — 배치 크기 1이어도 동작
통계 갱신μ̂ ← (1−m)·μ̂ + m·μ_B 로 이동평균 누적갱신 없음

PyTorch에서 model.eval()을 깜빡하면 추론 결과가 이상해지는 대표적인 이유가 바로 이것입니다. 반대로 fine-tuning 때 model.train() 상태로 배치 크기 1~2로 학습하면, 노이즈투성이 배치 통계가 이동평균을 오염시켜 성능이 무너지기도 합니다.

추론 시 BN은 공짜다 — BN folding

추론 모드의 BN은 고정된 μ̂, σ̂², γ, β로 하는 선형 변환일 뿐입니다. 선형 변환(conv)에 선형 변환(BN)이 이어지면 하나로 합칠 수 있습니다:

W′ = γ√(σ̂² + ε) W ,    b′ = γ (b − μ̂)√(σ̂² + ε) + β

배포용 모델에서 BN 레이어가 사라져 있는 이유입니다. conv 가중치에 접혀 들어가서(folded) 추론 비용이 0이 됩니다.

08BN의 한계, 그리고 그 다음

정리하면 — 정규화 기법은 여럿이지만 질문은 하나입니다. "깊은 네트워크에서 신호의 분포를 누가, 어디서, 어떻게 다시 잡아 줄 것인가." BN은 그 질문에 대한 2015년의 답이었고, ResNet은 그 답 위에서 세워졌습니다.

09확인 퀴즈

Q1. ResNet의 conv 레이어에 bias가 없는(bias=False) 이유는?

파라미터를 줄여 과적합을 막기 위해
바로 뒤의 BN이 평균을 다시 조정하므로 β와 역할이 중복되기 때문
skip connection이 bias 역할을 대신하기 때문

BN이 배치 평균을 빼는 순간 conv의 bias는 어떤 값이든 소거되고, 이동은 BN의 β가 담당합니다. bias는 있어 봐야 낭비입니다.

Q2. 추론(eval) 모드의 BN이 배치 크기 1에서도 동작할 수 있는 이유는?

추론 시에는 정규화를 생략하기 때문
한 장의 이미지 안에서 통계를 새로 계산하기 때문
학습 중 누적해 둔 이동평균 통계를 고정값으로 쓰기 때문

추론 시에는 배치 통계 대신 학습 중 쌓아 둔 running mean/var를 씁니다. 그래서 결정적이고, 배치가 없어도 되며, conv에 fold할 수도 있습니다.

Q3. Demo 2에서 skip connection만 켜고 BN을 끄면 분산이 여전히 층마다 증가합니다. 왜일까요?

매 블록의 출력이 "입력 + 새 신호"의 합이라 분산이 계속 누적되기 때문
skip connection이 gradient를 차단하기 때문
ReLU가 신호의 절반을 잘라내기 때문

독립에 가까운 두 신호를 더하면 분산은 대략 합산됩니다(Var(x+f(x)) ≈ Var(x)+Var(f(x))). 폭발적 곱셈이 덧셈으로 완화될 뿐 증가 자체는 계속되므로, 매 블록 BN이 분산을 다시 잡아 줘야 합니다.