pyTorch를 이용한 인공신경망 기초

Develope

인공 신경망의 구조

Image
실제 뉴런의 원리를 응용하여 컴퓨터가 자동으로 학습을 할 수 있도록 한다.

  • 수상 돌기 => 입력
  • 시냅스의 연결 강도 => 가중치
  • 입력 신호를 전부 합한다
  • 세포의 역치 => 활성 함수

Image
이런 형태로 뇌를 모방한다.
그런데 이때, 은닉층(Hidden Layer)가 2개 이상이면 일반적으로 딥러닝으로 부른다.

또한 뉴런들이 한줄로 쫙 한층만 있는것과 같은수여도 여러 층이 있는 경우의 차이는, 덜 민감해진다 할수있다.

지도 학습

지도 학습은 주어진 정답을 가장 잘 표현하는 함수를 찾는것으로, 이 학습은 주어진 정답과 예측한 정답의 차이를 줄이는, 다시 말해 손실 함수의 값을 0에 가깝게 만드는 과정이다. 손실 함수의 일종인 평균 제곱 오차(MSE)는 수식으로 이렇게 표현할수 있다.

$L=\frac{1}{3}((y_1-f(x_1))^2+(y_2-f(x_2))^2+(y_3-f(x_3))^2)$

Image

위 손실 함수는 2차 함수로 표현되며, 이를 미분하여 어느 방향으로 갈지 알아볼수 있으며, 이를 반복해 쭉 내려가 손실이 최소인 지점에 도달할 수 있다.

보편 근사 정리

하나의 은닉층을 갖는 인공 신경망은 임의의 연속인 함수를 원하는 정도의 정확도로 실행할 수 있다. 이 정리는 곧 인공신경망을 사용해서 모든 함수를 거의 정확하게 표현할수 있다는 것이며, 이는 인공신경망을 사용하는 이유가 될수있다.

인공 신경망을 통해 함수 예측하기

그렇다면, 인공 신경망을 $y=x^3+x^2-x-1$을 얼마나 정확하게 근사할 수 있을까?

import torch
import torch.nn as nn
import torch.nn.functional as F
import random

# Write neural network code
class NeuralNetwork(nn.Module):

    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.input_layer = nn.Linear(1, 6) 
        self.activation = nn.ReLU()
        self.output_layer = nn.Linear(6, 1)
        # 레이어 지정
        
    def forward(self, x):
        x = self.input_layer(x)
        x = self.activation(x)
        x = self.output_layer(x)
        return x

# Define the network and the loss function.
network = NeuralNetwork()
loss_function = nn.MSELoss() # 손실함수를 MSE로 설정

# Steps for gradient descent.
# 'lr' stands for learning rate.
optimizer = torch.optim.Adam(network.parameters(), lr=0.001)

# Training one step with sample data
x = torch.Tensor([1]) # 1을 입력해서 테스트
y = x**3 + x**2 - x - 1 # 함수


optimizer.zero_grad()
output = network(x)
loss = loss_function(y, output)
loss.backward()
optimizer.step() # 학습시키기 

# Check parameters
parameters = network.input_layer.state_dict()
w = parameters['weight']  # e.g. [-5, -1.2, 1.2, 1.2, 2, 5]
b = parameters['bias']  # e.g. [-7.7, -1.3, 1, -0.2, -1.1, -5]
print(w)
print(b)

위 코드는 딥러닝 환경을 구현하고 학습이 되는지 테스트를 해본다.

from tqdm import tqdm
import matplotlib.pyplot as plt

# Define the network and the loss function.
network = NeuralNetwork()
loss_function = nn.MSELoss()
optimizer = torch.optim.Adam(network.parameters(), lr=0.01)

pbar = tqdm(range(10000), desc="Loss: --")
for epoch in pbar:
    x = (torch.rand(1)-0.5)*10
    y = x**3 + x**2 - x - 1
    # y = x**4-14*x**3-83*x**2+168*x+540

    optimizer.zero_grad()
    output = network(x)
    loss = loss_function(y, output)
    loss.backward()
    optimizer.step()
    
    if epoch % 100 == 0:
        pbar.set_description(f"Loss: {loss.item():.3f}")

이후 10000회 랜덤값을 넣어주는 학습을 시켜줬고, 그 결과다.

Image

랜덤한 값이 입력되는 중간 부분은 학습이 제대로 진행되었지만, 양 끝부분은 그렇지 않다는 것을 볼수있다. 이는 학습이 진행되는 부분만 제대로 근사할수있다는것을 보여준다.

MNIST학습

그렇다면 조금 더 현실적인 학습을 시도해 본다. 손글씨 데이터셋을 통해 숫자를 구별하는 학습을 해볼것이다.

MNIST 데이터셋 받기

import torchvision

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

학습

class NeuralNetwork(nn.Module):

    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.input_layer = nn.Linear(784, 1568)
        self.activation = nn.ReLU()
        self.hidden_layers = [nn.Linear(1568, 1568) for _ in range(1)]
        self.output_layer = nn.Linear(1568, 10)
        
    def forward(self, x):
        x = self.input_layer(x)
        x = self.activation(x)
        for i in self.hidden_layers:
            x=i(x)
            self.activation(x)
        x = self.output_layer(x)
        return x

28*28의 그레이스케일 이미지를 784*1의 1차원 배열로 펼쳐서 학습한다. 1568개 뉴런을 가진 은닉층 1개와 10개의 출력이 있다.

from tqdm import tqdm
import matplotlib.pyplot as plt
import time
import random
import numpy

# Define the network and the loss function.
network = NeuralNetwork()
# loss_function = nn.MSELoss()
loss_function = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(network.parameters(), lr=0.01)

pbar = tqdm(range(50000), desc="Loss: --")
for epoch in pbar:
    test_case=random.randint(0, 59999)
    x = train_data[test_case][0].view(1, 784)
    y = [0 for i in range(10)]
    y[train_data[test_case][1]]=1
    y = torch.Tensor(y)
    y= y.view(1,10)
    # time.sleep(1)

    optimizer.zero_grad()
    output = network(x)
    loss = loss_function(output, y)
    loss.backward()
    optimizer.step()
    
    if epoch % 100 == 0:
        pbar.set_description(f"Loss: {loss.item():.3f}")

이후 50000회 학습을 진행하였다.

성공률 확인

end=0
pbar = tqdm(range(5000), desc="Loss: --")
for epoch in pbar:
    x = test_data[epoch][0].view(1, 784)
    y = [0 for i in range(10)]
    y[test_data[epoch][1]]=1
    y = torch.Tensor(y)
    y= y.view(1,10)
    # time.sleep(1)

    optimizer.zero_grad()
    output = network(x)
    loss = loss_function(output, y)
    loss.backward()
    optimizer.step()
    output=output.tolist()[0]
    
    if output.index(max(output))==test_data[epoch][1]:
        end+=1

    if epoch % 100 == 0:
        pbar.set_description(f"Loss: {loss.item():.3f}")

print(end/epoch*100)

테스트 케이스를 진행하여 얻어낸 성공률은 70%이다.