[PYTORCH]파이토치 사용하기

신동혁·2022년 12월 11일
1

Pytorch

목록 보기
1/4
post-custom-banner

1.파이토치란?

파이토치란 파이썬에서 제공되는 딥러닝 프레임워크다.

출처 : 파이토치 첫걸음 - 최건호

파이토치의 장점을 설명하기 위해 먼저 파이썬에서 많이 쓰이는 Numpy 라이브러리와 파이토치를 비교해보고, 현재 많은 사람이 사용하는 딥러닝 프레임 워크 Tensorflow와 비교를 통해 파이토치의 장점을 설명해보자.

먼저, Numpy만을 사용하는 경우와 파이토치 프레임워크를 사용한 경우를 비교해보자. x, y, z 세 변수에 대해 학습하는 간단한 예를 생각해보면 기울기를 계산하기 위해 연산 그래프를 쭉 따라서 미분해야 한다. 이때 Numpy만을 사용한다면 모든 미분 식을 직접 계산하고 코드로 작성해야 하므로 변수 하나당 두 줄씩 여섯 줄이 필요하다. 반면 파이토치를 사용하면 이 과정을 자동으로 계산해주기 때문에 backward() 라는 함수를 한 번 호출해주면 계산이 끝난다.

또한 Numpy를 사용하는 것과 파이토치 프레임워크를 사용하는 것에는 또 하나의 큰 차이가 있다. 바로 GPU를 통한 연산 가능 여부이다. Numpy만으로는 GPU로 값들을 보내 연산을 돌리고 다시 받는 것이 불가능하다. 이에 비해 파이토치는 내부적으로 CUDA, cuDNN이라는 API를 통해 GPU를 연산에 사용할 수 있고, 이로 인해 생기는 연산 속도의 차이는 엄청나다. CUDA는 엔비디아가 GPU를 통한 연산을 가능하게 만든 API 모델이며, cuDNN은 CUDA를 이용해 딥러닝 연산을 가속해주는 라이브러리이다. 병렬 연산에서 GPU의 속도는 CPU의 속도보다 월등히 빠르며 이 차이는 지속적으로 벌어지고 있다. CUDA와 cuDNN을 둘 다 사용하면 연산 속도가 CPU의 15배 이상이 된다고 알려져 있다. CPU로 한 달 걸릴 연산을 이틀이면 할 수 있는 셈이다. 심층 신경망을 만들 때 함수 및 기울기 계산, 그리고 GPU를 이용한 연산 가속 등의 장점이 있기 때문에 딥러닝 개발 시 프레임워크의 사용은 필수라고 할 수 있다.

널리 알려진 텐서플로와 파이토치를 비교해보면 어떨까? 텐서플로와 파이토치는 둘 다 연산에 GPU를 이용하는 프레임워크이다. 하지만 텐서플로는 연산 그래프를 먼저 만들고 실제 연산할 때 값을 전달하여 결과를 얻는 'Define and Run'방식이고, 파이토치는 그래프를 만듦과 동시에 값이 할당되는 'Define by Run'방식이다. 텐서플로의 '그래프를 먼저 정의하고 세션에서 실제로 값을 집어넣어 결과를 도출'하는 페러다임은 사람에 따라 직관적으로 받아들이기 어려울 수 있고, 그래프를 정의하는 부분과 이를 돌리는 부분이 분리되기 때문에 전체적으로도 코드 길이가 길어지게 된다. 반면 파이토치는 연산 그래프를 정의하는 것과 동시에 값도 초기화되어 연산이 이루어지는 'Define by Run'이므로 연산 그래프와 연산을 분리해서 생각할 필요가 없다.

또한 연산 속도에서도 차이가 있다. 연산 그래프를 고정해놓고 값만 전달하는 텐서플로가 더 빠른 환경도 있을 수 있겠지만, 텐서플로 깃허브에 올라온 이슈에 따르면 실험에 많이 사용되는 모델로 벤치마킹한 결과 파이토치가 텐서플로보다 2.5배 빠른 결과가 나왔다고 한다. 이슈를 올린 사람이 실험 환경을 공개하여 다른 사람들이 같은 코드로 실험한 결과 역시 파이토치가 빠르게 나왔다. 모델마다, 사용한 함수마다 차이는 있겠지만 파이토치는 전반적으로 속도 면에서 텐서플로보다 빠르거나, 적어도 밀리지는 않는다고 할 수 있다.

2.파이토치 시작하기

파이토치 공식문서 : https://tutorials.pytorch.kr/beginner/basics/intro.html

해당 주소의 사용법을 참고해 파이토치를 익혀본다.

1) Dataset, DataLoader

파이토치(PyTorch)에는 데이터 작업을 위한 기본 요소 두가지인 torch.utils.data.DataLoader 와 torch.utils.data.Dataset 가 있습니다. Dataset 은 샘플과 정답(label)을 저장하고, DataLoader 는 Dataset 을 순회 가능한 객체(iterable)로 감쌉니다.

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor

# 공개 데이터셋에서 학습 데이터를 내려받습니다.
training_data = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor(),
)

# 공개 데이터셋에서 테스트 데이터를 내려받습니다.
test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor(),
)

# 데이터로더를 생성합니다.
batch_size = 64
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)

for X, y in test_dataloader:
    print(f"Shape of X [N, C, H, W]: {X.shape}")
    print(f"Shape of y: {y.shape} {y.dtype}")
    break

2) Model 만들기

PyTorch에서 신경망 모델은 nn.Module 을 상속받는 클래스(class)를 생성하여 정의합니다. init 함수에서 신경망의 계층(layer)들을 정의하고 forward 함수에서 신경망에 데이터를 어떻게 전달할지 지정합니다. 가능한 경우 GPU로 신경망을 이동시켜 연산을 가속(accelerate)합니다.

# 학습에 사용할 CPU나 GPU 장치를 얻습니다.
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")

# 모델을 정의합니다.
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10)
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork().to(device)
print(model)

3) Model 매개변수 최적화

  • 손실함수와 옵티마이저 설정하기
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)
  • 학습함수 설정하기
    각 학습 단계(training loop)에서 모델은 (배치(batch)로 제공되는) 학습 데이터셋에 대한 예측을 수행하고, 예측 오류를 역전파하여 모델의 매개변수를 조정합니다.
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    for batch, (X, y) in enumerate(dataloader):
        X, y = X.to(device), y.to(device)

        # 예측 오류 계산
        pred = model(X)
        loss = loss_fn(pred, y)

        # 역전파
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if batch % 100 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")
  • test(validate)하는 함수 설정하기
    모델이 학습하고 있는지를 확인하기 위해 테스트 데이터셋으로 모델의 성능을 확인합니다.
def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    model.eval()
    test_loss, correct = 0, 0
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
  • 학습하기
    학습 단계는 여러번의 반복 단계 (에폭(epochs)) 를 거쳐서 수행됩니다. 각 에폭에서는 모델은 더 나은 예측을 하기 위해 매개변수를 학습합니다. 각 에폭마다 모델의 정확도(accuracy)와 손실(loss)을 출력합니다; 에폭마다 정확도가 증가하고 손실이 감소하는 것을 보려고 합니다.
epochs = 5
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model, loss_fn)
print("Done!")

4) Model 저장하기

모델을 저장하는 일반적인 방법은 (모델의 매개변수들을 포함하여) 내부 상태 사전(internal state dictionary)을 직렬화(serialize)하는 것입니다.

torch.save(model.state_dict(), "model.pth")
print("Saved PyTorch Model State to model.pth")

5) Model 불러오기

model = NeuralNetwork()
model.load_state_dict(torch.load("model.pth"))

6) Model 평가하기

classes = [
    "T-shirt/top",
    "Trouser",
    "Pullover",
    "Dress",
    "Coat",
    "Sandal",
    "Shirt",
    "Sneaker",
    "Bag",
    "Ankle boot",
]

model.eval()
x, y = test_data[0][0], test_data[0][1]
with torch.no_grad():
    pred = model(x)
    predicted, actual = classes[pred[0].argmax(0)], classes[y]
    print(f'Predicted: "{predicted}", Actual: "{actual}"')
post-custom-banner

0개의 댓글