활성화 함수
여러층의 신경망에 비선형성을 넣는것. 0근처나 1근처에서 기울기가 거의 0이 된다는 단점.
활성화 함수출력 범위장점단점주 사용처
| Step Function | 0, 1 | 구현 단순 | 미분 불가, 역전파 불가 | 초창기 퍼셉트론 |
| Sigmoid | 0 ~ 1 | 확률 해석 쉬움 | Vanishing Gradient | 이진분류 출력층 |
| Tanh | -1 ~ 1 | 평균 0 중심 | Vanishing Gradient | 과거 RNN |
| ReLU | 0 ~ ∞ | 빠름, 현재 표준 | Dead ReLU | 은닉층 |
| Leaky ReLU | -∞ ~ ∞ | Dead ReLU 완화 | 음수 기울기 수동 지정 | 은닉층 |
| PReLU | -∞ ~ ∞ | 음수 기울기 자동 학습 | 파라미터 증가 | 실험적 모델 |
| ELU | -1 ~ ∞ | 음수 영역도 Gradient 존재 | exp 연산 필요 | 일부 딥러닝 모델 |
| SELU | 약 -1.76 ~ ∞ | Self-Normalizing | 초기화 조건 필요 | 특수 심층 신경망 |
| GELU | -∞ ~ ∞ | 부드럽고 성능 우수 | 계산량 증가 | Transformer |
| Swish | -∞ ~ ∞ | ReLU보다 부드러움 | 계산량 증가 | EfficientNet 등 |
| Mish | -∞ ~ ∞ | 정보 보존 우수 | 계산량 큼 | 연구·실험 |
| Softmax | 0 ~ 1 (합=1) | 다중 클래스 확률화 | 출력층 전용 | 다중분류 출력층 |
Step Fumction 계단함수, 초창기 퍼셉트론 0보다 크면 1, 0보다 작으면 0, 현재는 거의 안쓴다. gradient가 없어 미분도 불가능하고 backpropagaion사용도 불가하다.
if x > 0:
return 1
else:
return 0
Sigmoid 0 ~ 1, 확률출력, 이진분류 출력층 Vanishing Gradient 단점
import torch
x = torch.tensor([-5., -1., 0., 1., 5.])
print(torch.sigmoid(x))
tensor([0.0067, 0.2689, 0.5000, 0.7311, 0.9933])
Tanh -1 ~ 1 sigmoid 개선형, 평균이 0이라 학습이 더 안정적임, Vanishing Gradient
import torch
x = torch.tensor([-5., -1., 0., 1., 5.])
print(torch.tanh(x))
tensor([-0.9999, -0.7616, 0.0000, 0.7616, 0.9999])
ReLU max(0, x) 빠르고 현재표준, 음수는 0으로 양수는 그대로. 다만 음수영역에 갇히면 계속 0만 출력됨. Dead ReLU (0만 나오는 뉴런),
import torch
import torch.nn.functional as F
x = torch.tensor([-3., -1., 0., 2., 5.])
print(F.relu(x))
tensor([0., 0., 0., 2., 5.])
Leaky ReLU Dead ReLU 해결, 음수도 조금 통과한다. ReLU보다 뉴런이 죽을 확률이 감소한다.
import torch
import torch.nn as nn
act = nn.LeakyReLU(0.01)
x = torch.tensor([-5., -1., 0., 2.])
print(act(x))
tensor([-0.0500, -0.0100, 0.0000, 2.0000])
PReLU Leaky ReLU는 음수는 무조건 1%만 살리는 등의 값이 고정되지만 데이터마다 1%가 좋은지, 10%가 좋은지는 다를 수 있다. PRELU는 데이터에 맞춰 최적화시킨다. 음수를 얼마나 살릴지를 모델이 스스로 결정하는것
import torch
import torch.nn as nn
act = nn.PReLU()
x = torch.tensor([-5., -1., 0., 2.])
print(act(x))
tensor([-1.2500, -0.2500, 0.0000, 2.0000])
ELU Exponential Linear Unit ReLU에서는 음수는 무조건 0이었다. 이때 gradient가 0이 되버리는데 Deep ReLU 가 발생한다. 이때 음수도 부드럽게 살리자는 아이디어로 사용된다. 음수구간이 부드럽다. gradient의 흐름이 좋으나 exp계산이 필요하거 느리다는 단점이있다.
import torch
import torch.nn as nn
act = nn.ELU()
x = torch.tensor([-5., -1., 0., 2.])
print(act(x))
tensor([-0.9933, -0.6321, 0.0000, 2.0000])
SELU Scaled ELU 딥러닝에서 가장 이상적인상태는 평균이 0 이고 분산이 1인 상태이다. 그래야 gradient폭발 가능성이 적어지고, gradient 반대로 소실될 가능성도 줄기 때문이다.
self Normalizing ELU, ELU를 개선한다. 자동으로 평균 0, 분산 1을 유지해 깊은 네트워크를 안정화하나 특정 초기화 조건이 필요하다는 단점이 있다.
이때 ElU와 SELU의 차이가 있다면 [-0.9933, -0.6321, 0, 2], [-1.7463, -1.1113, 0, 2.1014]로 SELU가 좀더 크게 스케일링된다 .핵심 장점은 학습중 자동으로 평균 0 분산 1 근처를 유지한다는것. 깊은 네트워크에 안정적이나 이 자기정규화라는 핵심은 특정 조건에서만 효과가 제대로 나온다. SELU 가 다음레이어에서도 수행되려면 가중치 초기화, dropout방식, 네트워크 구조가 특정 조건을 만족해야한다.
ReLU 에서 사용하는 nn.init.kaiming_normal()는 음수절반을 제거해버려 평균과분산에 영향을 준다. 논문에서는 LeCun Normal Initialization을 권장하고 PyTorch에는 LeCun 초기화가 없다. PyTorch는 기본적으로 nn.init.xavier.normal_, nn.init.xavier_uniform_, nn.init.kaiming_normal_ nn.init.kaiming_uniform_을 제공한다. so SELU를엄격히 쓰려면 직접 구현하는 경우가 많다 ㅜ
또 일반 nn.dropout으로 랜덤 제거해버리면 평균과분산이 변해버리기 때문에 nn.AlphaDropout()을 사용한다.
또 BatchNorm이 없어도 SELU는 정규화를 잘 유지함으로 굳이 함께 쓰지않는다 .
하지만 현재 ReLU + BatchNorm이나 GELU + LayerNorm 조합이 워낙 강력해 실무에서 SELU는 잘사용하지않느다.
import torch
import torch.nn as nn
act = nn.SELU()
x = torch.tensor([-5., -1., 0., 2.])
print(act(x))
tensor([-1.7463, -1.1113, 0.0000, 2.1014])
import torch.nn as nn
model = nn.Sequential(
nn.Linear(100, 256),
nn.SELU(),
nn.AlphaDropout(0.1),
nn.Linear(256, 128),
nn.SELU(),
nn.AlphaDropout(0.1),
nn.Linear(128, 10)
)
import math
import torch.nn as nn
def lecun_normal(layer):
if isinstance(layer, nn.Linear):
nn.init.normal_(
layer.weight,
mean=0,
std=math.sqrt(1 / layer.in_features)
)
model.apply(lecun_normal)
GELU ReLU보다 Gaussian Error Linear Unit 가우시안 오차 선형 유닛, 부드러움 성능 좋음, 현재 transformer 계열 표준. ReLU처럼 자르지않고 부드럽게 통과시킨다. 큰 음수들은 거의 0, 큰 양수들은 거의 x 중간은 부드럽게 통과시킴. ReLU보다 표현력이 좋아 NLP 모델의 표준이다. 이 값을 완전히 버릴지 조금만 살릴지를 부드럽게 결정하는 느낌이기에 transformer 계열에서는 reLU보다 더 많이 사용됨
import torch
import torch.nn as nn
gelu = nn.GELU()
x = torch.tensor([-3., -1., 0., 1., 3.])
print(gelu(x))
tensor([-0.0040,
-0.1587,
0.0000,
0.8413,
2.9960])
Swish Google 개발 ReLU보다 성능 좋음, sigmoid와 입력을 곱한것. ReLU보다 부드럽다. 일부모델에서 더 좋은 성능을 보인다.
import torch
x = torch.tensor([-3., -1., 0., 1., 3.])
y = x * torch.sigmoid(x)
print(y)
tensor([-0.1423,
-0.2689,
0.0000,
0.7311,
2.8577])
Mish
최신계열의 활성화함수. 2019년에 제안되었다. 매우 부드럽다. ReLU 처럼 정보를 너무 많이 버리지 말고 GELU나 Swish 처럼 부드럽게 통과시키자는 아이디어로 sofrplus + tanh이 수행된다.
gradient의 흐름이 우수하고 정보보존이 좋으나 계산량이 증가한다는 단점이 있다.
ReLU엿다면 -5는 즉시 정보가 삭제된다. Mish는 남겨두는것을 확인할 수 있다. 즉 음수 정보를 더 많이 보존한다. 큰 양수는 거의 그대로 통과한다.
딥러닝에서는 gradient가 잘 흘러야하고 ReLU는 음수영역에서 gradient가 0, Mish는 음수영역에도 gradient가 존재하니 장점이 있다. 하지만계산량이 증가하기에 작은모델에는 차이가 거의 없으나 초대형 모델에서 연산량이 꽤 증가한다는 단점이 있다. 실무적으로는 잘 사용하지않고 연구나 실험목적으로 자주 사용된다.
import torch
import torch.nn as nn
act = nn.Mish()
x = torch.tensor([-5., -1., 0., 2.])
print(act(x))
tensor([-0.0336, -0.3034, 0.0000, 1.9440])
import torch.nn as nn
model = nn.Sequential(
nn.Linear(128, 256),
nn.Mish(),
nn.Linear(256, 128),
nn.Mish(),
nn.Linear(128, 10)
)
Softmax 각 클래스 확률로 변환한다. 고양이 강아지 토끼중 하나 선택하는 경우.
import torch
x = torch.tensor([2.0, 1.0, 0.1])
print(torch.softmax(x, dim=0))
tensor([0.6590, 0.2424, 0.0986])
| Sigmoid | 0~1 | 확률 해석 쉬움 | 기울기 소실 | 이진분류 출력층 |
| Tanh | -1~1 | 0 중심 | 기울기 소실 | 과거 RNN |
| ReLU | 0~∞ | 빠름, 표준 | Dead ReLU | 은닉층 |
| Leaky ReLU | -∞~∞ | Dead ReLU 완화 | 추가 파라미터 | 은닉층 |
| GELU | -∞~∞ | 부드럽고 성능 좋음 | 계산량 증가 | Transformer |
| Swish | -∞~∞ | 부드러움 | 계산량 증가 | 일부 최신 모델 |
| Softmax | 0~1 (합=1) | 확률 변환 | 출력층 전용 | 다중분류 |