본문 바로가기
AI/AI노하우

Pytorch(tutorial)- MNIST 데이터 분류

by lucian 2021. 10. 4.

CNN을 하면서 MNIST 데이터셋은 거즘 튜토리얼 같은 것이다.

이전에도 MNIST로 몇 번 공부해 봤는데, 현재 공부하고 있는 책인 '파이토치 딥러닝 프로젝트 모음집'에서도 튜토리얼로 나와있어 복습할 겸 다시 따라해봤다.

 

1. 모듈을 불러온다.

import torch
import torch.nn as nn # 딥러닝 네트워크의 기본구성 요소를 포함한 모듈
import torch.nn.functional as F
import torch.optim as optim # 가중치 추정에 필요한 최적화 알고리즘
from torchvision import datasets, transforms


from matplotlib import pyplot as plt
%matplotlib inline

 

 

2. 분석환경을 설정한다.

   보통 난 colab환경에서 하므로 런타임을 GPU로 바꿔준 후 진행한다.

is_cuda = torch.cuda.is_available()
device = torch.device('cuda' if is_cuda else 'cpu')


print(device)

 

 

3. Hyperparameter 설정

   batch 사이즈, 학습률, epoch이 보통이다.

   batch 사이즈는 데이터셋의 사이즈를 몇으로 나눌 것이냐, epoch은 이 데이터셋을 몇번 다시 돌릴 것이냐, 학습률은 optimizer로 진행되는 가중치 최적화를 어느 정도 수준으로 바꿀 것이냐이다. 너무 크게 하면 왔다갔다 해서 loss를 낮출수가 없고, 너무 작게 하면 loss가 낮춰지는게 보여지지 않는다.

batch_size = 50 # 모델 가중치를 한번 언뎁이트 시킬 때 사용되는 샘플 단위 개수(=미니 배치 사이즈)
epoch_num=15 # 학습 데이터를 모두 사용하여 학습하는 기본 단위 횟수
learning_rate = 0.0001 # 가중치 업데이트의 정도(학습률)

 

 

4. 데이터를 불러오자.

   아까 torchvision에선 딥러닝 튜토리얼에 필요한 다양한 데이터셋이나 CNN 모델(VGG, googlenet 등)이 들어있다. 거기서 MNIST 데이터셋을 가져온다.

# MNIST 데이터 불러오기

train_data=datasets.MNIST(root='./data', train=True, download = True, transform=transforms.ToTensor())
test_data=datasets.MNIST(root='./data', train=False, download=True, transform=transforms.ToTensor())
# root : 데이터를 저장할 물리적 공간 위치
# train : 데이터를 학습용으로 사용할 것인지 지정하는 boolean값
# download : root옵션에서 지정된 위치에 데이터가 저장됨. 만약 처음 시행이 아니고 이미 저장된 데이터가 있다면, False입력
# transform : MNIST 데이터를 저장과 동시에 전처리를 할 수 있는 옵션. 이미지를 Tensor로 변형하는 전처리 transforms.ToTensor()함수를 사용

print('train data의 수 : ', len(train_data))
print('test data의 수 : ', len(test_data))

 

 

5. 데이터가 잘 나왔는지 확인해보자.

image, label= train_data[0]

plt.imshow(image.squeeze().numpy(), cmap='gray') # squeeze()함수는 크기가 1인 차원을 없애는 함수로 차원을 [1,28,28]에서 [28,28]로 만들어 준다.
plt.title('label : %s' %label)
plt.show()

   MNIST로 가져온 train_data에는 두개의 원소로 구성된 리스트로 60000개의 데이터가 구성되어 있다.

그래서 train_data[0]은 60000개 중 첫번째 데이터를 가져온 것이고 그 안에 두개의 원소 중 train_data[0][0]은 이미지로 [1,32,32]의 행렬? 리스트? 가 들어있다. 앞의 1은 채널의 수. 여기서 MNIST는 흑백 데이터라 0~1사이의 명암만 있는 이미지이기 때문에 채널이 1개이다. 만약 rgb 3가지로 구성된 색깔이 있는 이미지라면 채널의 수는 3개가 된다.(r, g, b 3채널)

다시 train_data로 넘어와서 train_data[0][1]은 target이다. 즉 label 정답값이 숫자로 기냥 들어가 있다.

 

 

6. 미니 배치 구성하기

   이제 이 불러온 데이터를 batch_size에 맞게 쪼개줘야한다. 데이터양이 너무 많기 때문에 자원의 효율을 위해서도 쪼개는게 좋다.

  이 때 유용하게 쓸 수 있는 torch.utils.data.DataLoader()함수이다. 알아서 batch_size대로 들어온 dataset를 쪼개 준다.

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)

# 기존 train_data는 (image, label)로 길게 늘어져있는 구조에서 data_loader를 해줌으로써 batch_size별로 나눠준다. 즉 50개씩 끊어주는 구조
# 보면 first_batch가 2인 구조인데 이것은 (image, label) 이고, first_batch[0].shape은 이미지로 [50,1,28,28] 이고 first_batch[1]은 label로 [50] 으로 나뉘진 것을 볼 수 있다.
first_batch=train_loader.__iter__().__next__()
print(first_batch)
print('\n\n')

print(len(first_batch))
#print(first_batch.shape)
print(first_batch[0].shape)
print(first_batch[1].shape)

 

 

7. CNN 구조 설계

   이제 직접 CNN을 만들어 본다.

   하나의 클래스로 설정해서 nn.Module을 상속해온 클래스라는 것을 명시해준 후, init함수에서 우리가 만들 CNN의 구조들을 설정해준다.

 

   그 후 forward함수에서 이 설정한 변수들을 순서대로 불러와 최종적인 output을 만든다. 마지막에 fc층이 10개인 이유는 MNIST데이터는 총 10개의 레이블로 구성되어 있기 때문이다.

class CNN(nn.Module):
  def __init__(self):
    super(CNN, self).__init__()
    self.conv1 = nn.Conv2d(1,32,3,1)
    self.conv2 = nn.Conv2d(32,64,3,1)
    self.dropout1 = nn.Dropout2d(0.25)
    self.dropout2 = nn.Dropout2d(0.5)
    self.fc1 = nn.Linear(9216,128)
    self.fc2 = nn.Linear(128,10)


  def forward(self, x):
    x = self.conv1(x)
    x = F.relu(x)
    x = self.conv2(x)
    x = F.relu(x)
    x = F.max_pool2d(x,2)
    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)
    return output

 

 

8. optimizer 및 손실함수 정의

model = CNN().to(device)
optimizer = optim.Adam(model.parameters(),lr=learning_rate) # 손실함수를 최소로 하는 가중치를 찾는 최적화 알고리즘은 Adam으로 설정
criterion = nn.CrossEntropyLoss() # 손실함수는 다중 클래스 분류 문제이기에 교차 엔트로피로 설정한다.

여기서 우린 GPU로 모델을 돌릴 것이기에 인스턴스를 GPU내에서 불러와준다.(이러면 data도 GPU에 불러와줘야한다. 아직은 CPU안에 있음)

그 후 가중치를 최적화해주는 함수 Adam과 CrossEntropy 손실함수를 정의해준다.

 

 

9. 모델 학습

   모델 학습 순으로는 model을 학습 모드로 실행할 것이라 명시해준 후, epoch for문, batch for문 순으로 진행된다.

   batch for문이 다 돌아가면 epoch문이 1올라가는 형식이고, batch for문을 한번 열때마다 batch로 나눠진 데이터를 불러와 GPU에 넣어준다.

   그 다음으로 최적화함수가 이전의 최적화에 영향이 가지 않게 초기화를 해준다.

   데이터를 모델에 넣어 결과값을 반환하고 그 반환한 결과값을 손실함수를 통해 loss가 얼마나 나왔나 계산한다. 또한 더 낮은 손실값을 위해 가중치들의 gradient를 구하는 backward()함수를 호출한다. 

   그 후 최적화 함수의 step()을 통해 gradient를 학습률과 값이 반영해준다. 

 

이것을 반복하면서 loss값을 줄인다.

model.train() #  model 인스턴스를 학습 모드로 실행할 것을 명시해준다.
i=0
for epoch in range(epoch_loss):
  for data, target in train_loader:
    data = data.to(device)
    target = target.to(device)

    optimizer.zero_grad()
    output=model(data)
    loss=criterion(output,model)
    loss.backward() # 손실함수를 통해 gradient를 계산한다.
    optimizer.step() # 계산된 gradient를 통해 모델의 가중치를 업데이트


    if i%1000 == 0:
      print('train step : {},\t loss : {:.3f}'.format(i,loss.item()))
    i +=1

 

 

10. 모델 평가

     모델을 평가할 땐, 모델의 gradient를 구할 필요가 없기 때문에 eval()함수를 호출해 학습 모드가 아니라 평가 모드라고 명시해준다.

    train 때와 동일하게 data들을 gpu에 불러와 모델이 제대로 학습했는지 평가하면 된다.

model.eval() # 평가 모드를 실행하기 위해 명시. eval() 함수를 호출하면, Dropout이 적용되지 않고 Batchnorm2d도 평가모드로 전환된다.

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]  # output의 data 중 최대값을 가져와서(1) 그것의 인덱스 값[1]을 prediction 변수에 넣어준다.
  correct += prediction.eq(target.data).sum() # prediction의 값과 target값을 비교해서 같으면 True, 틀리면 False로 벡터를 구성하고 더한다.


print('test set : accuracy : {:.2f}%'.format(100*correct/len(test_loader.dataset)))

 

'AI > AI노하우' 카테고리의 다른 글

pretrained된 가중치 불러오기  (0) 2021.09.01

댓글