빅데이터 인공지능/딥러닝

[딥러닝] MLP를 위한 PyTorch 구현

doeunnkimm 2022. 11. 26. 18:01

▶ MLP(Multi-Layer Perceptron)을 위한 PyTorch 구현

저번 글에 이어서 이번에는 MLP를 PyTorch로 구현해 보려고 합니다.

데이터셋은 손글씨 데이터(MNIST) 데이터셋을 사용합니다🙂

 

필요한 라이브러리 import
import numpy as np
import matplotlib.pyplot as plt
import torch
from torch import nn
from torch.nn import functional as F
from torchvision import transforms, datasets
from torch.utils.data import DataLoader

 

batch_size와 epoch 미리 정의
BATCH_SIZE = 32
EPOCHS = 10

 

MNIST 데이터 받기
train_dataset = datasets.MNIST(root = "../data/MNIST",
                               train = True,
                               download = True,
                               transform = transforms.ToTensor())

test_dataset = datasets.MNIST(root = "../data/MNIST",
                              train = False,
                              transform = transforms.ToTensor())

 

학습/테스트 데이터로더 정의

학습 및 테스트를 진행하기 위한 정보들을 저장하고 있는 데이터로더를 정의해 줍니다.

train_loader = DataLoader(dataset=train_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=BATCH_SIZE, shuffle=False)

정의한 train_loader를 통해 X_train과 y_train의 shape와 type을 확인해 보겠습니다.

for (X_train, y_train) in train_loader:
    print('X_train:', X_train.size(), 'type:', X_train.type())
    print('y_train:', y_train.size(), 'type:', y_train.type())
    break

출력된 결과를 살펴보면

우리가 batch_size를 32로 했기 때문에 하나의 mini batch가 32개의 이미지(데이터)로 구성되어 있다는 것을 알 수 있습니다.

또한 각각의 이미지들의 크기는 28x28로 되어있다는 것도 확인할 수 있었습니다.

y_train의 경우에는 해당 이미지들의 레이블 하나씩만이 포함되어 있기 때문에 batch_size만큼인 32만 되어있습니다.

 

현재 input 데이터가 어떻게 생겼는지 한번 출력해 보겠습니다.

pltsize = 1
plt.figure(figsize=(10 * pltsize, pltsize))
for i in range(10):
    plt.subplot(1, 10, i + 1)
    plt.axis('off')
    plt.imshow(X_train[i, :, :, :].numpy().reshape(28, 28), cmap = "gray_r")
    plt.title('Class: ' + str(y_train[i].item()))

출력해보면 위와 같이 이미지 데이터들로 이루어져 있다는 것을 알 수 있고

이미지와 관련된 레이블도 포함되어 있는 것을 확인할 수 있었습니다.

 

모델 class 정의

+Fully Connected Layer(완전 연결 계층) ( = Dense Layer)

1차원 배열의 형태로 평탄화(flatten)된 행렬을 통해 이미지를 분류하는데 사용되는 계층입니다.

 

  • forward method
    • x.view(-1, 28*28): FC layer는 평탄화된 행렬을 이용하기 때문에 forward 함수 맨 첫 줄에서 input 데이터를 flatten시키기 위해 view 함수를 사용하여 1차원으로 변형
class NeuralNet(torch.nn.Module):
  def __init__(self):
    super(NeuralNet, self).__init__()
    self.fclayer1 = torch.nn.Linear(28*28, 512)
    self.fclayer2 = torch.nn.Linear(512, 256)
    self.fclayer3 = torch.nn.Linear(256, 10)
    
  def forward(self, x):
    x = x.view(-1, 28*28)
    x = self.fclayer1(x)
    x = F.sigmoid(x)
    x = self.fclayer2(x)
    x = F.sigmoid(x)
    x = self.fclayer3(x)
    x = F.log_softmax(x, dim=1)
    return x

 

+ GPU를 사용하는 경우

if torch.cuda.is_available():
    DEVICE = torch.device('cuda')
else:
    DEVICE = torch.device('cpu')

 

정의한 모델을 변수로 저장해 두겠습니다.

model = NeuralNet().to(DEVICE)

 

지금 모델이 어떻게 정의(layer가 어떻게 되어있는지)가 되었는지 확인해 보겠습니다.

print(model)

 

optimizer와 loss 정의
  • momentum: 관성. 기울기에 따라 일정 비율을 주어 빠르게 내려갈 수 있다면 더 빠르게, 천천히 내려가야 한다면 천천히 weight를 찾아나갈 수 있도록
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
criterion = nn.CrossEntropyLoss()

 

학습을 위한 함수, 평가를 위한 함수 정의
def train(model, train_loader, optimizer, log_interval):
  model.train()
  for batch_idx, (image, label) in enumerate(train_loader):
    image = image.to(DEVICE)
    label = label.to(DEVICE)
    
    output = model(image)
    loss = criterion(output, label)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    if batch_idx % log_interval == 0:
      print("Train Epoch: {} [{}/{} ({:.0f}%)]\tTrain Loss: {:.6f}".format(
        epoch, batch_idx * len(image), 
        len(train_loader.dataset), 100. * batch_idx / len(train_loader), 
        loss.item()))

 

  • model.eval() : evalution 과정에서 사용하지 않아야 하는 layer들을 알아서 off 시키도록 하는 함수
  • torch.no_grad() : weight 활용하지 못하도록 정의해 주어야 함
  • prediction = output.max() : 우리가 쌓은 layer 중 마지막 레이어는 10개의 output을 도출합니다. 이는 10개의 레이블에 대하여 해당 이미지가 각 레이블에 속하게 될 확률 10개를 도출하게 되는데 이 중에서 가장 높은 확률을 가진 레이블이 예측한 최종 레이블이 될 테니 max 함수를 사용하여 추출
  • eq(): ==와 동일한 의미의 함수
  • view_as(): label에 해당하는 텐서를 view_as() 함수 안에 들어가는 prediction의 모양대로 다시 정렬
def evaluate(model, test_loader):
  model.eval()
  # 평가를 진행하면서 계속 누적시킬 값들 정의
  test_loss = 0
  correct = 0
  
  with torch.no_grad():
    for image, label in test_loader:
      image = image.to(DEVICE)
      label = image.to(DEVICE)
      
      # 평가 진행
      output = model(image)
      test_loss += criterion(output, label).item()
      prediction = output.max(1, keepdim=True)
      correct += prediction.eq(label.view_as(prediction)).sum().item()
      
  test_loss /= len(test_loader.dataset)
  test_accuracy = 100. * correct / len(test_loader.dataset)
  return test_loss, test_accuracy

 

학습 진행
for epoch in range(1, EPOCHS + 1):
  train(model, train_loader, optimizer, log_interval = 200)
  test_loss, test_accuracy = evaluate(model, test_loader)
  print("\n[EPOCH: {}], \tTest Loss: {:.4f}, \tTest Accuracy: {:.2f} % \n".format(
    epoch, test_loss, test_accuracy))

...

최종적으로 약 87% 정도의 test 정확도를 가진 것을 확인할 수 있었습니다.

 

추가. 일반화 적용 + Adam(Optimizer) 사용 + relu(activation function) 사용

모두 동일하고 class 정의 부분과 optimizer 부분을 수정해 주면 됩니다.

  • dropout은 활성화 함수가 적용된 직후에 적용됨
    • F.dropout(input, training(드롭아웃 적용할건지), p(얼마만큼 드롭아웃 할건지))
    • 활성화 함수가 ReLU일 때 그 이후 드롭아웃의 효과가 더 효과적 → 기존 코드에서 sigmoid를 relu로 변경
  • batch normalization은 활성화 함수가 적용되기 직전에 적용됨
    • nn.BatchNorm1d(512) : 1차원 형태의 배치 정규화. 뉴런의 수가 512개(들어오는 input size)
class NeuralNet(torch.nn.Module):
  def __init__(self):
    super(NeuralNet, self).__init__()
    self.fclayer1 = torch.nn.Linear(28*28, 512)
    self.fclayer2 = torch.nn.Linear(512, 256)
    self.fclayer3 = torch.nn.Linear(256, 10)
    self.dropout_prob = 0.5
    self.batch_norm1 = nn.BatchNorm1d(512)
    self.batch_norm2 = nn.BatchNorm1d(256)
    
  def forward(self, x):
    x = x.view(-1, 28*28)
    x = self.fclayer1(x)
    x = self.batch_norm1(x)
    x = F.relu(x)
    x = F.dropout(x, training=self.training, p=self.dropout_prob)
    x = self.fclayer2(x)
    x = self.batch_norm2(x)
    x = F.relu(x)
    x = F.dropout(x, training=self.training, p=self.dropout_prob)
    x = self.fclayer3(x)
    x = F.log_softmax(x, dim=1)
    return x
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

 

동일하게 학습을 진행해보면 결과는 다음과 같습니다.

...

기존 결과에서는 약 87%의 성능을 보였던 것과 비교해서

일반화 + Adam + relu 를 사용했을 때는 약 98%의 성능을 보인 것을 확인할 수 있었습니다.

728x90
LIST