Pytorch 1.11 이후) torchdata 알아보기

2022. 3. 14. 19:01분석 Python/Pytorch

3월 14일 버전에서는 현재 Beta Stage이고 Stable하지 않다고 합니다.

 

개인적으로 tensorflow에서는 dataset이라는 기능을 통해 큰 데이터를 다 불러오지 않고, 부분적으로 불러오게 하는 기능이 있는데, torch 이전까지 버전에서는 그런 기능들을 제공하지 않아 아쉬웠는데, 이렇게 반가운 기능이 나와 공유합니다.

What are DataPipes?

초기에 우리는 재사용 가능한 로딩 도구를 나타내는 PyTorch DataSet 사이의 광범위한 혼란을 관찰했다고 합니다.

DataPipe는 단순히 합성된 사용을 위해 PyTorch DataSet의 이름을 바꾸고 용도를 ​​변경하는 것입니다.

DataPipe는 Python 데이터 구조에 대한 일부 액세스 함수, IterDataPipes의 경우 __iter__ 및 MapDataPipes의 경우 __getitem__을 사용하고 약간의 변환이 적용된 새 액세스 함수를 반환합니다.

 

아래 예시는 jsonparser입니다. 

import json

class JsonParserIterDataPipe(IterDataPipe):
    def __init__(self, source_datapipe, **kwargs) -> None:
        self.source_datapipe = source_datapipe
        self.kwargs = kwargs

    def __iter__(self):
        for file_name, stream in self.source_datapipe:
            data = stream.read()
            yield file_name, json.loads(data)

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

 

패키지 설치

 pip install torchdata

 

Implementation

 

데이터 필터링 후 배치 단위로 추출

데이터가 큰 경우에 한 번에 가져오기 어려운 경우를 이런 식으로 해결할 수 있어보인다.

def is_even(n):
    return n % 2 == 0
dp = IterableWrapper(range(10000000))
dp = dp.filter(filter_fn=is_even)
dp = dp.batch(batch_size=10,drop_last=False)

 

여러가지 예제가 있지만, 여기서는 csv 데이터를 활용하는 방법에 대해 공유합니다.

CSV 생성기

import csv
import random

def generate_csv(file_label, num_rows: int = 5000, num_features: int = 20) -> None:
    fieldnames = ['label'] + [f'c{i}' for i in range(num_features)]
    writer = csv.DictWriter(open(f"sample_data{file_label}.csv", "w"), fieldnames=fieldnames)
    writer.writerow({col: col for col in fieldnames})  # writing the header row
    for i in range(num_rows):
        row_data = {col: random.random() for col in fieldnames}
        row_data['label'] = random.randint(0, 9)
        writer.writerow(row_data)

Pipeline 만들기

참고) 만약 csv에 string 데이터가 있다면 map 함수에서 특별한 처리가 필요해 보입니다.

import numpy as np
import torchdata.datapipes as dp

def build_datapipes(root_dir="."):
    datapipe = dp.iter.FileLister(root_dir)
    datapipe = datapipe.filter(filter_fn=lambda filename: "sample_data" in filename and filename.endswith(".csv"))
    datapipe = dp.iter.FileOpener(datapipe, mode='rt')
    datapipe = datapipe.parse_csv(delimiter=",", skip_lines=1)
    datapipe = datapipe.map(lambda row: {"label": np.array(row[0], np.int32),
                                         "data": np.array(row[1:], dtype=np.float64)})
    return datapipe

DataLoader까지 적용하는 예시

from torch.utils.data import DataLoader

num_files_to_generate = 3
for i in range(num_files_to_generate):
    generate_csv(file_label=i)
datapipe = build_datapipes()
dl = DataLoader(dataset=datapipe, batch_size=50, shuffle=True)
first = next(iter(dl))
labels, features = first['label'], first['data']
print(f"Labels batch shape: {labels.size()}")
print(f"Feature batch shape: {features.size()}")

 

MovieLens 데이터로 간단하게 해보기 

아직까진 API가 여러개가 있는 것 같고, 여기서는 movie lens의 tag 데이터를 불러오려고 한다.

여기서는 데이터를 읽어오면서 장르에 대한 태깅을 바로 인코딩하는 방식으로 해본다.

데이터를 한번에 모아서 하는 것이 좋을지 아니면 이렇게 하나씩 하는 것이 좋을지는 잘 모르겠지만

아무래도 한번에 모아서 하는 것이 더 빠를 것 같다.

데이터는 아래 Reference에 링크로 걸어두었으니 링크 눌러서 다운로드하시면 된다!

tag encoder 만들기

우선 아이러니하게도 torch data를 사용하고자 하는 이유가 모든 데이터를 불러오고 싶지 않아서 사용하는 것인데 어쩔 수 없이 tag를 만들기 위해서 전체 데이터를 불러와서, encoder를 만든다.

 

from sklearn.preprocessing import LabelEncoder
data_path = os.path.join(root_dir,"./data/ml-20/ml-20m/genome-tags.csv")
data = pd.read_csv(data_path)
tag_encoder = LabelEncoder()
tag_encoder.fit(data['tag'])

csv에서 바로 데이터 가지고 오기 (parse_csv)

아래와 같은 코드를 사용하면 10개의 배치를 셔플을 해서 가지고 올 수 있다.

그리고 맨 마지막에 10개가 안되면 안 되는 대로 가지고 오는 것을 확인했다.

def decoder(row) :
    
    data = {
        "label" : np.array([row[0]],np.int32),
        "tag" : tag_encoder.transform(np.array([row[1]])),
    }
    return data

datapipe = dp.iter.FileOpener([data_path], mode='rt')
datapipe = datapipe.parse_csv(delimiter=",", skip_lines=1)
datapipe = dp.iter.Mapper(datapipe, fn=decoder)
datapipe = dp.iter.Shuffler(datapipe)
datapipe = dp.iter.Batcher(datapipe, 10)
for i in datapipe :
    print(len(i))
    # 10
    # ...
    # 8

 

DataLoader) csv에서 바로 텐서로 가져오기 (parse_csv)

이번에는 csv를 가지고 와서 바로 tensor로 변환해보고자 한다.

이때 위의 코드와의 차이점은 Batcher를 사용하지 않는 것이다.

Batcher를 사용하면 기존에 생각하던 방식과는 다르게 뽑힌다.

Batcher의 batch size를 10으로 하고 DataLoader에서 batch size를 50으로 하면 list 형식으로 총 500개의 데이터가 생성된다. 

물론 이 기능도 필요한 상황이 있을 수 있지만, 이것은 원하는 상황이 아니기 때문에 Batcher를 제외한다.

from torch.utils.data import DataLoader

def decoder(row) :
    
    data = {
        "label" : np.array([row[0]],np.int32),
        "tag" : tag_encoder.transform(np.array([row[1]])),
    }
    return data


datapipe = dp.iter.FileOpener([data_path], mode='rt')
datapipe = datapipe.parse_csv(delimiter=",", skip_lines=1)
datapipe = dp.iter.Mapper(datapipe, fn=decoder)
datapipe = dp.iter.Shuffler(datapipe)
# datapipe = dp.iter.Batcher(datapipe, 10)
dl = DataLoader(datapipe , batch_size = 50 , shuffle=True)
result = next(iter(dl))

csv에서 바로 데이터 가지고 오기 (parse_csv_as_dict)

csv 데이터를 사전 형태로 가져올 수 있는 코드이다.

 

from torch.utils.data import DataLoader
import os
def get_name(path_and_stream):
    return os.path.basename(path_and_stream[0]), path_and_stream[1]


datapipe = dp.iter.FileOpener([data_path], mode='b')
datapipe = dp.iter.Mapper(datapipe, fn=get_name)
datapipe = datapipe.parse_csv_as_dict()
datapipe = dp.iter.Shuffler(datapipe)
datapipe = dp.iter.Batcher(datapipe, 10)
next(iter(datapipe))

DataLoader) csv에서 바로 데이터 가지고 오기 (parse_csv_as_dict)

from torch.utils.data import DataLoader
import os
def get_name(path_and_stream):
    return os.path.basename(path_and_stream[0]), path_and_stream[1]
def decoder(row) :
    
    data = {
        "label" : np.array([row[0]],np.int32),
        "tag" : tag_encoder.transform(np.array([row[1]])),
    }
    return data


datapipe = dp.iter.FileOpener([data_path], mode='b')
datapipe = dp.iter.Mapper(datapipe, fn=get_name)
datapipe = datapipe.parse_csv_as_dict()
datapipe = dp.iter.Shuffler(datapipe)
#datapipe = dp.iter.Batcher(datapipe, 10)
dl = DataLoader(datapipe , batch_size = 50 , shuffle=True)
result = next(iter(dl))

 

 

 

 

이상으로 torchdata를 알아봤고, 개인적으로 다른 방식을 이용해서 구현했는데, 그 코드는 버리고 이 라이브러리를 많이 연습해봐야 할 것 같습니다.

 

torchdata에서는 csv 말고도 일반적인 모든 데이터에 대해서 다루고 있는 일반화된 라이브러리로 사용할 수 있게 만들려고 하는 것 같다. 

Audio, Text, Vision, Recommender System 어디서든 사용할 수 있게 하려고 하기 때문에, torch 사용자들은 언젠가 한번쯤은 사용해보지 않을까 싶다.

라이브러리에 있는 다른 Pipe도 많이 있는데, 봤을 때는 잘만 사용하면 유용할 것이라고 생각한다.

 

 

https://github.com/pytorch/data

 

GitHub - pytorch/data: A PyTorch repo for data loading and utilities to be shared by the PyTorch domain libraries.

A PyTorch repo for data loading and utilities to be shared by the PyTorch domain libraries. - GitHub - pytorch/data: A PyTorch repo for data loading and utilities to be shared by the PyTorch domain...

github.com

 

https://pytorch.org/data/main/tutorial.html

 

Tutorial — TorchData main documentation

Shortcuts

pytorch.org

https://pytorch.org/data/main/tutorial.html#implementing-a-custom-datapipe

 

Tutorial — TorchData main documentation

Shortcuts

pytorch.org

https://grouplens.org/datasets/movielens/20m/

 

MovieLens 20M Dataset

MovieLens 20M movie ratings. Stable benchmark dataset. 20 million ratings and 465,000 tag applications applied to 27,000 movies by 138,000 users. Includes tag genome data with 12 million relevance …

grouplens.org

 

728x90