pytorch

[pytorch] Convolutional Neural Network (CNN) 로 FashionMNIST 구현해보기

독립성이 강한 ISFP 2023. 4. 28. 16:56
728x90
반응형

fashion_mnist 데이터셋을 사용하여 합성곱 신경망을 직접 구현해 보겠습니다.

 

fashion_mnist 데이터셋은 토치비전에 내장된 예제 데이터로 운동화, 셔츠, 샌들 같은 작은 이미지의 모음이며, 기본 MNIST 데이터셋처럼 열 가지로 분류될 수 있는 28x28 픽셀의 이미지 7만 개로 구성되어 있다.

데이터셋을 자세히 살펴보면 훈련 데이터는 0-255 사이의 값을 갖는 28x28 크기의 넘파이 배열이고, 레이블(정답) 데이터는 0-9 사이 정수 값을 갖는 배열입니다.

 

이전 포스팅에서는 Deep Neural Network (DNN)로 FashionMNIST를 구현해 보았는데요!

https://resultofeffort.tistory.com/95

 

[pytorch] Deep Neural Network (DNN) 로 FashionMNIST 구현해보기

fashion_mnist 데이터셋을 사용하여 심층 신경망을 직접 구현해 보겠습니다. fashion_mnist 데이터셋은 토치비전에 내장된 예제 데이터로 운동화, 셔츠, 샌들 같은 작은 이미지의 모음이며, 기본 MNIST 데

resultofeffort.tistory.com

이번엔 Convolutional Neural Network (CNN)로 구현해 보겠습니다.


1. 라이브러리 호출

import numpy as np
import matplotlib.pyplot as plt

import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.nn.functional as F

import torchvision
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader

파이토치는 기본적으로 GPU 사용을 권장합니다.

하지만 GPU가 장착되지 않은 환경에서도 파이토치를 정상적으로 실행하고 사용할 수 있습니다.

GPU가 장착되어 있고, GPU를 사용하기 위한 설정이 되어 있다면 파이토치에서 자동으로 인식합니다.

하지만 책에서는 아직 GPU를 설정하지 않았기 때문에 CPU를 사용할 것입니다.

2. CPU 혹은 GPU 장치 확인

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

※참고※

<하나의 GPU를 사용할 때>

device = torch.device("cuda : 0" if torch.cuda.is.available() else "cpu")
model = Net()
model.to(device)

<다수의 GPU를 사용할 때>

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = Net()
if torch.cuda.device_count() > 1:
    model = nn.DataParallel(net)
model.to(device)

3. fashion_mnist 데이터셋 다운로드하기

import torch
import torchvision
import torchvision.transforms as transforms

transform = transforms.Compose(
    [transforms.ToTensor()]
    )
    
trainset = torchvision.datasets.FashionMNIST(root='./data', train=True, 
                                        download=True, transform=transform)
                                        
testset = torchvision.datasets.FashionMNIST(root='./data', train=False,
                                        download=True, transform=transform)

torchvision.datasets torch.utils.data.Dataset의 하위 클래스로 다양한 데이터셋(CIFAR, COCO, MNIST, ImageNet 등)을 포함합니다.

 

< torchvision.datasets 파라미터 >

  • root: FashionMNIST 데이터셋이 저장될 경로입니다. 지정한 경로가 없으면 새로 생성됩니다.
  • train=True: 학습용 데이터를 다운로드합니다.
  • download=True: "데이터셋이 저장될 경로"에 데이터셋이 있는지 확인한 후 없는 경우 다운로드됩니다.
  • transform=trainsform: 입력된 이미지 데이터를 텐서(0~1) 형태로 변경합니다.

4. fashion_mnist 데이터를 데이터로더에 전달

다운로드한 fashion_mnist 데이터를 일정한 batch size로 묶어서  데이터로더(DataLoader)를 만들어주는 코드입니다.

train_loader = torch.utils.data.DataLoader(
    trainset, 
    batch_size=100, 
    shuffle=True
)

test_loader = torch.utils.data.DataLoader(
    testset, 
    batch_size=100, 
    shuffle=False
)

torch.utils.data.DataLoader()를 사용하여 원하는 크기의 배치 단위로 데이터를 불러오거나, 순서가 무작위로 섞이도록 할 수 있습니다.

 

< DataLoader 파라미터 >

  • trainset : 데이터를 불러올 데이터셋을 지정합니다.
  • batch_size : 데이터를 배치로 묶어줍니다. 100개 단위로 묶어줍니다.
  • shuffle : 데이터를 불러올 때마다 데이터를 섞어서 불러옵니다. 

5. 분류에 사용될 클래스 정의 

FashionMNIST 데이터셋으로부터 랜덤으로 24개의 샘플 이미지와 라벨을 가져와서 시각화하는 코드입니다.

DataLoader에서 shuffle=True로 설정되어 있기 때문에 매 epoch마다 데이터가 랜덤하게 섞입니다.

next(iter(train_loader))를 호출할 때마다 다른 미니배치가 반환됩니다.

import matplotlib.pyplot as plt
import numpy as np

# train_loader 에서 첫 번째 배치를 가져와 이미지와 라벨을 sample, labels에 저장
samples, labels = next(iter(train_loader))

# 각 클래스의 이름을 숫자와 매칭하여 저장 (총 10개)
classes = {0:'T-Shirt', 1:'Trouser', 2:'Pullover', 3:'Dress', 4:'Coar',
5:'Sandal', 6:'Shirt', 7:'Sneaker', 8:'Bag', 9:'Ankle Boot'}

fig, axs = plt.subplots(nrows=4, ncols=6, figsize=(16, 10)) # 4행 6열의 서브플롯을 생성

for ax, image, label in zip(axs.flatten(), samples, labels):
    image = np.transpose(image.numpy(), (1, 2, 0)) # 차원 변환: (C, H, W) -> (H, W, C)
    
    ax.imshow(image, cmap='gray') # 흑백 이미지로 출력
    ax.set_title(classes[label.item()], fontsize=12) # label.item(): torch.Tensor 타입의 스칼라 값(정수)을 얻기 위해 사용
    ax.set_xticks([])
    ax.set_yticks([])
    
plt.tight_layout()
plt.show()

24개의 이미지 데이터를 시각적으로 표현

6. 합성곱(CNN) 네트워크 생성

class FashionCNN(nn.Module):
    def __init__(self):
        super(FashionCNN, self).__init__()
        self.layer1 = nn.Sequential( ## 1
            nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1), ## 2
            nn.BatchNorm2d(32), ## 3
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2, stride=2) ## 4
        )
       
        self.layer2 = nn.Sequential(
            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )
        self.fc1 = nn.Linear(in_features=64*6*6, out_features=600) ## 5
        self.drop=nn.Dropout(0.25)
        self.fc2=nn.Linear(in_features=600, out_features=120)
        self.fc3=nn.Linear(in_features=120, out_features=10)
        
    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = out.view(out.size(0), -1) ## 6
        out = self.fc1(out)
        out = self.drop(out)
        out = self.fc2(out)
        out = self.fc3(out)
        return out

1.  nn.Sequential은 PyTorch에서 제공하는 클래스로, 여러 개의 neural network layer들을 연속적으로 실행하는 기능을 제공합니다. 이를 통해 각 layer에서의 출력은 그다음 layer의 입력으로 전달되고, 마지막 layer에서의 출력이 최종 출력으로 사용됩니다.

nn.Sequential을 사용하면 다양한 유형의 layer를 쉽게 연결할 수 있으며, 새로운 layer를 추가하기도 용이합니다. 이를 통해 복잡한 모델을 간단하게 구현할 수 있습니다.

 

2.  합성곱층이미지의 특징을 추출하는 데 사용됩니다. 이를 위해 커널(필터)이라는 n*m 크기의 행렬을 사용하여 이미지를 처음부터 끝까지 훑습니다. 커널의 각 원소 값과 이미지의 해당 부분의 원소 값끼리 곱한 후, 이들의 합을 출력합니다. 이 과정을 통해 이미지의 특징이 추출되며, 이 추출된 특징을 다음 층으로 전달합니다.

 

< nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1) 파라미터 >

  • in_channels=1: 입력 채널의 수. (흑백 이미지는 1, RGB 값을 가진 이미지는 3)
  • out_channels: 출력 채널의 수
  • kernel_size: 커널 크기. 커널은 입력 데이터를 스트라이드 간격으로 순화하면서 합성곱을 계산합니다. 
  • padding: 패딩 크기. 출력 크기를 조정하기 위해 입력 데이터 주위에 0을 채웁니다. 패딩 값이 클수록 출력 크기도 커집니다. 

3.  nn.BatchNorm2d배치 정규화를 수행하는 클래스입니다. 이를 사용하여 딥러닝 모델에서 과적합 문제나 gradient vanishing/exploding 문제를 해결할 수 있습니다. 배치 정규화는 데이터 분포가 평균 0, 분산 1이 되도록 정규화하여 모델 학습을 안정화시키는 과정입니다. 보통 2D 합성곱(Conv2d) 레이어 다음에 배치 정규화를 수행하는 것이 일반적이며, 이때 입력 데이터의 shape는 (batch_size, channels, height, width) 형태여야 합니다.

 

< nn.BatchNorm2d(num_features = 32) 파라미터 >

  • num_features: 입력 채널의 수

입력 데이터는 32개의 채널을 가지므로 num_features는 32로 설정됩니다.

배치 정규화는 학습하는 동안 평균과 표준편차를 계산하기 때문에 입력 채널의 수와 동일한 수의 파라미터가 필요합니다.

 

4.  MaxPool2d는 입력 데이터의 최댓값을 출력하는 연산이며, 이미지 크기를 축소시키는 용도로 사용합니다. 풀링 계층은 합성곱층의 출력 데이터를 입력으로 받아서 출력 데이터의 크기를 줄이거나 공간 정보를 보존하면서 핵심 정보를 추출하는 데에 활용됩니다. 

 

예를 들어, 2x2 크기의 필터와 2의 스트라이드를 가진 MaxPool2d 레이어를 적용하면 입력 데이터의 공간 크기는 1/2로 줄어들게 됩니다. 이를 반복하면 공간 크기를 계속해서 줄일 수 있습니다. 이러한 다운샘플링 과정을 통해 특징 맵의 크기를 줄이면서 공간 정보를 유지할 수 있으므로, 학습 속도를 향상하고 오버피팅을 방지하는 데에 도움이 됩니다.

 

< nn.MaxPool2d(kernel_size=2, stride=2) 파라미터> 

  • kernel_size: m * n 행렬로 구성된 가중치
  • stride: 입력 데이터에 커널을 적용할 때 이동할 간격

5.  미지 데이터를 분류하기 위해서는 배열 형태로 변환하여 처리해야 합니다.

Conv2d에서는 입력 데이터에 대해 패딩과 스트라이드 값을 조정하여 출력 크기를 다르게 할 수 있습니다.

따라서, 출력 크기는 Conv2d 계층을 거친 후에 최종적으로 분류를 위해 사용되는 완전 연결층에 전달됩니다. 

  • nn.Linear(in_features = 64*6*6, out_features = 600)

입력 특성의 수가 64x6x6(=2,304)이고 출력 특성의 수가 600인 fully connected layer(전결합층)을 정의하는 코드입니다.

Convolutional Neural Network(CNN)에서는 Convolutional Layer(합성곱층)을 여러 개 쌓고,

그다음에 Fully Connected Layer(전결합층)을 추가해서 최종적인 출력을 계산합니다.

따라서 CNN에서 Fully Connected Layer는 Convolutional Layer에서 나온 특징 맵(feature map)들을 일차원으로 펼친 후에 입력으로 사용됩니다.

 

위 코드에서는 64개의 채널을 가진 6x6 크기의 특징 맵이 입력으로 주어졌다고 가정했을 때, 이를 일렬로 펼쳐서 64x6x6=2,304차원의 벡터로 만들어주고, 이를 600차원의 출력으로 변환하는 fully connected layer를 정의합니다.

 

6. 합성곱층에서 완전연결층으로 변경되기 때문에 데이터의 형태를 1차원으로 바꿔주는 작업이 필요합니다. 이때 out.size(0)은 결국 100을 의미합니다. 따라서 (100, ?) 크기의 텐서로 변경하겠다는 의미입니다. out.view(out.size(0), -1)에서 -1은 행의 수는 정확히 알고 있지만 열의 수를 알지 못할 때 사용합니다.

 


 

Conv2d 계층에서의 출력 크기 구하는 공식

출력 크기 = ((W-F+2P)/S)+1

  • W : 입력 데이터의 크기
  • F : 커널의 크기
  • P : 패딩 크기
  • S : 스트라이드

 

MaxPool2d 계층에서의 출력 크기 구하는 공식

출력 크기 = ((W-F)/S)+1

  • W : 입력 데이터의 크기(Conv2d의 출력 크기)
  • F : 커널의 크기
  • S : 스트라이드

 

nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1)

nn.MaxPool2d(kernel_size=2, stride=2)

 

첫 번째 Conv2d 계층의 출력 크기는 (784-3+2*1)/2+1 = 784 이므로,

출력 형태는 (784, 784, 32)입니다.

첫 번째 MaxPool2d 계층의 출력 크기는 ((784-2/2)+1) = 392 이므로,

출력 형태는 (392, 392, 32)입니다.

 

7. 합성곱 네트워크를 위한 파라미터 정의

learning_rate = 0.001
model = FashionCNN()
model.to(device)

criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

모델을 학습시키지 전에 손실함수, 학습률, 옵티마이저에 대해 정의합니다.

옵티마이저를 위한 경사 하강법은 Adam을 사용하며, 학습률을 의미하는 lr은 0.001을 사용한다는 의미입니다.

8. 모델 학습 및 성능 평가

num_epochs = 5
count = 0

loss_list = []
iteration_list = []
accuracy_list = []

predictions_list = []
labels_list = []

for epoch in range(num_epochs): # 100개의 이미지와 레이블이 하나의 배치로 묶여서 가져옴
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)
        
        train = Variable(images.view(100, 1, 28, 28))
        labels = Variable(labels)
        
        outputs = model(train) # 학습 데이터(batch)를 모델에 입력하여 출력값 계산
        loss = criterion(outputs, labels) # 손실값 계산
        optimizer.zero_grad()  # 이전에 계산된 그래디언트 값 초기화
        loss.backward()        # 손실에 대한 그래디언트 계산
        optimizer.step()       # 계산된 그래디언트를 이용하여 가중치 업데이트
        count += 1
        
        if (count % 50)==0: #  매 50번째 iteration마다 test data를 이용해 accuracy를 계산
            total = 0
            correct = 0

            for images, labels in test_loader:
                images, labels = images.to(device), labels.to(device)

                labels_list.append(labels)
                test = Variable(images.view(-1, 1, 28, 28))
                
                outputs = model(test)
                predictions = torch.argmax(outputs, 1).to(device) # 최댓값의 위치
                predictions_list.append(predictions)
                
                correct += (predictions == labels).sum() # 정답을 맞춘 데이터의 수
                total += len(labels) # 총 데이터의 수
        
            accuracy = correct / total * 100 # 정확도
            loss_list.append(loss.data)
            iteration_list.append(count)
            accuracy_list.append(accuracy)
                
        if (count % 500)==0:
            print("Iteration: {}, Loss: {}, Accuracy: {}%".format(count, loss.data, 
                accuracy))

ㅁ1. Autograd는 자동 미분을 수행하는 파이토치의 패키지입니다. Autograd는 Variable을 사용해서 역전파를 위한 미분 값을 자동으로 계산해 줍니다. Variable은 PyTorch에서 기존의 tensor를 감싸서 미분 가능한 tensor로 만드는 역할을 합니다.

이것은 주어진 입력에 대한 기울기(gradient)를 자동으로 계산하기 위한 것입니다.  

 

images.view(100, 1, 28, 28)는 images tensor를 (100, 1, 28, 28) 크기의 tensor로 변환합니다.

이것은 이미지 데이터를 100개씩 묶어서 1 채널, 28x28 픽셀의 형태로 변환하는 것입니다.

따라서, 이 변환된 이미지 데이터를 모델에 입력하기 위해 Variable로 감싸서 미분 가능한 tensor로 만듭니다.

labels는 이미지 데이터에 해당하는 라벨 값입니다. 이것 역시 Variable로 감싸서 미분 가능한 tensor로 만듭니다.

 


합성곱 신경망과 심층 신경망을 비교해 보면, 합성곱 신경망이 조금 더 높은 정확도를 보입니다.

하지만 이미지 데이터가 적은 경우에는 심층 신경망으로도 충분히 분류가 가능하기 때문에 무난한 선택이 될 수 있습니다.

 

그러나 이미지 데이터가 많아지면 단순 심층 신경망으로는 특성 추출 및 분류가 정확히 이루어지지 않을 수 있으므로, 이 경우 합성곱 신경망을 활용하여 학습을 진행하는 것이 좋습니다.

 

합성곱 신경망을 구현하는 방법은 여기까지!

 

728x90
반응형