pytorch

[pytorch] MNIST 예제

math_tbro 2022. 1. 12. 02:27

기존 텐서플로로 공부를 해왔다. 그런데 최근 트렌드가 텐서플로에서 파이토치로 많이 넘어간다고 한다. 

파이썬과 많이 비슷하며 연구진들이 정말 많이 사용해 논문 같은 예시 코드가 파이토치로 많이 구현되있어서 이왕 제대로 각잡고 공부하는거 토치를 한 번 공부해 보려고 한다.

실제로 간단한 기본 MNIST를 하면서 느낀점은 ... 

더 어렵다. 확실히 더 어렵다... 아직 잘 몰라서 이런 말 하기도 조심스럽지만 텐서플로 같은 경우에는 모델을 돌릴 때 아주 간단하게 돌릴 수 있었다. 하지만 디테일하게 들어가서 구조를 들여다 보고 모델이 돌아가는 방식을 살피기는 쉽지 않았다. 그냥 필요한 값만 넣어주면 알아서 돌아가는 시스템이였다.

근데 파이토치는 모델의 구조 순서에 따라 직접 구성하는 방식이고 원리를 잘 이해한 상태라면 그냥 그 원리 그대로가 반영 되있는 느낌이였다. 그리고 파이썬에 가깝다는 말도 대충 무슨 말인지 알 것 같았다

일단은 예제 이기 때문에 코드와 결과만 설명하고 넘어가겠다.

 

import torch
is_cuda = torch.cuda.is_available()
device = torch.device('cuda' if is_cuda else 'cpu')
print(device)
import torch.nn as nn #딥러닝 네트워크의 기본 구성요소
import torch.nn.functional as F # 딥러닝에 자주 쓰는 함수를 F로 불러오겠다
import torch.optim as optim # 가중치 추정에 필요한 최적화 알고리즘을 포함한 모듈을 optim으로 지정
from torchvision import datasets, transforms #torchvision에서 데이터셋과 transform을 가져온다

from matplotlib import pyplot as plt
%matplotlib inline

HyperParameter 지정

batch_size = 50
epoch_num = 15
learning_rate = 0.0001
train_data = datasets.MNIST(root = './data', train = True, download = True, transform = transforms.ToTensor())
test_data = datasets.MNIST(root = './data', train = False, transform = transforms.ToTensor())
# transform : MNIST 저장과 동시에 전처리를 할 수 있는 옵션. 이미지를 텐서로 변형하는 전처리여서 ToTensor()를 써준다.

print('number of traning data: ', len(train_data))
print('number of test data: ', len(test_data))

root = ./data (현재 디텍토리에 data라는 폴더를 만들어서 저장하겠다.) 

transforms.ToTensor() : 이미지를 받음으로 동시에 tensor로 받는 전처리를 하는 것이다.

image, label = train_data[0]     # image 와 label 을 받아온다.

# MNIST는 단일 채널로 [1,28,28] 3차원 텐서이다. 3차원 텐서를 2차원으로 줄이기 위해 image.squeeze()를 사용한다.
plt.imshow(image.squeeze().numpy(), cmap = 'gray') #squeeze() : 크기가 1인 차원을 없애는 함수로 2차원 [28,28]로  만든다.
plt.title('label : %s' % label)
plt.show()

[미니 배치 구성하기]

 

# DataLoader는 손쉽게 배치를 구성하며 학습과정을 반복 시행 할때 마다 미니배치를 불러오는 유용한 함수
train_loader = torch.utils.data.DataLoader(dataset = train_data,
                                          batch_size = batch_size,
                                          shuffle = True)
test_loader = torch.utils.data.DataLoader(dataset = test_data,
                                          batch_size = batch_size,
                                          shuffle = True)


first_batch = train_loader.__iter__().__next__()
print('{:15s} | {:<25s} | {}'.format('name', 'type', 'size'))
print('{:15s} | {:<25s} | {}'.format('Num of Batch', '',
                                     len(train_loader)))
print('{:15s} | {:<25s} | {}'.format('first_batch', str(type(first_batch)),
                                    len(first_batch)))
print('{:15s} | {:<25s} | {}'.format('first_batch[0]',
                                    str(type(first_batch[0])),
                                    first_batch[0].shape))
print('{:15s} | {:<25s} | {}'.format('first_batch[1]',
                                    str(type(first_batch[1])),
                                    first_batch[1].shape))

[CNN 구조 설계하기]

class CNN(nn.Module):
    def __init__(self): #모델에 사용되는 가중치를 정의
        super(CNN,self).__init__() #super()를 통해 nn.Module 클래스의 속성을 상속 받고 초기화
        self.conv1 = nn.Conv2d(1,32,3,1)   #in_channel / out_channel / kernel_size / stride
        self.conv2 = nn.Conv2d(32, 64, 3, 1)
        self.dropout1 = nn.Dropout2d(0.25)   # 0.25 확률의 드랍아웃을 지정
        self.dropout2 = nn.Dropout2d(0.5)
        self.fc1 = nn.Linear(9216,128)   # 9264 -> 128 -> 10
        self.fc2 = nn.Linear(128,10)
    
    def forward(self,x) :  # 입력이미지와 정의한 가중치를 이용해 feed forward 계산
        x = self.conv1(x)
        x = F.relu(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = F.max_pool2d(x,2)     # (2x2 크기의 필터로 맥스 풀링 한다. 이건 가중치 할 필요가 없어서 위에서 정의 안했다)
        x = self.dropout1(x)
        x = torch.flatten(x,1)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.dropout2(x)
        x = self.fc2(x)
        output = F.log_softmax(x, dim=1) #softmax 말고 log_softmax()를 쓰면 연산속도를 올릴 수 있다.
        return output

구조를 그림으로 그리면

[ 모델 최적화]

model = CNN().to(device)
optimizer = optim.Adam(model.parameters(), lr = learning_rate)
criterion = nn.CrossEntropyLoss()    # 손실함수로 크로스엔트로피

[모델 학습]

model.train()
i = 0
for epoch in range(epoch_num):
    for data, target in train_loader:
        data = data.to(device)
        target = target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target) # 계산값과 결과값에 대해 손실을 구해보자
        loss.backward()
        optimizer.step()  # 역전파로 계산해서 모델 가중치 업데이트
        if i % 1000 == 0 :
            print('Train Step : {}\tLoss: {:.3f}'. format(i, loss.item()))
        i += 1

 

[모델 평가]

model.eval()
correct = 0
for data, target in test_loader:
    data = data.to(device)
    target = target.to(device)
    output = model(data)
    prediction = output.data.max(1)[1]
    correct += prediction.eq(target.data).sum()
    
print('Test set: Accuracy : {:.2f}%'.format(100 * correct / len(test_loader.dataset)))

 

정확도 99.09 % 가 나왔다.

 

이게 너무 잘 정형화된 사기 데이터여서 당연히 이런 결과가 나온다고 한다. 

파이토치에 빨리익숙해지고 싶다.