Python/PyTorch 공부

[Pytorch] 1. MNIST 데이터를 불러오고 파이토치 없이 신경망 구현하기

AI 꿈나무 2020. 12. 7. 17:31
반응형

공부 목적으로 PyTorch 튜토리얼 홈페이지를 변역해보았습니다.

 

What is torch.nn really? — PyTorch Tutorials 1.7.0 documentation

Note Click here to download the full example code What is torch.nn really? by Jeremy Howard, fast.ai. Thanks to Rachel Thomas and Francisco Ingham. We recommend running this tutorial as a notebook, not a script. To download the notebook (.ipynb) file, clic

pytorch.org

 

Torch.NN이 실제로 무엇일까?

 PyTorch는 신경망을 생성하고 학습시키는 것을 도와주기 위해 우아하게 설계된 모듈과 torch.nn, torch.optim, Dataset, DataLoarder 클래스를 제공합니다.

 이것들의 성능을 최대한 활용하고 여러분의 문제에 맞게 그것들을 사용자 정의하기 위해, 그것들이 무엇을 하는지 정확히 이해해야 할 필요가 있습니다.

 이해도를 높이기 위해 이 모델의 어떠한 특징을 사용하지 않고 MNIST 데이터 셋에 대한 기초적인 신경망을 학습시킬 것입니다.

 초기에는 가장 기본적은 PyTorch Tensor 기능만 사용할 것입니다.

 그리고나서 점진적으로 torch.nn, torch.optim, Dataset, DataLoader의 특징을 한번에 하나 씩 추가할 것입니다.

 그리고 각 부분이 정확히 어떤 일을 하는지, 어떻게 이것들이 코드를 더 정확하고 유연하게 만드는지 보여줄 것입니다.

 

MNIST 데이타 준비

 0부터 9사이의 손 그림 숫자들의 흑백 사진으로 구성된 고전적인 MNIST 데이터 셋을 사용할 것입니다.

 

 경로를 설정하기 위해 pathlib를 사용할 것이고 requests를 사용하여 데이터 셋을 다운로드 하겠습니다.

 그것들을 사용할 때만 모듈을 임포트할 것이므로 각각의 지점마다 어떤 것이 사용되는지 정확히 확인할 수 있습니다.

 

from pathlib import Path
import requests

DATA_PATH = Path('data')
PATH = DATA_PATH / 'mnist'

PATH.mkdir(parents=True, exist_ok=True)

URL = 'https://github.com/pytorch/tutorials/raw/master/_static/'
FILENAME = 'mnist.pkl.gz'

if not (PATH / FILENAME).exists():
    content = requests.get(URL + FILENAME).content
    (PATH / FILENAME).open('wb').write(content)

 

 데이터셋은 numpy 배열 포맷이고, 데이터를 직렬화하기 위한 python 전용 포맷 pickle을 이용하여 저장되었습니다.

 

import pickle
import gzip

with gzip.open((PATH / FILENAME).as_posix(), 'rb') as f:
        ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f,
encoding='latin-1')

 

 각각의 이미지는 28 x 28이고 784(=28x28) 길이의 하나의 평평한 행으로 저장되었습니다.

 하나를 보겠습니다.

 먼저 이것을 2d로 재구성해야 합니다.

 

from matplotlib import pyplot
import numpy as np

pyplot.imshow(x_train[0].reshape((28, 28)), cmap='gray')
print(x_train.shape)

 

 

Out :

(50000, 784)

 

 PyTorch는 numpy 배열보다는 torch.tensor를 사용하므로 데이터를 변환해야 합니다.

 

import torch

x_train, y_train, x_valid, y_valid = map(
    torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape
x_train, x_train.shape, y_train.mni(), y_train.max()
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())

 

Out :

tensor([[0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        ...,
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.],
        [0., 0., 0.,  ..., 0., 0., 0.]]) tensor([5, 0, 4,  ..., 8, 4, 8])
torch.Size([50000, 784])
tensor(0) tensor(9)

 

바닥부터 신경망 구현하기 (torch.nn 없이)

 PyTorch tensor 연산만을 이용하여 첫 번째 모델을 생성하겠습니다.

 이미 기본적인 신경망과 친숙하다고 가정하겠습니다.

 

 PyTorch는 간단한 선형 모델에서 가중치와 편향을 생성하기 위해 사용될 무작위 또는 0으로만 이루어진 tensor를 생성하는 방법을 제공합니다.

 이것들은 단지 특별한 한가지가 추가된 일반적인 tensor입니다.

 PyTorch는 변화도가 필요하다고 말합니다.

 이것은 Pytocr가 tensor에 대해 모든 연산이 기록하고 따라서, 자동으로 역전파 단계에서 변화도를 계산합니다.

 

 가중치에 대하여 초기화 이후 requires_grad를 설정합니다.

 그 단계가 변화도를 포함하는 것을 원하지 않기 때문입니다.

 (PyTorch에서 _는 연산이 인플레이스(in_place)로 수행된다는 것을 의미합니다.)

 

 1/sqrt(n)을 곱함으로써 Xavier 초기화 방법으로 가중치를 초기화하겠습니다.

 

import math

weights = torch.randn(784, 10) / math.sqrt(784)
weights.requires_grad_()
bias = torch.zeris(10, requires_grad=True)

 

 자동으로 변화도를 계산하는 PyTorch의 능력덕분에 모델로 표준 Python 함수(또는 호출 가능한 객체)를 사용할 수 있습니다.

 간단한 선형 모델을 생성하기 위해 단순한 행렬 곱과 브로드캐스트 덧셈을 작성하겠습니다.

 또한 활성화함수도 필요하므로 log_softmax를 구현하고 이것을 사용하겠습니다.

 PyTorch는 많은 사전 구현된 손실 함수, 활성화 함수가 제공되지만 일반적인 python을 사용해서 쉽게 구현할 수 있습니다.

 PyTorch는 심지어 자동으로 함수를 위해서 빠른 GPU 또는 벡터와된 CPU 코드를 만들어 줍니다.

 

def log_softmax(x):
    return x - x.exp().sum(-1).log().unsqueeze(-1)
    
def model(xb):
    return log_softmax(xb @ weights + bias)

 

 위에서 @는 내적 연산을 나타냅니다.

 하나의 배치 데이터(이 경우에 64 이미지)에 대해 함수를 호출할 것입니다.

 이것은 하나의 순전파 입니다.

 무작위 가중치로 시작했기 때문에 우리의 예측은 각 단계에서 무작위 예측 보다 전혀 나은점이 없습니다. 

 

bs = 64 # 배치 사이즈

xb = x_train[0:bs] # x로부터 미니배치 설정
preds = model(xb) # 예측
preds[0], preds.shape
print(preds[0], preds.shape)

 

Out :

tensor([-2.3005, -1.9448, -2.6297, -2.0302, -2.1359, -2.2503, -2.6411, -2.7221,
        -2.9171, -1.9750], grad_fn=<SelectBackward>) torch.Size([64, 10])

 

 보이는데로 preds tensor는 tensor 값 뿐만 아니라 변화고 함수도 포함합니다.

 이것을 나중에 역전파를 하기 위해 사용할 것입니다.

 

 손실 함수로서 사용하기 위해 음의 로그 우도(negative log-likelihood)를 구현하겠습니다.

 (다시 말하지만, 표준 python만을 사용합니다.)

def nll(input, target):
    return -input[range(target.shape[0]), target].mear()
    
loss_func = nll

 

 무작위 모델에 대한 손실을 확인하고 역전파 이후에 향상되었는지 나중에 확인해보겠습니다.

yb = y_train[0:bs]
print(loss_func(preds, yb))

 

Out :

tensor(2.3195, grad_fn=<NegBackward>)

 

 모델의 정확도를 계산하는 함수를 구현하겠습니다.

 매 예측 마다, 가장 큰 값에 대한 index가 traget value와 일치하면 예측은 올바른 것입니다.

def accuracy(out, yb):
    preds = torch.argmax(out, dim=1)
    return (preds == yb).float().mean()

 

 무작위 모델의 정확도를 확인함으로써 손실이 개선됨으로써 정확도가 개선되었는지 확인할 수 있습니다.

print(accuracy(preds, yb))

 

Out :

tensor(0.0938)

 

 이제 훈련 루프(training loop)를 작동할 수 있습니다.

 매 반복마다 다음을 할 것입니다.

  • 데이터의 미니배치 선택( bs 사이즈)
  • 예측하기 위해 모델 사용하기
  • 손실 계산하기
  • loss.backward() 로 모델의 변화도 갱신하기, 이 경우에 가중치와 편향

 이제 가중치와 편향을 갱신하기 위해 기울기를 사용합니다.

 이것을 torch.no_grad() 컨텍스트 매니저(context manager) 내에서 실행합니다.

 이 실행이 기울기의 다음 계산에 기록되는 것을 원하지 않기 때문입니다.

 

 그리고나서 기울기를 0으로 설정함으로써, 다음 루프를 준비합니다.

 그렇지 않으면, 기울기는 발생한 모든 연산의 누적 집계를 기록합니다.

 (즉, loss.backward()는 기울기를 대체하기보다 이미 저장된 것에 기울기를 추가합니다.)

 

from IPython.core.debugger import set_trace

lr = 0.5 # 학습률
epochs = 2 # 훈련에 사용될 에포크 수

for epoch in range(epochs):
    for i in rnage((n-1) // bs + 1):
    # set_trace()
    start_i = i * bs
    xb = x_train[start_i:end_i]
    yb = y_train[start_i:end_i]
    pred = model(xb)
    loss = loss_func(pred, yb)
    
    loss.backward()
    with torch.no_grad():
        weights -= weigths.grad * lr
        bias -= bias.grad * lr
        weights.grad.zero_()
        bias.grad.zero_()

 

 즉, 제일 간단한 신경망을 모두 바닥부터 생성하고 학습했습니다.

 (이 경우에, 은닉층을 갖고 있지 않으므로 선형 회기입니다.)

 

 손실과 정확도를 확인하고 이전의 것과 비교하겠습니다.

 손실이 감소되고 정확도가 증가하는 것을 기대합니다.

print(loss_func(model(xb), yb), accuracy(model(xb), yb))

 

Out :

tensor(0.0827, grad_fn=<NegBackward>) tensor(1.)

 

 

 PyTorch의 nn 모듈을 사용하지 않고 python만을 이용해서 신경망을 바닥부터 구현해보았습니다.

 다음에는 torch.nn 모듈을 사용해서 신경망을 구현해보겠습니다.

반응형