[카테고리:] 미분류

  • 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의 흐름을 모니터링하고, 문제가 발생하면 위의 해결책들을 적용해보세요!