개념
비선형성이 필요한 이유
행렬 곱셈(matrix multiplication)은 합성(composable)이 가능합니다. 벡터(vector)에 행렬(matrix) A를 곱한 뒤 행렬 B를 곱하는 것은 행렬 AB를 한 번 곱하는 것과 같습니다. 따라서 선형층(linear layer) 10개를 쌓는 것은 수학적으로 큰 행렬 하나를 가진 단일 선형층(single linear layer)과 같습니다. 수많은 파라미터(parameter)와 깊이(depth)가 모두 낭비됩니다. 이 사슬을 끊을 무언가가 필요합니다. 그것이 활성화 함수가 하는 일입니다.
proof는 간단합니다.
Layer 1: h = W1 * x + b1
Layer 2: y = W2 * h + b2
y = W2 * (W1 * x + b1) + b2
y = (W2 * W1) * x + (W2 * b1 + b2)
y = A * x + c
층(layer) 사이에 비선형 활성화 함수(nonlinear activation) g()를 넣습니다.
h = g(W1 * x + b1)
y = W2 * h + b2
이제 치환(substitution)이 깨집니다. W2 * g(W1 * x + b1) + b2는 더 이상 하나의 선형 변환(linear transformation)으로 줄일 수 없습니다. 네트워크(network)는 비선형 함수(nonlinear function)를 표현할 수 있고, 활성화 함수가 있는 층(layer)을 추가할수록 표현 용량(representational capacity)이 늘어납니다.
Sigmoid
신경망(neural network)의 초기 활성화 함수(activation function)입니다.
sigmoid(x) = 1 / (1 + e^(-x))
sigmoid'(x) = sigmoid(x) * (1 - sigmoid(x))
출력 범위(output range)는 (0, 1)입니다. 매끄럽고(smooth), 미분 가능하며(differentiable), 어떤 실수(real number)든 확률(probability)처럼 보이는 값으로 매핑(mapping)합니다. 도함수(derivative)의 최댓값은 x = 0에서 0.25입니다. 역전파(backpropagation)에서는 기울기(gradient)가 층을 지나며 곱해지므로 sigmoid 10층은 기울기를 최대 0.25^10 = 0.000000953674까지 줄입니다.
0.25^10 = 0.000000953674
원래 신호(signal)의 백만분의 일보다 작습니다. 이것이 기울기 소실(vanishing gradient) 문제입니다. 앞쪽 층(early layer)의 기울기가 너무 작아져 가중치(weight)가 거의 업데이트되지 않습니다. 네트워크가 학습하는 것처럼 보일 수는 있습니다. 뒤쪽 층(later layer)의 손실(loss)은 줄어들기 때문입니다. 하지만 첫 층들은 사실상 얼어 있습니다. 깊은 sigmoid 네트워크는 제대로 훈련되지 않습니다.
또 하나의 문제는 sigmoid 출력(output)이 항상 양수(positive), 즉 0에서 1 사이라는 점입니다. 이 때문에 가중치 기울기(weight gradient)가 항상 같은 부호가 되기 쉽고, 경사하강법(gradient descent)이 지그재그(zig-zag)로 움직일 수 있습니다.
Tanh
tanh는 sigmoid의 중심화된 버전(centered version)입니다.
tanh(x) = (e^x - e^(-x)) / (e^x + e^(-x))
tanh'(x) = 1 - tanh(x)^2
출력 범위(output range)는 (-1, 1)입니다. 0 중심(zero-centered)이므로 지그재그(zig-zag) 문제를 줄입니다. 도함수(derivative)의 최댓값은 x = 0에서 1.0으로 sigmoid보다 네 배 낫습니다. 하지만 기울기 소실(vanishing gradient) 문제는 여전히 존재합니다. 큰 양수나 음수 입력(input)에서는 도함수가 0에 가까워집니다. 10개 층을 지나면 기울기는 여전히 크게 약해집니다. 다만 sigmoid보다 덜 공격적으로 줄어듭니다.
ReLU: 전환점
정류 선형 유닛(Rectified Linear Unit, ReLU)은 Nair와 Hinton이 2010년에 딥러닝(deep learning)에서 널리 쓰이게 만든 활성화 함수입니다. 함수 자체는 Fukushima의 1969년 작업까지 거슬러 올라갑니다. ReLU는 많은 것을 바꾸었습니다.
relu(x) = max(0, x)
relu'(x) = 1 if x > 0
0 if x <= 0
출력 범위(output range)는 [0, infinity)입니다. 양수 입력(positive input)에서는 기울기(gradient)가 정확히 1로 그대로 전달됩니다. 그래서 깊은 네트워크(deep network)가 훈련 가능(trainable)해졌습니다. ReLU는 층을 지나도 기울기 크기(gradient magnitude)를 보존합니다.
하지만 실패 모드(failure mode)가 있습니다. 죽은 뉴런(dead neuron) 문제입니다. 어떤 뉴런의 가중 입력(weighted input)이 큰 음수 편향(negative bias)이나 좋지 않은 가중치 초기화(weight initialization) 때문에 항상 음수라면, 출력은 항상 0이고 기울기도 항상 0입니다. 그러면 업데이트(update)가 일어나지 않아 뉴런이 영구적으로 죽습니다. 실제로 ReLU 네트워크에서는 학습(training) 중 10-40%의 뉴런이 죽을 수 있습니다.
Leaky ReLU
죽은 뉴런(dead neuron)을 막는 가장 단순한 방법입니다.
leaky_relu(x) = x if x > 0
alpha * x if x <= 0
alpha는 보통 0.01 같은 작은 상수(constant)입니다. 음수 영역(negative side)에 0이 아닌 작은 기울기(slope)가 있으므로 죽은 뉴런도 기울기 신호(gradient signal)를 받고 회복할 수 있습니다.
GELU: 현대 기본값
가우시안 오차 선형 유닛(Gaussian Error Linear Unit, GELU)은 Hendrycks와 Gimpel이 2016년에 제안했습니다. BERT, GPT, 대부분의 현대 트랜스포머(modern transformer)에서 기본 활성화 함수로 사용됩니다.
gelu(x) = x * Phi(x)
gelu(x) ~= 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
Phi(x)는 표준정규분포(standard normal distribution)의 누적분포함수(cumulative distribution function, CDF)입니다. 아래 식은 실무에서 자주 쓰이는 근사식(approximation)입니다. GELU는 모든 곳에서 매끄럽고(smooth), ReLU처럼 음수(negative)를 0으로 강하게 잘라내지(hard-clip) 않으며 작은 음수 값을 허용합니다. 확률적으로는 "입력이 가우시안 분포(Gaussian distribution) 아래에서 양수일 가능성"으로 입력을 게이팅(gating)하는 해석을 가집니다. 이 매끄러운 게이팅(smooth gating)은 트랜스포머 아키텍처(transformer architecture)에서 ReLU보다 좋은 기울기 흐름(gradient flow)을 제공하고 죽은 뉴런(dead neuron) 문제를 완전히 피합니다.
Swish / SiLU
Swish 또는 SiLU는 Ramachandran 등이 2017년에 자동 탐색(automated search)으로 발견한 자기 게이팅 활성화 함수(self-gated activation)입니다.
swish(x) = x * sigmoid(x)
Swish는 정확히 x * sigmoid(x)입니다. Google은 활성화 함수 공간(activation function space)을 자동 탐색해 Swish를 발견했습니다. 신경망이 신경망의 일부를 설계한 셈입니다.
GELU처럼 매끄럽고(smooth), 비단조(non-monotonic)이며, 작은 음수 값(negative value)을 허용합니다. 차이는 미묘합니다. Swish는 sigmoid로 게이팅하고, GELU는 Gaussian CDF로 게이팅합니다. 실무에서는 성능이 거의 동일한 경우가 많습니다. Swish는 EfficientNet과 일부 비전 모델(vision model)에서 쓰이고, GELU는 언어 모델(language model)에서 지배적입니다.
소프트맥스(Softmax): 출력 활성화 함수
소프트맥스(softmax)는 은닉층(hidden layer)이 아니라 출력층(output layer)에서 사용합니다. 원점수(raw score), 즉 로짓(logit) 벡터(vector)를 확률분포(probability distribution)로 바꿉니다.
softmax(x_i) = e^(x_i) / sum(e^(x_j) for all j)
각 출력은 0과 1 사이이고 전체 합은 1입니다. 다중 클래스 분류(multi-class classification)의 표준 최종 활성화 함수(standard final activation)입니다. 가장 큰 로짓(logit)이 가장 높은 확률(probability)을 가지지만, argmax와 달리 미분 가능(differentiable)하고 상대적 확신도(relative confidence) 정보를 보존합니다.
활성화 함수 모양 비교
graph LR
subgraph "활성화 함수(Activation Functions)"
S["Sigmoid<br/>범위: (0,1)<br/>양끝 포화(saturation)"]
T["Tanh<br/>범위: (-1,1)<br/>0 중심(zero-centered)"]
R["ReLU<br/>범위: [0,inf)<br/>죽은 뉴런(dead neurons)"]
G["GELU<br/>범위: ~(-0.17,inf)<br/>매끄러운 게이팅(smooth gating)"]
end
S -->|"기울기 소실(Vanishing gradient)"| Problem["깊은 네트워크<br/>학습 어려움"]
T -->|"덜 심하지만<br/>여전히 소실됨"| Problem
R -->|"x > 0에서<br/>기울기(Gradient) = 1"| Solution["깊은 네트워크<br/>빠른 학습"]
G -->|"모든 곳에서<br/>매끄러운 기울기"| Solution
기울기 흐름(Gradient Flow) 비교
graph TD
Input["입력 신호(Input Signal)"] --> L1["층(Layer) 1"]
L1 --> L5["층(Layer) 5"]
L5 --> L10["층(Layer) 10"]
L10 --> Output["출력(Output)"]
subgraph "층 1의 기울기(Gradient)"
SigGrad["Sigmoid: ~0.000001"]
TanhGrad["Tanh: ~0.001"]
ReluGrad["ReLU: ~1.0"]
GeluGrad["GELU: ~0.8"]
end
언제 어떤 활성화 함수를 쓰는가
flowchart TD
Start["무엇을 만들고 있나요?"] --> Hidden{"은닉층(Hidden layer)<br/>또는 출력층(output)?"}
Hidden -->|"은닉층"| Arch{"아키텍처(Architecture)?"}
Hidden -->|"출력층"| Task{"문제 유형(Task type)?"}
Arch -->|"트랜스포머(Transformer) / NLP"| GELU["GELU 사용"]
Arch -->|"CNN / 비전(Vision)"| ReLU["ReLU 또는 Swish 사용"]
Arch -->|"RNN / LSTM"| Tanh["Tanh 사용"]
Arch -->|"단순 MLP(Simple MLP)"| ReLU2["ReLU 사용"]
Task -->|"이진 분류(Binary classification)"| Sigmoid["Sigmoid 사용"]
Task -->|"다중 클래스 분류(Multi-class classification)"| Softmax["Softmax 사용"]
Task -->|"회귀(Regression)"| Linear["선형(Linear) 사용 (활성화 함수 없음)"]
만들어 보기
Step 1: 활성화 함수(activation function)와 도함수(derivative) 구현
각 함수(function)는 단일 실수(single float)를 받아 실수를 반환합니다. 각 도함수 함수(derivative function)도 같은 입력(input)을 받아 기울기(gradient)를 반환합니다.
import math
def sigmoid(x):
x = max(-500, min(500, x))
return 1.0 / (1.0 + math.exp(-x))
def sigmoid_derivative(x):
s = sigmoid(x)
return s * (1 - s)
def tanh_act(x):
return math.tanh(x)
def tanh_derivative(x):
t = math.tanh(x)
return 1 - t * t
def relu(x):
return max(0.0, x)
def relu_derivative(x):
return 1.0 if x > 0 else 0.0
def leaky_relu(x, alpha=0.01):
return x if x > 0 else alpha * x
def leaky_relu_derivative(x, alpha=0.01):
return 1.0 if x > 0 else alpha
def gelu(x):
return 0.5 * x * (1 + math.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * x ** 3)))
def gelu_derivative(x):
phi = 0.5 * (1 + math.erf(x / math.sqrt(2)))
pdf = math.exp(-0.5 * x * x) / math.sqrt(2 * math.pi)
return phi + x * pdf
def swish(x):
return x * sigmoid(x)
def swish_derivative(x):
s = sigmoid(x)
return s + x * s * (1 - s)
def softmax(xs):
max_x = max(xs)
exps = [math.exp(x - max_x) for x in xs]
total = sum(exps)
return [e / total for e in exps]
소프트맥스(softmax)는 max_x를 빼서 수치적 오버플로(numerical overflow)를 줄입니다. 로짓(logit) 값이 커져도 안정적으로 확률(probability)을 계산하기 위한 기본 패턴입니다.
Step 2: 기울기(gradient)가 죽는 위치 보기
-5부터 5까지 균등하게 나눈 100개 지점에서 기울기(gradient)를 계산합니다. 각 활성화 함수의 기울기가 거의 0인 위치를 텍스트 히스토그램(text histogram)처럼 출력합니다.
def gradient_scan(name, derivative_fn, start=-5, end=5, n=100):
step = (end - start) / n
near_zero = 0
healthy = 0
for i in range(n):
x = start + i * step
g = derivative_fn(x)
if abs(g) < 0.01:
near_zero += 1
else:
healthy += 1
pct_dead = near_zero / n * 100
print(f"{name:15s}: 정상 {healthy:3d}개, 거의 0 {near_zero:3d}개 ({pct_dead:.0f}% 죽은 구간)")
gradient_scan("Sigmoid", sigmoid_derivative)
gradient_scan("Tanh", tanh_derivative)
gradient_scan("ReLU", relu_derivative)
gradient_scan("Leaky ReLU", leaky_relu_derivative)
gradient_scan("GELU", gelu_derivative)
gradient_scan("Swish", swish_derivative)
Step 3: 기울기 소실(vanishing gradient) 실험
sigmoid와 ReLU를 사용해 신호(signal)를 N개 층(layer)으로 순전파(forward-pass)시킵니다. 활성화 크기(activation magnitude)가 어떻게 변하는지 측정합니다.
import random
def vanishing_gradient_experiment(activation_fn, name, n_layers=10, n_inputs=5):
random.seed(42)
values = [random.gauss(0, 1) for _ in range(n_inputs)]
print(f"\n{name} - {n_layers}개 층 통과:")
for layer in range(n_layers):
weights = [random.gauss(0, 1) for _ in range(n_inputs)]
z = sum(w * v for w, v in zip(weights, values))
activated = activation_fn(z)
magnitude = abs(activated)
bar = "#" * int(magnitude * 20)
print(f" 층 {layer+1:2d}: 크기 = {magnitude:.6f} {bar}")
values = [activated] * n_inputs
vanishing_gradient_experiment(sigmoid, "Sigmoid")
vanishing_gradient_experiment(relu, "ReLU")
vanishing_gradient_experiment(gelu, "GELU")
Step 4: 죽은 뉴런 탐지기(dead neuron detector)
ReLU 네트워크(network)를 만들고 무작위 입력(random input)을 통과시킨 뒤, 한 번도 발화(fire)하지 않은 뉴런(neuron)을 셉니다.
def dead_neuron_detector(n_inputs=5, hidden_size=20, n_samples=1000):
random.seed(0)
weights = [[random.gauss(0, 1) for _ in range(n_inputs)] for _ in range(hidden_size)]
biases = [random.gauss(0, 1) for _ in range(hidden_size)]
fire_counts = [0] * hidden_size
for _ in range(n_samples):
inputs = [random.gauss(0, 1) for _ in range(n_inputs)]
for neuron_idx in range(hidden_size):
z = sum(w * x for w, x in zip(weights[neuron_idx], inputs)) + biases[neuron_idx]
if relu(z) > 0:
fire_counts[neuron_idx] += 1
dead = sum(1 for c in fire_counts if c == 0)
rarely_fire = sum(1 for c in fire_counts if 0 < c < n_samples * 0.05)
healthy = hidden_size - dead - rarely_fire
print(f"\n죽은 뉴런 보고서({hidden_size}개 뉴런, {n_samples}개 샘플):")
print(f" 죽음(한 번도 발화하지 않음): {dead}")
print(f" 약함(5% 미만 발화): {rarely_fire}")
print(f" 정상: {healthy}")
print(f" 죽은 뉴런 비율: {dead/hidden_size*100:.1f}%")
for i, c in enumerate(fire_counts):
status = "죽음" if c == 0 else "약함" if c < n_samples * 0.05 else "정상"
bar = "#" * (c * 40 // n_samples)
print(f" 뉴런 {i:2d}: {c:4d}/{n_samples}회 발화 [{status:4s}] {bar}")
dead_neuron_detector()
죽은 뉴런(dead neuron)은 실제 학습 중에도 관찰해야 합니다. 0 활성화(zero activation)가 많다면 Leaky ReLU나 GELU를 검토합니다.
Step 5: Sigmoid, ReLU, GELU 학습(training) 비교
같은 2층 네트워크(two-layer network)를 원 데이터셋(circle dataset)에서 세 활성화 함수로 학습하고 수렴 속도(convergence speed)를 비교합니다. 원 안쪽 점은 클래스 1, 바깥쪽 점은 클래스 0입니다.
def make_circle_data(n=200, seed=42):
random.seed(seed)
data = []
for _ in range(n):
x = random.uniform(-2, 2)
y = random.uniform(-2, 2)
label = 1.0 if x * x + y * y < 1.5 else 0.0
data.append(([x, y], label))
return data
class ActivationNetwork:
def __init__(self, activation_fn, activation_deriv, hidden_size=8, lr=0.1):
random.seed(0)
self.act = activation_fn
self.act_d = activation_deriv
self.lr = lr
self.hidden_size = hidden_size
self.w1 = [[random.gauss(0, 0.5) for _ in range(2)] for _ in range(hidden_size)]
self.b1 = [0.0] * hidden_size
self.w2 = [random.gauss(0, 0.5) for _ in range(hidden_size)]
self.b2 = 0.0
def forward(self, x):
self.x = x
self.z1 = []
self.h = []
for i in range(self.hidden_size):
z = self.w1[i][0] * x[0] + self.w1[i][1] * x[1] + self.b1[i]
self.z1.append(z)
self.h.append(self.act(z))
self.z2 = sum(self.w2[i] * self.h[i] for i in range(self.hidden_size)) + self.b2
self.out = sigmoid(self.z2)
return self.out
def backward(self, target):
error = self.out - target
d_out = error * self.out * (1 - self.out)
for i in range(self.hidden_size):
d_h = d_out * self.w2[i] * self.act_d(self.z1[i])
self.w2[i] -= self.lr * d_out * self.h[i]
for j in range(2):
self.w1[i][j] -= self.lr * d_h * self.x[j]
self.b1[i] -= self.lr * d_h
self.b2 -= self.lr * d_out
def train(self, data, epochs=200):
losses = []
for epoch in range(epochs):
total_loss = 0
correct = 0
for x, y in data:
pred = self.forward(x)
self.backward(y)
total_loss += (pred - y) ** 2
if (pred >= 0.5) == (y >= 0.5):
correct += 1
avg_loss = total_loss / len(data)
accuracy = correct / len(data) * 100
losses.append(avg_loss)
if epoch % 50 == 0 or epoch == epochs - 1:
print(f" 에폭 {epoch:3d}: 손실={avg_loss:.4f}, 정확도={accuracy:.1f}%")
return losses
data = make_circle_data()
configs = [
("Sigmoid", sigmoid, sigmoid_derivative),
("ReLU", relu, relu_derivative),
("GELU", gelu, gelu_derivative),
]
results = {}
for name, act_fn, act_d_fn in configs:
print(f"\n=== {name}로 학습 ===")
net = ActivationNetwork(act_fn, act_d_fn, hidden_size=8, lr=0.1)
losses = net.train(data, epochs=200)
results[name] = losses
print("\n=== 최종 손실 비교 ===")
for name, losses in results.items():
improvement = (1 - losses[-1] / losses[0]) * 100
print(f" {name:10s}: 시작={losses[0]:.4f} -> 종료={losses[-1]:.4f} (개선율: {improvement:.1f}%)")
이 실험은 활성화 함수 선택이 단지 취향이 아니라 학습 가능성과 수렴 속도(convergence speed)에 영향을 주는 설계 결정임을 보여 줍니다.
사용하기
PyTorch는 활성화 함수를 함수형(functional form)과 모듈형(module form)으로 모두 제공합니다.
import torch
import torch.nn as nn
import torch.nn.functional as F
x = torch.randn(4, 10)
relu_out = F.relu(x)
gelu_out = F.gelu(x)
sigmoid_out = torch.sigmoid(x)
swish_out = F.silu(x)
logits = torch.randn(4, 5)
probs = F.softmax(logits, dim=1)
model = nn.Sequential(
nn.Linear(10, 64),
nn.GELU(),
nn.Linear(64, 32),
nn.GELU(),
nn.Linear(32, 5),
)
트랜스포머(transformer)의 은닉층(hidden layer)은 GELU를 사용합니다. CNN 은닉층은 ReLU를 기본으로 두고, EfficientNet 계열에서는 Swish/SiLU를 검토합니다. 분류 출력층(classification output layer)은 소프트맥스(softmax), 회귀 출력층(regression output layer)은 활성화 함수 없이 선형(linear), 확률 출력(probability output)은 시그모이드(sigmoid)를 사용합니다. 먼저 이 기본값(default)으로 시작하고, 근거가 생겼을 때 바꿉니다.
RNN과 LSTM은 은닉 상태(hidden state)에 tanh, 게이트(gate)에 sigmoid를 사용합니다. 하지만 지금 처음부터 만든다면 RNN보다 트랜스포머를 선택하는 경우가 많습니다. ReLU 네트워크에서 뉴런(neuron)이 죽는다면 GELU로 바꿔 봅니다. Leaky ReLU는 특별한 이유가 있을 때 선택합니다.
산출물 만들기
이 lesson의 산출물은 다음입니다.
outputs/prompt-activation-selector.md: 아키텍처(architecture)에 맞는 활성화 함수(activation function)를 고르는 재사용 가능한 prompt
연습문제
- 음의 기울기(negative slope)
alpha가 학습 가능한 파라미터(learnable parameter)인 파라메트릭 렐루(Parametric ReLU; PReLU)를 구현합니다. 원 데이터셋(circle dataset)에서 학습하고 고정된 Leaky ReLU와 비교합니다.
- 기울기 소실 실험(vanishing gradient experiment)을 10층이 아니라 50층으로 실행합니다. sigmoid, tanh, ReLU, GELU의 층별 크기(magnitude)를 그래프로 그립니다. 각 활성화 함수는 몇 번째 층에서 신호(signal)가 사실상 0이 되나요?
- ELU(Exponential Linear Unit)를 구현합니다.
x > 0이면 x, x <= 0이면 alpha * (e^x - 1)입니다. 같은 네트워크에서 ReLU와 죽은 뉴런 비율(dead neuron rate)을 비교합니다.
- 학습 중 층별 평균 기울기 크기(average gradient magnitude)를 계산하는 기울기 건전성 모니터(gradient health monitor)를 만듭니다. 어떤 층의 기울기가 0.001보다 작거나 100보다 크면 경고(warning)를 출력합니다.
- 학습 비교(training comparison)를 원 데이터셋 대신 Lesson 01의 XOR 데이터셋으로 바꿉니다. XOR에서는 어떤 활성화 함수가 가장 빨리 수렴하나요? 원 데이터셋 결과와 다른 이유는 무엇인가요?
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| 활성화 함수(Activation function) | "비선형 부분" | 뉴런 출력에 적용되어 선형성(linearity)을 깨고 비선형 매핑(nonlinear mapping)을 가능하게 하는 함수 |
| 기울기 소실(Vanishing gradient) | "깊은 네트워크에서 기울기가 사라짐" | 활성화 도함수(activation derivative)가 1보다 작아 층을 지날수록 기울기가 지수적으로 줄어드는 현상 |
| 기울기 폭발(Exploding gradient) | "기울기가 터짐" | 유효 배수(effective multiplier)가 1보다 커서 층을 지날수록 기울기가 불안정하게 커지는 현상 |
| 죽은 뉴런(Dead neuron) | "학습을 멈춘 뉴런" | ReLU 입력이 계속 음수라 출력과 기울기가 0이 되는 뉴런 |
| Sigmoid | "0-1 사이로 압축" | 1/(1+e^-x), 역사적으로 중요하지만 깊은 네트워크에서 기울기 소실을 일으키기 쉬움 |
| ReLU | "음수를 0으로 잘라냄" | max(0, x), 양수 영역에서 기울기를 보존해 딥러닝을 실용적으로 만든 활성화 함수 |
| GELU | "트랜스포머 활성화 함수" | Gaussian Error Linear Unit, 입력이 양수일 확률로 값을 매끄럽게 게이팅(gating)하는 활성화 함수 |
| Swish/SiLU | "자기 게이팅 ReLU" | x * sigmoid(x), EfficientNet 등에 쓰이는 매끄러운 활성화 함수 |
| Softmax | "점수를 확률로 바꿈" | 로짓(logit) 벡터를 모든 값이 (0,1)이고 합이 1인 확률분포(probability distribution)로 정규화(normalize) |
| Leaky ReLU | "죽지 않는 ReLU" | max(alpha*x, x), 작은 음의 기울기를 허용해 죽은 뉴런을 줄임 |
| 포화(Saturation) | "시그모이드의 평평한 부분" | 도함수(derivative)가 0에 가까워져 기울기 흐름(gradient flow)을 막는 활성화 함수 구간 |
| 로짓(Logit) | "소프트맥스 전의 원점수" | 마지막 층(final layer)이 softmax나 sigmoid를 적용하기 전에 내는 정규화되지 않은 출력 |
더 읽을거리