
📓Google Colab에서 실습하기
이 레슨은 PyTorch/GPU가 필요합니다. 노트북을 다운로드 후 Google Colab에서 열어주세요.
학습 내용
활성화 함수 심화 (Activation Functions Deep Dive)
학습 목표
이 레슨을 완료하면:
- •활성화 함수가 왜 반드시 필요한지 수학적으로 증명할 수 있습니다
- •Sigmoid, Tanh, ReLU의 특성과 문제점을 비교할 수 있습니다
- •Leaky ReLU, ELU, GELU, Swish 등 최신 활성화 함수를 이해합니다
- •상황에 맞는 활성화 함수를 선택할 수 있습니다
핵심 메시지
"활성화 함수는 조명의 밝기 조절기(dimmer)와 같습니다" 단순한 on/off 스위치가 아니라, 신호의 세기를 적절히 조절하는 핵심 부품입니다.
1. 비유: 전등 스위치 vs 디머 스위치
| 종류 | 동작 | 활성화 함수 대응 |
|---|---|---|
| 일반 스위치 | 켜짐(1) / 꺼짐(0)만 가능 | Step Function (계단 함수) |
| 디머 스위치 | 0%~100% 밝기 조절 가능 | Sigmoid, Tanh |
| 스마트 디머 | 0% 이하는 차단, 이상은 비례 | ReLU |
| 고급 디머 | 부드러운 전환 + 약간의 음수 허용 | GELU, Swish |
신경망의 각 뉴런은 "이 신호를 얼마나 통과시킬까?"를 결정합니다. 활성화 함수가 바로 그 통과량 조절기 역할을 합니다.
2. 활성화 함수가 없으면? (치명적 문제)
활성화 함수 없이 선형 변환만 쌓으면 어떻게 될까요?
| 층 | 수식 |
|---|---|
| 층 1 | |
| 층 2 |
전개하면:
⚠️ 100층을 쌓아도 결국 "하나의 선형 변환"으로 축소!
실행해보기: 선형 층을 아무리 쌓아도 하나와 같다
pythonimport numpy as np # === 선형 변환 3개를 쌓아보자 === np.random.seed(42) W1 = np.random.randn(4, 3) b1 = np.random.randn(4, 1) W2 = np.random.randn(5, 4) b2 = np.random.randn(5, 1) W3 = np.random.randn(2, 5) b3 = np.random.randn(2, 1) x = np.array([[1.0], [2.0], [3.0]]) # 3층 순전파 (활성화 없음) z1 = W1 @ x + b1 z2 = W2 @ z1 + b2 z3 = W3 @ z2 + b3 print("3층 통과 결과:", z3.T) # 하나의 층으로 합친 것 W_combined = W3 @ W2 @ W1 b_combined = W3 @ W2 @ b1 + W3 @ b2 + b3 z_single = W_combined @ x + b_combined print("1층으로 합친 결과:", z_single.T) # 차이 확인 diff = np.max(np.abs(z3 - z_single)) print("\n차이:", diff) print("결론: 활성화 함수 없이 3층 = 1층과 완전히 동일!") print("\n즉, 깊은 네트워크를 만드는 의미가 없어집니다.") print("활성화 함수가 있어야 각 층이 새로운 비선형 특징을 학습합니다!")
3. Sigmoid 함수
수식: sigma(x) = 1 / (1 + e^(-x))
| 특성 | 값 |
|---|---|
| 출력 범위 | (0, 1) |
| 중심 | 0.5 |
| 장점 | 확률로 해석 가능, 부드러운 곡선 |
| 단점 | 기울기 소실(Vanishing Gradient) 문제 |
| 주 용도 | 이진 분류 출력층, 게이트 메커니즘 |
기울기 소실 문제란?
Sigmoid의 미분 최대값은 0.25입니다. 층이 깊어지면 0.25 x 0.25 x 0.25 ... = 거의 0에 수렴! 앞쪽 층의 가중치가 거의 업데이트되지 않습니다.
비유: 전화 돌리기 게임에서 10명을 거치면 원래 메시지가 사라지듯이, 기울기 신호도 층을 지날수록 사라집니다.
4. Tanh 함수
수식: tanh(x) = (e^x - e^(-x)) / (e^x + e^(-x))
| 특성 | 값 |
|---|---|
| 출력 범위 | (-1, 1) |
| 중심 | 0 (영점 중심!) |
| 장점 | 출력이 0 중심이라 학습이 더 안정적 |
| 단점 | 여전히 기울기 소실 존재 (최대 미분값 = 1) |
| 주 용도 | RNN/LSTM 내부, 출력 범위가 -1~1 필요할 때 |
Sigmoid vs Tanh 비교:
| 비교 항목 | Sigmoid | Tanh |
|---|---|---|
| 출력 범위 | 0 ~ 1 | -1 ~ 1 |
| 영점 중심 | X (0.5 중심) | O (0 중심) |
| 기울기 최대 | 0.25 | 1.0 |
| 기울기 소실 | 심각 | 덜 심각하지만 여전히 존재 |
5. ReLU - 딥러닝의 게임체인저
수식: ReLU(x) = max(0, x)
| 특성 | 값 |
|---|---|
| 출력 범위 | [0, +무한대) |
| 계산 비용 | 매우 저렴 (비교 연산 1회) |
| 장점 | 기울기 소실 해결, 희소 활성화, 빠른 계산 |
| 단점 | Dying ReLU 문제 |
| 주 용도 | 은닉층의 기본 활성화 함수 |
왜 ReLU가 혁명적이었나?
| 기존 (Sigmoid/Tanh) | ReLU |
|---|---|
| 미분값이 항상 < 1 | 양수 구간에서 미분값 = 1 |
| 층이 깊으면 기울기 소실 | 기울기가 그대로 전달! |
| exp() 계산 비용 | max() 한 번으로 끝 |
Dying ReLU 문제: 한번 음수 영역에 빠진 뉴런은 기울기가 0이라 영원히 죽습니다. 큰 학습률을 쓸 때 많이 발생합니다.
6. ReLU의 변형들
Leaky ReLU
- •수식: f(x) = x (x > 0), alpha * x (x <= 0), 보통 alpha = 0.01
- •음수 영역에서도 작은 기울기를 유지 -> Dying ReLU 해결
ELU (Exponential Linear Unit)
- •수식: f(x) = x (x > 0), alpha * (e^x - 1) (x <= 0)
- •음수 영역이 부드럽게 포화 -> 노이즈에 강함
GELU (Gaussian Error Linear Unit)
- •수식: f(x) = x * Phi(x) (Phi는 정규분포 누적분포함수)
- •입력값에 따라 확률적으로 통과 여부 결정
- •GPT, BERT, Vision Transformer 등에서 사용!
Swish (SiLU)
- •수식: f(x) = x * sigmoid(x)
- •자기 자신과 sigmoid의 곱. Google Brain이 자동 탐색으로 발견
- •부드러운 비단조 함수
| 활성화 함수 | 음수 처리 | 부드러움 | 주 사용처 |
|---|---|---|---|
| ReLU | 0으로 차단 | 꺾임 | CNN, 일반 |
| Leaky ReLU | 작게 통과 (0.01x) | 꺾임 | GAN |
| ELU | 부드럽게 포화 | 부드러움 | 깊은 네트워크 |
| GELU | 확률적 통과 | 매우 부드러움 | Transformer |
| Swish | 부드럽게 통과 | 매우 부드러움 | EfficientNet |
7. 모든 활성화 함수 시각화
실행해보기: 활성화 함수와 도함수 비교
pythonimport numpy as np import matplotlib.pyplot as plt x = np.linspace(-4, 4, 1000) # === 활성화 함수 정의 === def sigmoid(x): return 1 / (1 + np.exp(-x)) def tanh_fn(x): return np.tanh(x) def relu(x): return np.maximum(0, x) def leaky_relu(x, alpha=0.1): 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)) def gelu(x): return 0.5 * x * (1 + np.tanh(np.sqrt(2/np.pi) * (x + 0.044715 * x**3))) def swish(x): return x * sigmoid(x) # === 도함수 정의 === def sigmoid_deriv(x): s = sigmoid(x) return s * (1 - s) def tanh_deriv(x): return 1 - np.tanh(x)**2 def relu_deriv(x): return np.where(x > 0, 1.0, 0.0) def leaky_relu_deriv(x, alpha=0.1): return np.where(x > 0, 1.0, alpha) functions = [ ('Sigmoid', sigmoid, sigmoid_deriv, '#E91E63'), ('Tanh', tanh_fn, tanh_deriv, '#9C27B0'), ('ReLU', relu, relu_deriv, '#2196F3'), ('Leaky ReLU', leaky_relu, leaky_relu_deriv, '#FF9800'), ('GELU', gelu, None, '#4CAF50'), ('Swish', swish, None, '#00BCD4'), ] fig, axes = plt.subplots(2, 3, figsize=(15, 9)) axes = axes.flatten() for i, (name, func, deriv, color) in enumerate(functions): ax = axes[i] ax.plot(x, func(x), linewidth=2.5, color=color, label=name) if deriv is not None: ax.plot(x, deriv(x), linewidth=1.5, color=color, alpha=0.5, linestyle='--', label=name + " derivative") ax.axhline(y=0, color='gray', linewidth=0.5) ax.axvline(x=0, color='gray', linewidth=0.5) ax.set_title(name, fontsize=14, fontweight='bold') ax.set_xlim(-4, 4) ax.set_ylim(-2, 4) ax.legend(fontsize=9) ax.grid(True, alpha=0.3) plt.suptitle('Activation Functions & Their Derivatives', fontsize=16, fontweight='bold') plt.tight_layout() plt.show()
8. 기울기 소실 문제 체감하기
실행해보기: Sigmoid vs ReLU 기울기 전파 비교
pythonimport numpy as np # 10층 깊이의 네트워크에서 기울기가 어떻게 변하는지 시뮬레이션 def sigmoid(x): return 1 / (1 + np.exp(-x)) n_layers = 10 gradient_sigmoid = 1.0 gradient_relu = 1.0 print("층 | Sigmoid 기울기 | ReLU 기울기") print("-" * 50) for layer in range(n_layers): # Sigmoid: 최대 미분값 = 0.25 gradient_sigmoid *= 0.25 # ReLU: 양수 영역에서 미분값 = 1.0 gradient_relu *= 1.0 print(str(layer+1).rjust(2) + " | " + "{:.12f}".format(gradient_sigmoid).ljust(20) + "| " + "{:.1f}".format(gradient_relu)) print("\nSigmoid: 10층 후 기울기 =", gradient_sigmoid) print("ReLU: 10층 후 기울기 =", gradient_relu) print("\nSigmoid 기울기는 ReLU의", str(round(gradient_relu / gradient_sigmoid)) + "분의 1 !") print("이것이 깊은 네트워크에서 Sigmoid를 안 쓰는 이유입니다.")
9. 실전 비교: 활성화 함수별 학습 성능
같은 네트워크, 같은 데이터로 활성화 함수만 바꿔서 학습해 봅시다.
실행해보기: 활성화 함수별 학습 곡선 비교
pythonimport numpy as np import matplotlib.pyplot as plt # === 데이터: 원형 분류 문제 === np.random.seed(42) N = 200 theta = np.random.uniform(0, 2*np.pi, N) r_inner = np.random.uniform(0, 1, N//2) r_outer = np.random.uniform(1.5, 2.5, N//2) X_inner = np.column_stack([r_inner * np.cos(theta[:N//2]), r_inner * np.sin(theta[:N//2])]) X_outer = np.column_stack([r_outer * np.cos(theta[N//2:]), r_outer * np.sin(theta[N//2:])]) X = np.vstack([X_inner, X_outer]).T # 2 x N Y = np.hstack([np.zeros(N//2), np.ones(N//2)]).reshape(1, N) def sigmoid(z): return 1 / (1 + np.exp(-np.clip(z, -500, 500))) # === 활성화 함수 정의 === activations = { 'Sigmoid': (lambda z: sigmoid(z), lambda z: sigmoid(z) * (1 - sigmoid(z))), 'Tanh': (lambda z: np.tanh(z), lambda z: 1 - np.tanh(z)**2), 'ReLU': (lambda z: np.maximum(0, z), lambda z: (z > 0).astype(float)), 'Leaky ReLU': (lambda z: np.where(z > 0, z, 0.01*z), lambda z: np.where(z > 0, 1.0, 0.01)), } colors = {'Sigmoid': '#E91E63', 'Tanh': '#9C27B0', 'ReLU': '#2196F3', 'Leaky ReLU': '#FF9800'} results = {} for act_name, (act_fn, act_deriv) in activations.items(): np.random.seed(0) W1 = np.random.randn(16, 2) * 0.5 b1 = np.zeros((16, 1)) W2 = np.random.randn(1, 16) * 0.5 b2 = np.zeros((1, 1)) lr = 0.1 if act_name == 'Sigmoid' else 0.05 losses = [] for epoch in range(1500): Z1 = W1 @ X + b1 A1 = act_fn(Z1) Z2 = W2 @ A1 + b2 A2 = sigmoid(Z2) m = N loss = -np.mean(Y * np.log(A2+1e-8) + (1-Y) * np.log(1-A2+1e-8)) losses.append(loss) dZ2 = A2 - Y dW2 = (1/m) * dZ2 @ A1.T db2 = (1/m) * np.sum(dZ2, axis=1, keepdims=True) dA1 = W2.T @ dZ2 dZ1 = dA1 * act_deriv(Z1) dW1 = (1/m) * dZ1 @ X.T db1 = (1/m) * np.sum(dZ1, axis=1, keepdims=True) W2 -= lr * dW2 b2 -= lr * db2 W1 -= lr * dW1 b1 -= lr * db1 results[act_name] = losses print(act_name + ": final loss = " + str(round(losses[-1], 4))) # === 시각화 === plt.figure(figsize=(10, 6)) for name, losses in results.items(): plt.plot(losses, linewidth=2, label=name, color=colors[name]) plt.xlabel('Epoch', fontsize=12) plt.ylabel('Loss', fontsize=12) plt.title('Training Loss by Activation Function', fontsize=14) plt.legend(fontsize=12) plt.grid(True, alpha=0.3) plt.xlim(0, 1500) plt.tight_layout() plt.show() print("\nReLU and Leaky ReLU typically converge faster than Sigmoid/Tanh!")
10. 활성화 함수 선택 가이드
의사 결정 표
| 상황 | 추천 활성화 함수 | 이유 |
|---|---|---|
| 은닉층 (기본) | ReLU | 빠르고, 기울기 소실 없음 |
| 은닉층 (Dying ReLU 걱정) | Leaky ReLU | 음수 영역도 학습 가능 |
| Transformer 계열 | GELU | GPT, BERT 등의 표준 |
| EfficientNet 계열 | Swish | Google이 탐색으로 발견한 최적 |
| 이진 분류 출력층 | Sigmoid | 출력을 0~1 확률로 변환 |
| 다중 분류 출력층 | Softmax | 모든 클래스 확률의 합 = 1 |
| RNN/LSTM 내부 | Tanh | -1~1 범위, 영점 중심 |
| 깊은 네트워크 (30층+) | ELU 또는 GELU | 부드러운 곡선으로 안정적 |
실전 팁
| 팁 | 설명 |
|---|---|
| 1️⃣ 기본은 ReLU | 모르겠으면 ReLU - 대부분의 경우에 잘 작동 |
| 2️⃣ Transformer | GELU를 사용 |
| 3️⃣ 출력층 | 이진 분류 → Sigmoid, 다중 분류 → Softmax, 회귀 → Linear |
| 4️⃣ 학습 안 될 때 | 활성화 함수를 바꿔보는 것도 방법 |
| 5️⃣ BatchNorm | 함께 쓰면 활성화 함수 선택의 영향이 줄어듦 |
핵심 요약
| 개념 | 설명 | 비유 |
|---|---|---|
| 활성화 함수의 역할 | 비선형성 추가 | 조명 밝기 조절기 |
| 없으면? | 아무리 깊어도 1층과 동일 | 스위치 없는 전선 |
| Sigmoid | 0~1 출력, 기울기 소실 | 오래된 디머 |
| Tanh | -1~1 출력, 영점 중심 | 개선된 디머 |
| ReLU | 음수=0, 양수=그대로, 빠름 | 스마트 디머 |
| GELU/Swish | 부드러운 최신 함수 | 최신형 디머 |
학습 체크리스트
- • 활성화 함수 없이 층을 쌓으면 왜 무의미한지 설명 가능
- • Sigmoid의 기울기 소실 문제를 이해하고 설명 가능
- • ReLU가 딥러닝에서 표준이 된 이유를 설명 가능
- • Dying ReLU 문제와 해결책(Leaky ReLU 등)을 알고 있음
- • GELU, Swish 등 최신 함수의 특징을 이해함
- • 상황에 맞는 활성화 함수를 선택할 수 있음
다음 강의 예고
"역전파 이론" - 역전파의 수학적 이론을 더 깊이 파고들어 봅시다!
레슨 정보
- 레벨
- Level 3: 딥러닝 핵심
- 예상 소요 시간
- 25분
- 참고 영상
- YouTube 링크
💡실습 환경 안내
이 레벨은 PyTorch/GPU가 필요하여 Google Colab 사용을 권장합니다.
Colab은 무료 GPU를 제공하여 PyTorch, CNN, Transformer 등을 실행할 수 있습니다.