26. 정규화 레이어(Normalization Layers)

이전: 옵티마이저 | 다음: TensorBoard 시각화


26. 정규화 레이어(Normalization Layers)

학습 목표

  • 딥러닝에서 정규화의 필요성과 손실 함수 지형을 부드럽게 만드는 원리 이해하기
  • 배치 정규화, 레이어 정규화, 그룹 정규화와 각각의 사용 사례 마스터하기
  • 현대 대형 언어 모델에서 선호되는 RMSNorm 학습하기
  • 정규화 레이어를 처음부터 구현하고 계산상의 의미 이해하기
  • 아키텍처와 배치 크기 제약에 따라 적절한 정규화 기법 적용하기

1. 왜 정규화가 필요한가?

1.1 문제: 내부 공변량 변화(Internal Covariate Shift)

내부 공변량 변화는 훈련 중 네트워크 활성화(activation) 분포의 변화를 의미합니다. 초기 레이어의 파라미터가 변경되면 후반 레이어의 입력이 변화하여 지속적으로 적응해야 합니다.

원래 동기 (Ioffe & Szegedy, 2015): - 레이어 간 활성화 분포 안정화 - 각 레이어가 더 안정적인 입력 분포에서 학습할 수 있도록 함 - 발산 없이 더 높은 학습률 사용 가능

1.2 현대적 이해: 손실 함수 지형 평활화(Loss Landscape Smoothing)

최근 연구(Santurkar et al., 2018)는 정규화의 주요 이점이 손실 함수 지형 평활화라는 것을 보여줍니다:

정규화 없음:                   정규화 있음:

    |\                              /\
    | \        /\                  /  \
    |  \  /\  /  \                /    \
    |___\/  \/____\__           /______\_____

    거칠고 불규칙한               더 부드럽고 예측 가능한
    기울기                        기울기

이점: 1. 더 빠른 수렴 — 부드러운 기울기로 더 큰 스텝 가능 2. 더 높은 학습률 — 발산 위험 감소 3. 정규화 효과 — 배치 통계의 노이즈가 암묵적 정규화 역할 4. 초기화 민감도 감소 — 신중한 가중치 초기화에 대한 의존도 감소

1.3 정규화 축(Normalization Axes)

다양한 정규화 방법은 서로 다른 차원에서 정규화를 수행합니다:

입력 텐서 형태: (N, C, H, W)
N = 배치 크기
C = 채널
H, W = 공간 차원

┌─────────────────────────────────────────────────────────────┐
│  배치 정규화:       N에 대해 정규화      각 (C, H, W)에 대해  │
│  레이어 정규화:     C,H,W에 대해 정규화  각 N에 대해          │
│  인스턴스 정규화:   H,W에 대해 정규화    각 (N, C)에 대해     │
│  그룹 정규화:       C/G,H,W에 대해 정규화 각 (N, G)에 대해   │
└─────────────────────────────────────────────────────────────┘

2. 배치 정규화(Batch Normalization)

2.1 핵심 개념

배치 정규화(BatchNorm)는 각 특성에 대해 독립적으로 배치 차원에서 활성화를 정규화합니다.

알고리즘:

입력: 미니배치 B = {x₁, ..., xₘ}
파라미터: γ (스케일), β (시프트)  학습 가능

1. 배치 통계 계산:
   μ_B = (1/m) Σ xᵢ                    # 평균
   σ²_B = (1/m) Σ (xᵢ - μ_B)²          # 분산

2. 정규화:
   x̂ᵢ = (xᵢ - μ_B) / (σ²_B + ε)       # ε는 수치 안정성을 위함

3. 스케일  시프트:
   yᵢ = γ x̂ᵢ + β                        # 학습 가능한 변환

왜 스케일과 시프트가 필요한가? 네트워크가 필요한 경우 정규화를 취소하는 방법을 학습할 수 있습니다 (예: γ = √σ², β = μ로 원래 분포 복원).

2.2 훈련 모드 vs 추론 모드

훈련: - 배치 통계 사용 (μ_B, σ²_B) - 추론을 위한 이동 평균 업데이트: running_mean = momentum × running_mean + (1 - momentum) × μ_B running_var = momentum × running_var + (1 - momentum) × σ²_B

추론: - 이동 통계 사용 (고정됨) - 현재 배치에 의존하지 않음

import torch
import torch.nn as nn

# 훈련 모드
bn = nn.BatchNorm2d(64)
bn.train()
out = bn(x)  # 배치 통계 사용

# 추론 모드
bn.eval()
out = bn(x)  # 이동 통계 사용

2.3 BatchNorm을 어디에 배치할까?

옵션 1: 활성화 함수 이후 (원본 논문)

Linear/Conv → Activation → BatchNorm

옵션 2: 활성화 함수 이전 (일반적인 관행)

Linear/Conv → BatchNorm → Activation

현대적 합의: 활성화 함수 이전이 실제로 더 잘 작동하며, 특히 ReLU와 함께 사용할 때 그렇습니다.

2.4 PyTorch BatchNorm

import torch.nn as nn

# 완전 연결 레이어용 (1D)
bn1d = nn.BatchNorm1d(num_features=128)

# 합성곱 레이어용 (2D)
bn2d = nn.BatchNorm2d(num_features=64)

# 3D 합성곱용 (비디오, 볼륨 데이터)
bn3d = nn.BatchNorm3d(num_features=32)

# 예제 CNN 블록
class ConvBlock(nn.Module):
    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, 3, padding=1)
        self.bn = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

# 사용법
model = ConvBlock(3, 64)
x = torch.randn(32, 3, 224, 224)  # (N, C, H, W)
out = model(x)
print(out.shape)  # torch.Size([32, 64, 224, 224])

2.5 수동 구현

import torch
import torch.nn as nn

class BatchNorm2dManual(nn.Module):
    def __init__(self, num_features, eps=1e-5, momentum=0.1):
        super().__init__()
        self.eps = eps
        self.momentum = momentum

        # 학습 가능한 파라미터
        self.gamma = nn.Parameter(torch.ones(num_features))
        self.beta = nn.Parameter(torch.zeros(num_features))

        # 이동 통계 (경사 하강법으로 업데이트되지 않음)
        self.register_buffer('running_mean', torch.zeros(num_features))
        self.register_buffer('running_var', torch.ones(num_features))
        self.register_buffer('num_batches_tracked', torch.tensor(0, dtype=torch.long))

    def forward(self, x):
        # x 형태: (N, C, H, W)

        if self.training:
            # 배치 통계 계산
            # 각 채널 C에 대해 (N, H, W)에서 평균과 분산 계산
            mean = x.mean(dim=[0, 2, 3], keepdim=False)  # 형태: (C,)
            var = x.var(dim=[0, 2, 3], unbiased=False, keepdim=False)  # 형태: (C,)

            # 이동 통계 업데이트
            self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean
            self.running_var = (1 - self.momentum) * self.running_var + self.momentum * var
            self.num_batches_tracked += 1
        else:
            # 이동 통계 사용
            mean = self.running_mean
            var = self.running_var

        # 정규화
        # 브로드캐스팅을 위해 재구성: (1, C, 1, 1)
        mean = mean.view(1, -1, 1, 1)
        var = var.view(1, -1, 1, 1)
        gamma = self.gamma.view(1, -1, 1, 1)
        beta = self.beta.view(1, -1, 1, 1)

        x_norm = (x - mean) / torch.sqrt(var + self.eps)
        out = gamma * x_norm + beta

        return out

# 테스트
manual_bn = BatchNorm2dManual(64)
pytorch_bn = nn.BatchNorm2d(64)

x = torch.randn(32, 64, 16, 16)

# 훈련 모드
manual_bn.train()
pytorch_bn.train()
out_manual = manual_bn(x)
out_pytorch = pytorch_bn(x)

print(f"출력 형태: {out_manual.shape}")
print(f"평균이 0에 가까움: {out_manual.mean().item():.6f}")
print(f"표준편차가 1에 가까움: {out_manual.std().item():.6f}")

2.6 BatchNorm의 한계

1. 배치 크기 의존성 - 작은 배치 → 노이즈가 많은 통계 → 성능 저하 - 배치 크기 < 8은 문제가 됨

2. 시퀀스 모델 (RNN) - 배치 내 다른 시퀀스 길이 - 시간 차원에 적용하기 어려움

3. 분산 훈련 - 각 GPU가 다른 배치를 가짐 - Sync BatchNorm 필요 (비용이 높음)

4. 온라인 학습 - 한 번에 단일 샘플 - 배치 통계를 사용할 수 없음


3. 레이어 정규화(Layer Normalization)

3.1 핵심 개념

레이어 정규화(LayerNorm)는 각 샘플에 대해 독립적으로 모든 특성에서 정규화하므로 배치에 독립적입니다.

BatchNorm:   특성에 대해 샘플  정규화
LayerNorm:   샘플에 대해 특성  정규화

공식:

배치 내 각 샘플 x에 대해:
  μ = (1/D) Σ xᵢ                        # 특성 간 평균
  σ² = (1/D) Σ (xᵢ - μ)²                # 특성 간 분산
  x̂ᵢ = (xᵢ - μ) / √(σ² + ε)             # 정규화
  yᵢ = γ x̂ᵢ + β                         # 스케일 및 시프트

3.2 왜 트랜스포머에 LayerNorm을 사용하는가?

장점: 1. 배치 독립적 — 배치 크기 = 1에서 작동 2. 시퀀스 길이 독립적 — 각 위치가 동일한 방식으로 정규화됨 3. 추론 시 결정론적 — 이동 통계 불필요

트랜스포머 아키텍처:

┌─────────────────────────────────────────┐
│  Pre-Norm (현대):                        │
│    x → LayerNorm → Attention → Add(x)   │
│    x → LayerNorm → FFN → Add(x)         │
│                                          │
│  Post-Norm (원본):                       │
│    x → Attention → Add(x) → LayerNorm   │
│    x → FFN → Add(x) → LayerNorm         │
└─────────────────────────────────────────┘

Pre-Norm vs Post-Norm: - Pre-Norm: 더 나은 기울기 흐름, 훈련하기 쉬움, GPT, LLaMA에서 사용 - Post-Norm: 원본 트랜스포머 디자인, 신중한 튜닝으로 약간 더 나은 성능

3.3 PyTorch LayerNorm

import torch
import torch.nn as nn

# 트랜스포머용 LayerNorm
ln = nn.LayerNorm(512)  # d_model = 512

# 예제: Pre-Norm을 사용한 셀프 어텐션
class TransformerBlock(nn.Module):
    def __init__(self, d_model, num_heads):
        super().__init__()
        self.ln1 = nn.LayerNorm(d_model)
        self.attn = nn.MultiheadAttention(d_model, num_heads, batch_first=True)
        self.ln2 = nn.LayerNorm(d_model)
        self.ffn = nn.Sequential(
            nn.Linear(d_model, 4 * d_model),
            nn.GELU(),
            nn.Linear(4 * d_model, d_model)
        )

    def forward(self, x):
        # Pre-Norm
        x = x + self.attn(self.ln1(x), self.ln1(x), self.ln1(x))[0]
        x = x + self.ffn(self.ln2(x))
        return x

# 사용법
model = TransformerBlock(d_model=512, num_heads=8)
x = torch.randn(32, 100, 512)  # (batch, seq_len, d_model)
out = model(x)
print(out.shape)  # torch.Size([32, 100, 512])

3.4 수동 구현

import torch
import torch.nn as nn

class LayerNormManual(nn.Module):
    def __init__(self, normalized_shape, eps=1e-5):
        super().__init__()
        self.eps = eps
        self.normalized_shape = normalized_shape

        # 학습 가능한 파라미터
        self.gamma = nn.Parameter(torch.ones(normalized_shape))
        self.beta = nn.Parameter(torch.zeros(normalized_shape))

    def forward(self, x):
        # x 형태: (N, ..., normalized_shape)
        # 예: 트랜스포머의 경우 (N, seq_len, d_model)

        # 마지막 차원에서 평균과 분산 계산
        # 브로드캐스팅을 위해 차원 유지
        dims = list(range(-len(self.gamma.shape) if isinstance(self.normalized_shape, tuple)
                          else -1, 0))

        mean = x.mean(dim=-1, keepdim=True)
        var = x.var(dim=-1, unbiased=False, keepdim=True)

        # 정규화
        x_norm = (x - mean) / torch.sqrt(var + self.eps)

        # 스케일 및 시프트
        out = self.gamma * x_norm + self.beta

        return out

# 테스트
manual_ln = LayerNormManual(512)
pytorch_ln = nn.LayerNorm(512)

x = torch.randn(32, 100, 512)  # (batch, seq, features)

out_manual = manual_ln(x)
out_pytorch = pytorch_ln(x)

print(f"출력 형태: {out_manual.shape}")
print(f"샘플당 평균이 0에 가까움: {out_manual[0].mean().item():.6f}")
print(f"샘플당 표준편차가 1에 가까움: {out_manual[0].std().item():.6f}")

3.5 사용 사례

가장 적합한 경우: - 트랜스포머 (BERT, GPT, ViT) - RNN, LSTM - 작은 배치 크기 - 가변 시퀀스 길이


4. 그룹 정규화(Group Normalization)

4.1 핵심 개념

그룹 정규화(GroupNorm)는 채널을 그룹으로 나누고 각 그룹 내에서 정규화합니다.

입력: (N, C, H, W)
그룹: G

C 채널을 각각 (C/G) 채널씩 G개 그룹으로 분할
각 그룹을 독립적으로 정규화

특수 사례:
  G = 1     → 레이어 정규화 (한 그룹 = 모든 채널)
  G = C     → 인스턴스 정규화 (각 채널이 하나의 그룹)
  G = 32    → 일반적인 선택 (Wu & He, 2018)

시각화:

채널: [c0, c1, c2, c3, c4, c5, c6, c7]
그룹 (G=4): [c0,c1] [c2,c3] [c4,c5] [c6,c7]

배치의 각 샘플에 대해:
  각 그룹에 대해:
    (C/G, H, W)에서 평균/분산 계산
    정규화

4.2 공식

각 샘플 n, 그룹 g에 대해:
  μₙ,ₘ = (1/(C/G · H · W)) Σ x_n,g,h,w
  σ²ₙ,ₘ = (1/(C/G · H · W)) Σ (x_n,g,h,w - μₙ,ₘ)²
  x̂_n,g,h,w = (x_n,g,h,w - μₙ,ₘ) / √(σ²ₙ,ₘ + ε)
  y_n,c,h,w = γ_c · x̂_n,c,h,w + β_c

4.3 PyTorch GroupNorm

import torch
import torch.nn as nn

# 32개 그룹을 사용한 GroupNorm (일반적인 선택)
gn = nn.GroupNorm(num_groups=32, num_channels=64)

# 예제: GroupNorm을 사용한 ResNet 블록
class ResNetBlock(nn.Module):
    def __init__(self, channels, groups=32):
        super().__init__()
        self.conv1 = nn.Conv2d(channels, channels, 3, padding=1)
        self.gn1 = nn.GroupNorm(groups, channels)
        self.conv2 = nn.Conv2d(channels, channels, 3, padding=1)
        self.gn2 = nn.GroupNorm(groups, channels)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        identity = x

        out = self.conv1(x)
        out = self.gn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.gn2(out)

        out += identity
        out = self.relu(out)

        return out

# 사용법
model = ResNetBlock(channels=64, groups=32)
x = torch.randn(4, 64, 56, 56)  # 작은 배치 크기!
out = model(x)
print(out.shape)  # torch.Size([4, 64, 56, 56])

4.4 그룹 수 선택하기

import torch
import torch.nn as nn

# 규칙: num_channels는 num_groups로 나누어떨어져야 함

# 일반적인 구성
configs = [
    (64, 32),   # 64 채널, 32 그룹 → 그룹당 2 채널
    (128, 32),  # 128 채널, 32 그룹 → 그룹당 4 채널
    (256, 32),  # 256 채널, 32 그룹 → 그룹당 8 채널
]

for channels, groups in configs:
    gn = nn.GroupNorm(groups, channels)
    x = torch.randn(2, channels, 16, 16)  # 작은 배치!
    out = gn(x)
    print(f"{channels} 채널, {groups} 그룹 → "
          f"그룹당 {channels // groups} 채널, 형태: {out.shape}")

# 특수 사례
gn_layer = nn.GroupNorm(1, 64)      # G=1 → LayerNorm 동작
gn_instance = nn.GroupNorm(64, 64)  # G=C → InstanceNorm 동작

4.5 사용 사례

가장 적합한 경우: - 객체 탐지 (Mask R-CNN, Faster R-CNN) - 이미지 분할 - 작은 배치 크기 (배치 크기 = 1, 2, 4) - 고정된 BatchNorm을 사용한 전이 학습 - BatchNorm 통계가 신뢰할 수 없는 시나리오

성능: - COCO 객체 탐지: GroupNorm은 큰 배치에서 BatchNorm과 일치 - 작은 배치(1-4)에서: GroupNorm이 BatchNorm을 크게 능가함


5. 인스턴스 정규화(Instance Normalization)

5.1 핵심 개념

인스턴스 정규화(InstanceNorm)는 각 샘플의 각 채널을 독립적으로 정규화합니다.

각 샘플, 각 채널에 대해:
  공간 차원 (H, W)에서 평균과 분산 계산
  정규화

G = C인 GroupNorm과 동일 (각 채널이 자체 그룹).

5.2 공식

각 샘플 n, 채널 c에 대해:
  μₙ,c = (1/(H · W)) Σ x_n,c,h,w
  σ²ₙ,c = (1/(H · W)) Σ (x_n,c,h,w - μₙ,c)²
  x̂_n,c,h,w = (x_n,c,h,w - μₙ,c) / √(σ²ₙ,c + ε)
  y_n,c,h,w = γ_c · x̂_n,c,h,w + β_c

5.3 PyTorch InstanceNorm

import torch
import torch.nn as nn

# 2D 이미지용
in2d = nn.InstanceNorm2d(64)

# 1D 시퀀스용
in1d = nn.InstanceNorm1d(128)

# 예제: 스타일 전이 네트워크
class StyleTransferBlock(nn.Module):
    def __init__(self, channels):
        super().__init__()
        self.conv = nn.Conv2d(channels, channels, 3, padding=1)
        self.norm = nn.InstanceNorm2d(channels, affine=True)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        x = self.conv(x)
        x = self.norm(x)
        x = self.relu(x)
        return x

# 사용법
model = StyleTransferBlock(64)
x = torch.randn(1, 64, 256, 256)  # 배치 크기 = 1도 괜찮음!
out = model(x)
print(out.shape)  # torch.Size([1, 64, 256, 256])

5.4 왜 Instance Normalization을 사용하는가?

핵심 통찰: 스타일 전이의 경우, 인스턴스별 대비 정보를 정규화하고 싶습니다.

예제:

import torch
import torch.nn as nn
from torchvision import transforms
from PIL import Image

# 콘텐츠와 스타일 이미지 로드
content = Image.open('content.jpg')
style = Image.open('style.jpg')

# InstanceNorm을 사용한 스타일 전이
class FastStyleTransfer(nn.Module):
    def __init__(self):
        super().__init__()
        # 인코더
        self.encoder = nn.Sequential(
            nn.Conv2d(3, 32, 9, padding=4),
            nn.InstanceNorm2d(32, affine=True),
            nn.ReLU(inplace=True),
            nn.Conv2d(32, 64, 3, stride=2, padding=1),
            nn.InstanceNorm2d(64, affine=True),
            nn.ReLU(inplace=True),
        )

        # InstanceNorm을 사용한 잔차 블록
        self.residual = nn.Sequential(
            *[self._residual_block(64) for _ in range(5)]
        )

        # 디코더
        self.decoder = nn.Sequential(
            nn.ConvTranspose2d(64, 32, 3, stride=2, padding=1, output_padding=1),
            nn.InstanceNorm2d(32, affine=True),
            nn.ReLU(inplace=True),
            nn.Conv2d(32, 3, 9, padding=4),
            nn.Tanh()
        )

    def _residual_block(self, channels):
        return nn.Sequential(
            nn.Conv2d(channels, channels, 3, padding=1),
            nn.InstanceNorm2d(channels, affine=True),
            nn.ReLU(inplace=True),
            nn.Conv2d(channels, channels, 3, padding=1),
            nn.InstanceNorm2d(channels, affine=True)
        )

    def forward(self, x):
        x = self.encoder(x)
        x = self.residual(x)
        x = self.decoder(x)
        return x

5.5 사용 사례

가장 적합한 경우: - 스타일 전이 (neural style, fast style transfer) - 이미지 간 변환 (pix2pix, CycleGAN) - 생성 모델 (이미지 합성용 GAN) - 텍스처 합성


6. RMSNorm (Root Mean Square Normalization)

6.1 핵심 개념

RMSNorm은 평균 중심화를 제거하여 LayerNorm을 단순화하고 제곱 평균 제곱근(root mean square)으로만 정규화합니다.

핵심 차이:

LayerNorm:  x̂ = (x - μ) / σ           # 중심화  스케일 조정
RMSNorm:    x̂ = x / RMS(x)            # 스케일 조정만

공식:

RMS(x) = √((1/n) Σ xᵢ²)
x̂ᵢ = xᵢ / RMS(x)
yᵢ = γ · x̂ᵢ                            # 스케일 (바이어스 β 없음)

6.2 왜 RMSNorm을 사용하는가?

장점: 1. 더 간단한 계산 — 평균 계산이나 빼기 없음 2. 더 빠름 — 대형 모델에서 약 10-15% 속도 향상 3. 유사한 성능 — 경험적으로 LayerNorm과 일치 4. 광범위한 채택 — LLaMA, LLaMA 2, LLaMA 3, Gemma, Mistral

계산 절감:

LayerNorm:  2번의 패스 (평균,  다음 분산) + 2번의 연산 (빼기, 나누기)
RMSNorm:    1번의 패스 (RMS) + 1번의 연산 (나누기)

6.3 수동 구현

import torch
import torch.nn as nn

class RMSNorm(nn.Module):
    def __init__(self, dim, eps=1e-6):
        super().__init__()
        self.eps = eps
        self.weight = nn.Parameter(torch.ones(dim))

    def forward(self, x):
        # x 형태: (..., dim)

        # RMS 계산
        rms = torch.sqrt(torch.mean(x ** 2, dim=-1, keepdim=True) + self.eps)

        # 정규화 및 스케일
        x_norm = x / rms
        out = self.weight * x_norm

        return out

# 테스트
rms_norm = RMSNorm(512)
x = torch.randn(32, 100, 512)  # (batch, seq, features)
out = rms_norm(x)

print(f"출력 형태: {out.shape}")
print(f"RMS: {torch.sqrt(torch.mean(out[0] ** 2)).item():.6f}")  # 약 1.0이어야 함

6.4 LayerNorm과 비교

import torch
import torch.nn as nn
import time

class RMSNorm(nn.Module):
    def __init__(self, dim, eps=1e-6):
        super().__init__()
        self.eps = eps
        self.weight = nn.Parameter(torch.ones(dim))

    def forward(self, x):
        rms = torch.sqrt(torch.mean(x ** 2, dim=-1, keepdim=True) + self.eps)
        return self.weight * (x / rms)

# 벤치마크
dim = 4096
seq_len = 2048
batch_size = 8

x = torch.randn(batch_size, seq_len, dim, device='cuda')

layer_norm = nn.LayerNorm(dim).cuda()
rms_norm = RMSNorm(dim).cuda()

# 워밍업
for _ in range(10):
    _ = layer_norm(x)
    _ = rms_norm(x)

# LayerNorm 타이밍
torch.cuda.synchronize()
start = time.time()
for _ in range(100):
    _ = layer_norm(x)
torch.cuda.synchronize()
ln_time = time.time() - start

# RMSNorm 타이밍
torch.cuda.synchronize()
start = time.time()
for _ in range(100):
    _ = rms_norm(x)
torch.cuda.synchronize()
rms_time = time.time() - start

print(f"LayerNorm: {ln_time:.4f}s")
print(f"RMSNorm:   {rms_time:.4f}s")
print(f"속도 향상:   {ln_time / rms_time:.2f}x")

6.5 LLaMA에서의 RMSNorm

import torch
import torch.nn as nn
import torch.nn.functional as F

class LLaMATransformerBlock(nn.Module):
    """RMSNorm을 사용한 LLaMA 스타일 트랜스포머 블록."""

    def __init__(self, dim, num_heads, mlp_ratio=4):
        super().__init__()

        # RMSNorm을 사용한 Pre-normalization
        self.attn_norm = RMSNorm(dim)
        self.attn = nn.MultiheadAttention(dim, num_heads, batch_first=True)

        self.ffn_norm = RMSNorm(dim)
        self.ffn = nn.Sequential(
            nn.Linear(dim, mlp_ratio * dim, bias=False),
            nn.SiLU(),  # LLaMA는 SiLU (Swish) 활성화 함수 사용
            nn.Linear(mlp_ratio * dim, dim, bias=False)
        )

    def forward(self, x):
        # RMSNorm을 사용한 어텐션
        h = self.attn_norm(x)
        h = self.attn(h, h, h)[0]
        x = x + h

        # RMSNorm을 사용한 FFN
        h = self.ffn_norm(x)
        h = self.ffn(h)
        x = x + h

        return x

# 사용 예제
model = LLaMATransformerBlock(dim=4096, num_heads=32)
x = torch.randn(1, 2048, 4096)  # (batch, seq_len, dim)
out = model(x)
print(out.shape)  # torch.Size([1, 2048, 4096])

6.6 사용 사례

가장 적합한 경우: - 대형 언어 모델 (LLaMA, Mistral, Gemma) - 속도가 중요한 모든 트랜스포머 기반 모델 - 처음부터 훈련하는 모델 (LayerNorm 모델 미세 조정이 아님)


7. 기타 정규화 기법

7.1 가중치 정규화(Weight Normalization)

가중치 정규화는 크기와 방향을 분리하기 위해 가중치 벡터를 재매개변수화합니다.

원본:                w
재매개변수화:        w = g · (v / ||v||)

g = 스칼라 크기 (학습 가능)
v = 방향 벡터 (학습 가능)
import torch
import torch.nn as nn
from torch.nn.utils import weight_norm

# 레이어에 가중치 정규화 적용
linear = nn.Linear(128, 64)
linear = weight_norm(linear, name='weight')

# 가중치는 다음과 같이 재매개변수화됨: weight = g * v / ||v||
print(linear.weight_g)  # 크기 파라미터
print(linear.weight_v)  # 방향 파라미터

# 순전파
x = torch.randn(32, 128)
out = linear(x)

# 가중치 정규화 제거 (g와 v를 다시 weight로 병합)
linear = nn.utils.remove_weight_norm(linear)

사용 사례: RNN, GAN, 강화 학습 (A3C)

7.2 스펙트럼 정규화(Spectral Normalization)

스펙트럼 정규화는 가중치 행렬의 스펙트럼 노름(최대 특이값)을 1로 제한하여 GAN 훈련을 안정화합니다.

스펙트럼 노름: σ(W) = W의 최대 특이값
정규화된 가중치: W_SN = W / σ(W)
import torch
import torch.nn as nn
from torch.nn.utils import spectral_norm

# 스펙트럼 정규화 적용
conv = nn.Conv2d(3, 64, 3, padding=1)
conv = spectral_norm(conv)

# 스펙트럼 정규화를 사용한 판별자 (GAN용)
class SNDiscriminator(nn.Module):
    def __init__(self):
        super().__init__()
        self.model = nn.Sequential(
            spectral_norm(nn.Conv2d(3, 64, 4, stride=2, padding=1)),
            nn.LeakyReLU(0.2, inplace=True),
            spectral_norm(nn.Conv2d(64, 128, 4, stride=2, padding=1)),
            nn.LeakyReLU(0.2, inplace=True),
            spectral_norm(nn.Conv2d(128, 256, 4, stride=2, padding=1)),
            nn.LeakyReLU(0.2, inplace=True),
            spectral_norm(nn.Conv2d(256, 1, 4, stride=1, padding=0)),
        )

    def forward(self, x):
        return self.model(x)

# 사용법
disc = SNDiscriminator()
x = torch.randn(16, 3, 64, 64)
out = disc(x)
print(out.shape)  # torch.Size([16, 1, 5, 5])

사용 사례: GAN 판별자 (SNGAN, BigGAN, StyleGAN2)

7.3 적응적 인스턴스 정규화(Adaptive Instance Normalization, AdaIN)

AdaIN은 스타일 입력에 기반하여 InstanceNorm 통계를 적응적으로 조정하여 실시간 스타일 전이를 가능하게 합니다.

AdaIN(content, style) = σ(style) · ((content - μ(content)) / σ(content)) + μ(style)

스타일 통계 (평균, 표준편차)를 콘텐츠 특성으로 전송
import torch
import torch.nn as nn

class AdaIN(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, content, style):
        # content, style: (N, C, H, W)

        # 통계 계산
        content_mean = content.mean(dim=[2, 3], keepdim=True)
        content_std = content.std(dim=[2, 3], keepdim=True)

        style_mean = style.mean(dim=[2, 3], keepdim=True)
        style_std = style.std(dim=[2, 3], keepdim=True)

        # 콘텐츠 정규화, 그 다음 스타일 통계 적용
        normalized = (content - content_mean) / (content_std + 1e-5)
        stylized = normalized * style_std + style_mean

        return stylized

# AdaIN을 사용한 스타일 전이
class StyleTransferNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.encoder = nn.Sequential(
            nn.Conv2d(3, 64, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 128, 3, padding=1),
            nn.ReLU(inplace=True)
        )
        self.adain = AdaIN()
        self.decoder = nn.Sequential(
            nn.Conv2d(128, 64, 3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 3, 3, padding=1)
        )

    def forward(self, content, style):
        content_feat = self.encoder(content)
        style_feat = self.encoder(style)

        # AdaIN 레이어
        t = self.adain(content_feat, style_feat)

        out = self.decoder(t)
        return out

# 사용법
model = StyleTransferNet()
content = torch.randn(1, 3, 256, 256)
style = torch.randn(1, 3, 256, 256)
stylized = model(content, style)
print(stylized.shape)  # torch.Size([1, 3, 256, 256])

사용 사례: 실시간 스타일 전이, 이미지 간 변환

7.4 비교 표

방법 정규화 대상 학습 가능한 파라미터 배치 의존적 사용 사례
Batch Norm 각 (C,H,W)에 대해 (N) γ, β, 이동 통계 CNN, 큰 배치
Layer Norm 각 N에 대해 (C,H,W) γ, β 아니오 트랜스포머, RNN
Instance Norm 각 (N,C)에 대해 (H,W) γ, β (선택적) 아니오 스타일 전이, GAN
Group Norm 각 (N,G)에 대해 (C/G,H,W) γ, β 아니오 탐지, 작은 배치
RMSNorm 각 N에 대해 (C,H,W) γ 아니오 LLM, 빠른 트랜스포머
Weight Norm 가중치 벡터 g, v 아니오 RNN, GAN
Spectral Norm 가중치 행렬 아니오 GAN 판별자
AdaIN 스타일에 따라 (H,W) 아니오 스타일 전이

8. 종합 비교

8.1 시각적 비교

입력 텐서: (N, C, H, W)
N = 배치 (4개 샘플)
C = 채널 (3)
H, W = 높이, 너비 (32 × 32)

┌─────────────────────────────────────────────────────────────────────┐
│  배치 정규화                                                         │
│  ┌──────┬──────┬──────┬──────┐                                      │
│  │ N=0  │ N=1  │ N=2  │ N=3  │                                      │
│  ├──────┼──────┼──────┼──────┤  각 (C, H, W) 위치에 대해:           │
│  │ c=0  │ c=0  │ c=0  │ c=0  │  N에서 평균/분산 계산                │
│  │ h,w  │ h,w  │ h,w  │ h,w  │  (4개 값)                            │
│  └──────┴──────┴──────┴──────┘                                      │
│  정규화: (x - μ_batch) / σ_batch                                    │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│  레이어 정규화                                                       │
│  ┌───────────────────────────┐                                      │
│  │       N=0                 │  각 샘플에 대해:                      │
│  ├───────┬───────┬───────────┤  모든 (C, H, W)에서                  │
│  │ c=0   │ c=1   │ c=2       │  평균/분산 계산                       │
│  │ (all  │ (all  │ (all      │  (3 × 32 × 32 = 3072개 값)           │
│  │ h,w)  │ h,w)  │ h,w)      │                                      │
│  └───────┴───────┴───────────┘                                      │
│  정규화: (x - μ_layer) / σ_layer                                    │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│  인스턴스 정규화                                                     │
│  ┌────────────────┐                                                 │
│  │  N=0, C=0      │  각 (샘플, 채널)에 대해:                         │
│  ├────────────────┤  (H, W)에서                                     │
│  │   (all h,w)    │  평균/분산 계산                                  │
│  │                │  (32 × 32 = 1024개 값)                          │
│  └────────────────┘                                                 │
│  정규화: (x - μ_instance) / σ_instance                              │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│  그룹 정규화 (G=3, 그룹당 1채널)                                     │
│  ┌────────────────┐                                                 │
│  │ N=0, G=0       │  각 (샘플, 그룹)에 대해:                         │
│  │ (c=0만)        │  (C/G, H, W)에서                                │
│  ├────────────────┤  평균/분산 계산                                  │
│  │   (all h,w)    │  (1 × 32 × 32 = 1024개 값)                      │
│  └────────────────┘                                                 │
│  정규화: (x - μ_group) / σ_group                                    │
└─────────────────────────────────────────────────────────────────────┘

8.2 언제 무엇을 사용할까?

결정 트리

트랜스포머를 사용하고 있나요?
  ├─ 예 → LayerNorm 또는 RMSNorm
  │         ├─ 속도가 중요? → RMSNorm (LLaMA, Mistral)
  │         └─ 그렇지 않으면 → LayerNorm (BERT, ViT)
  │
  └─ 아니오 → CNN을 사용하고 있나요?
           ├─ 예 → 배치 크기가 큰가요 (≥16)?
           │         ├─ 예 → BatchNorm
           │         └─ 아니오 → GroupNorm
           │
           └─ 아니오 → GAN이나 스타일 전이인가요?
                     ├─ 예 → InstanceNorm 또는 AdaIN
                     └─ 아니오 → LayerNorm (안전한 기본값)

아키텍처별 권장 사항

합성곱 신경망(CNN):

# 표준 분류 (ImageNet)
# 배치 크기: 32-256
class ResNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer = nn.Sequential(
            nn.Conv2d(3, 64, 7, stride=2, padding=3),
            nn.BatchNorm2d(64),  # ← 큰 배치에 BatchNorm
            nn.ReLU(inplace=True)
        )

객체 탐지 / 분할:

# Mask R-CNN, Faster R-CNN
# 배치 크기: 1-4 (큰 이미지에 대해 GPU 메모리 제한)
class DetectionBackbone(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer = nn.Sequential(
            nn.Conv2d(3, 64, 7, stride=2, padding=3),
            nn.GroupNorm(32, 64),  # ← 작은 배치에 GroupNorm
            nn.ReLU(inplace=True)
        )

트랜스포머 (비전 또는 언어):

# BERT, GPT, ViT
class TransformerEncoder(nn.Module):
    def __init__(self, d_model):
        super().__init__()
        self.norm1 = nn.LayerNorm(d_model)  # ← LayerNorm 표준
        self.attn = nn.MultiheadAttention(d_model, 8, batch_first=True)
        self.norm2 = nn.LayerNorm(d_model)
        self.ffn = nn.Sequential(
            nn.Linear(d_model, 4 * d_model),
            nn.GELU(),
            nn.Linear(4 * d_model, d_model)
        )

대형 언어 모델:

# LLaMA, Mistral, Gemma
class LLMTransformerBlock(nn.Module):
    def __init__(self, dim):
        super().__init__()
        self.attn_norm = RMSNorm(dim)  # ← 속도를 위한 RMSNorm
        self.ffn_norm = RMSNorm(dim)

생성적 적대 신경망(GAN):

# 생성자: 스타일을 위한 InstanceNorm
class Generator(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer = nn.Sequential(
            nn.Conv2d(128, 64, 3, padding=1),
            nn.InstanceNorm2d(64),  # ← InstanceNorm
            nn.ReLU(inplace=True)
        )

# 판별자: 스펙트럼 정규화
class Discriminator(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer = nn.Sequential(
            spectral_norm(nn.Conv2d(3, 64, 4, stride=2, padding=1)),  # ← SpectralNorm
            nn.LeakyReLU(0.2, inplace=True)
        )

8.3 성능 벤치마크

import torch
import torch.nn as nn
import time

def benchmark_normalization(norm_layer, input_shape, num_iterations=1000):
    """정규화 레이어 벤치마크."""
    x = torch.randn(*input_shape, device='cuda')

    # 워밍업
    for _ in range(10):
        _ = norm_layer(x)

    torch.cuda.synchronize()
    start = time.time()
    for _ in range(num_iterations):
        _ = norm_layer(x)
    torch.cuda.synchronize()
    elapsed = time.time() - start

    return elapsed

# 테스트 구성
batch_size = 32
channels = 256
height, width = 56, 56
input_shape = (batch_size, channels, height, width)

# 정규화 레이어
norms = {
    'BatchNorm2d': nn.BatchNorm2d(channels).cuda(),
    'GroupNorm (G=32)': nn.GroupNorm(32, channels).cuda(),
    'InstanceNorm2d': nn.InstanceNorm2d(channels).cuda(),
}

print(f"입력 형태: {input_shape}")
print(f"반복 횟수: 1000\n")

results = {}
for name, norm in norms.items():
    norm.eval()
    elapsed = benchmark_normalization(norm, input_shape)
    results[name] = elapsed
    print(f"{name:20s}: {elapsed:.4f}s")

# 상대 속도
baseline = results['BatchNorm2d']
print("\nBatchNorm2d 대비:")
for name, elapsed in results.items():
    print(f"{name:20s}: {elapsed / baseline:.2f}x")

일반적인 결과 (RTX 3090):

BatchNorm2d         : 0.1234s  (1.00x)
GroupNorm (G=32)    : 0.1456s  (1.18x)
InstanceNorm2d      : 0.1389s  (1.13x)

트랜스포머의 경우 (seq_len=2048, d_model=4096):

LayerNorm           : 0.2145s  (1.00x)
RMSNorm             : 0.1876s  (0.87x)    13% 빠름

9. 실용적인 팁

9.1 γ와 β의 초기화

기본 초기화 (PyTorch):

# γ (스케일)은 1로 초기화
# β (시프트)는 0으로 초기화
# 처음에는 원래 분포를 유지함

특수 사례:

import torch.nn as nn

# 잔차 블록에 대해 γ를 0으로 초기화 (He et al., 2019)
# "Fixup Initialization" — 매우 깊은 네트워크에 도움
class ResidualBlock(nn.Module):
    def __init__(self, channels):
        super().__init__()
        self.conv1 = nn.Conv2d(channels, channels, 3, padding=1)
        self.bn1 = nn.BatchNorm2d(channels)
        self.conv2 = nn.Conv2d(channels, channels, 3, padding=1)
        self.bn2 = nn.BatchNorm2d(channels)

        # 각 블록의 마지막 BatchNorm을 0으로 초기화
        nn.init.constant_(self.bn2.weight, 0)  # γ = 0
        nn.init.constant_(self.bn2.bias, 0)    # β = 0

    def forward(self, x):
        identity = x
        out = self.conv1(x)
        out = self.bn1(out)
        out = F.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)  # 처음에는 0을 출력하므로 out = identity
        out += identity
        out = F.relu(out)
        return out

9.2 가중치 초기화와의 상호작용

BatchNorm은 네트워크를 가중치 초기화에 덜 민감하게 만들지만, 여전히 적절한 초기화를 사용해야 합니다:

import torch.nn as nn
import torch.nn.init as init

class ConvBNReLU(nn.Module):
    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, 3, padding=1)
        self.bn = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)

        # ReLU를 위한 He 초기화
        init.kaiming_normal_(self.conv.weight, mode='fan_out', nonlinearity='relu')
        init.constant_(self.conv.bias, 0)

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

9.3 정규화와 학습률

핵심 통찰: 정규화는 더 높은 학습률을 허용합니다.

import torch.optim as optim

# 정규화 없음
model_no_norm = MyModel(use_norm=False)
optimizer = optim.SGD(model_no_norm.parameters(), lr=0.01)  # 보수적인 학습률

# BatchNorm/LayerNorm 사용
model_with_norm = MyModel(use_norm=True)
optimizer = optim.SGD(model_with_norm.parameters(), lr=0.1)  # 10배 높은 학습률!

# LayerNorm을 사용한 현대 트랜스포머
# 워밍업과 함께 더 높은 학습률 사용 가능
optimizer = optim.AdamW(model.parameters(), lr=1e-3)

학습률 스케일링 규칙 (Goyal et al., 2017):

배치 크기를 k배 증가시킬 때, 학습률도 k배 증가
(BatchNorm에서만 작동!)

배치 256, 학습률 0.1  →  배치 1024, 학습률 0.4

9.4 일반적인 버그와 함정

버그 1: model.eval() 잊기

import torch
import torch.nn as nn

model = nn.Sequential(
    nn.Conv2d(3, 64, 3, padding=1),
    nn.BatchNorm2d(64),
    nn.ReLU()
)

# 훈련 모드
model.train()
x = torch.randn(1, 3, 32, 32)
out_train = model(x)

# 추론 — 잘못됨! 여전히 배치 통계 사용
# out_test = model(x)  # 버그: 여전히 훈련 모드!

# 추론 — 올바름
model.eval()  # 평가 모드로 전환!
out_test = model(x)

print(f"출력이 같은가? {torch.allclose(out_train, out_test)}")  # False

버그 2: 잘못된 차원 순서

# 잘못됨: LayerNorm에 대해 (seq_len, batch, features)
x = torch.randn(100, 32, 512)  # (seq, batch, features)
ln = nn.LayerNorm(512)
out = ln(x)  # 작동하지만 마지막 차원만 정규화

# 올바름: 특성에 대해 정규화 (마지막 차원)
# 텐서 레이아웃이 normalized_shape과 일치하는지 확인!

# (batch, seq, features)의 경우 — 현재 표준
x = torch.randn(32, 100, 512)  # (batch, seq, features)
ln = nn.LayerNorm(512)
out = ln(x)  # 올바름: 특성 차원에서 정규화

버그 3: 호환되지 않는 채널을 가진 GroupNorm

# 잘못됨: num_channels가 num_groups로 나누어떨어지지 않음
try:
    gn = nn.GroupNorm(num_groups=32, num_channels=50)  # 50 % 32 != 0
except ValueError as e:
    print(f"오류: {e}")

# 올바름: 나누어떨어지도록 확인
gn = nn.GroupNorm(num_groups=32, num_channels=64)  # 64 % 32 == 0 ✓

버그 4: batch_size = 1인 BatchNorm

# 문제: 단일 샘플을 가진 BatchNorm
bn = nn.BatchNorm2d(64)
x = torch.randn(1, 64, 32, 32)  # 배치 크기 = 1

bn.train()
out = bn(x)  # 분산 = 0! (단일 샘플)
# NaN 또는 불안정한 훈련 발생

# 해결책 1: GroupNorm 또는 LayerNorm 사용
gn = nn.GroupNorm(32, 64)
out = gn(x)  # batch_size=1에서 잘 작동

# 해결책 2: BatchNorm을 평가 모드로 설정
bn.eval()
out = bn(x)  # 이동 통계 사용

버그 5: 고정 및 훈련 가능 BatchNorm 혼합

# 문제: 고정된 BatchNorm 통계로 미세 조정
pretrained_model = torchvision.models.resnet50(pretrained=True)

# 모든 파라미터 고정
for param in pretrained_model.parameters():
    param.requires_grad = False

# 이것으로 충분하지 않음! BatchNorm은 여전히 훈련 모드 통계 사용
pretrained_model.train()  # 버그: BatchNorm이 훈련 모드!

# 해결책: 평가 모드로 설정 또는 BatchNorm 모듈을 평가로 설정
pretrained_model.eval()  # 추론에 안전

# 미세 조정의 경우:
def set_bn_eval(module):
    if isinstance(module, nn.BatchNorm2d):
        module.eval()

pretrained_model.train()
pretrained_model.apply(set_bn_eval)  # 훈련 중 BatchNorm을 평가 모드로 유지

9.5 모범 사례 체크리스트

추론 전에 항상 model.eval() 호출

아키텍처에 맞는 정규화 사용: - CNN (큰 배치) → BatchNorm - CNN (작은 배치) → GroupNorm - 트랜스포머 → LayerNorm 또는 RMSNorm - GAN → InstanceNorm (생성자), SpectralNorm (판별자)

적절한 초기화 사용 (ReLU에 Kaiming, Tanh에 Xavier)

정규화 사용 시 학습률 증가

전이 학습의 경우, BatchNorm 통계 고정 또는 GroupNorm으로 대체 고려

이동 통계 모니터링 — 훈련 중 안정화되는지 확인

분산 훈련의 경우, 필요시 SyncBatchNorm 사용:

model = nn.SyncBatchNorm.convert_sync_batchnorm(model)

연습 문제

연습 문제 1: 정규화 방법 구현 및 비교

다양한 정규화 방법을 사용하는 CNN을 구현하고 CIFAR-10에서 성능을 비교하세요.

과제: 1. 각각 다른 정규화를 사용하는 4개의 동일한 CNN 생성: - BatchNorm2d - GroupNorm (32개 그룹) - LayerNorm - 정규화 없음 (기준선) 2. 각각 CIFAR-10에서 20 에포크 훈련 3. 훈련 곡선 (손실 및 정확도) 플롯 4. 각각의 최종 테스트 정확도 보고 5. 배치 크기 [4, 16, 64]로 실험하고 어떤 정규화가 가장 견고한지 관찰

시작 코드:

import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms

class CIFAR10Net(nn.Module):
    def __init__(self, norm_type='batch'):
        super().__init__()
        self.norm_type = norm_type

        def conv_block(in_c, out_c):
            layers = [nn.Conv2d(in_c, out_c, 3, padding=1)]

            if norm_type == 'batch':
                layers.append(nn.BatchNorm2d(out_c))
            elif norm_type == 'group':
                layers.append(nn.GroupNorm(32, out_c))
            elif norm_type == 'layer':
                # 2D용 LayerNorm: (C, H, W)에 대해 정규화
                layers.append(nn.GroupNorm(1, out_c))  # G=1은 LayerNorm
            # 'none': 정규화 없음

            layers.append(nn.ReLU(inplace=True))
            return nn.Sequential(*layers)

        self.features = nn.Sequential(
            conv_block(3, 64),
            conv_block(64, 64),
            nn.MaxPool2d(2),
            conv_block(64, 128),
            conv_block(128, 128),
            nn.MaxPool2d(2),
            conv_block(128, 256),
            conv_block(256, 256),
            nn.MaxPool2d(2),
        )

        self.classifier = nn.Sequential(
            nn.AdaptiveAvgPool2d(1),
            nn.Flatten(),
            nn.Linear(256, 10)
        )

    def forward(self, x):
        x = self.features(x)
        x = self.classifier(x)
        return x

# TODO: 훈련 루프 및 비교 구현

연습 문제 2: 트랜스포머에서 RMSNorm vs LayerNorm

작은 트랜스포머를 구현하고 속도와 성능 측면에서 RMSNorm과 LayerNorm을 비교하세요.

과제: 1. 문자 수준 언어 모델 구현 (다음 문자 예측) 2. 두 버전 훈련: LayerNorm 사용, RMSNorm 사용 3. 텍스트 데이터셋 사용 (예: Shakespeare, WikiText-2) 4. 측정: - 에포크당 훈련 시간 - 최종 perplexity - 추론 속도 5. 분석: RMSNorm이 더 빠르면서도 LayerNorm 성능과 일치하는가?

시작 코드:

import torch
import torch.nn as nn

class TransformerLM(nn.Module):
    def __init__(self, vocab_size, d_model=512, num_heads=8,
                 num_layers=6, norm_type='layer'):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, d_model)
        self.pos_encoding = nn.Parameter(torch.randn(1, 1000, d_model))

        # 정규화 선택
        if norm_type == 'layer':
            norm_cls = lambda: nn.LayerNorm(d_model)
        elif norm_type == 'rms':
            norm_cls = lambda: RMSNorm(d_model)

        self.layers = nn.ModuleList([
            TransformerBlock(d_model, num_heads, norm_cls)
            for _ in range(num_layers)
        ])

        self.output = nn.Linear(d_model, vocab_size)

    def forward(self, x):
        # x: (batch, seq_len)
        x = self.embedding(x) + self.pos_encoding[:, :x.size(1), :]

        for layer in self.layers:
            x = layer(x)

        logits = self.output(x)
        return logits

# TODO: 훈련 및 벤치마킹 구현

연습 문제 3: 스타일 전이를 위한 적응적 인스턴스 정규화

AdaIN을 사용한 간단한 스타일 전이 네트워크를 구현하세요.

과제: 1. 중간에 AdaIN을 사용한 인코더-디코더 아키텍처 구현 2. 인코더로 사전 훈련된 VGG 네트워크 사용 (가중치 고정) 3. 이미지를 재구성하는 디코더 훈련 4. 스타일 통계를 전송하는 AdaIN 레이어 구현 5. 콘텐츠 및 스타일 이미지에서 테스트 (torchvision 데이터셋 또는 자체 이미지 사용) 6. 스타일화된 출력 시각화 7. 보너스: 제어 가능한 스타일 전이 구현 (콘텐츠/스타일 혼합을 위한 α 파라미터)

시작 코드:

import torch
import torch.nn as nn
from torchvision import models

class AdaIN(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, content, style):
        # TODO: AdaIN 구현
        # 1. 콘텐츠의 평균과 표준편차 계산
        # 2. 스타일의 평균과 표준편차 계산
        # 3. 콘텐츠 정규화, 스타일 통계 적용
        pass

class StyleTransferNet(nn.Module):
    def __init__(self):
        super().__init__()

        # 인코더: VGG19 (고정됨)
        vgg = models.vgg19(pretrained=True).features
        self.encoder = nn.Sequential(*list(vgg.children())[:21])  # relu4_1까지
        for param in self.encoder.parameters():
            param.requires_grad = False

        # AdaIN 레이어
        self.adain = AdaIN()

        # 디코더: 인코더의 거울
        self.decoder = nn.Sequential(
            # TODO: 디코더 구현 (인코더의 역순)
            # ConvTranspose2d 또는 Upsample + Conv2d 사용
        )

    def forward(self, content, style):
        # TODO:
        # 1. 콘텐츠와 스타일 인코딩
        # 2. AdaIN 적용
        # 3. 디코딩
        pass

# TODO: 지각 손실을 사용한 훈련 루프 구현

참고 자료

  1. Batch Normalization:
  2. Ioffe & Szegedy (2015). "Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift." ICML.
  3. Santurkar et al. (2018). "How Does Batch Normalization Help Optimization?" NeurIPS.

  4. Layer Normalization:

  5. Ba et al. (2016). "Layer Normalization." arXiv:1607.06450.

  6. Group Normalization:

  7. Wu & He (2018). "Group Normalization." ECCV.

  8. Instance Normalization:

  9. Ulyanov et al. (2016). "Instance Normalization: The Missing Ingredient for Fast Stylization." arXiv:1607.08022.

  10. RMSNorm:

  11. Zhang & Sennrich (2019). "Root Mean Square Layer Normalization." NeurIPS.
  12. Touvron et al. (2023). "LLaMA: Open and Efficient Foundation Language Models." arXiv:2302.13971.

  13. Spectral Normalization:

  14. Miyato et al. (2018). "Spectral Normalization for Generative Adversarial Networks." ICLR.

  15. AdaIN:

  16. Huang & Belongie (2017). "Arbitrary Style Transfer in Real-time with Adaptive Instance Normalization." ICCV.

  17. 종합 분석:

  18. Bjorck et al. (2018). "Understanding Batch Normalization." NeurIPS.
  19. Goyal et al. (2017). "Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour." arXiv:1706.02677.

  20. PyTorch 문서:

  21. https://pytorch.org/docs/stable/nn.html#normalization-layers
  22. https://pytorch.org/docs/stable/generated/torch.nn.BatchNorm2d.html
  23. https://pytorch.org/docs/stable/generated/torch.nn.LayerNorm.html
  24. https://pytorch.org/docs/stable/generated/torch.nn.GroupNorm.html

  25. 실용 가이드:

    • He et al. (2019). "Bag of Tricks for Image Classification with Convolutional Neural Networks." CVPR.
    • Xiong et al. (2020). "On Layer Normalization in the Transformer Architecture." ICML.
to navigate between lessons