본문 바로가기
Deep Learning

PyTorch Image Datasets, Custom Dataset, Fix Seed

by 아르카눔 2024. 3. 27.

 

PyTorch는 다양한 빌트인 데이터셋을 제공한다.

 

컴퓨터 비전 데이터의 경우 그 목록을 https://pytorch.org/vision/main/datasets.html 에서 볼 수 있다.

 

그중에서도 대표적인 이미지 데이터로는 CIFAR-10, 100, ImageNet 2012, MNIST, Fashion-MNIST, PASCAL VOC 등이 존재한다.

 

 

Custom Dataset with csv file

 

아래 PyTorch 튜토리얼에서 가져온 코드다.

 

https://tutorials.pytorch.kr/beginner/basics/data_tutorial.html

 

import os
import pandas as pd
from torchvision.io import read_image

class CustomImageDataset(Dataset):
    def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
        self.img_labels = pd.read_csv(annotations_file, names=['file_name', 'label'])
        self.img_dir = img_dir
        self.transform = transform
        self.target_transform = target_transform

    def __len__(self):
        return len(self.img_labels)

    def __getitem__(self, idx):
        img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
        image = read_image(img_path)
        label = self.img_labels.iloc[idx, 1]
        if self.transform:
            image = self.transform(image)
        if self.target_transform:
            label = self.target_transform(label)
        return image, label

 

csv 파일에 이미지와 라벨이 있는 데이터로 불러오게 된다.

맨 처음 실행하면 __init__ 메소드를 통해서 이미지 이름과 라벨이 있는 csv 파일을 불러오고,

이미지가 있는 디렉토리와 이미지와 타겟 밸류(여기서는 라벨)에 수행할 transform을 결정하게 된다.

이 CustomDataset을 사용할 때의 장점은 메모리를 아낄 수 있다는 점이다.

 

이미지가 1만장 이정도만 되어도 램에 모두 올릴 수 있지만

데이터가 엄청나게 많아진다면, 가령 기업에서 많은 이미지를 학습시키는 경우와 같을 때는 불가능함을 알 수 있다.

따라서 그때 그때 __getitem__ 메소르를 활용해서 idx에 속하는 일부 아이템들만 가져오는게 메모리 효율적인 방법이다.

 

만약 라벨 외의 추가적인 정보를 불러올 필요가 있다면 아래와 같이 코드를 짜면 된다.

 

# __init__ 내부 항목 변경
self.img_label_attrs = pd.read_csv(annotations_file, names=['file_name', 'label', 'attr1'])

#__getitem__ 내부 항목 추가
# attr을 별도로 불러오기
attr = self.img_labels.iloc[idx, 2]
return image, label, attr

 

Dataloader in PyTorch

PyTorch Dataset을 index를 통해서 불러오는 역할을 한다.

 

CLASS torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=None, 
	sampler=None, batch_sampler=None, num_workers=0, collate_fn=None, 
	pin_memory=False, drop_last=False, timeout=0, worker_init_fn=None, 
	multiprocessing_context=None, generator=None, *, prefetch_factor=None, 
	persistent_workers=False, pin_memory_device='')

 

엄청나게 arguments가 많은데 본인이 지금까지 빈번하게 사용했던 항목은 batch_size와 shuffle정도다.

batch_size는 말 그대로 한 번에 불러오는 배치의 크기고, shuffle은 epoch 마다 데이터를 섞을지의 여부다.

num_workers는 멀티코어 프로세싱 처리할 때 스는 옵션이다.

sampler는 index를 어떻게 생성할지에 대한 방법론인데 많은 방법이 있다. 그리고 shuffle과 함께 사용하면 안되는 옵션이다.

batch_sampler는 기존 sampler와 어떤 차이가 있는지 아직 모르겠다.

 

Sampler Class

Sampler에는 SequentialSampler, RandomSampler, SubsetRandomSampler, WeightedRandomSampler,

BatchSampler가 있다.

distributed 아래의 DistributedSampler라는 별도의 항목의 샘플러도 있는데 데이터의 서브셋에 제약을 주는 샘플러라고 한다.

 

Dataloader의 예시

Train, Val, Test를 각각의 dataloader로 만들어 보았다.

train_loader = torch.utils.data.DataLoader(train_dataset,
                                          batch_size=128,
                                          shuffle=True
                                          )

val_loader = torch.utils.data.DataLoader(validation_dataset,
                                          batch_size=128,
                                          shuffle=True
                                          )

test_loader = torch.utils.data.DataLoader(test_data,
                                          batch_size=128,
                                          shuffle=True
                                          )

 

Data Split

학습을 위해서는 보통 데이터를 train, validaiton, test의 세 가지로 나누게 된다.

 

Sklearn Data Split

머신 러닝 태스크에 있어서는 Scikit-learn의 train_test_split를 주로 사용하게 된다.

 

import numpy as np
from sklearn.model_selection import train_test_split

# X, y values
X, y = np.arange(100).reshape((50, 2)), range(100)

# Divide Data into Train and Test
# X와 y의 데이터 타입은 numpay array, python list, 
# pandas dataframe, scipy-sparse matrices이 가능
X_train, X_test, y_train, y_test = train_test_split(
                    X, y, test_size=0.2, random_state=42)

# Divide Train data into Train and Validation
# Validation을 위해서 다시 데이터를 추가로 나눠 준다.
X_train, X_val, y_val, y_val = train_test_split(
                    X_train, y_train, test_size=0.2, random_state=42)
                    
# 최종적으로 64: 16: 20의 비율로 Train, Validation, Test가 나눠진다.                    
print(f"# of Train: {len(X_train)}, Val: {len(X_val)}, Test: {len(X_test)}")

 

 

PyTorch Data Split

반면에 PyTorch를 사용한 딥러닝 태스크에서는 random_split를 사용하게 된다.

 

from torch.utils.data import random_split

# Dataset X는 PyTorch Dataset 형태의 데이터다.
# Train data와 Test data가 별도로 주어진 케이스

# Total 10,000 training data and 1,000 test data
# training data and test data are given separately
# 8,000 (80%) for train and 1,000 (10%) for validation, and 1,000 (10%) for test
train_size = int(len(training_data)*0.8) # size는 int 값이어야만 한다.
validation_size = len(training_data) - train_size 

train_dataset, validation_dataset = random_split(training_data, [train_size, validation_size])

len(train_dataset), len(validation_dataset)


# Dataset X는 PyTorch Dataset 형태의 데이터다.
# Train data와 Test data가 나누어 지지 않고 통째로 주어진 케이스

# Total 50,000 training data
# 40,000 (80%) for train and 5,000 (10%) for validation, and 5,000 (10%) for test
train_size = int(len(training_data)*0.8) # size는 int 값이어야만 한다.
validation_size = int(len(training_data)*0.1)
test_size = len(training_data) - train_size - validation_size

train_dataset, validation_dataset, test_dataset = random_split(training_data, 
                                    [train_size, validation_size, test_size])
                                    
len(train_dataset), len(validation_dataset), len(test_dataset)

 

사이킷런과는 다르게 train, val, test를 한 번에 나눌 수 있다.

 

 

Fix Seed

시드 고정은 RNG (Random Number Generating)에서 중요한데 컴퓨터의 난수 생성이 어떤 정해진 룰에 의해 작동하기 때문이다. 따라서 실험의 재현성을 위해서는 시드를 고정해야 한다.

학부 시절 R을 활용한 Simulation 수업 때 들었던 내용인데 자세한 내용은 기억이 나질 않는다.

 

# 시드 고정
import random
import numpy as np
import torch

seed = 42

"""
seed가 42는
은하수를 여행하는 히치하이커를 위한 안내서에 나온 숫자에서 따온것으로
관습적으로 사용한다.
"""

random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)

"""
# cudnn 고정은 학습 속도가 느려져서 제외
deterministic = True

if deterministic:
	torch.backends.cudnn.deterministic = True
	torch.backends.cudnn.benchmark = False
Ref: https://knowing.tistory.com/26
"""

 

이 블로그 https://knowing.tistory.com/26에서 참조한 코드다.

 

 

References:
https://velog.io/@olxtar/PyTorch-%EC%82%AC%EC%9A%A9%EB%B2%95

https://pytorch.org/docs/stable/data.html