본문 바로가기
Deep Learning

[딥러닝] 인공신경망(ANN), MNIST 데이터 셋 분류

by r4bb1t 2021. 2. 26.
반응형

본 포스팅은 고려대학교 전기전자공학부 김중헌 교수님의 데이터과학원 강의 '딥러닝 프로그래밍 기초 다지기- 인공신경망 프로그래밍 기초 / 인공신경망 프로그래밍 구현'을 토대로 정리했습니다. 강의는 Tenserflow로 진행되었지만 파이토치로 구현하기 위해 <펭귄브로의 3분 딥러닝 파이토치맛(김건우, 염상준)>를 참고했습니다. 기본 개념은 <밑바닥부터 시작하는 딥러닝 - 파이썬으로 익히는 딥러닝 이론과 구현 (사이토 고키)> 를 참고했습니다.

 

인공신경망(ANN)

인공신경망(ANN)은 생물학의 신경망(동물의 중추신경계중 특히 뇌)에서 영감을 얻은 통계학적 학습 알고리즘입니다. (한국어 위키백과)

 

 

하나의 레이어에서 다른 레이어로 정보를 보낼 때의 기본적인 구조입니다.

위에 있는 레이어로부터 받아들인 입력(X)를 가중치(W)를 통해 증폭(W>1) 혹은 감쇠(W<1)를 한 후 결과값을 취합해 역치(θ) 이상이면 활성화(activation)합니다.

기준 이상이면 신호 전달, 그렇지 않다면 신호를 전달하지 않는다는 점에서 이진분류기와 유사합니다.

입력층과 출력층 사이에 은닉층(hidden layer)이 여러 개 있는 구조를 인공 신경망이라고 합니다.

프로그래밍적 관점에서는 input층은 레이어라고 보지 않습니다. (가중치를 이용해서 연산을 하는 레이어만 레이어 취급)

 

선형 분류기에서는 AND게이트와 OR게이트같은 연산은 가능하지만,

선형으로 분류할 수 없는 XOR 게이트같은 경우 분류가 불가능하지만, 은닉층이 있다면 오른쪽 그림과 같은 XOR 분류도 가능합니다.

일반적으로 딥러닝의 경우 hidden layer 20개 이상으로 만듭니다.

이렇게 행렬 연산을 할 수 있도록 갯수만 맞춰주면 각 레이어 간 세포의 개수는 마음대로 조절할 수 있습니다.

이전 레이어의 출력 가로 길이 = 다음 레이어의 입력 가로 길이 = 다음 레이어의 가중치 행렬 세로 길이 입니다.

행렬의 곱셈 개념을 알면 됩니다.

ANN에서 이렇게 행렬 계산을 해준 후 시그모이드 함수에 통과시키기 때문에 결과값은 0과 1로 나옵니다.

그런데 이렇게 계속 중첩이 되면, 가중치나 편향으로 인한 영향이 줄어들게 됩니다. (Gradient Vanishing Problem)

그래서, 시그모이드 함수 대신 ReLU함수를 사용하게 됩니다. ReLU함수는 결과값이 0 이하이면 0을, 0 이상이면 결과값 그대로를 출력하는 함수입니다.

 

추가적으로, ANN에서 입력이 2차원 배열 (이미지 등)로 들어올 경우, 레이어를 통과하면 1차원 배열로 변환되기 때문에 적합하지 않았는데요.

CNN 구조를 이용하면 입력이 N차원 배열이더라도 처리할 수 있습니다.

 

ANN 구현 (MNIST 데이터 셋 분류)

6만개의 트레이닝 데이터 셋과 1만개의 테스트 데이터 셋으로 이루어진 MNIST 데이터 셋은 아래와 같은 손글씨로 적힌 숫자들이 어떤 숫자인지 판별하는 분류기를 만드는 데에 이용됩니다.

각 이미지는 28×28 픽셀의 크기를 가지고 있습니다. 이 이미지가 각각 어떤 숫자인지 판단하기 위해서 소프트맥스 함수를 사용합니다.

소프트맥스 함수

소프트맥스 함수는 입력받은 값 (Ai~An)을 0~1 사이의 값으로 정규화하는데 이 때 각 값의 합은 1이 됩니다. 이 때문에 소프트맥스 함수의 출력값을 확률로 해석할 수 있게 됩니다.

소프트맥스 함수의 식은 다음과 같습니다.

워드 수식 입력기로 열심히 침

n은 출력층의 노드 수, Yk는 그 중 k번째 출력을 뜻합니다. 입력 값들과 출력 값들의 대소 관계는 유지됩니다. 그래서 결국 가장 큰 값을 출력하는 노드의 결과도 달라지지 않기 때문에 출력층에서는 생략하기도 합니다.

데이터 셋 분류하기

아래 코드는 rueki.tistory.com/99 포스팅을 참고하였습니다.

입력의 크기는 28×28=784개이고, 출력의 크기는 0~9의 각 확률을 가지고 있는 10개의 배열이 될 것입니다.

 

import torch
from torch import nn
import numpy
from torchvision import datasets, transforms
import matplotlib.pyplot as plt

train_data = datasets.MNIST(root='../data', train=True, download=True, transform=transforms.ToTensor())
test_data = datasets.MNIST(root='../data', train=False, download=True, transform=transforms.ToTensor())

 

우선 torchvision에서 MNIST 데이터셋을 가져옵니다.

각 데이터는 [이미지 28×28 텐서, 레이블 값] 으로 구성되어있습니다.

 

plt.imshow(train_data[0][0].reshape(28, 28), cmap='gist_yarg')
plt.show()

 

첫 번째 이미지를 확인해보면

(VS code에서는 plt.show()를 해줘야 이미지를 볼 수 있습니다.)

 

이렇게 생겼습니다.

 

batch_size = 100

train_batch = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True)
test_batch = torch.utils.data.DataLoader(test_data, batch_size=batch_size, shuffle=False)

 

배치 크기를 정해주고 가져옵니다.

 

class ModelANN(nn.Module):
    def __init__(self, size_in, size_out, size_hidden):
        super().__init__()
        self.hidden1 = nn.Linear(size_in, size_hidden[0])
        self.hidden2 = nn.Linear(size_hidden[0], size_hidden[1])
        self.out = nn.Linear(size_hidden[1], size_out)

    def forward(self, x):
        h1 = nn.functional.relu(self.hidden1(x))
        h2 = nn.functional.relu(self.hidden2(h1))
        y = nn.functional.log_softmax(self.out(h2), dim=1)
        return y

 

은닉층을 더 많이 하고 싶었는데, 은닉층이 늘어난다고 정확도가 늘어나는 건 아닌 것 같더라구요..?

 

model = ModelANN(784, 10, [200, 100])

learning_rate = 0.0001
epochs = 10
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

 

모델을 생성해줍니다. 은닉층 1의 크기는 200, 2의 크기는 100으로 임의로 잡았습니다.

 

전부 0으로 찍는 것보다 낮은 정확도

learning_rate는 적절히 잡아줍니다. 0.03으로 잡았다가 정확도가 10%도 안 되게 나와서 멘탈이 터진 후 줄였더니 괜찮아졌어요.

criterion은 어떤 손실값을 줄일지 정해주는 함수입니다. 확률에 대한 손실을 줄여야 하므로 entropy loss를 써줍니다.

optimizer는 최적화를 할 때 가중치의 경로를 잡아주는 함수라고 알고 있는데 (부정확한 정보입니다.) 제가 했을 때는 다른 함수들을 사용해도 정확도는 비슷하게 나왔습니다.

 

for epoch in range(epochs):
    model.train()
    optimizer.zero_grad()
    train_result = 0
    test_result = 0
    for index, (x_train, y_train) in enumerate(train_batch):
        index += 1
        train_output = model(x_train.view(100, -1))
        answer = torch.max(train_output.data, 1)[1]
        batch_result = (answer == y_train).sum()
        train_result += batch_result
        train_loss = criterion(train_output, y_train)
        if index % 100 == 0:
            print('Loss of {}.{} : {} ({}%)'.format(epoch, index,
                                                    train_loss.item(), train_result.item() / index))
        train_loss.backward()
        optimizer.step()
    model.eval()
    with torch.no_grad():
        for index, (x_test, y_test) in enumerate(test_batch):
            test_output = model(x_test.view(100, -1))
            answer = torch.max(test_output.data, 1)[1]
            test_result += (answer == y_test).sum()
        loss = criterion(test_output, y_test)
        print('test {} : {}%'.format(epoch, test_result.item() / 100))

학습 및 테스트를 시켜줍니다.

 

이렇게저렇게 수치를 수정하고 (learning_rate를 줄여서) 최종적으로 95%까지 정확도를 높일 수 있었습니다.


솔직히 아직 코드를 봐도 어렵습니다. 그리고 수식을 입력하기 위해 블로그에 LaTeX를 달아야 하나 고민 중입니다. 인생

 

 

반응형

댓글