Lec 10-2: CNN으로 MNIST 분류기 구현하기
딥러닝 공부 19일차, 21일차
오늘은 MNIST 손글씨 분류기를 CNN을 통해 구현해보는 시간을 가져보겠습니다.
먼저 필요한 도구들을 import 해줍시다.
import torch
import torch.nn as nn
import torchvision.datasets as dsets
import torchvision.transforms as transforms
import torch.nn.init
GPU 연산을 위한 설정을 해줍니다.
device = 'cuda' if torch.cuda.is_available() else 'cpu'
torch.manual_seed(777)
if device == 'cuda':
torch.cuda.manual_seed_all(777)
print(device)
cpu
# 하이퍼파라미터
learning_rate = 0.001
training_epochs = 15
batch_size = 100
# MNIST datasets
mnist_train = dsets.MNIST(root = 'MNIST_data/',
train = True,
transform = transforms.ToTensor(),
download = True)
mnist_test = dsets.MNIST(root = 'MNIST_data/',
train = False,
transform = transforms.ToTensor(),
download = True)
훈련데이터와, 테스트데이터셋을 불러옵니다.
root
는 저장하는 저장소 위치를 의미합니다.
train
은 이 데이터로 학습을 진행할지를 설정해주는 값입니다. 테스트데이터셋은 학습하면 안되므로 false 설정을 해줍니다.
transform
함수를 통해 입력값인 이미지 파일을 텐서값으로 변환시켜줍니다.
download
True를 해주면, root
경로에 파일이 없다면 다운받아줘 라는 의미입니다.
data_loader = torch.utils.data.DataLoader
(dataset = mnist_train,
batch_size = batch_size,
shuffle = True,
drop_last = True)
data_loader
파이토치에서는 데이터를 좀 더 쉽게 다룰 수 있도록 유용한 도구로서 dataset
과 dataloader
를 제공합니다.
이를 사용하면 미니배치학습, 데이터셔플, 병렬처리까지 간단히 수행할 수 있습니다.
기본적인 사용방법은 Dataset을 정의하고, 이를 Dataloader에 전달하는 것입니다.
shuffle
값을 True로 해주게되면 데이터셋의 순서를 학습하게되는 경우를 방지 할 수 있습니다.
drop_last
값을 True로 하게 되면, 총 데이터 개수를 미니배치로 할당한 값으로 나누었을때, 애매하게 남는경우 그 남는 부분을 버리겠다라는 의미입니다.
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.layer1 = nn.Sequential
(
nn.Conv2d(1, 32, Kernel_size = 3, stride = 1, padding = 1),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.layer2 = nn.Sequential
(
nn.Conv2d(32, 64, kernel_size = 3, stride = 1, padding = 1),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.fc = nn.Linear(7*7*64, 10, bias = True)
torch.nn.init.xavier_uniform_(self.fc.weight)
def forward(self, x):
out = self.layer1(x)
out2 = self.layer2(out)
out3 = out2.view(out2.size(0), -1)
out4 = self.fc(out3)
return out
model = CNN().to(device)
super().__init__()
super()로 부모 클래스를 초기화해줌으로써, 부모 클래스의 속성을 sub class가 받아오도록하는 함수입니다.
이것을 사용하지 않으면, 부모 클래스의 속성을 사용할 수 없으므로 학습도 진행이 되지 않습니다! 중요
super().__init__() vs super(~~,self).__init__()
super를 더 명확하게 사용하기 위해서는 단순히 super().init()을 하는게 아니라,
super(파생클래스,self).__init__() 을 해줍니다. 이와같이 적어주면 기능적으로는 차이가 없지만, 파생클래스와 self를 넣어서 현재 클래스가 어떤 클래스인지 명확하게 표시해줄 수 있는 장점이 있습니다.
out3 = out2.view(out2.size(0), -1)
의미를 조금 더 알아보겠습니다. 일단 의미를 파악하기위해 torch.size()
에 대해서 조금 더 알아보면
예를들어
torch.size([128, 3, 28, 28])
다음과 같은 형태가 있다고보면
128 : mini-batch-size 3 : channel size 28 : img size 28 : img size
다음과 같고, 여기서 0차원 즉 첫번째 차원은 배치사이즈가 됩니다. 그러므로 view 함수에서 -1은 알아서 크기를 조절해달라는 의미이므로, 행의 크기를 배치사이즈로 맞추게되면 1차원 벡터(텐서)로 치환되게 되는 것입니다.
그런 1차원 사이즈로 바꿔야하는 이유는 FC Layer에 들어가야하기 때문입니다.
이러한 과정을 Flatten 과정이라고 부릅니다.
이제 비용함수와 옵티마이저를 정의합니다.
criterion = torch.nn.CrossEntropyLoss().to(device)
# 비용 함수에 소프트맥스 함수 포함되어져 있음.
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
총 배치의 수를 출력해보겠습니다.
total_batch = len(data_loader)
print('총 배치의 수 : {}'.format(total_batch))
총 배치의 수 : 600
총 배치의 수가 600개인데, 배치크기를 100으로 했으므로 결국 훈련 데이터는 총 60,000개라는 의미입니다. 이제 모델을 훈련시켜보면
for epoch in range(training_epochs):
avg_cost = 0
for X, Y in data_loader:
# 미니 배치 단위로 꺼내온다. X는 미니 배치, Y는 레이블.
# image is already size of (28x28), no reshape
# label is not one-hot encoded
X = X.to(device)
Y = Y.to(device)
optimizer.zero_grad()
hypothesis = model(X)
cost = criterion(hypothesis, Y)
cost.backward()
optimizer.step()
avg_cost += cost / total_batch
print('[Epoch: {:>4}] cost = {:>.9}'.format(epoch + 1, avg_cost))
위 프린트함수에서 epoch+1 값이 4자리배열중 우측정렬로 프린트되고, avg_cost 값이 소수점 9자리중 우측정렬되어서 프린트되게 됩니다.
잘 이해가 안가시는 분들은 아래 설명을 참고하시면 좋을 것 같습니다.
format() in python
format함수는 파이썬에서 출력문을 작성할 때, {} 안에 들어갈 것들은 순차적으로 적어주면 되는 이점이 있습니다.
여기서 딥러닝 코드를 보며 조금 궁금하던 것이 있었는데, 미루다가 오늘 드디어 해결하고갑니다.
점수 = 95
이름 = "이포맷"
#{인덱스:전체자리수}
print("김파이의 점수는 {0:10} 점 입니다." .format(점수)) #치환하는 값에 10칸 자리수가 생성
print("김파이의 점수는 {0:5} 점 입니다." .format(점수)) #치환하는 값에 5칸 자리수가 생성
#{인덱스:정렬방향 전체자리수}
print("김파이의 점수는 {0:<5} 점 입니다." .format(점수)) #5칸 자리수 주고 왼쪽 정렬
print("김파이의 점수는 {0:>5} 점 입니다." .format(점수)) #5칸 자리수 주고 오른쪽 정렬
#{인덱스:공백에문자넣기 정렬방향 전체자리수}
print("김파이의 점수는 {0:*>5} 점 입니다." .format(점수)) #5칸 자리수 주고 공백엔 *표시 오른쪽 정렬
##### result #####
김파이의 점수는 95 점 입니다.
김파이의 점수는 95 점 입니다.
김파이의 점수는 95 점 입니다.
김파이의 점수는 95 점 입니다.
김파이의 점수는 ***95 점 입니다.
다시 본래 코드로 돌아오면
[Epoch: 1] cost = 0.224006683
[Epoch: 2] cost = 0.062186949
[Epoch: 3] cost = 0.0449030139
[Epoch: 4] cost = 0.0355709828
[Epoch: 5] cost = 0.0290450025
[Epoch: 6] cost = 0.0248527844
[Epoch: 7] cost = 0.0207189098
[Epoch: 8] cost = 0.0181982815
[Epoch: 9] cost = 0.0153046707
[Epoch: 10] cost = 0.0124179339
[Epoch: 11] cost = 0.0105423154
[Epoch: 12] cost = 0.00991860125
[Epoch: 13] cost = 0.00894770492
[Epoch: 14] cost = 0.0071221008
[Epoch: 15] cost = 0.00588585297
이제 테스트를 해봅시다.
# 학습을 진행하지 않을 것이므로 torch.no_grad()
with torch.no_grad():
X_test = mnist_test.test_data.view(len(mnist_test), 1, 28, 28).float().to(device)
Y_test = mnist_test.test_labels.to(device)
prediction = model(X_test)
correct_prediction = torch.argmax(prediction, 1) == Y_test
accuracy = correct_prediction.float().mean()
print('Accuracy:', accuracy.item())
Accuracy: 0.9883000254631042
98프로의 정확도가 나오는 것을 보실 수가 있습니다.
참고문헌
Leave a comment