AI 기초

GUIDE / 중급

기울기 소실과 폭발: 깊은 네트워크의 고질적 문제

깊은 신경망 학습을 방해하는 기울기 소실(Vanishing Gradient)과 기울기 폭발(Exploding Gradient)의 수학적 원인을 이해한다. ReLU, 잔차 연결, 배치 정규화, Gradient Clipping 등 현대적 해결책을 코드와 함께 정리한다.

PALDYN Team7 MIN READ

지난 글에서 드롭아웃이 과적합을 막는 앙상블 정규화임을 배웠다. 이번 글에서는 깊은 신경망 학습의 가장 근본적인 난제인 기울기 소실(Vanishing Gradient)과 기울기 폭발(Exploding Gradient)을 다룬다. 이 두 문제는 1990년대에 처음 발견되었고, 2012년 딥러닝 붐 이전까지 깊은 네트워크 학습을 사실상 불가능하게 만들었다. 오늘날 ResNet, LSTM, Transformer 등 현대 아키텍처들은 모두 이 문제의 해결책을 내포하고 있다.

기울기 소실의 수학적 원인

역전파는 연쇄 법칙으로 기울기를 계산한다. N층 네트워크에서 첫 번째 층의 기울기는:

LW1=LaNk=2Nakak1a1W1\frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial a_N} \cdot \prod_{k=2}^{N} \frac{\partial a_k}{\partial a_{k-1}} \cdot \frac{\partial a_1}{\partial W_1}

각 층의 로컬 기울기 ∂aₖ/∂aₖ₋₁ = W^(k) · f'(z^(k-1))를 곱한다.

Sigmoid를 사용하는 경우:

  • σ'(z) = σ(z)(1−σ(z)) ≤ 0.25
  • N층이면 최대 기울기 ≤ 0.25ᴺ
  • 10층이면 최대 0.25¹⁰ ≈ 10⁻⁶ (사실상 0)
import torch
import torch.nn as nn
import matplotlib.pyplot as plt

# Sigmoid 기울기 소실 실험
def check_gradient_flow(model, x):
    y = model(x)
    loss = y.sum()
    loss.backward()
    grad_norms = []
    for name, param in model.named_parameters():
        if param.grad is not None and 'weight' in name:
            grad_norms.append(param.grad.norm().item())
    return grad_norms

# Sigmoid 20층 네트워크
sigmoid_model = nn.Sequential(
    *[layer for _ in range(20)
      for layer in [nn.Linear(64, 64), nn.Sigmoid()]]
)

x = torch.randn(32, 64)
grads = check_gradient_flow(sigmoid_model, x)
for i, g in enumerate(grads):
    print(f"층 {i+1:2d}: 기울기 norm = {g:.2e}")
# 층 20: 기울기 norm = 2.15e-01
# 층 15: 기울기 norm = 3.47e-03
# 층 10: 기울기 norm = 1.82e-05
# 층  1: 기울기 norm = 4.23e-09  ← 사실상 0!

기울기 폭발

반대로 가중치 행렬의 최대 특이값(spectral norm)이 1보다 크면 기울기가 지수적으로 증가한다.

# 기울기 폭발 시뮬레이션
import torch

# 큰 가중치를 가진 선형 변환 반복
x = torch.tensor([1.0])
W = torch.tensor([[2.0]])  # |W| = 2 > 1

for i in range(20):
    x = W @ x
    print(f"Step {i+1:2d}: x = {x.item():.2e}")
# Step  1: x = 2.00e+00
# Step  5: x = 3.20e+01
# Step 10: x = 1.02e+03
# Step 20: x = 1.05e+06  → 폭발

# RNN에서 시퀀스 길이만큼 반복되므로 특히 심각

기울기 폭발은 NaN이나 Inf가 파라미터에 나타나며 학습이 완전히 망가진다.

기울기 소실과 폭발 문제

해결책 1: ReLU 활성화

ReLU의 미분은 z > 0이면 1, z ≤ 0이면 0이다. 양수 영역에서 기울기가 그대로 통과되어 소실이 일어나지 않는다.

# ReLU vs Sigmoid 기울기 비교
relu_model = nn.Sequential(
    *[layer for _ in range(20)
      for layer in [nn.Linear(64, 64), nn.ReLU()]]
)
nn.init.kaiming_normal_(relu_model[0].weight)

x = torch.randn(32, 64)
grads_relu = check_gradient_flow(relu_model, x)
# 층 20: 기울기 norm = 2.15e-01
# 층  1: 기울기 norm = 1.87e-01  ← 소실 없음!

# Dying ReLU: 모든 입력이 음수면 기울기 = 0
# → Leaky ReLU, ELU, He 초기화로 완화

해결책 2: 잔차 연결 (Residual Connection)

2015년 He et al.이 제안한 ResNet의 핵심 아이디어. 서브레이어의 출력에 입력을 더한다.

class ResidualBlock(nn.Module):
    def __init__(self, dim, dropout=0.1):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Linear(dim, dim),
            nn.LayerNorm(dim),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(dim, dim),
        )
        self.norm = nn.LayerNorm(dim)

    def forward(self, x):
        # 잔차 연결: f(x) + x
        # 기울기: ∂L/∂x = ∂L/∂y · (I + ∂f/∂x)
        # I (항등 행렬)로 인해 항상 최소 기울기 1 보장
        return self.norm(self.layers(x) + x)

# 100층도 안정적으로 학습 가능
deep_resnet = nn.Sequential(
    nn.Linear(784, 256),
    *[ResidualBlock(256) for _ in range(50)],
    nn.Linear(256, 10),
)
print(f"파라미터: {sum(p.numel() for p in deep_resnet.parameters()):,}")

잔차 연결이 있으면 기울기가 f(x) + x → x 경로를 통해 직접 초기 층까지 흐를 수 있다. 이것이 ResNet이 100층, 200층도 학습 가능한 이유다.

해결책 3: Gradient Clipping

기울기 폭발은 주로 RNN/LSTM에서 발생한다. Gradient Clipping은 기울기 벡터의 노름이 임계값을 초과하면 스케일다운한다.

import torch.nn as nn

model = nn.LSTM(input_size=128, hidden_size=256,
                num_layers=4, batch_first=True)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)

for batch in dataloader:
    optimizer.zero_grad()
    out, _ = model(batch)
    loss = criterion(out, targets)
    loss.backward()

    # 기울기 노름 계산 및 클리핑
    total_norm = nn.utils.clip_grad_norm_(
        model.parameters(),
        max_norm=1.0  # 임계값
    )

    if total_norm > 10:  # 폭발 감지
        print(f"Warning: grad norm = {total_norm:.2f}")

    optimizer.step()

기울기 모니터링

def log_gradient_norms(model, step, writer=None):
    total_norm = 0.0
    for name, param in model.named_parameters():
        if param.grad is None:
            continue
        norm = param.grad.data.norm(2).item()
        total_norm += norm ** 2
        if writer:
            writer.add_scalar(f"grad/{name}", norm, step)
    total_norm = total_norm ** 0.5

    if total_norm < 1e-6:
        print(f"Step {step}: 기울기 소실 의심 ({total_norm:.2e})")
    elif total_norm > 100:
        print(f"Step {step}: 기울기 폭발 의심 ({total_norm:.2e})")
    return total_norm

기울기 소실 해결책 코드

현대 아키텍처의 종합적 해결책

문제 해결책 적용된 아키텍처
기울기 소실 ReLU/GELU 활성화 모든 현대 네트워크
기울기 소실 잔차 연결 ResNet, Transformer
기울기 소실 배치/레이어 정규화 ResNet, Transformer
기울기 소실 He/Xavier 초기화 모든 현대 네트워크
기울기 폭발 Gradient Clipping RNN, Transformer 학습
기울기 소실/폭발 LSTM/GRU 게이트 순환 신경망

기울기 소실·폭발 문제는 완전히 "해결"된 것이 아니라, 적절한 기법들의 조합으로 관리되는 것이다. 현대 딥러닝의 여러 설계 결정들—ReLU, 잔차 연결, 배치 정규화, 신중한 초기화—이 모두 이 문제를 염두에 두고 개발되었다. 신경망의 기초는 여기까지 살펴봤다. 다음 단계는 이미지를 위한 합성곱 신경망(CNN)이다.


지난 글: 드롭아웃: 과적합을 막는 앙상블 정규화

다음 글: 합성곱 연산: CNN의 핵심 원리


읽어주셔서 감사합니다. 😊

KEEP READING

이어 읽기

AI 기초2026.07.29

검증자 모델: 답을 고르는 눈을 따로 기른다

규칙 검증기, 결과 보상 모델(ORM), 과정 보상 모델(PRM), 자기 검증의 특성과 한계를 비교하고, 여러 후보 중 정답을 골라내는 검증 계층을 실무에서 어떻게 구성할지 코드와 함께 정리합니다.

중급16 MIN
AI 기초2026.07.29

사고 예산 제어: 얼마나 생각하게 할 것인가

사고 예산과 출력 상한의 관계, 난도에 따른 적응형 예산 배분, 예산 소진으로 답변이 잘리는 실패 모드와 그 방어책을 실무 코드와 운영 지표 중심으로 정리합니다.

중급14 MIN