elevne's Study Note

딥러닝 파이토치 교과서 (4: 합성곱신경망 (2)) 본문

Machine Learning/Pytorch

딥러닝 파이토치 교과서 (4: 합성곱신경망 (2))

elevne 2023. 3. 5. 19:00

이번에는 예제 코드, 데이터를 활용하여 CNN 을 직접 코드로 구현해보았다. 우선 필요한 라이브러리들을 import 하고, 가능하다면 deviceGPU 로 설정해준다.

 

 

 

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

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

 

 

 

 

 

이번 실습에서 사용된 Fashion MNIST 데이터는 torchvision 을 통해서 다운로드 받을 수 있다. 다운로드 시 첫 번째 파라미터로 다운로드 경로를 설정해줄 수 있으며, 두 번째 인자(download)를 True 로 설정하면 첫 번째 인자의 위치에 해당 데이터셋이 있는지 확인한 후 다운로드를 진행한다. 또 tranform 인자를 받는데, 이는 이미지를 텐서로 변환해주는 역할을 한다.

 

 

 

train_dataset = torchvision.datasets.FashionMNIST("/content/drive/MyDrive/Colab Notebooks/딥러닝파이토치/data", download=True, transform=transforms.Compose([transforms.ToTensor()]))
test_dataset = torchvision.datasets.FashionMNIST("/content/drive/MyDrive/Colab Notebooks/딥러닝파이토치/data", download=True, train=False, transform=transforms.Compose([transforms.ToTensor()]))

 

 

 

 

다운로드 받은 Fashion MNIST 데이터는 DataLoader 객체 안에 담아서 메모리로 불러올 수 있다. DataLoader() 을 사용하면 원하는 크기의 배치 단위로 데이터를 불러올 수 있다. shuffle = True 로 설정하면 순서가 무작위로 섞이게도 할 수 있다. 

 

 

 

train_loader = DataLoader(train_dataset, batch_size=100)
test_loader = DataLoader(test_dataset, batch_size=100)

 

 

 

모델을 정의해주기 전에 우선 데이터를 임의로 추출하여 시각화해보았다. 아래와 같은 코드로 진행하였다.

 

 

 

labels_map = {
    0 : "T-Shirt",
    1 : "Trouser",
    2 : "Pullover",
    3 : "Dress",
    4 : "Coat",
    5 : "Sandal",
    6 : "Shirt",
    7 : "Sneaker",
    8 : "Bag",
    9 : "Ankle Boot"
}

fig = plt.figure(figsize=(8, 8))
columns = 4
rows = 5
for i in range(1, columns * rows + 1):
  img_xy = np.random.randint(len(train_dataset))
  img = train_dataset[img_xy][0][0, :, :]
  fig.add_subplot(rows, columns, i)
  plt.title(labels_map[train_dataset[img_xy][1]])
  plt.axis("off")
  plt.imshow(img, cmap="gray")
plt.show()

 

 

result

 

 

 

DNNCNN 의 성능을 비교해보기 위해 우선 DNN Model 을 정의하고 훈련을 시켜보았다. DNN 코드는 아래와 같다.

 

 

 

class FashionDNN(nn.Module):
  def __init__(self):
    super(FashionDNN, self).__init__()
    self.fc1 = nn.Linear(in_features = 784, out_features = 256)
    self.drop = nn.Dropout(0.25)
    self.fc2 = nn.Linear(in_features = 256, out_features = 128)
    self.fc3 = nn.Linear(in_features = 128, out_features = 10)
  def forward(self, input_data):
    out = input_data.view(-1, 784)
    out = F.relu(self.fc1(out))
    out = self.drop(out)
    out = F.relu(self.fc2(out))
    out = self.fc3(out)
    return out

 

 

 

torch 에서 모델 클래스를 작성할 때는 항상 torch.nn.Module상속받도록 한다. nn 은 딥러닝 모델 (네트워크) 구성에 필요한 모듈이 모여있는 패키지이며, Linear() 은 단순한 선형회귀 모델을 만들 때 사용된다. in_featuresout_features 를 파라미터로 받게된다. forward() 함수 내에서 사용된 view 메서드는 넘파이의 reshape 메서드와 같은 역할로, 텐서의 크기를 변경해주는 역할을 한다. 

 

 

 

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

num_epochs = 5
count = 0
loss_list = []
iteration_list = []
accuracy_list = []
predictions_list = []
labels_list = []

for epoch in range(num_epochs):
  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)
    loss = criterion(outputs, labels)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    count += 1
    if not (count % 50):
      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(100, 1, 28, 28))
        outputs = model(test)
        predictions = torch.max(outputs, 1)[1].to(device)
        predictions_list.append(predictions)
        correct += (predictions == labels).sum()
        total += len(labels)
      accuracy = correct * 100 / total
      loss_list.append(loss.data)
      iteration_list.append(count)
      accuracy_list.append(accuracy)
    if not (count % 500):
      print("Iteration: {}, Loss: {}, Accuracy: {}".format(count, loss.data, accuracy))

 

 

result

 

 

 

모델의 optimizerAdam, loss functionCross Entropy 가 사용되었다. 학습을 진행할 때 images.to(device), labels.to(device) 와 같이 작성하였는데 이는 모델이 데이터를 처리하기 위해서는 모델과 데이터가 동일한 장치에 있어야하기 때문이다. 그 후에 데이터를 autograd 에서 import 한 Variable 객체 안에 넣어준다. 이는 torch 에서 자동 미분을 수행하는 핵심 패키지로, 자동 미분에 대한 값을 저장하기 위해 tape 를 사용한다. 순전파 단계에서 tape 는 수행하는 모든 연산을 저장한다. 그리고 역전파 단계에서 저장된 값들을 꺼내서 사용하는 것이다. Autograd 는 Variable 을 사용해서 역전파를 위한 미분 값을 자동으로 계산해주는 것이다. 5 번의 epoch 을 거치면서 약 87 % 의 Accuracy 가 나온 것을 확인할 수 있었다. 그 다음에는 CNN 모델을 정의해주었다.

 

 

 

class FashionCNN(nn.Module):
  def __init__(self):
    super(FashionCNN, self).__init__()
    self.layer1 = nn.Sequential(
        nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1),
        nn.BatchNorm2d(32),
        nn.ReLU(),
        nn.MaxPool2d(kernel_size=2, stride=2)
    )
    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)
    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)
    out = self.fc1(out)
    out = self.drop(out)
    out = self.fc2(out)
    out = self.fc3(out)
    return out

 

 

 

위에서는 DNN 과 다르게 nn.Sequential 을 사용하였다. 이를 사용하면 __init__() 에서 사용할 네트워크 모델을 정의해줄 뿐만 아니라 forward 함수에서 구현될 순전파를 계층 형태로 좀 더 가독성이 뛰어난 코드로 작성할 수 있다고 한다. nn.Sequentail 내에서 nn.Conv2d 를 사용하여 합성곱 연산을 통해 이미지의 특징을 추출한다. 파라미터로 in_channels (입력 채널의 수, Gray Scale 이면 1, RGB 이면 3 을 가지는 경우가 많다) , out_channels (출력 채널의 수) , kernel_size (커널 크기), padding (패딩 크기, 입력 데이터 주위에 0 을 채우는 것) 을 받는다. 그 후 BatchNorm2d 로 각 배치 단위별로 데이터가 다양한 분포를 가지더라도 평균과 분산을 이용하여 정규화해준다. 분포를 가우시안 형태로 만들 수 있는 것이다. 다음은 MaxPool2d 를 사용하여 이미지 크기를 축소시킨다. kernelstride 를 인자로 받는다. Conv2d layer 을 쌓은 출력크기를 계산해주고 Fully Connected Layer 로 이어주어야 하는데, 이는 다음 시간에 알아볼 예정이다. 작성한 CNN 모델 또한 위에서 사용한 학습 코드와 동일한 코드를 사용하여 학습시킨다. 결과는 아래와 같다.

 

 

 

result

 

 

 

DNN 보다 결과가 발전된 것을 확인할 수 있다.

 

 

 

 

 

 

 

Reference:

딥러닝 파이토치 교과서