손으로 쓴 숫자(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 를 사용하므로, 데이터를 변환
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) 을 곱해서 초기화)
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)를 위해 사용
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))
무작위 모델에 대한 손실을 점검, 그럼으로써 나중에 역전파 이후에 개선이 있는지 확인할 수 있다.
def accuracy(out, yb):
preds = torch.argmax(out, dim=1)
return (preds == yb).float().mean()
모델의 정확도(accuracy)를 계산하기 위한 함수를 구현
print(accuracy(preds, yb))
무작위 모델의 정확도를 점검, 손실이 개선됨에 따라서 정확도가 개선되는지 확인할 수 있다.
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).
이제 손실과 정확도를 이전 값들과 비교하면서 확인.
cross_entropy 손실함수를 사용함으로써 활성화 함수(log_softmax)를 사용하지 않을 수 있다.
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을 사용해서 매개변수들을 관리한다.
모델을 함수가 아닌 오브젝트로 만들었기에 인스턴트화를 해줘야 한다.
weights 및 bias 를 수동으로 정의 및 초기화하고, xb @ weight + bias 를 계산하는 대신에,
위의 모든 것을 해줄 PyTorch 클래스인 nn.Linear 를 선형 레이어로 사용
각 매개변수를 수동으로 업데이트 하는 대신, 옵티마이저(optimizer)의 step 메소드를 사용
from torch.utils.data import TensorDataset
train_ds = TensorDataset(x_train, y_train)
이전에는 x와 y의 미니배치를 각각 변경해야 했지만 dataset을 사용하여 한줄로 처리
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
개발자로서 성장하는 데 큰 도움이 된 글이었습니다. 감사합니다.