이 포스트는 Github 접속 제약이 있을 경우를 위한 것이며, 아래와 동일 내용을 관련 그림 및 실행 결과와 함께 Jupyter notebook으로도 보실 수 있습니다.
You can also see the following as Jupyter notebook along with related images and execution result screens if you have no trouble connecting to the Github.

MNIST by CNN

image-center

import torch
import torchvision.datasets as dsets
import torchvision.transforms as transforms
import random

USE_CUDA = torch.cuda.is_available()
device = torch.device("cuda" if USE_CUDA else "cpu")
print("다음 기기로 학습합니다:", device)

# 랜덤 시드 고정
random.seed(777)
torch.manual_seed(777)

# GPU 사용 가능일 경우 랜덤 시드 고정
if device == 'cuda':
    torch.cuda.manual_seed_all(777)

MNIST Data

mnist_train = dsets.MNIST(root='MNIST_data/',
                          train=True,
                          transform=transforms.ToTensor(),
                          download=True)

mnist_test = dsets.MNIST(root='MNIST_data/',
                         train=False,
                         transform=transforms.ToTensor(),
                         download=True)

Dataset 구성 확인

# MNIST dataset 크기
print("train data 크기:", len(mnist_train), "      ", "test data 크기:",len(mnist_test))

# i번째 data 구성 요소확인
i=1234
print(type(mnist_train[i]), len(mnist_train[i]))

first, second= mnist_train[i]
print("first type :", type(first), "       first len :", len(first), "         first size :", first.size())
print("second type :", type(second), "      second :", second)
  • 2개의 tuple을 분리하여 first 성분을 확인해보니, 1 x 28 x 28 행렬(image)인 Tensor이고, second는 이 행렬의 Label 정보(정답)을 가지고 있다.
# image가 3차원이므로, 의미없는 1을 없애고 2차원으로 만들기 위해 view(28, 28) 진행하여 image확인
import matplotlib.pyplot as plt

plt.imshow(first.view(28, 28), cmap='Greys', interpolation='nearest')

Data Loading

from torch.utils.data import DataLoader

data_loader = DataLoader(dataset=mnist_train, batch_size=100, shuffle=True, drop_last=True)

print(len(data_loader), type(data_loader))
  • 전체 크기가 60000개인 data를 100개의 batch_size로 나누므로, data_loader는 600개 data로 구성됨.
  • 이 때, data_loader의 한개 data는 자동으로 ‘100 x input차원’의 행렬이 된다.

Modeling

# 1. nn.Sequential 사용하여 Convolution Net 정의
# 이 때 ReLU 함수는 nn.ReLU()를 사용한다.

class CNN(torch.nn.Module):

    def __init__(self):
        super(CNN, self).__init__()
        # 첫번째층
        # ImgIn shape=(?, 28, 28, 1)
        #    Conv     -> (?, 28, 28, 32)
        #    Pool     -> (?, 14, 14, 32)
        self.layer1 = torch.nn.Sequential(
            torch.nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size=2, stride=2))

        # 두번째층
        # ImgIn shape=(?, 14, 14, 32)
        #    Conv      ->(?, 14, 14, 64)
        #    Pool      ->(?, 7, 7, 64)
        self.layer2 = torch.nn.Sequential(
            torch.nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size=2, stride=2))

        # 전결합층 7x7x64 inputs -> 10 outputs
        self.fc = torch.nn.Linear(7*7*64, 10, bias=True)

        # 전결합층 한정으로 가중치 초기화
        torch.nn.init.xavier_uniform_(self.fc.weight)

    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = out.view(out.size(0), -1)   # 전결합층을 위해서 Flatten
        out = self.fc(out)
        return out

mymodel = CNN().to(device)
# 2. Conv. ReLU, Rooling 직접 forward 연산 수행.
# 직접 forward 연산 시, ReLU 함수는 F.relu를 사용한다.
# nn.Sequential 사용에 비해, 계산 효율이 떨어짐.

import torch.nn as nn
import torch.nn.functional as F

class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1,32, kernel_size=3)
        self.conv2 = nn.Conv2d(32,64, kernel_size=3)
        self.mp = nn.MaxPool2d(kernel_size=2, stride=2)
        
        self.fc = torch.nn.Linear(1600, 10, bias=True)  # Linear 함수의 input 차원을 직접계산하지 않더라도,
                                                         # Runtime Error 발생을 통해 그 값을 알 수 있다
        
        # 전결합층 한정으로 가중치 초기화
        torch.nn.init.xavier_uniform_(self.fc.weight)
        
    def forward(self, x):
        x = self.mp(F.relu(self.conv1(x)))
        x = self.mp(F.relu(self.conv2(x)))         
        x = x.view(x.size(0), -1)   # FC 위해서 Flatten, x.size(0)는 batch size로 이는 고정, 나머지는 펼침.
        x = self.fc(x)
        return x

mymodel = MyModel().to(device)
# 3.  MaxPooling과 ReLU 순서 변경했을 경우도 가능하나, cost 및 정확도가 조금 낮아졌음.

import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer1 = nn.Sequential(
            nn.Conv2d(1,32, kernel_size=3),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.ReLU())
        
        self.layer2 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=3),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.ReLU())
        
        self.fc = nn.Linear(1600, 10, bias=True)
        
        nn.init.xavier_uniform_(self.fc.weight)
        
    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x

mymodel = MyModel().to(device)

학습

# optimizer 정의
learning_rate = 0.001

optimizer = torch.optim.Adam(mymodel.parameters(), lr=learning_rate)

# Cost 정의
criterion = torch.nn.CrossEntropyLoss().to(device)    # 소프트맥스 함수 포함됨.
training_epochs = 15
total_batch = len(data_loader)

for epoch in range(training_epochs):
    avg_cost = 0

    for X, Y in data_loader:
        # image is already size of (28x28), no reshape
        # label is not one-hot encoded
        X = X.to(device)
        Y = Y.to(device)

        hypothesis = mymodel(X)
        cost = criterion(hypothesis, Y)       
        
        optimizer.zero_grad()
        cost.backward()
        optimizer.step()

        avg_cost += cost / total_batch

    print('[Epoch: {:>4}] cost = {:>.9}'.format(epoch + 1, avg_cost))
    
print('Learning Finished!')

Test Data

# 서두에서 mnist_test가 10000개임을 확인했고, j번째 data확인
j=540
print(type(mnist_test[j]), len(mnist_test[j]))

test_first, test_second= mnist_test[j]
print("first type :", type(test_first), "       first len :", len(test_first), "         first size :", test_first.size())
print("second type :", type(test_second), "      second :", test_second)

import matplotlib.pyplot as plt

plt.imshow(test_first.view(28, 28), cmap='Greys', interpolation='nearest')

Model 검증

  • 위에서 mnist_test[j]의 image값인 test_first와 Label인 test_second로 model 검증
# 우선 위 mnist_test[j]를 검증
# conv2d의 input 차원은 4차원으로 '배치 크기 × 채널 × 높이× 너비' 로 구성되어야 함.
X_test1 = test_first.view(-1, 1, 28, 28).float().to(device)     # model 함수 input 차원과 맞춤
Y_test1 = test_second

prediction = torch.argmax(mymodel(X_test1),1)
print(prediction)

print(prediction == Y_test1)
  • 위에서 test_first 차원 지정없이 다음과 같이 정의하면 RuntimeError 발생하므로, 모르는 batch 차원은 -1로 지정
# minist_test 모든 data에 대해 정확도 통계 계산
# test_data, test_labels method로 전체 data 계산

with torch.no_grad():             # gradient 계산 않음.
    X_test = mnist_test.test_data.view(-1, 1, 28, 28).float().to(device)
    Y_test = mnist_test.test_labels.to(device)

    prediction = mymodel(X_test)
    correct_prediction = torch.argmax(prediction, 1) == Y_test
    accuracy = correct_prediction.float().mean()
    print('Accuracy:', accuracy.item())