pytorch fashion-mnist 실습¶
학습 목표¶
- pytorch를 활용하여 모델을 구축해 본다.
- 데이터 셋은 fashion-mnist를 활용한다.
- torchvision
- torchvision 패키지는 컴퓨터 비전을 위한 데이터 셋, 모델 구조, 컴퓨터 비전을 위한 여러가지 기능 패키지
- torchvision을 이용하여 CIFAR10 훈련, 테스트 데이터 셋을 불러오고 정규화한다.
- 설치 : pip install torchvision
In [2]:
import numpy as np
import torch
import torchvision
from torch.utils.data import Dataset
from torchvision import datasets, transforms
print(torch.__version__)
print(torchvision.__version__)
2.3.1+cu121 0.18.1+cu121
Image Transform¶
- torchvision의 transforms를 활용하여 정규화를 적용가능
- transforms.ToTensor()
- 정규화(Normalize)한 결과가 0~1 범위로 변환
In [3]:
transform = transforms.Compose([
transforms.ToTensor(),
])
Fashion MNIST 데이터셋 로드¶
학습용 및 테스트용 데이터 셋 가져오기¶
In [4]:
train_data = datasets.FashionMNIST(root='data',
train=True, # 학습용 데이터셋 설정(True)
download=True,
transform=transform # 정규화
)
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to data/FashionMNIST/raw/train-images-idx3-ubyte.gz
100%|██████████| 26421880/26421880 [00:00<00:00, 45746159.06it/s]
Extracting data/FashionMNIST/raw/train-images-idx3-ubyte.gz to data/FashionMNIST/raw Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw/train-labels-idx1-ubyte.gz
100%|██████████| 29515/29515 [00:00<00:00, 6784024.69it/s]
Extracting data/FashionMNIST/raw/train-labels-idx1-ubyte.gz to data/FashionMNIST/raw Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz
100%|██████████| 4422102/4422102 [00:00<00:00, 8475887.31it/s]
Extracting data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to data/FashionMNIST/raw Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz
100%|██████████| 5148/5148 [00:00<00:00, 18470724.54it/s]
Extracting data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to data/FashionMNIST/raw
In [5]:
test_data = datasets.FashionMNIST(root='data',
train=False, # 검증용 데이터셋 설정(False)
download=True,
transform=transform
)
데이터 시각화¶
In [6]:
import matplotlib.pyplot as plt
In [9]:
class_names = {
0: "t-shirt/top", # 티셔츠/상의
1: "trouser", # 바지
2: "pullover", # 풀오버 (스웨터)
3: "dress", # 원피스
4: "coat", # 코트
5: "sandal", # 샌들
6: "shirt", # 셔츠
7: "sneaker", # 운동화
8: "bag", # 가방
9: "ankle boot", # 앵클 부츠
}
In [14]:
plt.figure(figsize=(10,10))
for i in range(25):
plt.subplot(5,5,i+1) # 그래프의 표시 위치
img, label = train_data[i]
plt.xticks([])
plt.yticks([])
plt.grid(False) # 그리드선
plt.imshow(torch.permute(img, (1, 2, 0)), cmap=plt.cm.binary)
plt.xlabel(class_names[label])
plt.show()
In [15]:
import os
os.cpu_count()
Out[15]:
2
In [16]:
batch_size = 32 # batch_size 지정
num_workers = 8 # Thread 숫자 지정
In [17]:
train_loader = torch.utils.data.DataLoader(train_data,
batch_size=batch_size,
shuffle=True,
num_workers=num_workers)
/usr/local/lib/python3.10/dist-packages/torch/utils/data/dataloader.py:558: UserWarning: This DataLoader will create 8 worker processes in total. Our suggested max number of worker in current system is 2, which is smaller than what this DataLoader is going to create. Please be aware that excessive worker creation might get DataLoader running slow or even freeze, lower the worker number to avoid potential slowness/freeze if necessary. warnings.warn(_create_warning_msg(
In [18]:
test_loader = torch.utils.data.DataLoader(test_data,
batch_size=batch_size,
shuffle=False,
num_workers=num_workers)
train_loader 활용하여 하나의 배치 확인¶
In [19]:
# 1개의 배치 추출 후 Image, label의 shape 출력
img, lbl = next(iter(train_loader))
img.shape, lbl.shape
Out[19]:
(torch.Size([32, 1, 28, 28]), torch.Size([32]))
- 배치 사이즈가 32, 채널(1), 세로(28), 가로(28)
- cuda 설정이 있다면 cuda
- cpu 설정이 있다면 cpu로 학습 진행
In [20]:
torch.cuda.is_available()
Out[20]:
False
In [21]:
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
cpu
- GPU가 지정되어 있다면 cuda:0, 2대는 cuda:1로 지정
In [22]:
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
- torch.nn.functional : https://pytorch.org/docs/stable/nn.functional.html
In [23]:
class DNNModel(nn.Module):
def __init__(self):
super(DNNModel, self).__init__()
self.fc1 = nn.Linear(28*28, 128)
self.fc2 = nn.Linear(128, 32)
self.output = nn.Linear(32, 10)
def forward(self, x):
# 텐서는 같지만 새로운 텐서 반환(모양 변환)
x = x.view(-1, 28*28)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.output(x)
return x
In [24]:
model = DNNModel() # Model 생성
model.to(device) # device 로드
Out[24]:
DNNModel( (fc1): Linear(in_features=784, out_features=128, bias=True) (fc2): Linear(in_features=128, out_features=32, bias=True) (output): Linear(in_features=32, out_features=10, bias=True) )
최적화함수(optimizer) 및 손실함수(loss function)¶
- 옵티마이저는 model.parameters()를 지정해야 한다.
- 다항분류이므로 CrossEntropy 손실을 지정한다.
In [25]:
optimizer = optim.Adam(model.parameters(), lr=0.0005)
loss_fn = nn.CrossEntropyLoss()
In [26]:
from tqdm import tqdm
model.train()
- 모델을 학습 모드로 설정한다. 학습 모드일때 Gradient가 업데이트가 가능하다. 반드시 train()로 변경 필요.
optimizer.zero_grad()
- Gradient를 초기화
- 한번의 학습이 완료가 되면, gradients를 항상 0으로 만들어 주어야 한다.
- 만약 0으로 초기화가 되지 않으면 의도한 방향과 다른 방향으로 가게 될 가능성이 있다.
loss = loss_fn(output, lbl)
- 손실함수를 이용하여 손실계산
loss.backward() : 예측 손실을 역전파. Pytorch는 각 매개변수에 대한 손실의 변화도를 저장.
optimizer.step() : 역전파 단계에서 Gradient를 업데이트 시키기.
total_batch_loss += loss.item() * img.size(0)
- loss.item()은 1개 배치의 평균 손실(loss)
- img.size(0)은 배치사이즈(batch size)
- 1개 배치의 전체 손실을 구해서 계속 더해준다.
acc = total_num / len(data_loader.dataset)
- total_num : 전체 누적된 잘 맞춘 개수
- len(data_loader.dataset) : 전체 데이터 개수
In [27]:
def model_train(model, data_loader, loss_fn, optimizer, device):
model.train()
# 초기화
total_batch_loss = 0 # 총 손실
total_num = 0 # 정답 개수
prograss_bar = tqdm(data_loader)
# mini-batch 학습
for img, lbl in prograss_bar:
# image, label 데이터를 device에 올리기.
img, lbl = img.to(device), lbl.to(device)
optimizer.zero_grad() # 그래디언트 초기화
output = model(img) # Forward Propagation을 진행. 결과 획득.
loss = loss_fn(output, lbl) # 손실 계산
loss.backward() # 오차역전파(Back Propagation) 진행. 미분 값을 계산
optimizer.step() # 가중치 업데이트
# output의 max(dim=1)은 max probability와 max index를 반환.
# max probability는 무시하고, max index는 pred에 저장
_, pred = output.max(dim=1)
# pred.eq(lbl).sum() 은 정확히 맞춘 label의 합계를 계산합니다.
# item()은 tensor에서 값을 추출.
total_num += pred.eq(lbl).sum().item() #
# 이를 누적한 뒤 Epoch 종료시 전체 데이터셋의 개수로 나누어 평균 loss를 산출합니다.
# total_batch_loss에 1개 배치의 전체 loss를 더하기
total_batch_loss += loss.item() * img.size(0)
acc = total_num / len(data_loader.dataset) # 정확도 계산
# 평균 손실(loss)과 정확도를 반환합니다.
# train_loss, train_acc
return total_batch_loss / len(data_loader.dataset), acc
model.eval()
- 모델을 평가모드로 변경합니다.
- dropout와 같은 layer의 역할 변경을 위해 evaluation 진행시 꼭 필요한 절차이다.
with torch.no_grad():
- Pytorch는 autograd engine를 off를 시킨다.
- 자동으로 gradient를 트래킹하지 않겠다라는 의미.
- 주된 목적은 autograd를 off를 시켜 메모리 사용량을 줄이고, 연산속도의 향상을 가져온다.
total_num += torch.sum(pred.eq(lbl)).item()
- pred.eq(lbl) : 정확하게 맞추어는가?
- torch.sum(pred.eq(lbl)).item() : 맞춘 개수
total_batch_loss += loss_fn(output, lbl).item() * img.size(0)
- loss_fn(output, lbl).item()은 1개 배치의 평균 손실(loss)
- img.size(0)은 배치사이즈(batch size)
- 1개 배치의 전체 손실을 구해서 계속 더해준다.
In [28]:
def model_evaluate(model, data_loader, loss_fn, device):
model.eval() # model.eval()은 모델을 평가모드로 설정 변경
with torch.no_grad():
# 손실과 정확도 계산을 위한 초기화
total_num = 0
total_batch_loss = 0
# 배치별 evaluation을 진행
for img, lbl in data_loader:
# device에 데이터를 올리기
img, lbl = img.to(device), lbl.to(device)
output = model(img) # Forward Propagation을 진행. 결과 획득.
# output의 max(dim=1)은 max probability와 max index를 반환.
_, pred = output.max(dim=1)
total_num += torch.sum(pred.eq(lbl)).item() # 정확한 것 개수 더하기(누적)
# 이를 누적한 뒤 Epoch 종료시 전체 데이터셋의 개수로 나누어 평균 loss를 산출합니다.
total_batch_loss += loss_fn(output, lbl).item() * img.size(0)
acc = total_num / len(data_loader.dataset) # 정확도 계산
# 결과를 반환 - val_loss, val_acc
return total_batch_loss / len(data_loader.dataset), acc
In [29]:
%%time
# epochs 지정, 최소 손실 초기화
num_epochs = 20
min_loss = np.inf
# Epoch 별 훈련 및 검증을 수행합니다.
for epoch in range(num_epochs):
# 모델 학습 - 학습 손실과 정확도를 얻기
train_loss, train_acc = model_train(model, train_loader, loss_fn, optimizer, device)
# 모델 검증 - 검증 손실과 검증 정확도를 얻기
val_loss, val_acc = model_evaluate(model, test_loader, loss_fn, device)
# val_loss가 개선시, model의 가중치(weights)를 저장.
if val_loss < min_loss:
print(f'[INFO] val_loss 개선 from {min_loss:.5f} to {val_loss:.5f}. 모델 저장!')
min_loss = val_loss
torch.save(model.state_dict(), 'DNNModel.pth')
# Epoch 별 결과를 출력
print(f"epoch {epoch+1:02d}")
print(f"loss: {train_loss:.5f}, acc: {train_acc:.5f}", end=" ")
print(f"val_loss: {val_loss:.5f}, val_accuracy: {val_acc:.5f}")
100%|██████████| 1875/1875 [00:18<00:00, 103.84it/s]
[INFO] val_loss 개선 from inf to 0.47117. 모델 저장! epoch 01 loss: 0.57661, acc: 0.80183 val_loss: 0.47117, val_accuracy: 0.83260
100%|██████████| 1875/1875 [00:19<00:00, 97.72it/s]
[INFO] val_loss 개선 from 0.47117 to 0.43217. 모델 저장! epoch 02 loss: 0.40842, acc: 0.85520 val_loss: 0.43217, val_accuracy: 0.84550
100%|██████████| 1875/1875 [00:17<00:00, 105.49it/s]
[INFO] val_loss 개선 from 0.43217 to 0.38365. 모델 저장! epoch 03 loss: 0.36454, acc: 0.86970 val_loss: 0.38365, val_accuracy: 0.86170
100%|██████████| 1875/1875 [00:18<00:00, 103.90it/s]
[INFO] val_loss 개선 from 0.38365 to 0.37367. 모델 저장! epoch 04 loss: 0.33754, acc: 0.87740 val_loss: 0.37367, val_accuracy: 0.86350
100%|██████████| 1875/1875 [00:17<00:00, 105.30it/s]
[INFO] val_loss 개선 from 0.37367 to 0.37178. 모델 저장! epoch 05 loss: 0.31868, acc: 0.88337 val_loss: 0.37178, val_accuracy: 0.86560
100%|██████████| 1875/1875 [00:18<00:00, 99.78it/s]
[INFO] val_loss 개선 from 0.37178 to 0.36091. 모델 저장! epoch 06 loss: 0.30062, acc: 0.89042 val_loss: 0.36091, val_accuracy: 0.87220
100%|██████████| 1875/1875 [00:18<00:00, 104.00it/s]
[INFO] val_loss 개선 from 0.36091 to 0.35075. 모델 저장! epoch 07 loss: 0.28793, acc: 0.89418 val_loss: 0.35075, val_accuracy: 0.87440
100%|██████████| 1875/1875 [00:19<00:00, 96.50it/s]
[INFO] val_loss 개선 from 0.35075 to 0.34312. 모델 저장! epoch 08 loss: 0.27585, acc: 0.89842 val_loss: 0.34312, val_accuracy: 0.87660
100%|██████████| 1875/1875 [00:17<00:00, 107.01it/s]
epoch 09 loss: 0.26662, acc: 0.90168 val_loss: 0.34368, val_accuracy: 0.87810
100%|██████████| 1875/1875 [00:17<00:00, 105.73it/s]
[INFO] val_loss 개선 from 0.34312 to 0.33519. 모델 저장! epoch 10 loss: 0.25649, acc: 0.90398 val_loss: 0.33519, val_accuracy: 0.88020
100%|██████████| 1875/1875 [00:17<00:00, 107.30it/s]
epoch 11 loss: 0.24658, acc: 0.90893 val_loss: 0.33741, val_accuracy: 0.87590
100%|██████████| 1875/1875 [00:17<00:00, 108.69it/s]
[INFO] val_loss 개선 from 0.33519 to 0.33344. 모델 저장! epoch 12 loss: 0.23930, acc: 0.91013 val_loss: 0.33344, val_accuracy: 0.88120
100%|██████████| 1875/1875 [00:18<00:00, 100.12it/s]
epoch 13 loss: 0.23230, acc: 0.91365 val_loss: 0.35067, val_accuracy: 0.87530
100%|██████████| 1875/1875 [00:17<00:00, 108.29it/s]
epoch 14 loss: 0.22548, acc: 0.91623 val_loss: 0.33544, val_accuracy: 0.88300
100%|██████████| 1875/1875 [00:17<00:00, 104.67it/s]
epoch 15 loss: 0.21749, acc: 0.91940 val_loss: 0.33798, val_accuracy: 0.88280
100%|██████████| 1875/1875 [00:17<00:00, 108.22it/s]
epoch 16 loss: 0.21204, acc: 0.92047 val_loss: 0.34565, val_accuracy: 0.88300
100%|██████████| 1875/1875 [00:21<00:00, 88.41it/s]
epoch 17 loss: 0.20549, acc: 0.92292 val_loss: 0.34130, val_accuracy: 0.88170
100%|██████████| 1875/1875 [00:17<00:00, 105.46it/s]
epoch 18 loss: 0.20131, acc: 0.92502 val_loss: 0.34430, val_accuracy: 0.88600
100%|██████████| 1875/1875 [00:18<00:00, 99.78it/s]
epoch 19 loss: 0.19553, acc: 0.92810 val_loss: 0.39169, val_accuracy: 0.87510
100%|██████████| 1875/1875 [00:18<00:00, 103.38it/s]
epoch 20 loss: 0.19025, acc: 0.92808 val_loss: 0.33803, val_accuracy: 0.88970 CPU times: user 3min 22s, sys: 25 s, total: 3min 47s Wall time: 6min 54s
모델의 가중치를 가져와 검증 손실과 검증 정확도를 계산¶
In [31]:
# 가중치 로드
model.load_state_dict(torch.load('DNNModel.pth'))
# 최종 검증 손실(validation loss)와 검증 정확도(validation accuracy)를 계산
loss, acc = model_evaluate(model, test_loader, loss_fn, device)
print(f'검증 손실: {loss:.5f}, 평가 정확도: {acc:.5f}')
검증 손실: 0.33344, 평가 정확도: 0.88120