import numpy as np
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
train_dataset = torchvision.datasets.MNIST(root='./data/',
train=True,
transform=transforms.ToTensor(),
download=True)
test_dataset = torchvision.datasets.MNIST(root='./data/',
train=False,
transform=transforms.ToTensor())
train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
batch_size=100,
shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
batch_size=100,
shuffle=False)
class FashionDNN(nn.Module):
def __init__(self):
super(FashionDNN, self).__init__()
self.fc1 = nn.Linear(28*28, 256)
self.drop = nn.Dropout2d(0.25)
self.fc2 = nn.Linear(256, 128)
self.fc3 = nn.Linear(128, 10)
def forward(self, x):
out = x.view(-1, 28*28)
out = F.relu(self.fc1(out))
out = self.drop(out)
out = F.relu(self.fc2(out))
out = self.fc3(out)
return out
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
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 = list()
iteration_list = list()
accuracy_list = list()
predictions_list = list()
labels_list = 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)
output = model(train)
loss = criterion(output, 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))
output = model(test)
predictions = torch.max(output,1)[1].to(device)
predictions_list.append(predictions)
correct += (predictions == labels).sum()
total += len(labels)
accuracy = correct*100/total
loss_list.append(loss.data) # torch loss
iteration_list.append(count)
accuracy_list.append(accuracy)
if not (count %500):
print("Iteration: {}, Loss: {}, Accuracy: {}%".format(count, loss.data, accuracy))
이렇게 하면 간단한 DNN 모델을 만들고, MNIST 데이터를 이용해 training과 test까지 진행할 수 있다. 이렇게 보면 좀 간단해 보이는데, 자세한 내부 동작 방식을 알고싶어 검색을 통해 찾은 정보들을 글로 한번에 정리하려고 한다.
먼저 모델 구현에 필요한 패키지들을 import 한다. 여기서 torch.utils.data 라이브러리를 통해 MNIST 데이터셋을 다운받을 수 있다.
그러고 train_loader 와 test_loader에 각각 데이터셋을 배치 단위로 묶어서 넣는다.
그 다음에는 내가 정의한 DNN 모델이다.
class FashionDNN(nn.Module):
def __init__(self):
super(FashionDNN, self).__init__()
self.fc1 = nn.Linear(28*28, 256)
self.drop = nn.Dropout2d(0.25)
self.fc2 = nn.Linear(256, 128)
self.fc3 = nn.Linear(128, 10)
def forward(self, x):
out = x.view(-1, 28*28)
out = F.relu(self.fc1(out))
out = self.drop(out)
out = F.relu(self.fc2(out))
out = self.fc3(out)
return out
여기서 super로 nn.Module의 init 메소드를 상속받는다. 그 다음 fc1, fc2, fc3으로 fully-connected layer를 설정해준다. 책에서는 dropout(0.25)을 써서 그대로 따라했다. dropout(0.25)는 0.25퍼센트의 뉴런을 무작위로 비활성화시켜 overfitting을 방지하는 목적으로 쓰인다.
forward 메소드를 보면, x.view(-1, 28*28)을 볼 수 있다. 이때 view에 -1로 인자를 넣어주는 것은, shape를 자동으로 맞추겠다는 얘기다. 들어오는 데이터를 세로는 28x28을 유지하고, 가로는 남는 shape가 자동으로 맞춰진다. 아마 배치 사이즈때문에 이렇게 쓰는 것 같다.
그 다음 코드는 사실 대단한 것이 없다. 그냥 ReLU 함수 통과하는게 끝이다.
사실 이 글을 쓰는 이유는, output = model(train) 이 한줄 때문이다. 나는 문득 궁금해진게 내가 model을 선언하고 달랑 train만 넣어주면 학습이 된다는게 좀 안와닿았다. 그래서 관련된 것들을 찾아보고, 정리해본다.
간단하게는, 클래스 자체를 호출할 때 __call__ method가 호출되는데, pytorch의 nn.Module에서는 __call__ method가 pytorch 내부의 _call_impl 함수를 호출한다. 이 함수에서 관련 로직들이 수행되기 때문에 model(train)이라고 간단하게 데이터를 넣어줘도 forward 연산을 수행할 수 있게 되는 것이다. 좀 자세히 코드를 들여보면 hook라는 개념도 나오고 좀 복잡한데, 이건 지금 내가 이해할만한 내용은 못 되는거 같아 나중을 기약해보겠다..