[딥러닝]Artificial Neural Network(인공 신경망)

Date:     Updated:

카테고리:

Aritifical Neural Network(인공 신경망, ANN)

1. Neural Network

사람의 뇌는 뉴런(신경세포)로 이루어져있다. 뉴런들은 신경전달물질들을 만들어 자극을 전달한다. 여러 뉴런간의 상호작용으로 신호가 전달되어 인간은 그 자극에 대해 반응을 하게된다. 이러한 뉴런을 모델링해 만들 개념이다. 인공 신경망은 노드(Node)엣지(Edge)로 이루어진다. 노드는 하나의 뉴런을 말하며, 엣지는 뉴런간의 연결을 말하며 이 정도를 가중치(weight)라고 한다.

1

Bias는 일종의 기준선을 주어 민감도를 조정한다. ANN의 목적은 주어진 입력에 대해 원하는 출력이 나오도록 weight와 bias를 알아내는 것이다. 이를 모델이 학습한다고 한다.

  1. Weight를 Signal에 곱한다.
  2. Bias(민감도)를 더한다.
  3. Activation function을 먹인다.

2. Activation Funciton(활성 함수)

입력 신호의 총합을 출력 신호로 변환하는 함수이다. 입력 신호들의 Weighted Sum을 활성함수에 넣어주어 출력된 신호가 다음 층의 신경층의 입력이 된다. Activation function은 비선형 함수(Nonlinear function)여야 한다. 신경망에서 선형 함수를 이용하면 신경망의 층을 깊게하는 의미가 없이진다. 다음의 예로 확인할 수 있다.

Activation_function = (3x + 2) 
Input = a

Output1 = 3a + 2
Output2 = 3(3a + 2) 2 # The second layer's output is still linear

인공 신경망의 장점은 선형 함수의 경우 Binary하게 classification하는 반면(2차원에서), 다중 분류를 할 수 있게 만들어주며 그 역할을 하는 것이 활성화 함수이다.(가장 쉬운 예는 XOR classification) 따라서, 활성화 함수를 거친 출력 값은 선형이면 안된다. 또한, 선형함수가 출력으로 나올 경우, Hidden layer가 없어도 표현이 가능하기 때문에 그 의미가 퇴색된다.

1) Sigmoid(시그모이드) 함수

1

Sigmoid란 ‘S’모양을 말한다. 입력을 받아 그 값을 [0, 1]범위로 압축하여 출력한다. 물론 마냥 좋은 것만은 아니다. 단점도 역시 존재한다.

  • 단점
    1. 기울기 소면(Vanishing Gradient): 미분시 미분값이 0으로 수렴
      1. Sigmoid 함숫값의 중심이 0(Zero-Centered)이 아니다.


2) tanh

1

생긴 것은 Sigmoid와 비슷하지만, 그 출력값은 -1 ~ 1 사이의 값의 범위를 가진다. 사실 tanh함수는 sigmoid를 두배해 -1한 값과 같다.

$$tanh(x) \; = \; 2sigmoid(2x) - 1$$
  • 단점
    1. 기울기 소면(Vanishing Gradient): 미분시 미분값이 0으로 수렴


3) ReLU(Rectified Linear Unit) 함수

1

가장 많이 사용하는 함수이다. 입력이 음수이면 출력값이 0으로 되는데, 아주 작은 값으로 출력되게끔 기울기를 아주 작은 값으로 설정한 Leaky ReLU를 일반적으로 더 많이 사용한다. 또한 값이 아무리 커져도 Saturation이 일어나지 않으며 exponetial이 수식에 포함되지 않아, 연산이 더 빠르다.

  • 단점
    1. Zero-Centered가 아니기 때문에 지그재그 문제가 생할 수 있다.

1

3. Back Propagation(오차 역전파법)

신경망은 순환하지 않는 그래프로 연결된 뉴런의 집합이다. 이는 크게 세 개의 부분으로 나눠진다. 입력층(Input Layer), 은닉층(Hidden Layer), 출력층(Output Layer)이다. 입력층에서 signal을 받으면 기존에 미리 부여된 가중치와 Weighted Sum을 거치고, 활성화 함수를 거쳐 은닉층으로 전달된다. 은닉층의 깊이에 따라 모델의 계산이 복잡해지며, 층이 많아질수록 Deep neural network라고 한다.

1

오차 역전파법을 이용해 각 계층에 전달된 오차(Error)를 바탕으로 가중치를 갱신하는 방법은 Gradient Descent를 오차함수에 적용해 최소화하는 문제와 같다.

1

1

1



Example with Pytorch

1. Dataset Load

  1. 데이터 증진(data augmentation)을 명시하여 초기화할 수 있다.
    • 이미지를 불러올 때 어떤 방법(회전, 자르기, 뒤집기 등)을 사용할 것인지 명시한다.
  2. 이후에 DataLoader()를 이용하여 실질적으로 데이터를 불러올 수 있다.
    • 어떤 데이터를 사용할 것인지, 배치 크기(batch size), 데이터 셔플(shuffle) 여부 등을 명시한다.
    • next() 함수를 이용하여 tensor 형태로 데이터를 배치 단위로 얻을 수 있다.
import torch
import torchvision
import torchvision.transforms as transforms
import torchvision.models as models
import torchvision.datasets as datasets

import torch.optim as optim
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import random_split

import matplotlib.pyplot as plt
import matplotlib.image as image
import numpy as np

transform_train = transforms.Compose([
    transforms.Resize((256, 256)),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize(
        mean=[0.5, 0.5, 0.5],
        std=[0.5, 0.5, 0.5]
    )
])

transform_val = transforms.Compose([
    transforms.Resize((256, 256)),
    transforms.ToTensor(),
    transforms.Normalize(
        mean=[0.5, 0.5, 0.5],
        std=[0.5, 0.5, 0.5]
    )
])

transform_test = transforms.Compose([
    transforms.Resize((256, 256)),
    transforms.ToTensor(),
    transforms.Normalize(
        mean=[0.5, 0.5, 0.5],
        std=[0.5, 0.5, 0.5]
    )
])

train_dataset = datasets.ImageFolder(
    root='train/',
    transform=transform_train
)
dataset_size = len(train_dataset)
train_size = int(dataset_size * 0.8)
val_size = dataset_size - train_size


train_dataset, val_dataset = random_split(train_dataset, [train_size, val_size])
test_dataset = datasets.ImageFolder(
    root='test/',
    transform=transform_test
)

train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)
val_dataloader = torch.utils.data.DataLoader(val_dataset, batch_size=64, shuffle=False)
test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=64, shuffle=False)

2. Data Visualization

plt.rcParams['figure.figsize'] = [12, 8]
plt.rcParams['figure.dpi'] = 60
plt.rcParams.update({'font.size': 20})


def imshow(input):
    # torch.Tensor => numpy
    input = input.numpy().transpose((1, 2, 0))
    # undo image normalization
    mean = np.array([0.5, 0.5, 0.5])
    std = np.array([0.5, 0.5, 0.5])
    input = std * input + mean
    input = np.clip(input, 0, 1)
    # display images
    plt.imshow(input)
    plt.show()


class_names = {
  0: "Cloudy",
  1: "Rain",
  2: "Shine",
  3: "Sunrise"
}

# load a batch of train image
iterator = iter(train_dataloader)

# visualize a batch of train image
imgs, labels = next(iterator)
out = torchvision.utils.make_grid(imgs[:4])
imshow(out)
print([class_names[labels[i].item()] for i in range(4)])

3. Model Class 정의

class Model1(nn.Module):
    def __init__(self):
        super(Model1, self).__init__()
        self.linear1 = nn.Linear(256 * 256 * 3, 4)
        self.flatten = nn.Flatten()

    def forward(self, x):sss
        x = self.flatten(x)
        x = self.linear1(x)
        return x


class Model2(nn.Module):
    def __init__(self):
        super(Model2, self).__init__()
        self.linear1 = nn.Linear(256 * 256 * 3, 64)
        self.linear2 = nn.Linear(64, 4)
        self.flatten = nn.Flatten()

    def forward(self, x):
        x = self.flatten(x)
        x = self.linear1(x)
        x = self.linear2(x)
        return x


class Model3(nn.Module):
    def __init__(self):
        super(Model3, self).__init__()
        self.linear1 = nn.Linear(256 * 256 * 3, 128)
        self.dropout1 = nn.Dropout(0.5)
        self.linear2 = nn.Linear(128, 64)
        self.dropout2 = nn.Dropout(0.5)
        self.linear3 = nn.Linear(64, 32)
        self.dropout3 = nn.Dropout(0.5)
        self.linear4 = nn.Linear(32, 4)
        self.flatten = nn.Flatten()

    def forward(self, x):
        x = self.flatten(x)
        x = F.relu(self.linear1(x))
        x = self.dropout1(x)
        x = F.relu(self.linear2(x))
        x = self.dropout2(x)
        x = F.relu(self.linear3(x))
        x = self.dropout3(x)
        x = self.linear4(x)
        return x
import time


def train():
    start_time = time.time()
    print(f'[Epoch: {epoch + 1} - Training]')
    model.train()
    total = 0
    running_loss = 0.0
    running_corrects = 0

    for i, batch in enumerate(train_dataloader):
        imgs, labels = batch
        imgs, labels = imgs.cuda(), labels.cuda()

        outputs = model(imgs)
        optimizer.zero_grad()
        _, preds = torch.max(outputs, 1)
        loss = criterion(outputs, labels)

        loss.backward()
        optimizer.step()

        total += labels.shape[0]
        running_loss += loss.item()
        running_corrects += torch.sum(preds == labels.data)

        if i % log_step == log_step - 1:
            print(f'[Batch: {i + 1}] running train loss: {running_loss / total}, running train accuracy: {running_corrects / total}')

    print(f'train loss: {running_loss / total}, accuracy: {running_corrects / total}')
    print("elapsed time:", time.time() - start_time)
    return running_loss / total, (running_corrects / total).item()


def validate():
    start_time = time.time()
    print(f'[Epoch: {epoch + 1} - Validation]')
    model.eval()
    total = 0
    running_loss = 0.0
    running_corrects = 0

    for i, batch in enumerate(val_dataloader):
        imgs, labels = batch
        imgs, labels = imgs.cuda(), labels.cuda()

        with torch.no_grad():
            outputs = model(imgs)
            _, preds = torch.max(outputs, 1)
            loss = criterion(outputs, labels)

        total += labels.shape[0]
        running_loss += loss.item()
        running_corrects += torch.sum(preds == labels.data)

        if (i == 0) or (i % log_step == log_step - 1):
            print(f'[Batch: {i + 1}] running val loss: {running_loss / total}, running val accuracy: {running_corrects / total}')

    print(f'val loss: {running_loss / total}, accuracy: {running_corrects / total}')
    print("elapsed time:", time.time() - start_time)
    return running_loss / total, (running_corrects / total).item()


def test():
    start_time = time.time()
    print(f'[Test]')
    model.eval()
    total = 0
    running_loss = 0.0
    running_corrects = 0

    for i, batch in enumerate(test_dataloader):
        imgs, labels = batch
        imgs, labels = imgs.cuda(), labels.cuda()

        with torch.no_grad():
            outputs = model(imgs)
            _, preds = torch.max(outputs, 1)
            loss = criterion(outputs, labels)

        total += labels.shape[0]
        running_loss += loss.item()
        running_corrects += torch.sum(preds == labels.data)

        if (i == 0) or (i % log_step == log_step - 1):
            print(f'[Batch: {i + 1}] running test loss: {running_loss / total}, running test accuracy: {running_corrects / total}')

    print(f'test loss: {running_loss / total}, accuracy: {running_corrects / total}')
    print("elapsed time:", time.time() - start_time)
    return running_loss / total, (running_corrects / total).item()

import time

def adjust_learning_rate(optimizer, epoch):
    lr = learning_rate
    if epoch >= 3:
        lr /= 10
    if epoch >= 7:
        lr /= 10
    for param_group in optimizer.param_groups:
        param_group['lr'] = lr

4. 학습 결과 확인

learning_rate = 0.01
log_step = 20

model = Model1()
model = model.cuda()

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9)

num_epochs = 20
best_val_acc = 0
best_epoch = 0

history = []
accuracy = []
for epoch in range(num_epochs):
    adjust_learning_rate(optimizer, epoch)
    train_loss, train_acc = train()
    val_loss, val_acc = validate()
    history.append((train_loss, val_loss))
    accuracy.append((train_acc, val_acc))

    if val_acc > best_val_acc:
        print("[Info] best validation accuracy!")
        best_val_acc = val_acc
        best_epoch = epoch
        torch.save(model.state_dict(), f"best_checkpoint_epoch_{epoch + 1}.pth")

torch.save(model.state_dict(), f"last_checkpoint_epoch_{num_epochs}.pth")

plt.plot([x[0] for x in accuracy], 'b', label='train')
plt.plot([x[1] for x in accuracy], 'r--',label='validation')
plt.xlabel("Epochs")
plt.ylabel("Accuracy")
plt.legend()

test_loss, test_accuracy = test()
print(f"Test loss: {test_loss:.8f}")
print(f"Test accuracy: {test_accuracy * 100.:.2f}%")

Reference

  • CS7015 Lecture 9
  • Reconstruction of porous media from extremely limited information using conditional generative adversarial networks - Scientific Figure on ResearchGate. Available from: https://www.researchgate.net/figure/Commonly-used-activation-functions-a-Sigmoid-b-Tanh-c-ReLU-and-d-LReLU_fig3_335845675 [accessed 13 Aug, 2023]
  • Fast Campus 강의: 딥러닝, 인공지능 Signaturer 초격차

DeepLearning 카테고리 내 다른 글 보러가기

댓글 남기기