[딥러닝] MLP를 위한 PyTorch 구현
▶ 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%의 성능을 보인 것을 확인할 수 있었습니다.