Gradient Vanishing과 Exploding 완벽 정리

딥러닝을 공부하다 보면 반드시 마주치게 되는 두 가지 문제가 있습니다: **Gradient Vanishing(기울기 소실)**과 **Gradient Exploding(기울기 폭발)**입니다. 이 두 문제는 심층 신경망 학습을 어렵게 만드는 주요 원인이며, 오늘은 이에 대해 자세히 알아보겠습니다.

🧠 기본 개념 이해하기

Gradient란?

Gradient(기울기)는 손실 함수의 각 파라미터에 대한 편미분값입니다. 역전파(Backpropagation) 과정에서 이 gradient를 계산하여 모델의 가중치를 업데이트합니다.

새로운 가중치 = 기존 가중치 - 학습률 × gradient

문제의 핵심

심층 신경망에서는 역전파 시 chain rule에 의해 gradient들이 계속 곱해집니다. 이 과정에서:

  • 0에 가까운 값들이 계속 곱해지면 → Gradient Vanishing
  • 1보다 큰 값들이 계속 곱해지면 → Gradient Exploding

📉 Gradient Vanishing (기울기 소실)

정의

심층 신경망의 역전파 과정에서 gradient가 점점 작아져서 거의 0에 가까워지는 현상입니다. 이로 인해 초기 레이어들의 가중치가 거의 업데이트되지 않게 됩니다.

발생 원인

1. Sigmoid/Tanh 활성화 함수

import numpy as np
import matplotlib.pyplot as plt

# Sigmoid 함수와 그 도함수
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sigmoid_derivative(x):
    s = sigmoid(x)
    return s * (1 - s)

# 시각화
x = np.linspace(-10, 10, 100)
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.plot(x, sigmoid(x))
plt.title('Sigmoid Function')
plt.subplot(1, 2, 2)
plt.plot(x, sigmoid_derivative(x))
plt.title('Sigmoid Derivative (max: 0.25)')
plt.show()

Sigmoid 도함수의 최댓값은 0.25입니다. 여러 층을 거치면서 이 값들이 곱해지면:

  • 4층: 0.25^4 ≈ 0.004
  • 10층: 0.25^10 ≈ 0.000001

2. 깊은 네트워크 구조

레이어가 많을수록 gradient가 더 많이 곱해져서 소실 문제가 심해집니다.

증상

  • 학습 초기에 손실이 잘 감소하지 않음
  • 초기 레이어의 가중치가 거의 변하지 않음
  • 학습이 매우 느리거나 멈춤

해결 방법

1. ReLU 계열 활성화 함수 사용

def relu(x):
    return np.maximum(0, x)

def leaky_relu(x, alpha=0.01):
    return np.where(x > 0, x, alpha * x)

def elu(x, alpha=1.0):
    return np.where(x > 0, x, alpha * (np.exp(x) - 1))

2. 적절한 가중치 초기화

# He 초기화 (ReLU용)
def he_initialization(input_dim, output_dim):
    return np.random.randn(input_dim, output_dim) * np.sqrt(2.0 / input_dim)

# Xavier 초기화 (Sigmoid/Tanh용)
def xavier_initialization(input_dim, output_dim):
    return np.random.randn(input_dim, output_dim) * np.sqrt(1.0 / input_dim)

3. Batch Normalization

class BatchNormalization:
    def __init__(self, dim, eps=1e-5, momentum=0.9):
        self.gamma = np.ones(dim)
        self.beta = np.zeros(dim)
        self.running_mean = np.zeros(dim)
        self.running_var = np.ones(dim)
        self.eps = eps
        self.momentum = momentum
    
    def forward(self, x, training=True):
        if training:
            mean = np.mean(x, axis=0)
            var = np.var(x, axis=0)
            self.running_mean = self.momentum * self.running_mean + (1 - self.momentum) * mean
            self.running_var = self.momentum * self.running_var + (1 - self.momentum) * var
        else:
            mean = self.running_mean
            var = self.running_var
        
        x_normalized = (x - mean) / np.sqrt(var + self.eps)
        return self.gamma * x_normalized + self.beta

4. Skip Connections (ResNet)

class ResidualBlock:
    def __init__(self, dim):
        self.fc1 = Dense(dim, dim)
        self.fc2 = Dense(dim, dim)
    
    def forward(self, x):
        identity = x
        out = relu(self.fc1(x))
        out = self.fc2(out)
        return relu(out + identity)  # Skip connection

📈 Gradient Exploding (기울기 폭발)

정의

역전파 과정에서 gradient가 점점 커져서 매우 큰 값이 되는 현상입니다. 이로 인해 가중치 업데이트가 불안정해지고 학습이 발산할 수 있습니다.

발생 원인

1. 큰 가중치 초기화

# 잘못된 초기화 예시
weights = np.random.randn(100, 100) * 10  # 너무 큰 표준편차

2. 깊은 네트워크에서 gradient 누적

각 레이어에서 1보다 큰 gradient가 계속 곱해지면 기하급수적으로 증가합니다.

증상

  • 손실 함수가 NaN 또는 Inf로 발산
  • 가중치가 매우 큰 값으로 업데이트됨
  • 학습 중 갑작스런 성능 악화

해결 방법

1. Gradient Clipping

def gradient_clipping(gradients, max_norm=1.0):
    total_norm = np.sqrt(sum(np.sum(grad ** 2) for grad in gradients))
    clip_coef = max_norm / (total_norm + 1e-6)
    clip_coef = np.minimum(clip_coef, 1.0)
    return [grad * clip_coef for grad in gradients]

2. 적절한 학습률 설정

# 학습률 스케줄링
class ExponentialDecay:
    def __init__(self, initial_lr=0.01, decay_rate=0.96, decay_steps=1000):
        self.initial_lr = initial_lr
        self.decay_rate = decay_rate
        self.decay_steps = decay_steps
        self.step = 0
    
    def get_lr(self):
        lr = self.initial_lr * (self.decay_rate ** (self.step / self.decay_steps))
        self.step += 1
        return lr

3. L2 정규화

def l2_regularization_loss(weights, lambda_reg=0.01):
    return lambda_reg * sum(np.sum(w ** 2) for w in weights)

🔍 진단 및 모니터링

Gradient 모니터링

def monitor_gradients(model):
    for name, param in model.named_parameters():
        if param.grad is not None:
            grad_norm = param.grad.norm()
            print(f"{name}: {grad_norm:.6f}")
            
            # 경고 발생
            if grad_norm < 1e-6:
                print(f"Warning: Possible vanishing gradient in {name}")
            elif grad_norm > 10:
                print(f"Warning: Possible exploding gradient in {name}")

시각화

import matplotlib.pyplot as plt

def plot_gradient_flow(named_parameters):
    ave_grads = []
    max_grads = []
    layers = []
    
    for n, p in named_parameters:
        if(p.requires_grad) and ("bias" not in n):
            layers.append(n)
            ave_grads.append(p.grad.abs().mean())
            max_grads.append(p.grad.abs().max())
    
    plt.bar(np.arange(len(max_grads)), max_grads, alpha=0.1, lw=1, color="c")
    plt.bar(np.arange(len(ave_grads)), ave_grads, alpha=0.1, lw=1, color="b")
    plt.hlines(0, 0, len(ave_grads)+1, lw=2, color="k" )
    plt.xticks(range(0,len(ave_grads), 1), layers, rotation="vertical")
    plt.xlim(left=0, right=len(ave_grads))
    plt.ylim(bottom = -0.001, top=0.02)
    plt.xlabel("Layers")
    plt.ylabel("Average gradient")
    plt.title("Gradient flow")
    plt.tight_layout()
    plt.show()

🛠️ 실전 체크리스트

Gradient Vanishing 방지

  • [ ] ReLU 계열 활성화 함수 사용
  • [ ] He/Xavier 초기화 적용
  • [ ] Batch Normalization 추가
  • [ ] Skip connections 고려
  • [ ] 학습률 증가 시도

Gradient Exploding 방지

  • [ ] Gradient clipping 적용
  • [ ] 학습률 감소
  • [ ] 가중치 초기화 확인
  • [ ] L2 정규화 추가
  • [ ] Batch size 조정

💡 핵심 요약

  1. Gradient Vanishing: gradient가 0에 가까워져 학습이 멈추는 현상
    • 주 원인: Sigmoid/Tanh 활성화 함수, 깊은 네트워크
    • 해결책: ReLU, BatchNorm, Skip connections
  2. Gradient Exploding: gradient가 폭발적으로 커져 학습이 불안정해지는 현상
    • 주 원인: 큰 가중치 초기화, gradient 누적
    • 해결책: Gradient clipping, 적절한 학습률, 정규화
  3. 공통 해결책:
    • 적절한 가중치 초기화
    • Batch Normalization
    • 신중한 아키텍처 설계
    • Gradient 모니터링

이 두 문제를 이해하고 적절히 대응하는 것은 성공적인 딥러닝 모델 학습의 핵심입니다. 항상 gradient의 흐름을 모니터링하고, 문제가 발생하면 위의 해결책들을 적용해보세요!

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다