Posted:      Updated:

AI 스터디를 하며 ‘파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습’ 교재를 정리한 글입니다.

활성화 함수(Activation Function)

활성화: 인공 신경망의 뉴런의 출력값을 선형에서 비선형으로 변환

은닉층을 활성화하기 위한 함수로, 가중치와 편향으로 이루어진 노드를 선형에서 비선형으로 갱신
비선형 구조를 가져 역전파 과정에서 미분값을 통해 학습이 진행될 수 있게 함
입력을 정규화(Normalization)하는 과정

네트워크에 포함된 노드마다 출력값에 동일한 영향을 미치지 않으므로, 노드마다 전달돼야 하는 정보량이 다름
ex) 출근 시간 예측 모델에서 일어난 시간($x_1$), 기상 상태($x_2$) 중 일어난 시간($x_1$)이 더 많이 활성화 되어야 함

이진 분류(Binary Classification)

입력된 값을 두 그룹으로 분류하는 작업
분류 결과가 맞다면 1, 아니라면 0 반환

논리 회귀(로지스틱 회귀, Ligistic Regression), 논리 분류(Logistic Classification)라고도 불림

독립 변수(X)의 선형 결합을 활용해 결과 예측
종속 변수(Y)를 범주형 데이터를 대상으로 계산하여 결과가 특정 분류로 나뉘게 됨
-> 분류에서도 사용될 수 있음

시그모이드 함수(Sigmoid Function)

S자형 곡선 모양으로, 반환값이 0~1 또는 -1~1의 범위를 가짐
$x$의 계수가 0에 가까워질수록 완만한 경사, 멀어질수록 급격한 경사를 갖게 됨
주로 로지스틱 회귀에 사용

\[Sigmoid(x) = \frac 1 {1+e^{-x}}\]

출력값이 0.5보다 낮으면 거짓, 0.5보다 크면 참으로 분류
입력에 따라 값이 급격하게 변하지 않으며, 출력값의 범위가 0~1 사이로 제한되므로 정규화 중 기울기 폭주(Gradient Exploding) 문제가 발생하지 않음
기울기 소실 문제가 발생할 수 있음

기울기 소실 문제(Gradient Vanishing): 기울기를 이용해 최적화된 값을 찾을 때, 계층이 많아지면 값이 0에 수렴되는 문제

이진 교차 엔트로피(Binary Cross Entropy, BCE)

이진 분류에서는 평균 제곱 오차 함수를 활용하면 좋은 결과를 얻기 어려우므로, 이진 교차 엔트로피를 사용함
오차 함수를 두 가지 로그 함수를 교차해 오차를 계산하여 구현

\[BCE\#1=-Y_i \cdot \log( \hat{Y_l})\] \[BCE\#2=-(1-Y_i) \cdot \log(1- \hat{Y_l})\] \[\begin{align} BCE = BCE\#1 + BCE\#2 \\ = -(Y_i \cdot \log( \hat{Y_l}) + (1-Y_i) \cdot \log (1-\hat{Y_l})) \end{align}\]

BCE#1 수식은 실젯값($Y_i=1$)이 1일 때 적용하는 수식이고, BCE#2 수식은 실젯값($Y_i=0$)이 0일 때 적용하는 수식
로그함수는 한쪽으로 무한대로 이동하며 다른 한쪽은 0에 가까워지기 때문에 기울기가 0이 되는 지점을 찾을 수 있게 됨
최종 반환되는 이진 교차 엔트로피 함수는 오차를 계산하기 위해 각 손실값의 평균을 반환

\[BCE = - \frac 1 n \sum _{i=1} ^n (Y_i \cdot \log (\hat {Y_l}) + (1 - Y_i) \cdot \log (1 - \hat {Y_l}))\]

파이토치로 구현한 이진분류

책에서 제공하는 binary.csv 데이터셋 사용
x, y, z가 모두 40 이상이고 평균이 60 이상일 때 True 반환

import torch
import pandas as pd
from torch import nn
from torch import optim
from torch.utils.data import Dataset, DataLoader, random_split
class CustomDataset(Dataset):
    def __init__(self, file_path):  # cvs 파일 경로 입력 받음
        df = pd.read_csv(file_path)
        self.x1 = df.iloc[:, 0].values
        self.x2 = df.iloc[:, 1].values
        self.x3 = df.iloc[:, 2].values
        self.y = df.iloc[:, 3].values
        self.length = len(df)

    def __getitem__(self, index):
        x = torch.FloatTensor([self.x1[index], self.x2[index], self.x3[index]])
        y = torch.FloatTensor([self.y[index]])
        return x, y

    def __len__(self):
        return self.length
class CustomModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer = nn.Sequential(  # 여러 계층을 하나로 묶음
            nn.Linear(3, 1),
            nn.Sigmoid()  # 시그모이드 함수 연결
        )

    def forward(self, x):
        x = self.layer(x)
        return x
dataset = CustomDataset("datasets/binary.csv")
dataset_size = len(dataset)

train_size = int(dataset_size * 0.8)
validation_size = int(dataset_size * 0.1)
test_size = dataset_size - train_size - validation_size
train_dataset, validation_dataset, test_dataset = random_split(
    dataset, [train_size, validation_size, test_size], torch.manual_seed(4)
)
train_dataloader = DataLoader(train_dataset, batch_size=64, shuffle=True, drop_last=True)
validation_dataloader = DataLoader(validation_dataset, batch_size=4, shuffle=True, drop_last=True)
test_dataloader = DataLoader(test_dataset, batch_size=4, shuffle=True, drop_last=True)
device = "cuda" if torch.cuda.is_available() else "cpu"
model = CustomModel().to(device)
criterion = nn.BCELoss().to(device)  # BCE (이진 교차 엔트로피)
optimizer = optim.SGD(model.parameters(), lr=0.0001)
for epoch in range(10000):
    cost = 0.0

    for x, y in train_dataloader:
        x = x.to(device)
        y = y.to(device)

        output = model(x)
        loss = criterion(output, y)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        cost += loss

    cost = cost / len(train_dataloader)

    if (epoch + 1) % 1000 == 0:
        print(f"Epoch : {epoch+1:4d}, Model : {list(model.parameters())}, Cost : {cost:.3f}")
with torch.no_grad():
    model.eval()
    for x, y in validation_dataloader:
        x = x.to(device)
        y = y.to(device)

        outputs = model(x)

        print(outputs)
        print(outputs >= torch.FloatTensor([0.5]).to(device))
        print("-------------------")

출력

Epoch : 1000, Model : [Parameter containing:
tensor([[ 0.0028, -0.0006,  0.0036]], device='cuda:0', requires_grad=True), Parameter containing:
tensor([0.0949], device='cuda:0', requires_grad=True)], Cost : 0.680
Epoch : 2000, Model : [Parameter containing:
tensor([[0.0034, 0.0005, 0.0041]], device='cuda:0', requires_grad=True), Parameter containing:
tensor([-0.0346], device='cuda:0', requires_grad=True)], Cost : 0.666
Epoch : 3000, Model : [Parameter containing:
tensor([[0.0045, 0.0017, 0.0051]], device='cuda:0', requires_grad=True), Parameter containing:
tensor([-0.1609], device='cuda:0', requires_grad=True)], Cost : 0.652
Epoch : 4000, Model : [Parameter containing:
tensor([[0.0052, 0.0017, 0.0053]], device='cuda:0', requires_grad=True), Parameter containing:
tensor([-0.2843], device='cuda:0', requires_grad=True)], Cost : 0.636
Epoch : 5000, Model : [Parameter containing:
tensor([[0.0059, 0.0036, 0.0064]], device='cuda:0', requires_grad=True), Parameter containing:
tensor([-0.4046], device='cuda:0', requires_grad=True)], Cost : 0.627
Epoch : 6000, Model : [Parameter containing:
tensor([[0.0065, 0.0034, 0.0068]], device='cuda:0', requires_grad=True), Parameter containing:
tensor([-0.5220], device='cuda:0', requires_grad=True)], Cost : 0.613
Epoch : 7000, Model : [Parameter containing:
tensor([[0.0064, 0.0036, 0.0069]], device='cuda:0', requires_grad=True), Parameter containing:
tensor([-0.6366], device='cuda:0', requires_grad=True)], Cost : 0.604
Epoch : 8000, Model : [Parameter containing:
tensor([[0.0073, 0.0050, 0.0079]], device='cuda:0', requires_grad=True), Parameter containing:
tensor([-0.7485], device='cuda:0', requires_grad=True)], Cost : 0.591
Epoch : 9000, Model : [Parameter containing:
tensor([[0.0074, 0.0058, 0.0082]], device='cuda:0', requires_grad=True), Parameter containing:
tensor([-0.8577], device='cuda:0', requires_grad=True)], Cost : 0.585
Epoch : 10000, Model : [Parameter containing:
tensor([[0.0089, 0.0070, 0.0096]], device='cuda:0', requires_grad=True), Parameter containing:
tensor([-0.9644], device='cuda:0', requires_grad=True)], Cost : 0.576
tensor([[0.6707],
        [0.7298],
        [0.7368],
        [0.7800]], device='cuda:0')
tensor([[True],
        [True],
        [True],
        [True]], device='cuda:0')
-------------------
tensor([[0.5723],
        [0.6746],
        [0.4676],
        [0.3640]], device='cuda:0')
tensor([[ True],
        [ True],
        [False],
        [False]], device='cuda:0')
-------------------
tensor([[0.5711],
        [0.6363],
        [0.3985],
        [0.6238]], device='cuda:0')
tensor([[ True],
        [ True],
        [False],
        [ True]], device='cuda:0')
-------------------
...
tensor([[0.5797],
        [0.7276],
        [0.6039],
        [0.5545]], device='cuda:0')
tensor([[True],
        [True],
        [True],
        [True]], device='cuda:0')
-------------------

비선형 활성화 함수(Non-linear Activations Function)

네트워크에 비선형성을 적용하기 위해 인공 신경망에서 사용하는 함수
실제 많은 입출력 관계가 대부분 비선형 구조를 가지고 있기 때문에, 선형 구조를 사용하지 않음

계단 함수(Step Function)

이진 활성화 함수(Binary Activation Function)라고도 하며, 퍼셉트론(Perception)에서 최초로 사용한 활성화 함수
계단 함수의 입력값의 합이 임곗값을 넘으면 0을, 넘지 못하면 1을 출력

임곗값에서 불연속점(Point of discontinuity)을 가지므로 미분이 불가능해 학습이 불가능하여 딥러닝 모델에서는 사용하지 않음
역전파 과정에서 데이터가 극단적으로 변경되어 적합하지 않음

임곗값 함수(Threshold Funciton)

임곗값(threshold)보다 크면 입력값(x) 그대로 전달, 작으면 특정 값으로 변경
선형 함수와 계단 함수의 조합

출력이 0 또는 1인 이진 분류 작업을 위해 신경망에서 사용
입력에 대한 함수의 기울기를 계산할 수 없어 네트워크를 최적화하기 어려워 특별한 경우가 아니라면 사용되지 않음

시그모이드 함수(Sigmoid Function)

모든 입력값을 0과 1 사이의 값으로 매핑
이진 분류 신경망의 출력 계층에서 활성화 함수로 사용
출력값이 0~1 범위를 가지므로 기울기 폭주 현상을 막을 수 있으나, 기울기 소실이 발생함

출력값 중심이 0이 아니므로 입력 데이터가 항상 양수라면, 기울기가 모두 양수 또는 음수가 되어 지그재그 형태로 변동하여 학습 효율성이 감소됨
계층이 많아지면 값이 0에 수렴하는 문제로 성능이 떨어짐
은닉층에서는 사용하지 않고 주로 출력층에서만 사용

하이퍼볼릭 탄젠트 함수(Hyperbolic Tangent Function)

시그모이드와 유사한 형태지만 출력값의 중심이 0이고, -1~1의 범위를 가짐
출력값의 범위가 넓어 기울기 소실이 비교적 덜 발생
입력값이 4보다 크면 출력값이 1에 수렴하여 기울기 소실 발생

ReLU 함수(Rectified Linear Unit Function)

딥러닝 네트워크에서 널리 사용하는 효과적인 활성화 함수
0보다 작거나 같으면 0 반환, 0보다 크면 선형 함수에 값 대입하는 구조
선형 함수에 대입하므로 입력 값이 양수면 출력값이 제한되지 않아 기울기 소실이 발생하지 않음

수식이 간단하여 순전파, 역전파 연산이 매우 빠름
입력값이 음수면 항상 0을 반환하여 가중치나 편향이 갱신되지 않을 수 있음

가중치 합이 음수가 되면 해당 노드는 더 이상 값을 갱신하지 않아 죽은 뉴런(Dead Neuron, Dying ReLU)이 됨

LeakyReLU 함수(Leaky Rectified Linear Unit Function)

음수 기울기(negative slope)를 제어하여 죽은 뉴런 현상을 방지하기 위해 사용
양수는 ReLU 함수와 동일, 음수는 작은 값이라도 출력시켜 기울기를 갱신함
작은 값을 출력시켜 더 넓은 범위의 패턴을 학습시켜 네트워크 성능 향상에 도움이 됨

PReLU 함수(Parametric Rectified Linear Unit Function)

LeakyReLU 함수와 형태가 동일하지만, 음수 기울기(negative slope) 값을 학습을 통해 갱신되는 값으로 간주함
학습 데이터세트에 영향을 받음

ELU 함수(Exponential Linear Unit Function)

지수 함수를 사용해 부드러운 곡선 형태를 가짐
음의 기울기에서 비선형 구조를 가짐
입력값이 0인 경우에도 출력값이 급변하지 않음
경사하강법 수렴 속도가 비교적 빠름

복잡한 연산을 진행하게 되므로 학습 속도가 느려짐
데이터의 복잡한 패턴과 관계를 학습하는 네트워크 능력 향상에 도움

소프트맥스 함수(Softmax Function)

차원 벡터에서 특정 출력값이 $k$ 번째 클래스에 속할 확률 계산
클래스에 속할 확률을 계산하는 활성화 함수
은닉층에서 사용하지 않고 출력층에서 사용함
네트워크의 출력을 가능한 클래스에 대한 확률 분포로 매핑

소프트민 함수(Softmin Function), 로그 소프트맥스 함수(Log Softmax Function)도 있음

Categories:

댓글남기기