[Project] Fashion MNIST Classifier (패션 아이템 분류기)
728x90
반응형
※ 학습 프로젝트 목표
: Fashion MNIST Dataset을 활용하여 분류기(classifier) 학습하고, 성능 검사!
→ 28 x 28 픽셀 70,000개의 흑백 이미지로, 신발, 드레스 등 10개의 카테고리(class)가 존재한다.
→ 패션 이미지 데이터를 입력으로 주면 어떤 이미지인지 예측하여 반환되는 모델 설계
# 개요
→ Multi layer perceptron, Batch normalization, ReLU를 사용하여 Neural Network 설계
→ 성능도(Accuracy) 개선 - Augmentation, Dropout 등 적용
1. Package Load (import 및 환경 설정)
import torch
import torch.nn as nn
import torchvision.datasets as dset
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
# cuda 사용 설정 및 가능 여부 확인
device = "cuda" if torch.cuda.is_available() else "cpu"
print('GPU 사용 가능 여부: {}'.format(torch.cuda.is_available()))
'''GPU 사용 가능 여부 : True'''
# Random seed 설정
torch.manual_seed(1)
if device == 'cuda':
torch.cuda.manual_seed_all(1)
# Google Drive 연동
from google.colab import drive
drive.mount('/content/gdrive')
- torchvision.datasets
: 패션 아이템 Dataset이 들어있다. (torch.utils.data.Dataset을 상속한 이미지 데이터셋
- torchvision.transforms
: 이미지에 쓸 수 있는 다양한 변환 필터를 포함하고 있는 모듈 (텐서 변환, Resize, Crop 등)
- matplotlib.pyplot : 데이터 시각화를 위해 사용
2. 하이퍼파라미터(Hyperparameter) 설정
batch_size = 100 # mini-batch의 크기
num_epochs = 5 # 학습할 epoch의 수
learning_rate = 0.001 # 학습율
- 하이퍼파라미터는 Neural Network를 통해 학습되는 것이 아니라 설계자가 직접 결정하는 값
- batch_size는 한번에 몇개씩 데이터를 읽을 것인가를 의미한다.
→ DataLoader에 데이터셋을 넣고 batch_size를 지정해주면 그만큼 데이터를 읽는다.
ex) size = 100 -> 1 epoch 마다 100개의 이미지 데이터를 읽을 것이다.
3. Dataset과 DataLoader 할당
torchvision에서 제공하는 Fashion MNIST datasete을 정의하고, 전체 dataset을 mini-batch 단위로 쪼개어 neural network에서 학습 반복문 안에 데이터를 공급하도록 DataLoader를 정의한다.
from torch.utils.data import DataLoader
transform = transforms.Compose([transforms.ToTensor()]) # Tensor로 변환
train_data = dset.FashionMNIST(root='./data', train=True, transform=transform, download=True)
test_data = dset.FashionMNIST(root='./data', train=False, transform=transform, download=True)
# root = FashionMNIST 데이터셋이 위치하는 경로
# 모델에게 전달할 데이터 공급 코드 - DataLoader
train_loader = DataLoader(
dataset=train_data,
batch_size=batch_size, # batch_size만큼 잘라서 Load
shuffle=True,
drop_last=True # 마지막 Data가 batch_size만큼 안되면 버려(True)
)
test_loader = DataLoader(
dataset=test_data,
batch_size=batch_size,
shuffle=False,
drop_last=True
)
- FashionMNIST 데이터셋을 가져오기 위해 torchvision의 datasets 패키지를 통해 다운!
- torch.utils.data : Dataset의 표준을 정의하고 Dataset을 불러오고 자르는데 사용한다.
- transforms.Compose() : 여러 전처리 함수들을 하나로 묶어서 사용할 수 있다.
4. 샘플 데이터 시각화 - 테스트용
- 로드한 이미지 데이터를 테스트 용도로 출력해보자!
- 각 class의 이름과 인덱스를 Dictionary 형태로 저장하고, for문을 이용하여 train_data 중 무작위로 25개 추출 후 (5 x 5) 형태로 출력!
labels_map = {0 : 'T-Shirt', 1 : 'Trouser', 2 : 'Pullover', 3 : 'Dress', 4 : 'Coat', 5 : 'Sandal',
6 : 'Shirt', 7 : 'Sneaker', 8 : 'Bag', 9 : 'Ankle Boot'}
# 출력 이미지 사이즈 설정
columns = 5
rows = 5
fig = plt.figure(figsize=(8,8))
# 데이터를 불러서 numpy 배열에 넣어주고 imshow하면 시각화 가능
for i in range(1, columns*rows+1):
data_idx = np.random.randint(len(train_data))
img = train_data[data_idx][0][0,:,:].numpy() # numpy()를 통해 torch Tensor를 numpy array로 변환
label = labels_map[train_data[data_idx][1]] # item()을 통해 torch Tensor를 숫자로 변환
fig.add_subplot(rows, columns, i)
plt.title(label)
plt.imshow(img, cmap='gray')
plt.axis('off')
plt.show()
>>
- img(이미지)는 파이토치 Tensor형태이기 때문에, numpy() 함수를 사용하여 matplotlib과 호환되는 numpy 배열로 변환해야 한다. + 이미지 데이터 = (가로, 세로, 색상), 흑백 = 1로 표현
- plt.figure()를 통해 이미지를 출력할 도화지 생성 - matplotlib
- add_subplot() : 생성한 figure 객체에 대해 도화지 속에 일부 이미지를 삽입 가능하도록 한다.
→ 즉, 큰 도화지 위에 작은 이미지를 구역마다 출력할 수 있도록 한다.
5. DNN 모델 설계 ***
class DNN(nn.Module):
# 초기화
def __init__(self, num_classes=10):
# super(부모님) 꼭 호출! (DNN(자기자신) 넣어서)
super(DNN, self).__init__()
# 시퀀스를 하나의 레이어로 묶을 수 있다
self.layer1 = nn.Sequential(
# layer_1
nn.Linear(28*28, 512),
nn.BatchNorm1d(512), # BatchNorm 적용
nn.ReLU() # ReLU
)
self.layer2 = nn.Sequential(
# layer_2
nn.Linear(512, 256),
nn.BatchNorm1d(256), # BatchNorm 적용
nn.ReLU() # ReLU
)
self.layer3 = nn.Sequential(
# layer_3
nn.Linear(256,10) # 마지막 class는 10
)
# x값 받아서 forward
def forward(self, x):
x = x.view(x.size(0), -1) # flatten (데이터를 펼쳐서 집어넣는 애)
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
return out
- 3 layer를 거치면서 입력 feature의 수(= 입력 이미지의 픽셀의 수, 28x28)에서 최종 출력은 10개.
- Classification 네트워크의 마지막 activation function은 주로 softmax가 적용되기 때문에, 마지막을 제외한 모든 layer에 ReLU, batchnorm을 적용시킨다.
- CrossEntropy Loss function에 softmax function 포함되기 때문에, 별도 선언은 불필요하다
6. Weight initialization 정의 및 모델 선언
- Network 내 weight를 Xavier_normal를 통해 초기화
- model.apply( )를 통해서 가중치 초기화를 적용할 수 있다.
# Weigh 초기화 함수 선언
def weights_init(m):
if isinstance(m, nn.Linear): # 모델의 모든 MLP 레이어에 대해서
# Weight를 xavier_normal로 초기화
nn.init.xavier_normal_(m.weight) # 언더바_ 가 맨 뒤에 붙으면 "실제 그 값을 바꾼다"라는 뜻
# 앞서 생성한 DNN 함수 호출
model = DNN().to(device)
# 모델에 weight_init 함수를 적용하여 weight를 초기화
model.apply(weights_init)
>>
DNN(
(layer1): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): BatchNorm1d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(layer2): Sequential(
(0): Linear(in_features=512, out_features=256, bias=True)
(1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
)
(layer3): Sequential(
(0): Linear(in_features=256, out_features=10, bias=True)
)
)
7. Loss function과 Optimizer 정의
- 생성한 모델 학습을 위해 손실함수(loss funcion)을 정의
- Neural Network는 경사하강(Gradient descent) 방법을 이용하여 손실함수의 값을 줄이는 방향으로 parameter를 update 하게 된다. -> 이 때, Optimizer를 사용한다.
# 손실함수 정의
criterion = nn.CrossEntropyLoss()
# 손실함수의 최소값을 찾아주는 알고리즘 Adam
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
8. Training (학습)
데이터를 공급해주는 train_loader가 for문을 통해 mini-batch만큼 데이터를 가져오면 위에 정의한 model에게 전달하고, 출력값을 loss function을 통해 손실값(loss값)을 얻어낸다.
→ 모델은 이 loss값이 적어지는 방향으로 parameter를 업데이트하며, 이 수행은 optimizer가 한다.
for epoch in range(num_epochs):
# 모델 train mode
model.train()
for i, (imgs, labels) in enumerate(train_loader):
imgs, labels = imgs.to(device), labels.to(device)
outputs = model(imgs)
loss = criterion(outputs, labels)
optimizer.zero_grad() # 이전에 계산된 gradient 모두 초기화
loss.backward() # 가중치 계산
optimizer.step() # optimizer를 통해 parameter 업그레이드
_, argmax = torch.max(outputs, 1)
accuracy = (labels == argmax).float().mean()
if (i+1) % 100 == 0:
print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}, Accuracy: {:.2f}%'.format(
epoch+1, num_epochs, i+1, len(train_loader), loss.item(), accuracy.item() * 100))
- outputs = model(imgs) : model에 imgs 데이터를 주고, 그 출력을 outputs 변수에 저장
- loss = criterion(outputs, labels)
: 모델의 outputs과 train_loader에서 제공된 실제값 labels를 통해, 그 차이로 손실값(loss)를 구하고 그 결과를 loss 변수에 저장한다.
>>
Epoch [1/5], Step [100/600], Loss: 0.4120, Accuracy: 85.00%
Epoch [1/5], Step [200/600], Loss: 0.4327, Accuracy: 85.00%
Epoch [1/5], Step [300/600], Loss: 0.3966, Accuracy: 86.00%
...
Epoch [5/5], Step [400/600], Loss: 0.3932, Accuracy: 83.00%
Epoch [5/5], Step [500/600], Loss: 0.3408, Accuracy: 88.00%
Epoch [5/5], Step [600/600], Loss: 0.3412, Accuracy: 88.00%
9. Test (테스트 및 성능도 체크)
- model.eval( ) - 모델을 평가(evaluation) 모드로 설정
: Batch norm과 dropout이 training을 수행할 때와 test의 경우 작동되는 방식이 서로 다르기 때문에 eval mode를 꼭 설정해야 한다. (test시, 일관된 결과를 얻기 위해)
- torch.no_grad( ) - torch.Tensor의 requires_grad를 False!
: test할 때, backpropagation으로 gradient를 계산할 필요가 없기 때문에 'requires_grad=False'으로 바꿔줌으로써 메모리 낭비하는 것을 방지할 수 있다.
# 모델 eval mode
model.eval()
test_loss = 0
with torch.no_grad():
correct = 0
total = 0
for i, (imgs, labels) in enumerate(test_loader):
imgs, labels = imgs.to(device), labels.to(device)
# 신경망에 이미지를 통과시켜서 출력 계산 - 데이터를 outputs에 삽입
outputs = model(imgs).to(device)
# loss율 계산
loss = criterion(outputs, labels)
# test_loss 업데이트
test_loss += loss.item()*imgs.size(0)
# max()를 통해 최종 출력이 가장 높은 class 선택
# 예측과 정답을 비교하여 일치할 경우, correct에 1을 더한다
_, argmax = torch.max(outputs, 1)
total += imgs.size(0)
correct += (labels == argmax).sum().item()
# test loss율 평균 계산
test_loss = test_loss/len(test_loader.dataset)
print('Test Loss : {:.4f}'.format(test_loss))
print(
'Test accuracy of the network on the {} test images: {:.2f}%'.format(total, correct / total * 100))
>>
Test Loss : 0.3500
Test accuracy of the network on the 10000 test images: 87.64%
→ 최종 성능이 대략 88% 전후로 나온다면 학습이 잘 된 것으로 해석할 수 있다.
10. 성능도(Accuracy) 향상!
→ Dropout, Data Augmentation을 적용 후, 모델 성능도 향상되는 것을 확인해보자
10-1. Data Augmentation 적용
transform = transforms.Compose(
[transforms.ToTensor(), # Tensor로 변환
transforms.RandomRotation(30), # 랜덤 각도로 회전
transforms.RandomHorizontalFlip(), # 랜덤으로 수평 뒤집기
transforms.Normalize(mean=[0.5,], std=[0.5,]) # RGB의 모든 픽셀 정규화
])
10-2. Dropout 적용 (Overfitting 방지)
self.layer1 = nn.Sequential(
# layer_1
nn.Linear(28*28, 512),
nn.BatchNorm1d(512), # BatchNorm 적용
nn.ReLU(), # ReLU
nn.Dropout(0.25) # Dropout 적용(0.25 비율)
)
self.layer2 = nn.Sequential(
# layer_2
nn.Linear(512, 256),
nn.BatchNorm1d(256), # BatchNorm 적용
nn.ReLU(), # ReLU
nn.Dropout(0.25) # Dropout 적용
)
10-3. Training과 Test 후 Loss값과 Accuracy 확인
>>
# Training part
Epoch [1/50], Step [100/600], Loss: 0.6822, Accuracy: 70.00%
Epoch [1/50], Step [200/600], Loss: 0.6680, Accuracy: 75.00%
Epoch [1/50], Step [300/600], Loss: 0.6136, Accuracy: 80.00%
...
Epoch [50/50], Step [400/600], Loss: 0.4216, Accuracy: 85.00%
Epoch [50/50], Step [500/600], Loss: 0.3225, Accuracy: 87.00%
Epoch [50/50], Step [600/600], Loss: 0.1827, Accuracy: 94.00%
# Test part
Test Loss : 0.1682
Test accuracy of the network on the 10000 test images: 95.28%
- epoch이 증가할수록 점차 Loss값은 감소하고 Accuracy는 증가하는 것을 확인할 수 있다.
- 모델에 Augmentation과 Dropout을 적용 후 높은 성능에 도달하는데, 어느 정도 시간이 소요됨으로, epoch을 더 늘리는 것이 좋다. (위의 실습은 epoch = 50)
[ 참고 : 파이토치로 시작하는 딥러닝 기초(boostcourse), 모두의딥러닝 ]
댓글