쉬엄쉬엄블로그

Custom Dataset 및 Custom DataLoader 생성 본문

부스트캠프 AI Tech 4기

Custom Dataset 및 Custom DataLoader 생성

쉬엄쉬엄블로그 2023. 5. 25. 11:21
728x90

이 색깔은 주석이라 무시하셔도 됩니다.

PyToch의 DataLoader

DataLoader의 기본 구성 요소

dataset (★★★)

  • DataLoader에는 앞서 생성한 dataset 인스턴스가 들어감

batch_size (★★★)

  • 인자가 나타내고 있는 뜻 그대로 배치 사이즈를 의미

shuffle (★★★)

  • 데이터를 DataLoader에서 섞어서 사용하겠는지를 설정할 수 있음

sample / batch_sample (★)

  • sampler는 index를 컨트롤하는 방법
    • 데이터의 index를 원하는 방식대로 조정
    • index를 컨트롤하기 때문에 설정하고 싶다면 shuffle 파라미터는 False(기본값)이어야 함
  • 불균형 데이터셋의 경우, 클래스의 비율에 맞게끔 데이터를 제공해야할 필요가 있음
    • 이 때 사용하는 옵션이 sampler

num_workers (★★)

  • 데이터를 불러올 때 사용하는 서브 프로세스(subprocess) 개수
  • 무작정 num_workers를 높인다고 좋진 않음
    • 데이터를 불러올 때 CPU와 GPU 사이에서 많은 교류가 일어나면 오히려 병목이 생길 수 있음

출처 : https://deepinsight.tistory.com/106

collate_fn (★★)

출처 : https://coastalcreative.com

  • collate : 함께 합치다
  • map-style 데이터셋에서 sample list를 batch 단위로 바꾸기 위해 필요한 기능
  • zero-padding이나 Variable Size 데이터 등 데이터 사이즈를 맞추기 위해 사용

collate_fn을 활용하여 하나의 batch에 동일한 길이를 반환할 수 있도록 만들기

  • 완성 예시

          tensor([[0., 0.],
                  [1., 1.]]) tensor([0., 1.])
          tensor([[2., 2., 2., 0.],
                  [3., 3., 3., 3.]]) tensor([2., 3.])
          tensor([[4., 4., 4., 4., 4., 0.],
                  [5., 5., 5., 5., 5., 5.]]) tensor([4., 5.])
          tensor([[6., 6., 6., 6., 6., 6., 6., 0.],
                  [7., 7., 7., 7., 7., 7., 7., 7.]]) tensor([6., 7.])
          tensor([[8., 8., 8., 8., 8., 8., 8., 8., 8., 0.],
                  [9., 9., 9., 9., 9., 9., 9., 9., 9., 9.]]) tensor([8., 9.])
dataloader_example = torch.utils.data.DataLoader(dataset_example, batch_size=1)
for d in dataloader_example:
    print(d['X'])

# batch_size가 1일 때는 문제가 없어보임
tensor([[0.]])
tensor([[1., 1.]])
tensor([[2., 2., 2.]])
tensor([[3., 3., 3., 3.]])
tensor([[4., 4., 4., 4., 4.]])
tensor([[5., 5., 5., 5., 5., 5.]])
tensor([[6., 6., 6., 6., 6., 6., 6.]])
tensor([[7., 7., 7., 7., 7., 7., 7., 7.]])
tensor([[8., 8., 8., 8., 8., 8., 8., 8., 8.]])
tensor([[9., 9., 9., 9., 9., 9., 9., 9., 9., 9.]])
dataloader_example = torch.utils.data.DataLoader(dataset_example, batch_size=2)
for d in dataloader_example:
    print(d['X'])

# batch_size가 2가 되면 에러 발생
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-30-dcb26b561383> in <module>
      1 dataloader_example = torch.utils.data.DataLoader(dataset_example, batch_size=2)
----> 2 for d in dataloader_example:
      3     print(d['X'])

... 중략 ...

~/anaconda3/envs/pytorch/lib/python3.7/site-packages/torch/utils/data/_utils/collate.py in default_collate(batch)
     54             storage = elem.storage()._new_shared(numel)
     55             out = elem.new(storage)
---> 56         return torch.stack(batch, 0, out=out)
     57     elif elem_type.__module__ == 'numpy' and elem_type.__name__ != 'str_' \
     58             and elem_type.__name__ != 'string_':

RuntimeError: stack expects each tensor to be equal size, but got [1] at entry 0 and [2] at entry 1
  • collate_fn 적용하기

    # 적절한 collate_fn을 만듬 
    def my_collate_fn(samples): 
      collate_X = [] 
      collate_y = [] 
      # print('Original:\n', samples) 
      # print('-'*100) 
    
      max_len = 0 
      for sample in samples: 
          max_len = max(max_len, len(sample['X'])) 
      # print(max_len) 
    
      for sample in samples: 
          m = torch.nn.ConstantPad1d((0, max_len - len(sample['X'])), 0) 
          collate_X.append(m(sample['X'])) 
          collate_y.append(sample['y']) 
    
      # print('Collated:\n', [collate_X, collate_y]) 
      # print('-'*100) return {'X': torch.stack(collate_X), 'y': torch.stack(collate_y)} 
    
    # collate_fn을 적용하여 다시 데이터를 불러옴 
    dataloader_example = torch.utils.data.DataLoader(dataset_example, 
                                                   batch_size=2, 
                                                   collate_fn=my_collate_fn) 
    
    for d in dataloader_example: 
      print(d['X'], d['y']) 
    
    # 정상적으로 출력 
    tensor([[0., 0.],
          [1., 1.]]) tensor([0., 1.])
    tensor([[2., 2., 2., 0.],
          [3., 3., 3., 3.]]) tensor([2., 3.])
    tensor([[4., 4., 4., 4., 4., 0.],
          [5., 5., 5., 5., 5., 5.]]) tensor([4., 5.])
    tensor([[6., 6., 6., 6., 6., 6., 6., 0.],
          [7., 7., 7., 7., 7., 7., 7., 7.]]) tensor([6., 7.])
    tensor([[8., 8., 8., 8., 8., 8., 8., 8., 8., 0.],
          [9., 9., 9., 9., 9., 9., 9., 9., 9., 9.]]) tensor([8., 9.])

pin_memory

  • DataLoader에서 이걸 True로 바꾸면 Tensor를 CUDA 고정 메모리에 할당시킴
  • 고정된 메모리에서 데이터를 가져오기 때문에 데이터 전송이 훨씬 빠름
  • 일반적인 경우에는 많이 사용하지 않음

drop_last (★)

  • batch 단위로 데이터를 불러올 때 batch_size에 따라 마지막 batch의 길이가 달라질 수 있음.
  • batch의 길이가 다르면 loss를 구하기 까다로워지는 경우가 생기고, batch의 크기에 따른 의존도 높은 함수를 사용할 때 걱정이 되는 경우 drop_last를 통해 마지막 batch를 사용하지 않도록(생략하도록) 설정할 수 있음

time_out

  • 양수로 주어지는 경우, DataLoader가 data를 불러오는데 제한시간을 설정

worker_init_fn

  • num_worker가 개수라면, 이 파라미터는 어떤 worker를 불러올 것인가를 리스트로 전달

torchvision에서 제공하는 몇가지 transform 함수

  • transform을 하는 이유?
    • 딥러닝 학습을 위해선 고정된 입력값이 보장되어야 함
    • 하지만 수집한 모든 데이터의 크기가 동일하지 않을 수도 있음
      • 이미지의 경우, 정사각형 형태의 이미지가 있는 반면 직사각형 형태의 이미지가 있을 수 있음
    • PyTorch에서는 이를 위해 이미지를 Resize하는 함수 등을 제공

transforms.Resize (torchvision.transforms.Resize)

  • 이미지 사이즈 변환
  • transforms.Resize((200,200))(im)

transforms.RandomCrop (torchvision.transforms.RandomCrop)

  • 지정된 이미지를 임의의 위치에서 자름
  • transforms.RandomCrop((100,100))(im)

transforms.RandomRotation (torchvision.transforms.RandomRotation)

  • 지정된 이미지를 임의의 각도만큼 회전시킴
  • transforms.RandomRotation(30)(im)

transforms.ToTensor (torchvision.transforms.ToTensor)

  • 지정된 이미지를 tensor로 변환
    • 이미지를 하나씩 지정해줘야 함 (2-D array)
    • transforms.ToTensor()(im)

transforms.Compose (torchvision.transforms.Compose)

  • 여러 transforms들을 하나로 묶어서 처리
  • transforms.Compose([transforms.Resize((224,224)), transforms.RandomVerticalFlip(0.5), transforms.CenterCrop(150)])(im)

그 외 transforms

dir(transforms)

# ['AutoAugment',
#  'AutoAugmentPolicy',
#  'CenterCrop',
#  'ColorJitter',
#  'Compose',
#  'ConvertImageDtype',
#  'FiveCrop',
#  'GaussianBlur',
#  'Grayscale',
#  'InterpolationMode',
#  'Lambda',
#  'LinearTransformation',
#  'Normalize',
#  'PILToTensor',
#  'Pad',
#  'RandAugment',
#  'RandomAdjustSharpness',
#  'RandomAffine',
#  'RandomApply',
#  'RandomAutocontrast',
#  'RandomChoice',
#  'RandomCrop',
#  'RandomEqualize',
#  'RandomErasing',
#  'RandomGrayscale',
#  'RandomHorizontalFlip',
#  'RandomInvert',
#  'RandomOrder',
#  'RandomPerspective',
#  'RandomPosterize',
#  'RandomResizedCrop',
#  'RandomRotation',
#  'RandomSizedCrop',
#  'RandomSolarize',
#  'RandomVerticalFlip',
#  'Resize',
#  'Scale',
#  'TenCrop',
#  'ToPILImage',
#  'ToTensor',
#  'TrivialAugmentWide',
#  '__builtins__',
#  '__cached__',
#  '__doc__',
#  '__file__',
#  '__loader__',
#  '__name__',
#  '__package__',
#  '__path__',
#  '__spec__',
#  'autoaugment',
#  'functional',
#  'functional_pil',
#  'functional_tensor',
#  'transforms']

PyTorch의 여러가지 Dataset

torchvision에서의 Dataset

MNIST

  • MNIST Dataset
    dataset_train_MNIST = torchvision.datasets.MNIST('data/MNIST/', # 다운로드 경로 지정
                                                   train=True, # True를 지정하면 훈련 데이터로 다운로드
                                                   transform=transforms.ToTensor(), # 텐서로 변환
                                                   download=True, 
                                                  )
  • MNIST DataLoader
    dataloader_train_MNIST = DataLoader(dataset=dataset_train_MNIST,
                                      batch_size=16,
                                      shuffle=True,
                                      num_workers=4,
                                     )

torchtext에서의 Dataset

AG_NEWS

  • AG_NEWS Dataset

    # !pip install torchdata
    dataset_train_AG_NEWS, dataset_test_AG_NEWS = torchtext.datasets.AG_NEWS(root='./data')
    classes = ['World', 'Sports', 'Business', 'Sci/Tech']
    
    dataset_train_AG_NEWS = list(dataset_train_AG_NEWS)
    dataset_test_AG_NEWS = list(dataset_test_AG_NEWS)
  • AG_NEWS DataLoader

    # this collate function gets list of batch_size tuples, and needs to 
    # return a pair of label-feature tensors for the whole minibatch
    def bowify(b):
      return (
              torch.stack([to_bow(t[1]) for t in b]),
              torch.LongTensor([t[0]-1 for t in b]),
      )
    
    dataset_train_AG_NEWS = list(dataset_train_AG_NEWS)
    dataset_test_AG_NEWS = list(dataset_test_AG_NEWS)

PyTorch의 Custom Dataset 과 DataLoader

(정형 데이터) Titanic 데이터로 Dataset과 DataLoader 만들어보기

Titanic 데이터 다운로드

dataset_train_titanic = TitanicDataset('./data/titanic/train.csv', 
                                       drop_features=['PassengerId', 'Name', 'Ticket', 'Cabin'],
                                       train=True)

(이미지 데이터) MNIST 데이터로 Dataset과 DataLoader 만들어보기

MNIST 데이터 다운로드

BASE_MNIST_PATH = '/data/MNIST/MNIST/raw'
TRAIN_MNIST_IMAGE_PATH = os.path.join(BASE_MNIST_PATH, 'train-images-idx3-ubyte.gz')
TRAIN_MNIST_LABEL_PATH = os.path.join(BASE_MNIST_PATH, 'train-labels-idx1-ubyte.gz')
TEST_MNIST_IMAGE_PATH = os.path.join(BASE_MNIST_PATH, 't10k-images-idx3-ubyte.gz')
TEST_MNIST_LABEL_PATH = os.path.join(BASE_MNIST_PATH, 't10k-labels-idx1-ubyte.gz')

TRAIN_MNIST_PATH = {
    'image': TRAIN_MNIST_IMAGE_PATH,
    'label': TRAIN_MNIST_LABEL_PATH
}

dataset_train_MNIST = torchvision.datasets.MNIST('/data', # 다운로드 경로 지정
                                                 train=True, # True를 지정하면 훈련 데이터로 다운로드
                                                 transform=transforms.ToTensor(), # 텐서로 변환
                                                 download=True, 
                                                )

# MNIST RAW 데이터를 가져오는 함수
# https://stackoverflow.com/questions/40427435/extract-images-from-idx3-ubyte-file-or-gzip-via-python
def read_MNIST_images(path):
    with gzip.open(path, 'r') as f:
        # first 4 bytes is a magic number
        magic_number = int.from_bytes(f.read(4), 'big')
        # second 4 bytes is the number of images
        image_count = int.from_bytes(f.read(4), 'big')
        # third 4 bytes is the row count
        row_count = int.from_bytes(f.read(4), 'big')
        # fourth 4 bytes is the column count
        column_count = int.from_bytes(f.read(4), 'big')
        # rest is the image pixel data, each pixel is stored as an unsigned byte
        # pixel values are 0 to 255
        image_data = f.read()
        images = np.frombuffer(image_data, dtype=np.uint8)\
            .reshape((image_count, row_count, column_count))
        return images

def read_MNIST_labels(path):
    with gzip.open(path, 'r') as f:
        # first 4 bytes is a magic number
        magic_number = int.from_bytes(f.read(4), 'big')
        # second 4 bytes is the number of labels
        label_count = int.from_bytes(f.read(4), 'big')
        # rest is the label data, each label is stored as unsigned byte
        # label values are 0 to 9
        label_data = f.read()
        labels = np.frombuffer(label_data, dtype=np.uint8)
        return labels

class MyMNISTDataset(Dataset):
    def __init__(self, path, transform, train=True):
        ######################################TODO######################################
        self.train = train
        self.path = path
        self.X = read_MNIST_images(self.path['image'])
        self.y = pd.Series(read_MNIST_labels(self.path['label']))
        self.classes = self.y.unique()
        self._repr_indent = 4
        pass      
        ################################################################################

    def __len__(self):
        len_dataset = None
        ######################################TODO######################################
        len_dataset = len(self.X)
        pass
        ################################################################################
        return len_dataset

    def __getitem__(self, idx):
        X,y = None, None
        ######################################TODO######################################
        X = self.X[idx]
        if self.transform:
            X = self.transform(X)
        y = self.y[idx]
        if not self.train:
            return torch.tensor(X, dtype=torch.double)    
        pass
        ################################################################################
        return torch.tensor(X, dtype=torch.double), torch.tensor(y, dtype=torch.long)

    def __repr__(self):
        '''
        https://github.com/pytorch/vision/blob/master/torchvision/datasets/vision.py
        '''
        head = "(PyTorch HomeWork) My Custom Dataset : MNIST"
        data_path = self._repr_indent*" " + "Data path: {}".format(self.path['image'])
        label_path = self._repr_indent*" " + "Label path: {}".format(self.path['label'])
        num_data = self._repr_indent*" " + "Number of datapoints: {}".format(self.__len__())
        num_classes = self._repr_indent*" " + "Number of classes: {}".format(len(self.classes))

        return '\n'.join([head,
                          data_path, label_path, 
                          num_data, num_classes])

dataset_train_MyMNIST = MyMNISTDataset(path=TRAIN_MNIST_PATH,
                                       transform=transforms.Compose([transforms.ToTensor()]),
                                       train=True,
                                       )

(텍스트 데이터) AG_NEWS 데이터를 이용하여 Dataset과 DataLoader 만들어보기

AG_NEWS 데이터 다운로드

dataset_train_AG_NEWS, dataset_test_AG_NEWS = torchtext.datasets.AG_NEWS(root='./data')
dataset_train_AG_NEWS = list(dataset_train_AG_NEWS)
dataset_test_AG_NEWS = list(dataset_test_AG_NEWS)
os.listdir('data/datasets/AG_NEWS')

BASE_AG_NEWS_PATH = 'data/datasets/AG_NEWS'
TRAIN_AG_NEWS_PATH = os.path.join(BASE_AG_NEWS_PATH, 'train.csv')
TEST_AG_NEWS_PATH = os.path.join(BASE_AG_NEWS_PATH, 'test.csv')

dataset_train_MyAG_NEWS = MyAG_NEWSDataset(TRAIN_AG_NEWS_PATH, train=True)

요약 및 정리

Dataset

from torch.utils.data import Dataset

class CustomDataset(Dataset):

    def __init__(self,):
        '''
        데이터의 위치나 파일명과 같은 초기화 작업을 위해 동작
        '''
        pass

    def __len__(self):
        '''
        Dataset의 최대 요소 수를 반환하는데 사용
        '''
        pass

    def __getitem__(self, idx):
        '''
        데이터셋의 idx번째 데이터를 반환하는데 사용
        '''
        pass

dataset_custom = CustomDataset()

DataLoader

DataLoader(dataset,            # Dataset 인스턴스가 들어감
           batch_size=1,       # 배치 사이즈를 설정
           shuffle=False,      # 데이터를 섞어서 사용하겠는지를 설정
           sampler=None,       # sampler는 index를 컨트롤
           batch_sampler=None, # 위와 비슷하므로 생략
           num_workers=0,      # 데이터를 불러올때 사용하는 서브 프로세스 개수
           collate_fn=None,    # map-style 데이터셋에서 sample list를 batch 단위로 바꾸기 위해 필요한 기능
           pin_memory=False,   # Tensor를 CUDA 고정 메모리에 할당
           drop_last=False,    # 마지막 batch를 사용 여부
           timeout=0,          # data를 불러오는데 제한시간
           worker_init_fn=None # 어떤 worker를 불러올 것인가를 리스트로 전달
          )

dataloader_custom = DataLoader(dataset_custom)

일반적인 학습 과정

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from network import CustomNet
from dataset import ExampleDataset
from loss import ExampleLoss

###############################
# Custom modeling #
###############################

# 모델 생성
model = CustomNet()
model.train()

# 옵티마이저 정의
params = [param for param in model.parameters() if param.requires_grad]
optimizer = optim.Example(params, lr=lr)

# 손실함수 정의
loss_fn = ExampleLoss()

###########################################
# Custom Dataset & DataLoader #
###########################################

# 학습을 위한 데이터셋 생성
dataset_example = ExampleDataset()

# 학습을 위한 데이터로더 생성
dataloader_example = DataLoader(dataset_example)

출처: 부스트캠프 AI Tech 4기(NAVER Connect Foundation)

'부스트캠프 AI Tech 4기' 카테고리의 다른 글

(딥러닝) Historical Review  (0) 2023.05.27
Transfer Learning + Hyper Parameter Tuning  (0) 2023.05.26
PyTorch Troubleshooting  (0) 2023.05.24
Hyperparameter Tuning  (0) 2023.05.23
Multi-GPU - PyTorch  (0) 2023.05.22
Comments