What is TORCH.NN really?

21900772·2023년 7월 24일
0

PyTorch 스터디

목록 보기
6/11
post-thumbnail

MNIST 데이터 준비

손으로 쓴 숫자(0에서 9 사이)의 흑백 이미지로 구성된 클래식 MNIST 데이터셋을 사용

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/main/_static/"
FILENAME = "mnist.pkl.gz"

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

경로 설정을 담당하는 pathlib 을 사용할 것이고, requests 를 이용하여 데이터셋을 다운로드

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")

이 데이터셋은 NumPy 배열 포맷이고, 데이터를 직렬화하기 위한 python 전용 포맷 pickle 을 이용하여 저장

from matplotlib import pyplot
import numpy as np

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

각 이미지는 28 x 28 형태 이고, 784 (=28x28) 크기를 가진 하나의 행으로 저장되어 있다.
하나를 살펴 보면, 먼저 이 이미지를 2D로 재구성해야 합니다.

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
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())

PyTorch는 torch.tensor 를 사용하므로, 데이터를 변환


(torch.nn없이) 밑바닥부터 신경망 만들기

PyTorch에게 이들이 기울기(gradient)가 필요하다고 알려준다.
이를 통해 PyTorch는 텐서에 행해지는 모든 연산을 기록하게 하고, 따라서 자동적으로 역전파(back-propagation) 동안에 기울기를 계산할 수 있.

가중치에 대해서는 requires_grad 를 초기화(initialization) 다음에 설정.
왜냐하면 우리는 해당 단계가 기울기에 포함되는 것을 원치 않기 때문

import math

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

Xavier initialisation 기법을 이용하여 가중치를 초기화 (1/sqrt(n) 을 곱해서 초기화)

activation function & model

def log_softmax(x):
    return x - x.exp().sum(-1).log().unsqueeze(-1)

def model(xb):
    return log_softmax(xb @ weights + bias)

간단한 선형 모델을 만들기 위해서 단순한 행렬 곱셈과 브로드캐스트(broadcast) 덧셈을 사용.
또한, 우리는 활성화 함수(activation function)가 필요하므로, log_softmax 를 구현하고 사용.

PyTorch에서 많은 사전 구현된 손실 함수(loss function), 활성화 함수들이 제공되지만, 일반적인 python을 사용하여 자신만의 함수를 쉽게 작성할 수 있음을 기억.

@ 기호는 행렬 곱셈(matrix multiplication) 연산을 나타낸다.

bs = 64  # 배치 크기

xb = x_train[0:bs]  # x로부터 미니배치(mini-batch) 추출
preds = model(xb)  # 예측
preds[0], preds.shape
print(preds[0], preds.shape)

하나의 배치(batch) 데이터(이 경우에는 64개의 이미지들)에 대하여 함수를 호출할 것이다. 이것은 하나의 포워드 전달(forward pass)이다.
이 단계에서 우리는 무작위(random) 가중치로 시작했기 때문에 예측이 무작위 예측보다 전혀 나은 점이 없을 것이다.

preds 텐서(tensor)는 텐서 값 외에도, 또한 기울기 함수(gradient function)를 담고 있다.
나중에 이것을 역전파(backpropagation)를 위해 사용

loss function

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

loss_func = nll

이제 손실함수(loss function)로 사용하기 위한 negative log-likelihood를 구현

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

무작위 모델에 대한 손실을 점검, 그럼으로써 나중에 역전파 이후에 개선이 있는지 확인할 수 있다.

Accuracy

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

모델의 정확도(accuracy)를 계산하기 위한 함수를 구현

print(accuracy(preds, yb))

무작위 모델의 정확도를 점검, 손실이 개선됨에 따라서 정확도가 개선되는지 확인할 수 있다.

Training loop

from IPython.core.debugger import set_trace

lr = 0.5  # 학습률(learning rate)
epochs = 2  # 훈련에 사용할 에폭(epoch) 수

for epoch in range(epochs):
    for i in range((n - 1) // bs + 1):
        #         set_trace()
        start_i = i * bs
        end_i = start_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 -= weights.grad * lr
            bias -= bias.grad * lr
            weights.grad.zero_()
            bias.grad.zero_()

기울기를 0으로 초기화, 그럼으로써 다음 루프(loop)에 준비하게 된다. 그렇지 않으면, 기울기들은 일어난 모든 연산의 누적 집계를 기록하게 되버린다.
(즉, loss.backward() 가 이미 저장된 것을 대체하기보단, 기존 값에 기울기를 더하게 된다.)

PyTorch 코드에 대하여 표준 python 디버거(debugger)를 사용할 수 있으므로, 매 단계마다 다양한 변수 값을 점검할 수 있다. set_trace() 를 주석 해제하여 사용

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

hidden layer가 없기 때문에, 로지스틱 회귀(logistic regression).

이제 손실과 정확도를 이전 값들과 비교하면서 확인.


Refactoring

nn.functional

cross_entropy 손실함수를 사용함으로써 활성화 함수(log_softmax)를 사용하지 않을 수 있다.

nn.Module

from torch import nn

class Mnist_Logistic(nn.Module):
    def __init__(self):
        super().__init__()
        self.weights = nn.Parameter(torch.randn(784, 10) / math.sqrt(784))
        self.bias = nn.Parameter(torch.zeros(10))

    def forward(self, xb):
        return xb @ self.weights + self.bias
        
model = Mnist_Logistic()

nn.Module을 사용해서 매개변수들을 관리한다.
모델을 함수가 아닌 오브젝트로 만들었기에 인스턴트화를 해줘야 한다.

nn.Linear

weights 및 bias 를 수동으로 정의 및 초기화하고, xb @ weight + bias 를 계산하는 대신에,
위의 모든 것을 해줄 PyTorch 클래스인 nn.Linear 를 선형 레이어로 사용

torch.optim

각 매개변수를 수동으로 업데이트 하는 대신, 옵티마이저(optimizer)의 step 메소드를 사용

Dataset

from torch.utils.data import TensorDataset

train_ds = TensorDataset(x_train, y_train)

이전에는 x와 y의 미니배치를 각각 변경해야 했지만 dataset을 사용하여 한줄로 처리

DataLoader

from torch.utils.data import DataLoader

train_ds = TensorDataset(x_train, y_train)
train_dl = DataLoader(train_ds, batch_size=bs)

DataLoader는 미니배치를 자동적으로 제공함으로써 배치들에 대해서 반복하기 용이하게 만들어 준다

출처 : PyTorch Tutorials https://pytorch.org/tutorials/beginner/nn_tutorial.html

profile
HGU - 개인 공부 기록용 블로그

1개의 댓글

comment-user-thumbnail
2023년 7월 24일

개발자로서 성장하는 데 큰 도움이 된 글이었습니다. 감사합니다.

답글 달기