<aside> 📢 파이토치 사용하여, CNN으로 MNIST 학습하는 코드 작성

</aside>

MNIST 데이터베이스 (Modified National Institute of Standards and Technology database)는 손으로 쓴 숫자들로 이루어진 대형 데이터베이스이며, 다양한 화상 처리 시스템을 트레이닝하기 위해 일반적으로 사용된다. 이 데이터베이스는 또한 기계 학습 분야의 트레이닝 및 테스트에 널리 사용된다. NIST의 오리지널 데이터셋의 샘플을 재혼합하여 만들어졌다. 개발자들은 NIST의 트레이닝 데이터셋이 미국의 인구조사국 직원들로부터 취합한 이후로 테스팅 데이터셋이 미국의 중등학교 학생들로부터 취합되는 중에 기계 학습 실험에 딱 적합하지는 않은 것을 느꼈다. 게다가 NIST의 흑백 그림들은 28x28 픽셀의 바운딩 박스와 앤티엘리어싱 처리되어 그레이스케일 레벨이 들어가 있도록 평준화되었다.

MNIST 테스트 데이터셋의 샘플 이미지.

MNIST 테스트 데이터셋의 샘플 이미지.

MNIST 데이터베이스는 60,000개의 트레이닝 이미지와 10,000개의 테스트 이미지를 포함한다. 트레이닝 세트의 절반과 테스트 세트의 절반은 NIST의 트레이닝 데이터셋에서 취합하였으며, 그 밖의 트레이닝 세트의 절반과 테스트 세트의 절반은 NIST의 테스트 데이터셋으로부터 취합되었다.

Untitled

CNN 구성도

Input
|
| (1, H, W)
|   |
|   | Conv2d(1, 32, kernel_size=3, padding=1)
|   | ReLU
|   | MaxPool2d(2, 2)
|   |
|   | (32, H/2, W/2)
|   |
|   | Conv2d(32, 64, kernel_size=3, padding=1)
|   | ReLU
|   | MaxPool2d(2, 2)
|   |
|   | (64, H/4, W/4)
|   |
|   | Flatten
|   |
|   | (64*H/4*W/4,)
|   |
|   | Linear(64*H/4*W/4, 128)
|   | ReLU
|   |
|   | (128,)
|   |
|   | Linear(128, 10)
|   |
|   | (10,)
|
Output
  1. 입력 이미지는 크기 (1, H, W)의 2차원 텐서

    H와 W는 이미지의 높이와 너비

  2. 첫 번째 컨볼루션 레이어(Conv2d)는 입력 채널 수가 1이고 출력 채널 수가 32이며, 커널(필터) 크기가 3x3

    패딩(padding)이 1로 설정되어 있으므로, 입력의 가장자리에 0으로 패딩이 채워짐

    ReLU 활성화 함수를 거친 후 MaxPooling을 통해 크기가 절반으로 줄어든다

  3. 두 번째 컨볼루션 레이어(Conv2d)는 입력 채널 수가 32이고 출력 채널 수가 64이며, 마찬가지로 3x3 크기의 커널을 사용

    또한 패딩(padding)이 1로 설정되어 있으므로, 입력의 가장자리에 0으로 패딩이 채워진다

    이후 ReLU 활성화 함수를 거치고 다시 MaxPooling을 통해 크기가 절반으로 줄어든다

  4. Flatten 연산을 통해 3차원 텐서를 1차원으로 변환

  5. 두 개의 fully connected 레이어(Linear)

    1. 첫 번째 fully connected 레이어는 64 * (H/4) * (W/4)의 크기를 가진 입력을 받고 128의 출력을 생성
    2. 두 번째 fully connected 레이어는 128의 입력을 받고 10의 출력을 생성
  6. 출력은 크기 (10,)의 1차원 텐서로 나타나며, 이는 각 클래스에 대한 확률을 나타냅니다.

    이후 max(data,1)를 통해 라벨을 선정

CNN 코드

#파이토치를 이용한 CNN으로 MNIST 학습하는 코드 작성
#SAINT Lab. Q1 [강화학습]
#60201969 이유현 [2024.02.14]

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt

# 데이터셋 불러오기
transform = transforms.Compose([
    transforms.ToTensor(),#이미지를 Pytorch tensors 타입으로 변형, Pixels 값들을 [0~255]에서 [0.0~1.0]으로 자동 변환
    transforms.Normalize((0.5,), (0.5,))#각 채널별 평균(mean,0.5)을 뺀 뒤 표준편자(std,0.5)로 나누어 정규화를 진행
])#최종적으로 범위는 -1~1의 값으로 변환

train_set = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform)#MNIST의 트레이닝셋
test_set = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform)#MNIST의 테스트셋

train_loader = torch.utils.data.DataLoader(train_set, batch_size=64, shuffle=True)#사이즈가 64인 batch를 사용하는 트레이닝셋
test_loader = torch.utils.data.DataLoader(test_set, batch_size=64, shuffle=False)#사이즈가 64인 batch를 사용하는 테스트셋

# CNN 모델 정의
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__() #기존 CNN의 초기 설정 구성
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)#2차원 컨볼루션 연산, 필터 크기 = (3,3), padding(가장자리) = 1
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.pool = nn.MaxPool2d(2, 2)#Max-pooling
        self.fc1 = nn.Linear(64 * 7 * 7, 128)#선형 변환(입력,출력)
        self.fc2 = nn.Linear(128, 10)#선형 변환(입력,출력)

    def forward(self, x): #forward propagation
        x = self.pool(torch.relu(self.conv1(x)))#ReLU 연산을 max-pooling을 통해 텐서의 크기를 줄이고, overfitting 조질 및 특징화한다
        x = self.pool(torch.relu(self.conv2(x)))
        x = torch.flatten(x, 1)#1차원 이후에 평평하게 편다(하나의 텐서로 Flatten)
        x = torch.relu(self.fc1(x))
        x = self.fc2(x)
        return x

model = CNN()

# 손실 함수와 최적화기 정의
criterion = nn.CrossEntropyLoss() #손실함수, Softmax()포함
optimizer = optim.Adam(model.parameters(), lr=0.001) #경사 하강법에 Adam(모멘텀 및 학습률 감소와 같은 기능이 추가된 최적화 알고리즘) 추가, Learning rate = 0.001(기본값)

# 모델 학습
for epoch in range(5): #Epoch(에포크) = 전체 데이터셋을 학습한 횟수
    running_loss = 0.0 #초기 로스값
    for i, data in enumerate(train_loader, 0): #train_loader를 index와 data로 unpacking
        inputs, labels = data #unpack된 data를 input값과 라벨로 분류
        optimizer.zero_grad() #그라디언트를 0으로 초기화
        outputs = model(inputs) #CNN모델로 input을 학습
        loss = criterion(outputs, labels) #아웃풋과 라벨을 비교하여 loss값 계산
        loss.backward() #backpropagation(기울기 계산)
        optimizer.step() #기울기 업데이트
        running_loss += loss.item() #loss값 업데이트
        if i % 100 == 99: #100번마다 진행 상황 출력 및 로스 초기화
            print(f"[Epoch {epoch+1}, Batch {i+1:5d}] Loss: {running_loss / 100:.3f}")
            running_loss = 0.0

print('Finished Training') #학습 종료문 출력

# 모델 테스트
correct = 0 #올바르게 분류된 샘플
total = 0 #전체 테스트 샘플
with torch.no_grad(): #그라디언트 계산을 비활성화
    for data in test_loader: #test_loader에서 data를 하나씩 가져옴
        inputs, labels = data #input = 이미지, labels = 실제 라벨(정답)
        outputs = model(inputs) #CNN을 통해 이미지를 넣고, 출력값을 뽑아냄
        _, predicted = torch.max(outputs.data, 1) #각 샘플에 대한 최대 스코어와 해당 클래스
        total += labels.size(0) #현재 미니배치의 샘플 수를 더함
        correct += (predicted == labels).sum().item() #올바르게 분류된 샘플 수를 더함

print(f'Accuracy of the network on the 10000 test images: {100 * correct / total:.2f}%') 

import random #랜덤하게 Picking을 위한 random

# 랜덤하게 10개의 예시 선택
random_indices = random.sample(range(len(test_set)), 10) #test_set 중 10개 랜덤 선택

# 선택된 예시 출력
with torch.no_grad(): #그라디언트 계산을 비활성화
    for idx in random_indices:
        image, label = test_set[idx]
        outputs = model(image) #CNN을 통해 이미지를 넣고, 출력값을 뽑아냄
        _, predicted = torch.max(outputs.data, 1) #각 샘플에 대한 최대 스코어와 해당 클래스
        plt.imshow(image.squeeze(), cmap='gray') #샘플의 이미지 출력
        plt.show()
        print('학습 결과 이 이미지의 레이블은', predicted, '입니다.') #CNN결과 예측값 출력

CE Loss Function

CE Loss Function

결과

MNIST Database 다운로드 과정

MNIST Database 다운로드 과정

비교적 짧은 학습 시간과 높은 정답률을 보여주는 효율적인 코드라고 생각한다.

12.PNG