비둘기 둥지

[인공지능 기초 / pytorch] 3. DNN 본문

인공지능 공부/Pytorch

[인공지능 기초 / pytorch] 3. DNN

KimDove 2022. 6. 23. 13:36
728x90

1.Fashion MNIST 데이터 셋 살펴보기

  • 28 x 28 픽셀 70,000개의 흑백 이미지로 구성된 이미지 데이터 셋
  • 레이블은 신발, 드레스, 가방 등 총 10가지 카테고리 존재

< ! > 이미지 데이터를 다루기 위한 파이토치, 토치비전 모듈들

  • torch.utils.data
    → 데이터 셋의 표준을 정의, 로딩, 셔플에 쓰는 도구들이 들어있는 모듈
    → 파이토치 모델을 학습시키기 위한 데이터 셋 표준을 torch.utils.data.Dataset에 정의
    → 데이터 셋 모듈을 상속하는 파생 클래스는 학습에 필요한 데이터를 로딩해주는 
         torch.utils.data.DataLoader 인스턴스 입력으로 사용
  • torchvision.datasets
    → torch.utils.data.Dataset을 상속하는 이미지 데이터 셋 모음
    → Fashion MNIST 데이터 셋을 포함하고 있음.
  • torchvision.transforms
    → 이미지 데이터 셋에 쓸 수 있는 여러 변활 필터를 담고 있는 모듈
    → 크기 조절, 크롭 등 이미지를 수정할 수 있고, 밝기, 대비도 조절할 수 있음.
  • torchvision.utils
    → 이미지 데이터를 저장하고 시각화 하기 위한 도구가 들어있는 모듈
from torchvision import datasets, transforms, utils
import matplotlib.pyplot as plt
from torch.utils import data
import numpy as np
## 이미지를 텐서로 변환해주는 코드 
## transforms.Resize(), transforms.Normalize() 등 사용할 수 있다.
## Compose 함수 안에 리스트로 입력하면 순서대로 변환이 이루어짐.

transform = transforms.Compose([
                                 transforms.ToTensor()
                                ])

## 학습용, 검증용 Fashion MNIST 데이터 셋 다운받아주는 함수.
## download = True | 데이터 셋을 root로 지정한 경로에 저장
## train    = True | 학습용 데이터 셋일때 사용

train_dataset = datasets.FashionMNIST(
     root      = './dataset',
     train     = True,
     download  = True,
     transform = transform
 )

valid_dataset = datasets.FashionMNIST(
     root      = './dataset',
     train     = False,
     download  = True,
     transform = transform
 )
 
 ## 출력 결과

[그림 1] Fashion MNIST 다운로드하는 로그

< ! >  torchvision.Transforms에서 자주 사용되는 함수들

함수 이름 설명
ToTensor 데이터를 파이토치 텐서로 변환
Resize 이미지 크기 조정
Normalize 주어진 평균과 표준편차를 이용하여 정규화
RandomHorizontalFlip 무작위로 이미지의 좌우를 반전시킴
RandomCrop 이미지를 무작위로 잘라주는 기능

 

## torchvision.datasets로 생성한 데이터 셋은 torch.utils.data.Dataset에 넣어 바로 사용할 수 있다.
## batch_size도 인자값으로 입력하여 한 번에 불러올 데이터의 갯수를 지정할 수 있다.
batch_size = 16

train_loader = data.DataLoader(
    dataset    = train_dataset,
    batch_size = batch_size
)

valid_loader = data.DataLoader(
    dataset    = valid_dataset,
    batch_size = batch_size
)

## 배치 한 개만 뽑아 데이터를 확인해 봄.
sample_data    = iter(train_loader)
images, labels = next(sample_data)

## 여러 이미지를 모아 하나의 이미지로 생성 가능
image   = utils.make_grid(images, padding = 0)

## image는 파이토치 텐서 : matplotlib과 호환 X → matplotlib과 호환되는numpy 행렬로 변환
np_img  = image.numpy()

plt.figure(figsize = (10, 7))
## matplotlib이 인식하는 순서가 달라 np.transpose 함수를 사용하여 첫 번째 차원을 맨 뒤로 보냄.
plt.imshow(np.transpose(np_img, (1, 2, 0)))
plt.show()

print(f'\nlabels : {labels}, length of labels : {len(labels)}')

[그림 2] Fashion MNIST 데이터 셋 16개만 꺼내와 출력

## 각 인덱스 번호에 맞는 레이블을 지정한 딕셔너리
CLASSES = {
    0: 'T-shirt/top',
    1: 'Trouser',
    2: 'Pullover',
    3: 'Dress',
    4: 'Coat',
    5: 'Sandal',
    6: 'Shirt',
    7: 'Sneaker',
    8: 'Bag',
    9: 'Ankle Boot'
}

print(f'labels : {[CLASSES[int(idx)] for idx in labels]}')

## 출력 결과
labels : ['Ankle Boot', 'T-shirt/top', 'T-shirt/top', 'Dress', 'T-shirt/top', 'Pullover', 'Sneaker', 'Pullover', 'Sandal', 'Sandal', 'T-shirt/top', 'Ankle Boot', 'Sandal', 'Sandal', 'Sneaker', 'Ankle Boot']

2. Fashion MNIST 분류모델 학습

  • torch.cuda.is_available() 함수를 사용해 CUDA를 이용할 수 있는지 알 수 있음.
from torchvision import transforms, datasets
import torch.nn.functional as F
import torch.optim as optim
import torch.nn as nn
import torch

USE_CUDA = torch.cuda.is_available()
DEVICE = torch.device("cuda" if USE_CUDA else "cpu")

DEVICE

## 출력 결과
device(type='cuda')

2-1. 이미지 분류를 위한 인공 신경망 구현

## 하이퍼 파라미터 지정
#! CPU, GPU의 메모리 크기가 2의 배수이기 때문에 배치 크기가 2의 거듭제곱이면 메모리 면에서 데이터를 
## 주고받는 효율을 높일 수 있다고 한다.
EPOCHS, BATCH_SIZE, LR = 30, 64, 1e-2

class NN(nn.Module):
  def __init__(self):
    super(NN, self).__init__()

    ## 입력 이미지 데이터가 28 x 28 x 1 = 784이기 때문에
    self.fc1 = nn.Linear(784, 256)
    self.fc2 = nn.Linear(256, 128)
    
    ## 분류해야하는 라벨의 갯수가 10개 이므로. 
    self.fc3 = nn.Linear(128, 10)

  def forward(self, x):
    x = x.view(-1, 784)
    x = F.relu(self.fc1(x))
    x = F.relu(self.fc2(x))
    x = self.fc3(x)

    return x

2-2. 학습 및 시험 과정 작성

  • torch_ANN에서는 이진분류로 binary cross entropy를 사용하였다.
  • 하지만 이번 작업에서는 10개의 레이블을 분류하는 다중 분류로 cross entropy를 사용한다.

< ! > 크로스 엔트로피 참조 | [김성훈 교수님 강의]

## to() 함수를 통해 모델 학습을 CPU로 진행할지, GPU로 진행할지 지정해줌.
model = NN().to(DEVICE)
optimizer = optim.SGD(model.parameters(), lr = LR)

## 학습 함수
def train(model, train_loder, optim):
  ## 모델을 학습 모드로 전환
  model.train()
  train_loss, correct = 0, 0

  for batch_idx, (data, lb) in enumerate(train_loader):
    data, lb = data.to(DEVICE), lb.to(DEVICE)
    optimizer.zero_grad()

    output = model(data)
    loss   = F.cross_entropy(output, lb)
    loss.backward()

    optimizer.step()

    ## output.max는 가장 큰 값과 인덱스 값을 반환해준다.
    pred     = output.max(1, keepdim = True)[1]

    ## eq() 함수는 값이 일치하면 1, 아니면 0을 출력
    ## sum() 함수를 거쳐 미니 배치에서 모델이 정답을 맞힌 개수를 구할 수 있다.
    ## view_as() 함수는 인자값에 들어가는 텐서의 크기대로 바꿔준다.
    correct   += pred.eq(lb.view_as(pred)).sum().item()
    train_loss += loss 

  train_loss /= len(train_loader.dataset)
  train_acc   = 100 * correct / len(train_loader.dataset)

  return train_loss, train_acc
  • 모델을 학습 시킬때 학습 데이터에 최적화한 모델이 아니라 모든 데이터에서 높은 성능을 보여야 한다.
  • 모든 데이터에 최적화하는 것을 일반화 (generalization)이라 한다.
  • 학습 데이터를 기반으로 한 모델이 학습하지 않은 데이터에 얼마나 적응하는지를 수치로 나타낸 것을
    일반화 오류(generlization error)라고 한다.
  • 모델 층의 종류와 크기, 배치 크기, 학습률 등 머신러닝 모델이 배우지 않고 사용자가 지정해주는 값을
    하이퍼 파라미터(hyper parameter)라고 한다.
## 시험 함수
def evaluate(model, test_loader):
  
  ## 모델을 평가 모드로 바꿈.
  test_loss, correct = 0, 0

  ## 평가 과정에서는 기울기를 계산하지 않아도 되므로 torch.no_grad를 수행해줌.
  with torch.no_grad():
    for data, lb in test_loader:
      data, lb = data.to(DEVICE), lb.to(DEVICE)
      output   = model(data)

      ## 모든 오차 더하기
      ## reduction = 'sum'을 지정해주어 미니배치의 평균 대신 합을 받아온다.
      test_loss += F.cross_entropy(output, lb, reduction = 'sum').item()

      ## output.max는 가장 큰 값과 인덱스 값을 반환해준다.
      pred     = output.max(1, keepdim = True)[1]
      
      ## eq() 함수는 값이 일치하면 1, 아니면 0을 출력
      ## sum() 함수를 거쳐 미니 배치에서 모델이 정답을 맞힌 개수를 구할 수 있다.
      ## view_as() 함수는 인자값에 들어가는 텐서의 크기대로 바꿔준다.
      correct += pred.eq(lb.view_as(pred)).sum().item()

  test_loss /= len(test_loader.dataset)
  test_acc   = 100 * correct / len(test_loader.dataset)

  return test_loss, test_acc
## 학습을 돌려보자
for epoch in range(1, EPOCHS + 1):
  train_loss, train_acc = train(model, train_loader, optim)
  test_loss, test_acc = evaluate(model, valid_loader)

  print(f'[{epoch} / {EPOCHS}] \nTrain Loss : {train_loss:.3f} | Train Acc : {train_acc:.3f} \nTest Loss : {test_loss:.3f} | Test Acc : {test_acc:.3f}')
  
## 출력 결과
[1 / 30] 
Train Loss : 0.014 | Train Acc : 70.517 
Test Loss : 0.656 | Test Acc : 76.650
[2 / 30] 
Train Loss : 0.008 | Train Acc : 80.910 
Test Loss : 0.536 | Test Acc : 80.480
[3 / 30] 
Train Loss : 0.008 | Train Acc : 83.017 
Test Loss : 0.489 | Test Acc : 82.620
[4 / 30] 
Train Loss : 0.007 | Train Acc : 84.153 
Test Loss : 0.606 | Test Acc : 76.860
[5 / 30] 
Train Loss : 0.007 | Train Acc : 84.948 
Test Loss : 0.458 | Test Acc : 83.940

(... 중략 ...)

[29 / 30] 
Train Loss : 0.004 | Train Acc : 90.772 
Test Loss : 0.333 | Test Acc : 88.170
[30 / 30] 
Train Loss : 0.004 | Train Acc : 90.755 
Test Loss : 0.335 | Test Acc : 88.250
  • 학습 데이터 셋에 대해서만 모델 성능이 제대로 나오고, 시험용 데이터 셋에 대해서 성능이 나오지 않는 경우 
    과대 적합(overfitting)이라 한다.
    < ! > 너무 학습 데이터에만 치중되어 유연성이 부족해지고, 새로운 데이터에 대해 성능이 잘 나오지 않는 현상이다.
  • 과적합과 반대로 학습을 제대로 진행하지 않은 상황을 과소 적합(underfitting)현상이라고 한다.
  • 검증 데이터셋에 대한 성능이 나빠지기 시작하기 직전이 가장 적합한 모델이다.
  • 이 타이밍에 모델을 저장하여 이용하는 것을 조기 종료 (early stopping)이라 한다.

[그림 3] overfitting이 발생하면 나타나는 재밌는 예

  • 이 경우는 데이터가 너무 적어 발생한 overfitting의 예시로 보면된다.

2-3. 과대 적합 피하는 방법

  • 과대 적합을 막는 방법으로는 두 가지 방법이 있다.
    (1) 데이터를 늘리는 방법(data augmentation)
    (2) 드랍아웃(dropout)을 모델 구조에 적용

2-3.1. 데이터 늘리기

  • 세상의 모든 데이터를 모으고 레이블링 하는 것은 불가능 하기 때문에, 이미 가진 데이터를 최대한 늘려야한다.
  • 파이토치에서 데이터를 늘리는 방법으로는 토치비전의 transforms 패키지를 사용한다.

[그림 4] 데이터를 모두 모아 레이블링 해야한다면...

train_loader = torch.utils.data.DataLoader(
    datasets.FashionMNIST('./.data',
                  train     = True, 
                  download  = True,
                  transform = transforms.Compose([
                                          transforms.RandomHorizontalFlip(),
                                          transforms.ToTensor(),
                                          transforms.Normalize((0.1307, ), (0.3081, ))          
                                        ])),
                  batch_size = BATCH_SIZE, shuffle = True)

valid_loader = torch.utils.data.DataLoader(
    datasets.FashionMNIST('./.data',
                  train     = False, 
                  download  = True,
                  transform = transforms.Compose([
                                          transforms.ToTensor(),
                                          transforms.Normalize((0.1307, ), (0.3081, ))          
                                        ])),
                  batch_size = BATCH_SIZE, shuffle = True)

2-3.2. 드롭아웃 (Dropout)

  • 드롭아웃은 학습 진행과정에서 신경망의 일부를 사용하지 않는 방법
  • 학습에서 배재된 뉴런 외에 다른 뉴런들에 가중치를 분산시키고 개별 뉴런이 특징에 고정되는 현상을 방지하는 기능
    < ! > 검증과 시험 단계에서는 모든 뉴런을 사용함.
class Net(nn.Module):

  def __init__(self, dropout_p = 0.2):
    super(Net, self).__init__()
    self.fc1 = nn.Linear(784, 256)
    self.fc2 = nn.Linear(256, 128)
    self.fc3 = nn.Linear(128, 10)

    ## 드롭아웃 지정
    self.dropout_p = dropout_p 

  def forward(self, x):
    x = x.view(-1, 784)
    x = F.relu(self.fc1(x))

    ## 드롭아웃 추가
    ## self.training은 model.train()인 경우 True, model.eval()인 경우 False로 변함.
    x = F.dropout(x, training = self.training, p = self.dropout_p)
    x = F.relu(self.fc2(x))

    x = F.dropout(x, training = self.training, p = self.dropout_p)
    x = self.fc3(x)

    return x

model = Net().to(DEVICE)
optimizer = optim.SGD(model.parameters(), lr = LR)
## 학습을 돌려보자
for epoch in range(1, EPOCHS + 1):
  train_loss, train_acc = train(model, train_loader, optim)
  test_loss, test_acc = evaluate(model, valid_loader)

  print(f'[{epoch} / {EPOCHS}] \nTrain Loss : {train_loss:.3f} | Train Acc : {train_acc:.3f} \nTest Loss : {test_loss:.3f} | Test Acc : {test_acc:.3f}')
  
## 출력 결과
[1 / 30] 
Train Loss : 0.016 | Train Acc : 65.202 
Test Loss : 0.703 | Test Acc : 75.560
[2 / 30] 
Train Loss : 0.010 | Train Acc : 78.405 
Test Loss : 0.590 | Test Acc : 78.910
[3 / 30] 
Train Loss : 0.008 | Train Acc : 81.563 
Test Loss : 0.524 | Test Acc : 81.400
[4 / 30] 
Train Loss : 0.008 | Train Acc : 82.907 
Test Loss : 0.497 | Test Acc : 82.400

(... 중략 ...)

[27 / 30] 
Train Loss : 0.005 | Train Acc : 89.115 
Test Loss : 0.368 | Test Acc : 86.930
[28 / 30] 
Train Loss : 0.005 | Train Acc : 89.272 
Test Loss : 0.357 | Test Acc : 87.350
[29 / 30] 
Train Loss : 0.005 | Train Acc : 89.367 
Test Loss : 0.355 | Test Acc : 87.440
[30 / 30] 
Train Loss : 0.005 | Train Acc : 89.377 
Test Loss : 0.357 | Test Acc : 86.980

99. 참고자료

99-1. 도서

  • 한빛 미디어 | 펭귄브로의 3분 딥러닝 - 파이토치 맛

99-2. 웹사이트

99-3. 데이터 셋

  • torchvision | Fashion MNIST

전체코드

 

GitHub - EvoDmiK/TIL: Today I Learn

Today I Learn. Contribute to EvoDmiK/TIL development by creating an account on GitHub.

github.com


내용 추가 이력


부탁 말씀

개인적으로 공부하는 과정에서 오류가 있을 수 있으니, 오류가 있는 부분은 댓글로 정정 부탁드립니다.

728x90
Comments