딥러닝을 공부하다 보면 반드시 마주치게 되는 두 가지 문제가 있습니다: **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 조정
💡 핵심 요약
- Gradient Vanishing: gradient가 0에 가까워져 학습이 멈추는 현상
- 주 원인: Sigmoid/Tanh 활성화 함수, 깊은 네트워크
- 해결책: ReLU, BatchNorm, Skip connections
- Gradient Exploding: gradient가 폭발적으로 커져 학습이 불안정해지는 현상
- 주 원인: 큰 가중치 초기화, gradient 누적
- 해결책: Gradient clipping, 적절한 학습률, 정규화
- 공통 해결책:
- 적절한 가중치 초기화
- Batch Normalization
- 신중한 아키텍처 설계
- Gradient 모니터링
이 두 문제를 이해하고 적절히 대응하는 것은 성공적인 딥러닝 모델 학습의 핵심입니다. 항상 gradient의 흐름을 모니터링하고, 문제가 발생하면 위의 해결책들을 적용해보세요!
답글 남기기