[Pytorch 기초 - 4] MNIST data를 활용하여 CNN모델의 학습과 Optimizer, Evaluation

2020. 9. 1. 21:23DL in Python/Pytorch 기초

 

 

 

 

 

PyTorch: Optimization & Training

In [1]:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms

import numpy as np
In [2]:
seed = 1                # 난수 고정

batch_size = 64
test_batch_size = 64

no_cuda = False
In [3]:
# gpu 사용?
use_cuda = not no_cuda and torch.cuda.is_available()
device = torch.device("cuda" if use_cuda else "cpu")
 

Preprocess

In [4]:
torch.manual_seed(seed)

train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('dataset', train=True, download=True,
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=batch_size, shuffle=True)


test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('dataset', train=False, transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=test_batch_size, shuffle=True)
 

Model

In [5]:
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, 5, 1)
        self.conv2 = nn.Conv2d(20, 50, 5, 1)
        self.fc1 = nn.Linear(4*4*50, 500)
        self.fc2 = nn.Linear(500, 10)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = F.max_pool2d(x, 2, 2)
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, 2, 2)
        x = x.view(-1, 4*4*50)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)
 

Optimization

 
  • Model과 Optimization 설정
In [6]:
model = Net().to(device)
In [7]:
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.5)
 

Before Training

 
  • 학습하기 전에 Model이 Train할 수 있도록 Train Mode로 변환
    • Convolution 또는 Linear 뿐만 아니라, DropOut과 추후에 배우게 될 Batch Normalization과 같이 parameter를 가진 Layer들도 학습하기 위해 준비
In [9]:
# parameter shape 확인
params = list(model.parameters())
for i in range(8):
    print(params[i].size())
 
torch.Size([20, 1, 5, 5])
torch.Size([20])
torch.Size([50, 20, 5, 5])
torch.Size([50])
torch.Size([500, 800])
torch.Size([500])
torch.Size([10, 500])
torch.Size([10])
In [10]:
model.train() # train mode
Out[10]:
Net(
  (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(20, 50, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=800, out_features=500, bias=True)
  (fc2): Linear(in_features=500, out_features=10, bias=True)
)
 
  • 모델에 넣기 위한 첫 Batch 데이터 추출
In [11]:
data, target = next(iter(train_loader))
In [12]:
data.shape, target.shape
Out[12]:
(torch.Size([64, 1, 28, 28]), torch.Size([64]))
 
  • 추출한 Batch 데이터를 cpu 또는 gpu와 같은 device에 compile
In [13]:
data, target = data.to(device), target.to(device)
In [14]:
data.shape, target.shape
Out[14]:
(torch.Size([64, 1, 28, 28]), torch.Size([64]))
 
  • gradients를 clear해서 새로운 최적화 값을 찾기 위해 준비
In [15]:
optimizer.zero_grad()
 
  • 준비한 데이터를 model에 input으로 넣어 output을 얻음
In [16]:
output = model(data)
 
  • Model에서 예측한 결과를 Loss Function에 넣음
    • 여기 예제에서는 Negative Log-Likelihood Loss 라는 Loss Function을 사용
In [17]:
loss = F.nll_loss(output, target)
 
  • Back Propagation을 통해 Gradients를 계산
In [18]:
loss.backward()
 
  • 계산된 Gradients는 계산된 걸로 끝이 아니라 Parameter에 Update
In [19]:
optimizer.step()
 

Start Training

 

위의 최적화 과정을 반복하여 학습 시작

In [20]:
epochs = 1
log_interval = 100
In [41]:
for epoch in range(1, epochs+1):
    # Train mode
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        
        if batch_idx % log_interval == 0 :
            print('Train Epoch: {} [ {}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * len(data), len(train_loader.dataset),
                100 * batch_idx / len(train_loader), loss.item()
            ))
    
    # 한 EPOCH에서 학습 후 바로 TEST 진행, TEST는 아래에 과정 있음.
    model.eval()

    test_loss = 0
    correct = 0

    with torch.no_grad():
        for data, target in test_loader : 
            data, target = data.to(device), target.to(device)
            output = model(data)
            test_loss += F.nll_loss(output, target, reduction='sum').item()
            pred = output.argmax(dim=1, keepdim = True)
            correct += pred.eq(target.view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset)

    print('Test set: Average Loss: {:.4f}, Accuracy: {}/{} ( {:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset), 100 * correct / len(test_loader.dataset)))
 
Train Epoch: 1 [ 0/60000 (0%)]	Loss: 0.515351
Train Epoch: 1 [ 6400/60000 (11%)]	Loss: 0.622132
Train Epoch: 1 [ 12800/60000 (21%)]	Loss: 0.396870
Train Epoch: 1 [ 19200/60000 (32%)]	Loss: 0.472825
Train Epoch: 1 [ 25600/60000 (43%)]	Loss: 0.378246
Train Epoch: 1 [ 32000/60000 (53%)]	Loss: 0.401584
Train Epoch: 1 [ 38400/60000 (64%)]	Loss: 0.338465
Train Epoch: 1 [ 44800/60000 (75%)]	Loss: 0.490297
Train Epoch: 1 [ 51200/60000 (85%)]	Loss: 0.234467
Train Epoch: 1 [ 57600/60000 (96%)]	Loss: 0.192764
Test set: Average Loss: 0.2958, Accuracy: 9140/10000 ( 91%)

 

Evaluation

 
  • 앞에서 model.train() 모드로 변한 것처럼 평가 할 때는 model.eval()로 설정
    • Batch Normalization이나 Drop Out 같은 Layer들을 잠금
In [22]:
# 평가모드
model.eval()
Out[22]:
Net(
  (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(20, 50, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=800, out_features=500, bias=True)
  (fc2): Linear(in_features=500, out_features=10, bias=True)
)
 
  • autograd engine, 즉 backpropagatin이나 gradient 계산 등을 꺼서 memory usage를 줄이고 속도를 높임
In [24]:
test_loss = 0
correct = 0

with torch.no_grad():
    data, target = next(iter(test_loader))
    data, target = data.to(device), target.to(device)
    output = model(data)
    
    test_loss += F.nll_loss(output, target, reduction = 'sum').item() 
    # sum을 해줌으로 batch_size, shape에 관계없이 하나의 scalar로 return하고 이를 더해줌.

    pred = output.argmax(dim=1, keepdim = True)
    # keepdim을 사용해 차원수를 계속 유지
    
    correct = pred.eq(target.view_as(pred)).sum().item()
In [25]:
test_loss
Out[25]:
29.748153686523438
In [37]:
# output은 softmax를 거친 아직 argmax에 들어가지 않은 값이기에 확률값으로 출력됨. 
output.shape
Out[37]:
torch.Size([64, 10])
In [32]:
# pred와 target의 shape을 맞춰 비교, 동일하다는 것을 확인 가능
pred.shape, target.view_as(pred).shape
Out[32]:
(torch.Size([64, 1]), torch.Size([64, 1]))
In [35]:
pred.eq(target.view_as(pred)).sum().item() / 64 # 몇 퍼센트가 동일하게 예측이 되었나?
Out[35]:
0.84375
In [38]:
test_loss /= len(test_loader.dataset)
In [39]:
test_loss
Out[39]:
0.002974815368652344
 

Evaluation 정리

In [40]:
model.eval()

test_loss = 0
correct = 0

with torch.no_grad():
    for data, target in test_loader : 
        data, target = data.to(device), target.to(device)
        output = model(data)
        test_loss += F.nll_loss(output, target, reduction='sum').item()
        pred = output.argmax(dim=1, keepdim = True)
        correct += pred.eq(target.view_as(pred)).sum().item()
        
test_loss /= len(test_loader.dataset)

print('Test set: Average Loss: {:.4f}, Accuracy: {}/{} ( {:.0f}%)\n'.format(
    test_loss, correct, len(test_loader.dataset), 100 * correct / len(test_loader.dataset)))
 
Test set: Average Loss: 0.4799, Accuracy: 8661/10000 ( 87%)

In [ ]:
 
In [ ]:
 
In [ ]: