MNIST 숫자 분류 정확도 99.5% 모델

0~9까지의 손글씨 숫자 데이터인 MNIST을 대상으로 99% 이상의 분류 정확도에 대한 모델 테스트에 대한 글입니다. 이와 유사한 글이 2개 있었는데, 첫번째는 퍼셉트론 방식이고 두번째는 DNN 방식입니다. 아래는 MNIST 데이터셋을 DNN 방식으로 분류한 글입니다.

Model 확장과 가중치값 변경을 통한 예측 정확도 향상

위의 글과는 다르게 이 글은 CNN 방식으로 분류 정확도를 개선했습니다. MNIST 분류에 대한 정확도 순위를 보면 약 40개여의 순위가 존재합니다. 다행인건 이 글의 모델이 이 순위의 상단에 위치한다는 것입니다.

파이토치를 사용했으며, 해당 코드를 대략적으로 살펴보면 다음과 같습니다. 늘 그렇듯이 필요한 패키지를 포함합니다.

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.datasets as dset
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torch.optim import lr_scheduler

배치 크기와 학습률, 에폭단위의 반복할 학습수를 먼저 지정합니다. 손실값에 큰 영향을 주는 하이퍼 파라메터입니다.

batch_size = 100
learning_rate = 0.001
num_epoch = 20

학습과 테스트를 위한 MNSIT 데이터를 로딩합니다.

mnist_train = dset.MNIST("./", train=True, 
    transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(mean=(0.1307,), std=(0.3081,))
    ]),
    target_transform=None,
    download=True)

mnist_test = dset.MNIST("./", train=False, 
    transform=transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize(mean=(0.1307,), std=(0.3081,))
    ]), target_transform=None, download=True)

train_loader = DataLoader(mnist_train, batch_size=batch_size, shuffle=True, drop_last=True)
test_loader = DataLoader(mnist_test, batch_size=batch_size, shuffle=False, drop_last=True)

중요한 점은 데이터에 대한 정규화가 적용됩니다. 정규화 방식은 전체 데이터에 대한 평균과 표준편차를 사용했습니다. 이 표준과 표준편차를 구하는 방식은 아래글을 참고하기 바랍니다.

이미지 Dataset에 대한 평균과 표준편차 구하기

다음은 신경망입니다.

class CNN(nn.Module):
    def __init__(self):
        super(CNN,self).__init__()
        self.layer = nn.Sequential(
            nn.Conv2d(1, 16, 3, padding=1),
            nn.BatchNorm2d(16),
            nn.ReLU(),
            nn.Conv2d(16, 32, 3,padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),           
            nn.Conv2d(32, 64, 3,padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)        
        )
        self.fc_layer = nn.Sequential(
            nn.Linear(64*7*7, 128),
            nn.BatchNorm1d(128),
            nn.ReLU(),
            nn.Linear(128, 64),
            nn.BatchNorm1d(64),
            nn.ReLU(),            
            nn.Linear(64, 10),
        )       

    def forward(self,x):
        out = self.layer(x)
        out = out.view(batch_size, -1)
        out = self.fc_layer(out)
        return out

중요한 부분은 Convolution Layer와 Affine Layer에 배치정규화가 반영되어 있다는 점입니다. 물론 신경망 구성 레이어와 각 레이어의 뉴런수도 중요한 하이퍼파라메터입니다.

학습 코드입니다. 미분값 계산을 통해 얻어진 경사도 방향으로의 하강 방식은 Adam 방식을 사용했고, 학습률을 고정하지 않고 ReduceLROnPlateau 클래스를 사용하여 손실값의 감소 경향에 따라 변경하도록 하였습니다.

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

model = CNN().to(device)
loss_func = nn.CrossEntropyLoss().to(device)
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
scheduler = lr_scheduler.ReduceLROnPlateau(optimizer,threshold=0.1, patience=1, mode='min')    

for i in range(1, num_epoch+1):
    for _,[image,label] in enumerate(train_loader):
        x = image.to(device)
        y_= label.to(device)
        
        optimizer.zero_grad()
        output = model.forward(x)
        loss = loss_func(output, y_)
        loss.backward()
        optimizer.step()

    scheduler.step(loss)      
    print('Epoch: {}, Loss: {}, LR: {}'.format(i, loss.item(), scheduler.optimizer.state_dict()['param_groups'][0]['lr']))

학습 데이터를 통해 학습된 모델을 테스트 데이터를 통해 정확도를 얻는 코드는 다음과 같습니다.

correct = 0
total = 0

model.eval()
with torch.no_grad():
  for image,label in test_loader:
      x = image.to(device)
      y_= label.to(device)

      output = model.forward(x)
      _,output_index = torch.max(output, 1)

      total += label.size(0)
      correct += (output_index == y_).sum().float()

  print("Accuracy of Test Data: {}%".format(100.0*correct/total))

실제 실행해 보면, 정확도가 99.5을 넘기는 것을 볼 수 있었습니다. 아래는 그 중 하나의 출력 결과입니다.

Epoch:1, Loss: 0.015423115342855453, LR: 0.001
Epoch:2, Loss: 0.00971189048141241, LR: 0.001
Epoch:3, Loss: 0.030652683228254318, LR: 0.001
Epoch:4, Loss: 0.011247940361499786, LR: 0.0001
Epoch:5, Loss: 0.001827826490625739, LR: 0.0001
Epoch:6, Loss: 0.0049532032571733, LR: 0.0001
Epoch:7, Loss: 0.0009714126354083419, LR: 0.0001
Epoch:8, Loss: 0.001510353060439229, LR: 0.0001
Epoch:9, Loss: 0.00044545173295773566, LR: 0.0001
Epoch:10, Loss: 0.0010514688910916448, LR: 0.0001
Epoch:11, Loss: 0.0006617116741836071, LR: 1e-05
Epoch:12, Loss: 0.0009317684452980757, LR: 1e-05
Epoch:13, Loss: 0.00043862819438800216, LR: 1.0000000000000002e-06
Epoch:14, Loss: 0.011570921167731285, LR: 1.0000000000000002e-06
Epoch:15, Loss: 0.0028407489880919456, LR: 1.0000000000000002e-07
Epoch:16, Loss: 0.00031417846912518144, LR: 1.0000000000000002e-07
Epoch:17, Loss: 0.0014804458478465676, LR: 1.0000000000000002e-07
Epoch:18, Loss: 0.012818637304008007, LR: 1.0000000000000004e-08
Epoch:19, Loss: 0.0010410761460661888, LR: 1.0000000000000004e-08
Epoch:20, Loss: 0.00025289534823969007, LR: 1.0000000000000004e-08
Accuracy of Test Data: 99.52999877929688%

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다