Week2 - PyTorch Custom Dataset & DataLoader

1. PyTorch Dataset 관련 모듈

  • torch.utils.data: 데이터셋의 표준을 정의, 데이터셋을 불러오고 자르고 섞는데 쓰는 도구들이 들어있는 모듈. 파이토치 모델을 학습시키기 위한 데이터셋의 표준을 torch.utils.data.Dataset에 정의하고 Dataset 모듈을 상속하는 파생 클래스는 학습에 필요한 데이터를 로딩해주는 torch.utils.data.DataLoader 인스턴스의 입력으로 사용할 수 있음. torch.utils.data
  • torchvision.datasettorch.utils.data.Dataset을 상속하는 이미지 데이터셋의 모음, MNIST나 CIFAR-10과 같은 데이터셋을 제공함. torchvision.dataset

  • torchtext.datasettorch.utils.data.Dataset을 상속하는 텍스트 데이터셋의 모음, IMDb나 AG_NEWS와 같은 데이터셋을 제공함. torchtext.dataset

  • torchvision.transforms: 이미지 데이터셋에 쓸 수 있는 여러 가지 변환 필터를 담고 있는 모듈입니다. 예를 들어 Tensor로 변환한다던지, 크기 조절(Resize)과 잘라내기(Crop)으로 이미지를 수정할 수 있고 밝기(Brightness)와 같은 값을 조절하는데 사용될 수 있음. 대부분의 transform 함수는 PIL과 tensor 형태의 이미지를 모두 사용 가능하지만, 둘 중 하나만 지원하는 경우도 있으며, tensor 형식이라면 이미지에 batch를 적용할 수 있다. torchvision.transforms

  • torchvision.utils: 이미지 데이터를 저장하고 시각화하기 위한 도구가 들어있는 모듈. torchvision.utils

2. Dataset의 기본 구성 요소

  • Dataset을 구성할 때는 PyTorch의 torch.utils.data에서 Dataset 클래스를 상속해서 만든다. 이렇게 생성된 Dataset 클래스는  __init__ 메서드, __len__ 메서드, __getitem__ 메서드 총 3개의 메서드로 구성된다. 이런 방식은 map-style dataset과 같은 Dataset일때에만 가능하다. PyTorch Documentations 참고

  • __init__ 메서드
    일반적으로 해당 메서드에서는 데이터의 위치나 파일명과 같은 초기화 작업을 위해 동작한다. 일반적으로 CSV파일이나 XML파일과 같은 데이터를 이때 불러온다. 모든 데이터를 메모리에 로드하지 않고 효율적으로 사용할 수 있으며, 이미지를 처리할 transforms들을 Compose해서 정의한다.

  • __len__ 메서드
    Dataset의 최대 요소 수를 반환하는데 사용된다. 해당 메서드를 통해서 현재 불러오는 데이터의 인덱스가 적절한 범위 안에 있는지 확인할 수 있다.

  • __getitem__ 메서드
    데이터셋의 idx번째 데이터를 반환하는데 사용된다. 원본 데이터를 가져와 전처리하고 데이터 증강하는 부분이 이루어진다.

3. PyTorch DataLoader

Dataloader는 모델 학습을 위해서 데이터를 미니 배치(Mini batch)단위로 제공해주는 역할을 한다. PyTorch Documentations을 확인해보면 아래와 같이 DataLoader가 정의되어 있고, dataset은 앞서 만든 Dataset을 인자로 넣어주시면 된다. 보통 batch_size나 collate_fn와 같은 인자를 주로 사용한다.

DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
           batch_sampler=None, num_workers=0, collate_fn=None,
           pin_memory=False, drop_last=False, timeout=0,
           worker_init_fn=None)
  • dataset: DataLoader에는 앞서 생성한 Dataset 인스턴스가 들어간다.
  • batch_size: 배치 사이즈를 의미한다.
  • shuffle: 데이터를 DataLoader에서 섞어서 사용할지 여부를 의미한다.
  • sampler: index를 컨트롤하는 방법으로 데이터의 index를 원하는 방식대로 조정한다. 즉 index를 컨트롤하기 때문에 설정하고 싶다면 shuffle 파라미터는 False(기본값)여야 한다. shuffle은 단순히 섞는 기능이고, sampler는 내가 원하는 규칙대로 섞거나 데이터를 뽑아서 사용하는 기능이다. 홀수 인덱스만 불러오는 기능 등을 구현 가능하고, 불균형한 데이터셋의 비율을 맞출 때 많이 사용된다.
  • num_workers: 데이터를 불러올때 사용하는 서브 프로세스(subprocess) 개수, context switching 때문에 개수를 늘릴수록 빨라지지는 않는다. 무작정 num_workers를 높인다고 좋진 않다! 데이터를 불러 CPU와 GPU 사이에서 많은 교류가 일어나면 오히려 병목이 생길 수 있다. 참고
  • collate_fn: collate는 “함께 합치다”라는 의미로, map-style 데이터셋에서 sample list를 batch 단위로 바꾸기 위해 필요한 기능이다. zero-padding이나 Variable Size 데이터 등 데이터 사이즈를 맞추기 위해 많이 사용한다. feature는 feature끼리, target은 target끼리 feeding하고 싶을 때가 있음 → NLP에서는 문장마다 길이가 다르기 때문에, 객체검출을 할 때는 한 장의 이미지에 객체 개수가 달라지기 때문에 collate() 함수를 많이 사용한다. 참고
  • pin_memory: DataLoader에서  pin_memory=True 이면 Tensor를 CUDA 고정 메모리에 할당시킨다. 고정된 메모리에서 데이터를 가져오기 때문에 데이터 전송이 훨씬 빨라지지만, 일반적인 경우에는 많이 사용하지 않을 argument이다. 자세한 기술적인 내용은 출처1 및 출처2
    • Pageable Memory: Memory 내용(contents)이 DRAM에서 하드디스크 (Secondary Storage Device)로 page out 되거나 반대로 하드디스크에서 DRAM으로 page in이 가능한 메모리를 의미한다. Page in/Page out을 하기 위해서는 CPU (Host)의 도움이 필요하다고 합니다. 보통 OS에서 User Memory Space의 경우 Pageable Memory이다.
    • Non-Pageable Memory: Pageable Memory와 반대로 page in/page out이 불가능한 메모리를 Non-Pageable Memory라 한다. 결과적으로 하드디스크로 데이터를 page out/page in 하는 작업이 필요없다. OS에서 Kernel Memory Space는 보통 Non-Pageable Memory라고 한다.
  • drop_last: batch 단위로 데이터를 불러온다면, batch_size에 따라 마지막 batch의 길이가 달라질 수 있다. batch의 길이가 다른 경우에 따라 loss를 구하기 귀찮은 경우가 생기고, batch의 크기에 따른 의존도 높은 함수를 사용할 때 걱정이 되는 경우 마지막 batch를 사용하지 않을 수 있다.  drop_last=True 이면 마지막 배치를 사용하지 않는다.
  • time_out: 양수로 주어지는 경우 DataLoader가 데이터를 불러오는 시간 제한
  • worker_init_fn: 어떤 worker를 불러올지 리스트로 전달

4. torchvision에서 제공하는 transform

  • torchvision은 항상 PIL 객체로 받아야 한다.
  • transforms.Resize(): 이미지의 사이즈를 변환
  • transforms.Resize((200,200))(im)
  • transforms.RandomCrop(): 이미지를 임의의 위치에서 자름
  • transforms.RandomRotation(): 이미지를 임의의 각도만큼 회전
  • transforms.Compose: 여러개의 transforms를 하나로 묶어서 처리 가능

5. 실습

1. Iris Custom Dataset 만들기

scikit-learn에서 제공하는 붓꽃 데이터를 이용해서 간단한 Dataset을 만들기

  • __init__메서드: 데이터를 불러오고 변수 X에는 feature를, 변수 y에는 target을 넣는다.
  • __len__메서드: 데이터의 총 갯수를 반환한다.
  • __getitem__메서드: idx가 주어질 때, X와 y를 반환한다.
# 라이브러리 임포트
import pandas as pd
from sklearn.datasets import load_iris

# 붓꽃 데이터 다운로드
iris = load_iris()
iris_df = pd.DataFrame(iris['data'], columns=iris['feature_names'])
iris_df['target'] = iris['target']

붓꽃 데이터셋은 5개의 컬럼씩 150개의 데이터로 이루어져 있다.

class IrisDataset(Dataset):
    def __init__(self):
        iris = load_iris()
        # X에는 feature, y에는 target 
        self.X = iris['data']
        self.y = iris['target']

        self.feature_names = iris['feature_names']
        self.target_names = iris['target_names']

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

    def __getitem__(self, idx):
        X = torch.FloatTensor(self.X[idx])
        y = torch.tensor(self.y[idx])
        return X, y
        
        
 dataset_iris = IrisDataset()
 print(len(dataset_iris))  # 150
 print(dataset_iris[0])  # (tensor([5.1000, 3.5000, 1.4000, 0.2000]), tensor(0))

2. Titanic Custom Dataset

Kaggle에서 제공하는 Titanic 데이터를 다운로드 받아서 직접 data/titanic 폴더에 넣고 Custom Dataset과 DataLoader를 만들어본다. 7개의 feature를 사용해 Survived/Dead 예측에 사용되는 데이터셋이다.

  • __init__함수 : X,y 이외에도 features와 classes 를 포함해서 만들기
  • __getitem__함수 : X,y 반환하되 학습 데이터가 아닐 경우, y를 반환하지 않기
train = pd.read_csv('data/titanic/train.csv')
test = pd.read_csv('data/titanic/test.csv')

class TitanicDataset(Dataset):
    def __init__(self, path, drop_features, train=True):
        self.data = pd.read_csv(path)  # dataframe 형태
        self.data['Sex'] = self.data['Sex'].map({'male':0, 'female':1})  # 문자열을 인덱스로 매핑
        self.data['Embarked'] = self.data['Embarked'].map({'S':0, 'C':1, 'Q':2})

        self.train = train
        self.data = self.data.drop(drop_features, axis=1)

        self.X = self.data.drop('Survived', axis=1)
        self.y = self.data['Survived']

        self.features = self.X.columns.tolist()
        self.classes = ['Survived', 'Dead']

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

    def __getitem__(self, idx):
        X, y = None, None
        X = self.X.loc[idx]
        if self.train:
            y = self.y.loc[idx]
        return torch.tensor(X), torch.tensor(y)


dataset_train_titanic = TitanicDataset('./data/titanic/train.csv', 
                                       drop_features=['PassengerId', 'Name', 'Ticket', 'Cabin'],
                                       train=True)
dataloader_train_titanic = DataLoader(dataset=dataset_train_titanic,
                                      batch_size=8,
                                      shuffle=True,
                                      num_workers=4,
                                      )

3. AG NEWS dataset

AG NEWS 데이터를 통해서 직접 자신만의 Dataset 만들기

  • __init__함수 : X,y 이외에도 class와 vocab과 encoder 및 decoder까지 같이 넣기
  • __getitem__함수 : X,y 반환하되, 학습 데이터가 아닐 경우, y를 반환하지 않기
  • _preprocess함수 : 영어와 숫자를 제외한 모든 문자를 제외시키고 영어는 모두 소문자로 변환하기
BASE_AG_NEWS_PATH = 'data/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')

path = TRAIN_AG_NEWS_PATH
data = pd.read_csv(path, sep=',', header=None, 
				   names=['class','title','description'])

AG 뉴스 데이터는 100만 개가 넘는 뉴스 기사 모음으로 ComeToMyHead가 1년 넘게 활동하면서 2000개 이상의 뉴스 출처에서 뉴스 기사를 수집했다. ComeToMyHead는 2004년 7월부터 운영되고 있는 학술 뉴스 검색 엔진이다. 데이터셋은 데이터 마이닝(클러스터링, 분류 등), 정보 검색(순위, 검색 등), xml, 데이터 압축, 데이터 스트리밍 및 기타 모든 비상업적 활동에 대한 연구 목적으로 학계에서 제공한다.

class MyAG_NEWSDataset(Dataset):
    def __init__(self, path='./data/AG_NEWS/train.csv', train=True):
        tqdm.pandas()
        self._repr_indent = 4
        self.data = pd.read_csv(path, sep=',', header=None, names=['class','title','description'])
        self.classes = ['World', 'Sports', 'Business', 'Sci/Tech']
        self.path = path
        self.train = train
        counter = collections.Counter()

        # 내 풀이 - apply()를 사용하면 for문을 사용하지 않고도 한 번에 처리 가능함
        self.X = (self.data['title'] + self.data['description']).apply(self._preprocess)
        self.y = self.data['class']
        self.X.apply(str.split).apply(counter.update)
        
        self.vocab = torchtext.vocab.vocab(counter, min_freq=1)
        self.encoder = self.vocab.get_stoi()
        self.decoder = self.vocab.get_itos()

        # # 해설
        # self.X = (self.data['title'] + " " + self.data['description']).progress_apply(lambda s: self._preprocess(s))
        # self.y = self.data['class']
        
        # self.vocab = list(set([w for s in self.X for w in s.split(' ')])) + [' ']
        # self.encoder = {w:n for n,w in enumerate(self.vocab)}
        # self.decoder = {n:w for n,w in enumerate(self.vocab)}


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

    def __getitem__(self, idx):
        X = self.X[idx]
        y = None
        if self.train:
            y = self.y[idx]
        return y, X 

    def __repr__(self):
        '''
        https://github.com/pytorch/vision/blob/master/torchvision/datasets/vision.py
        '''
        head = "(PyTorch HomeWork) My Custom Dataset : AG_NEWS"
        data_path = self._repr_indent*" " + "Data path: {}".format(self.path)
        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, num_data, num_classes])
    
    def _preprocess(self, s):
        s = re.sub('[^a-zA-Z0-9]+', ' ', s).lower()
        return s
        
dataset_train_MyAG_NEWS = MyAG_NEWSDataset(TRAIN_AG_NEWS_PATH, train=True)
dataset_train_MyAG_NEWS.encoder['hello']  # 해당 단어에 매핑된 숫자 출력

좋은 웹페이지 즐겨찾기