CNN MNIST 테스트 (PyTorch)

CNN 알고리즘을 MNIST 데이터셋을 활용해서 테스트해봅니다.

CNN 알고리즘에 대한 다양한 많은 설명이 있으니 자세한 내용은 아래의 강의를 참고하시기 바랍니다. 비록 작은 부분의 차이들은 있을 수 있지만 본 예제 역시 인터넷에 많은 소스 코드와 다르지 않습니다.
단, 아래의 영상은 텐서플로우로 설명하는 영상이지만 본 예제는 파이토치로 구현되어 있으며 학습도 전체 데이터를 대상으로 하지 않고 첫번째 미니배치만 학습하는 것으로 작성했습니다.

필요한 라이브러리를 임포트하고 GPU 사용 설정하는 부분과 MNIST 데이터를 로드하는 부분에 대해서는 자세한 설명을 하지 않고 지나가겠습니다. GPU 설정이 필요 없는 CPU 상에서 예제를 구동하는 경우는 device 설정을 하지 않고 넘어가셔도 무방합니다.

GPU 서버가 없는 경우는 무료로 Colab을 이용하시는 것도 추천합니다.

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import transforms, datasets

import matplotlib.pyplot as plt

USE_CUDA = torch.cuda.is_available()
DEVICE = torch.device("cuda" if USE_CUDA else "cpu")

BATCH_SIZE = 128

train_loader = torch.utils.data.DataLoader(
    datasets.MNIST('../data',
                   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('../data',
                   train=False, 
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=BATCH_SIZE, shuffle=True)

MNIST 데이터는 DataLoader로 불러왔습니다. 데이터는 (128,1,28,28) 형태로 149개로 분리되어 있습니다. 테스트 데이터 역시 마찬가지입니다. 128은 배치사이즈, 1은 채널 사이즈, (28*28)은 이미지의 크기입니다.

본 테스트에서는 전체 149개 데이터를 모두 학습하지 않고 1세트만 301회 학습을 수행합니다. 더 높은 정확도 얻고자 하시는 분은 전체 데이터를 통해 더 많은 학습을 해보시기 바랍니다.

dataset = next(iter(train_loader)) # 학습 데이터
x_data = dataset[0] # x 데이터
y_data = dataset[1] # label 데이터

dataset = next(iter(test_loader)) # 검증 데이터
x_test = dataset[0].to(DEVICE) # x 데이터
y_test = dataset[1].to(DEVICE) # label 데이터

각 데이터 셋에 어떤 이미지가 있는지 확인해보기 위해서 아래와 같은 코드를 수행합니다. MNIST 데이터 셋은 동일한 크기의 손글씨 이미지가 들어있기 때문에 각각의 이미지를 표시해보면 0-9까지의 손글씨 이미지가 저장되어 있는 것을 확인 할 수 있습니다.
아래와 같이 0번째 배열에 숫자 5가 있는 것을 확인 할 수 있습니다.

plt.imshow(x_data[0,0,:])

참고로 CNN의 입력 데이터의 Shape은 아래의 그림과 같습니다. 입력 데이터는 4개의 차원으로 구성되어 있습니다. 가장 먼저는 Batch_Size로 해당 이미지의 갯수를 의미합니다. 다음에 나오는 것은 이미지의 Channel입니다. 3인경우는 RGB 값을 가지고 1인 경우는 대부분 단일 색상으로 표현하는 값입니다. 그리고 나오는 값은 Height, Width 값입니다.

이런 상태에서 [0,0,:]의 의미는 0번째 이미지에서 첫번째 채널의 이미지 데이터를 가지고 온다는 의미가 됩니다.

이제 학습을 위한 모델을 구성합니다. 본 모델을 크게 두부분으로 되어 있습니다. self.convs는 convolution을 수행하는 부분으로 원본 이미지에서 특징정보를 추출하는 부분입니다. 이때 중요한 것은 각 Conv2d를 수행하며 어떤 형태의 아웃풋이 나오는지 확인하는 것이 중요합니다.

예를 들어 28*28 이미지를 kernel_size 3으로 계산하면 출력되는 이미지 사이즈는 (26*26)입니다. 어떻게 이렇게 나오는지는 아래 식에서 확인하실 수 있습니다.

https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html

하지만 매번 위의 식을 통해서 계산하는 것은 좀 귀찮고 힘든 입니다. 위의 공식을 간단한 함수로 구현한 내용을 공유해드립니다.

def conv_output_shape(h_w, kernel_size=1, stride=1, pad=0, dilation=1):
    from math import floor
    if type(kernel_size) is not tuple:
        kernel_size = (kernel_size, kernel_size)
    h = floor( ((h_w[0] + (2 * pad) - ( dilation * (kernel_size[0] - 1) ) - 1 )/ stride) + 1)
    w = floor( ((h_w[1] + (2 * pad) - ( dilation * (kernel_size[1] - 1) ) - 1 )/ stride) + 1)
    return h, w

위와 같은 식을 거쳐서 self.convs 레이어의 최종 output_shape은 총 5*5 이미지 사이즈를 가진 40장의 이미지 데이터를 얻을 수 있습니다.

이렇게 얻은 데이터는 self.layers를 거치다 보면 최종 0~9까지의 숫자 정보를 얻을 수 있습니다.

PyTorch의 Conv2d 패키지는 프로그래머가 간단히 Convolution Layer를 구성할 수 있도록 해줍니다. 프로그래머는 간단히 입력 채널의 수와 출력 채널의 수 그리고 커널 사이즈와 스트라이드 정보만 맞춰주면 자동으로 이미지를 구성해줍니다.

아래의 경우는 최초 28×28 이미지를 입력하고 커널을 3으로 맞춰서 앞선 함수를 통해서 출력 shape을 보면 26×26의 이미지를 출력한다는 것을 확인 할 수 있습니다. 또 다음 레이어는 커널을 3, 스트라이드를 2로 정의하고 이전에 입력된 이미지의 크기를 입력하면 출력 이미지는 12×12로 표시되는 것을 확인 할 수 있습니다. 이런 방법으로 마지막 이미지가 출력되는 크기는 5×5의 이미지가 됩니다.

그렇게 되면 마지막의 fully-connected layer에 들어가는 값은 40×5×5의 입력 값이 됩니다. 그리고 맨 마지막까지 Linear Layer를 거치게 되면 10개의 값으로 출력되고 이에 Softmax를 취하면 0-9 중에 하나의 값을 예측하게 됩니다.

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        
        self.convs = nn.Sequential(
            nn.Conv2d(1, 10, kernel_size=3), # input_channel, output_channel, kernel_size
            nn.ReLU(),
            nn.BatchNorm2d(10),
            nn.Conv2d(10, 20, kernel_size=3, stride=2),
            nn.ReLU(),
            nn.BatchNorm2d(20),
            nn.Conv2d(20, 40, kernel_size=3, stride=2)
        )
        
        self.layers = nn.Sequential(
            nn.Linear(40*5*5, 500),
            nn.ReLU(),
            nn.BatchNorm1d(500),
            nn.Linear(500,250),
            nn.Linear(250,100),
            nn.ReLU(),
            nn.BatchNorm1d(100),
            nn.Linear(100,50),
            nn.Linear(50, 10),
            nn.Softmax(dim=-1)
        )

    def forward(self, x):
        x = self.convs(x)
        x = x.view(-1, 40*5*5)
        return self.layers(x)
    
cnn = Net().to(DEVICE)

이제 학습을 위한 모든 준비가 완료되었고 아래와 같이 학습을 수행합니다. 예제에서는 간단히 301회 학습을 수행했습니다. 학습이 진행되면서 loss와 accuracy 정보의 변화를 기록하기 위해서 list 변수를 각각 선언해줍니다.

또 100회 학습이 완료될 때마다 변화되는 loss와 accuracy 값을 화면에 출력해줍니다.

optimizer = optim.Adam(cnn.parameters())
criterion = nn.CrossEntropyLoss()

hist_loss = []
hist_accr = []

epochs = 301
for epoch in range(epochs):
    cnn.train()
    output = cnn(x_data)
    loss = criterion(output, y_data)
    
    predict = torch.argmax(output, dim=-1) == y_data
    accuracy = predict.float().mean().item()
    
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    hist_loss.append(loss)
    hist_accr.append(accuracy)
    
    if epoch % 100 == 0:
        print('epoch{}, {:.5f}, {:.5f}'.format(epoch, loss.item(), accuracy))

학습이 완료되고 loss와 accuracy 값을 그래프로 그려줍니다.
그래프를 보니 학습의 곡선이 완만하게 내려가고 정확도는 1에 가까운 값을 나타내어 학습이 잘이뤄지는 것을 확인할 수 있습니다.

그러나 training 데이터를 통한 학습정확도이기 때문에 검증용 데이터를 통해서 정확도 계산을 다시 할 필요가 있습니다.

fig, ax = plt.subplots(2,1)
fig.set_size_inches((12,8))

ax[0].set_title('Loss')
ax[0].plot(hist_loss, color='red')
ax[0].set_ylabel('Loss')
ax[1].set_title('Accuracy')
ax[1].plot(hist_accr, color='blue')
ax[1].set_ylabel('Accuracy')
ax[1].set_xlabel('Epochs')

검증용 데이터셋(test_dataloader)를 통해서 한 배치 정보를 얻어서 방금 수행한 모델의 정확도를 테스트해봅니다. 테스트 결과 0.7109375 값을 얻을 수 있었습니다. 높은 값은 아니지만 전체 469개 미니배치 중에서 1개 데이터셋만 테스트 했기 때문에 전체 데이터를 대상으로 테스트하면 보다 높은 정확도를 얻을 수 있을 것입니다.

cnn.eval()

with torch.no_grad():
    output = cnn(x_test)
    loss = criterion(output, y_test)
    
    predict = torch.argmax(output, dim=-1) == y_test
    accuracy =  predict.float().mean().item()
    
    print(accuracy)

“CNN MNIST 테스트 (PyTorch)”에 대한 한개의 댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다