Are you looking for an answer to the topic “pytorch dataset 만들기“? We answer all your questions at the website https://ph.taphoamini.com in category: 152+ tips for you. You will find the answer right below.
PyTorch 기초 – Dataset 만들기
딥러닝 모델을 학습하기 전에 필요한 첫 번째 준비물은 데이터라고 할 수 있습니다. 주어진 데이터를 활용해 효과적으로 모델에 입력하기 위해서 PyTorch는 Dataset 클래스를 제공하고 있습니다. 이 포스트에서는 이를 활용해서 데이터셋을 만드는 방법에 대해 알아보겠습니다.
간단한 Dataset 만들기
먼저 이 포스트에서 사용할 모듈들을 불러오겠습니다.
import os
import torch
from torch.utils.data import Dataset
from PIL import Image
import torchvision.transforms as transforms
커스텀 데이터셋을 만들기 위해서 다음과 같이 클래스를 구현해보겠습니다.
MyBaseDataset(Dataset):
def __init__(self, x_data, y_data):
self.x_data = x_data
self.y_data = y_data
def __getitem__(self, index):
return self.x_data[index], self.y_data[index]
def __len__(self):
return self.x_data.shape[0]
이번에 구현한 MyBaseDataset는 Dataset 클래스를 상속하게 됩니다. 이 클래스를 상속했을 때, 구현해야 하는 메서드는 __init__, __getitem__, __len__입니다.
__init__ 메서드는 객체를 생성할 때 실행되는 메서드, 즉 생성자입니다. 여기에는 모델에 사용할 데이터를 담아두는 등 어떤 인덱스가 주어졌을 때 반환할 수 있게 만드는 초기 작업을 수행합니다.
__getitem__ 메서드는 어떤 인덱스가 주어졌을 때 해당되는 데이터를 반환하는 메서드입니다. numpy 배열이나 텐서 형식으로 반환합니다. 보통 입력과 출력을 튜플 형식으로 반환하게 됩니다.
__len__은 학습에 사용할 데이터의 총 개수라고 볼 수 있는데, 즉 얼마만큼의 인덱스를 사용할지를 반환하는 메서드입니다.
위 코드로 구현한 BaseDataset은 정형적인 데이터셋을 나타내고 있습니다. 가장 높은 차원에 인덱스로 접근해 데이터를 반환하게 됩니다. 이 클래스에 간단한 데이터를 넣은 예시를 보이겠습니다.
x_data = torch.arange(100)
y_data = x_data * x_data
dataset = MyBaseDataset(x_data, y_data)
print(“dataset example: “, dataset[0], dataset[1], dataset[2])
print(“dataset length:”, len(dataset))
dataset example: (tensor(0), tensor(0)) (tensor(1), tensor(1)) (tensor(2), tensor(4))
dataset length: 100
이 코드는 y=x2y=x^2y=x2에 대해 xxx가 0에서 99까지 주어진 데이터셋을 만든 것으로 볼 수 있습니다. 만들어진 데이터셋은 구현한 메서드를 기반으로 리스트처럼 인덱스로 접근할 수 있고, 길이를 알 수 있습니다.
인덱스에 접근할 때 데이터 불러오기
위 예시와 같이 모든 데이터를 Dataset에 저장할 수 있다면 구현하기도 편할 것입니다. 그러나 ImageNet과 같이 1400만개가 넘는 데이터셋을 메모리에 모두 불러오는 것은 사실상 불가능할 것입니다. 그래서 일반적으로는 생성자에서 모든 데이터를 불러오지는 않고, 불러올 이미지들의 경로를 저장하는 방식을 사용합니다. 그리고 데이터셋에 인덱스로 접근할 때 경로에 존재하는 데이터를 불러와 메모리에 적재하면 이 문제를 해결할 수 있습니다. 물론 보조기억장치에서 데이터를 불러올 때의 지연이 나타날 수 있지만 보통은 무시할 수 있습니다. 참고로 데이터들을 한 파일로 만들어서 활용하고 싶다면, HDF5 파일을 활용해 볼 수 있습니다. 전처리로 HDF5 파일을 만든 후, 학습에 이 파일을 불러와 사용하는 방법을 사용할 수 있습니다.
그러면 인덱스에 접근할 때 파일 경로로부터 데이터를 불러오는 Dataset 클래스를 구현해보겠습니다.
DogCatDataset(Dataset):
def __init__(self, data_dir):
self.data_dir = data_dir
self.image_path_list = os.listdir(data_dir)
self.transform = transforms.ToTensor()
def __getitem__(self, index):
image_path = os.path.join(self.data_dir, self.image_path_list[index])
x_data = Image.open(image_path)
x_data = self.transform(x_data)
y_data = 1 if “dog” in self.image_path_list[index] else 0
return x_data, y_data
def __len__(self):
return len(self.image_path_list)
이 코드에서는 개(Dog)와 고양이(Cat)을 구별하기 위한 데이터셋으로 볼 수 있습니다. 생성자에서는 인수로 넘어온 data_dir의 파일들을 데이터로 사용하기 위해 리스트로 저장하고, 이미지를 텐서로 변환하는 transform을 사용합니다. (관련 내용은 잠시 뒤에 다루겠습니다.) __getitem__로 인덱스가 넘어오면, 경로에 해당하는 이미지를 불러와 텐서로 변환해 x_data로 반환합니다. 타겟 데이터(y_data)는 파일 이름으로부터 추출하게 됩니다. 이 예시에서는 파일들이 dog.10.jpg, cat.3.jpg 등으로 구분되어 있다고 가정해 볼 때, 개일 경우에는 dog 문자열이 포함되어 있고 고양이일 경우에는 이 문자열이 포함되어 있지 않다고 바꾸어 볼 수 있습니다. 이를 if else문으로 판단해 반환하게 됩니다.
불러온 이미지를 변형하기
여기서 불러온 이미지들을 바로 텐서로 변환해서 반환할 수도 있지만, 이미지의 크기가 일정하지 않거나, 데이터 증강(Data augmentation)을 하고 싶을 때가 있습니다. 이 때에는 torchvision에서 제공하는 transforms 모듈들을 사용하면 됩니다.
간단한 사용방법을 위 코드의 일부에서 가져와서 살펴보겠습니다.
transform = transforms.ToTensor()
x_data = transform(x_data)
가져올 변형(여기서는 ToTensor)을 가져와서 변수로 선언한 다음에, 변형할 데이터를 매개변수로 넣는 방식으로 진행합니다. 그래서 생성자에서 transform을 인스턴스 변수로 선언하고 __getitem__에서 이를 활용하는 방식으로 사용하는 것입니다.
이 변형 중에서 주요한 몇몇 모듈들을 살펴보겠습니다.
torchvision.transforms.Resize(size, interpolation=’bilinear’, …)
torchvision.transforms.CenterCrop(size)
torchvision.transforms.RandomHorizontalFlip(p=0.5)
torchvision.transforms.RandomVerticalFlip(p=0.5)
torchvision.transforms.RandomRotation(degrees, interpolation=’nearest’, …)
torchvision.transforms.RandomCrop(size, padding=None, …)
위 변형들은 이미지 전처리 또는 데이터 증강에 주로 사용됩니다. 크기 조절, 뒤집기, 회전하기 등의 작업을 수행할 수 있습니다.
torchvision.transforms.ToTensor()
torchvision.transforms.ToPILImage(mode=None)
텐서와 PIL 이미지로 상호 변환하는 변형들입니다. ToTensor은 불러온 이미지를 텐서로 불러올 때, ToPILImage는 모델의 결과로 나온 영상을 저장하기 위해 주로 사용됩니다. 여기서 ToTensor로 텐서로 변환한 이미지의 값은 [0, 1]의 범위를 가집니다.
torchvision.transforms.Normalize(mean, std, inplace=False)
Normalize는 텐서를 정규화하는 모듈입니다. mean과 std를 매개변수로 입력하게 되는데, 들어온 텐서 xxx에 대해 (x−mean)/std(x-mean) / std(x−mean)/std를 수행한 텐서를 리턴받습니다. 보통 이 모듈을 사용할 때에는 [0, 1]과 [-1, 1] 사이의 변환과 PyTorch에서 제공하는 이미 학습된(pretrained) 모델을 파인튜닝(fine-tuning)하기 위해 RGB 데이터 분포에 맞춰주는 것입니다. RGB 영상에 대해 각각에 해당하는 예시를 살펴보게습니다.
transform_1 = transforms.Normalize([0.5, 0.5, 0.5], [[0.5, 0.5, 0.5])
transform_2 = transforms.Normalize([1, 1, 1], [[2, 2, 2])
transform_3 = transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
transform_1은 [0, 1]에서 [-1, 1]로, transform_2은 그 반대, transform_3은 파인튜닝하기 위해 사용되는 값들이라고 볼 수 있습니다.
마지막으로, 2개 이상의 변형들을 한번에 적용하기 위한 모듈을 살펴보겠습니다.
torchvision.transforms.Compose(transforms)
여기서 매개변수로 들어가는 transforms에 사용할 변형들을 리스트로 넣어주면 순서대로 수행해 반환해줍니다. 일반적으로 사용할 수 있는 예시는 다음과 같습니다.
transform_compose = transforms.Compose([
transforms.RandomResizedCrop(input_size),
transforms.RandomRotation(degrees=(-30, 30))
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
그 외 모든 transforms 모듈들에 대한 정보를 찾을 때에는 공식 링크를 참조하시면 됩니다.
[Pytorch] 진짜 커스텀 데이터셋 만들기, 몇 가지 팁
Pytorch 개발자들이 이미 데이터셋, 데이터로더 클래스를 여러 개 만들어 두었다.
데어터셋의 경우 ImageFolder, DatasetFolder 와 같이 내 폴더 안에 있는 데이터들을 돌게 해주는 애들과 CIFAR10, ImageNet 등 유명한 베이스라인 데이터셋을 다운로드부터 train/test 스플릿까지 손쉽게 해주는 클래스 들이 있다.
이번에는 이런 것보다 조금 더 low-level로 직접 데이터셋 클래스를 만들어서 이를 데이터로더에 집어 넣는 것까지 해보겠다.
핵심은 바로 위 사진에 있는 torch.utils.data.Dataset 이라는 를 상속받는 자식 클래스를 만들 것이다.
이 자식 클래스가 필요로 하는 메소드는 3가지이며, 다음과 같다. (언더바가 두 개임) 설명을 잘 모르더라도 조금 있다가 코드를 함께 보면 이해가 조금 더 잘 될 것이다.
__init__(self, 인수들) : 데이터셋을 처음 선언할 때, 즉 데이터셋 오브젝트가 생길 때 자동으로 불리는 함수이고, 여기에 우리가 몇 가지 인수들을 입력받도록 만들 수 있다 (path, transform 같은 것들).
__len__(self) : 데이터셋의 길이다. 만약 dataset을 선언하고 나서 len(어떤 dataset)을 하면 내부적으로는 이 len 함수가 불리는 것이다. 이 len은 나중에 데이터셋을 선언하고 데이터로더를 사용할 때 또 내부적으로 사용된다. (데이터셋의 len을 알아야 데이터로더가 미니 배치 샘플링을 하면서 지금 다 돌았는지 아닌지를 알 수 있으니까)
__getitem__(self, x) : 이름에서 알 수 있듯이 데이터셋의 본분인 데이터 하나씩 뽑기이다. x는 index를 말하는데, 몇 번째 데이터를 뽑을 건지에 대한 변수이다. 이는 데이터로더에서 또 사용될 것이다.
이러한 함수들을 만들고 나면 우리의 custom dataset을 하나 만든 것이고, 이 데이터셋을 하나 선언해서 사용하면 된다. 이 경우 말한대로 next와 iter 라는 함수를 사용하면 데이터셋 내부의 데이터를 하나하나씩 뽑을 수 있다. (next는 파이썬의 iterator object에서 다음 아이템을 뽑는 함수고 iter는 어떤 오브젝트를 iterator로 바꿔주는 함수이다.) 일단 실생활에서 잘 쓰이지는 않을 정말 단순한 Dataset을 만들어보았다. (이런 간단한 데이터셋도 연구에 사용되는 경우가 있다!…)
(난이도 쉬움) 복붙해서 돌려보기를 추천하는 코드 (매우 짧음!)
import torch
from torch.utils.data import Dataset, DataLoader
SimpleDataset(Dataset):
def __init__(self, t):
self.t = t
def __len__(self):
return self.t
def __getitem__(self, x):
return torch.LongTensor([x])
if __name__ == “__main__”:
dataset = SimpleDataset(t=5)
print(len(dataset))
it = iter(dataset)
for i in range(10):
print(i, next(it))
우선 SimpleDataset을 선언했고, torch.utils.data.Dataset를 상속받았다. 하나씩 보자. 이 SimpleDataset은 t라는 숫자를 받고 self.t에 넣어준다.
여기서 중요한 점은, __init__ 함수는 데이터셋이 선언되는 그 때 (여기서는 dataset = SimpleDataset(t=5)) 딱 한 번 불려지고 그 이후에는 단 한 번도 안 사용된다. 그리고 return 도 하지 않기 때문에 __init__(self, 인수) 에서 인수들은 이 함수가 끝나고 나면 다 없어져버린다. 그래서 self.t = t 처럼 self에다가 값을 대입해주는 게 필요하다. 객체 지향 프로그래밍이나 파이썬의 클래스에 대해 알면 뭔 느낌인지 올 것이다.
그리고 len 함수는 입력받은 self.t를 return한다. 때문에 아래에 있는 print(len(dataset))에서는 5가 출력된다.
마지막으로 getitem에서는 x라는 숫자를 그냥 return한다.
위 코드를 돌려보면 len은 5가 나오고, i가 range(10)으로 10번 도는 동안 그냥 next(it)에서는 0부터 9가 나온다. 정말 별 거 없다. 그냥 데이터셋 데이터로더의 개념을 보기 위해 만들어봤다. 그러면 이제 데이터로더를 사용해보자. 위에서 설명한 함수들을 데이터로더 내부에서 어떤 식으로 작동하는지와 함께 보면 이해가 좀 된다. 위 코드블럭에서 메인 함수를 다음과 같이 바꿔보자.
(난이도 쉬움) 복붙해서 돌려보기를 추천하는 코드 (매우 짧음!)
if __name__ == “__main__”:
dataset = SimpleDataset(t=5)
dataloader = DataLoader(dataset=dataset,
batch_size=2,
shuffle=True,
drop_last=False)
for epoch in range(2):
print(f”epoch : {epoch} “)
for batch in dataloader:
print(batch)
이 경우 다음과 같이 출력된다.
설명을 해보자면, 데이터로더를 batch_size는 2로, shuffle은 True로, drop_last는 False로 해놨다. batch size를 바꿔보거나, shuffle, drop_last를 True/False로 바꿔보면서 체크해봐도 좋다.
결과를 보면 for batch in dataloader에서 처음에는 [[3], [4]]이, 다음에는 [[0], [2]]이 나오고 마지막으로 [[1]]이 나온다. 일단 dataloader가 하는 일은, Dataset의 __len__함수를 통해 길이를 파악한다. 그리고 데이터로더의 shuffle이 False면 0, 1, 2, … len(dataset) – 1 순서의 index array를 만들고 만약 shuffle이 True면 0부터 len(dataset) – 1까지의 index array를 만들고 순서를 랜덤하게 섞는다. torch.randperm처럼! drop_last라는 거는 batch_size 개수로 미니배치를 돌 것인데 만약에 맨 뒤에 길이가 조금 짧게 자투리가 남으면 이것도 뽑아줄지 아니면 그냥 버릴지에 대한 인수이다. 지금은 True라서 마지막에 길이가 1짜리인 애가 나온다 (len이 5인데 batch_size가 2니까)
https://pytorch.org/docs/stable/generated/torch.randperm.html
그리고 dataloader에서 데이터를 샘플링을 하면 데이터가 아닌 미니 배치가 나온다. 내부적으로는, 아까 만들어놓은 index array를 앞에서부터 차례대로 batch_size 개수만큼 뽑고, 그 뽑은 index를 하나씩 dataset의 __getitem__ 함수에 집어넣어 return된 데이터를 뽑아 놓는다. 그러면 이 데이터들이 batch_size개의 index만큼 따로 있을 것이고, 데이터로더가 이 데이터들을 미니배치라는 큰 텐서 하나로 concatenate을 해준다. 이 경우 concatenate은 0번째 차원으로 하는데, 그 전에 데이터들에 0번째 차원을 만든다. 코드로 보자면 다음일을 한다 (엄밀하게는 이렇지 않지만 이런 플로우로 이루어진다.) 여기서 torch.stack이 새로운 차원을 만들고 concatenate을 하는 코드이다. list안의 모든 tensor를 각각 unsqueeze하고 concatenate하는 것과 stack이 같은 역할을 한다.
index_array = torch.randperm(5)
# [1, 4, 0, 2, 3]
index = index_array[:batch_size]
# [1, 4]
data_list = []
for i in range(index.size(0)):
data_list.append(dataset(index[i]))
# data_list : [tensor[1], tensor[4]]
batch = torch.stack(data_list, dim=0)
print(batch)
# [[1], [4]]
대충 __init__, __len__, __getitem__ 함수에 대해서 알 수 있었고, 커스텀 데이터셋을 선언하고 데이터로더를 사용하면 어떤 식으로 나오는지 알 수 있었을 것이다. 사실 여기까지는 약간 너무 디테일하다고 생각할 수도 있지만, 우리가 원하는대로 custom을 하려면 그래도 어떻게 작동하는지는 알아야 잘못된 움직임을 막을 수 있다. 그러면 이제 실생활로 가보자.
1. 직접 저장해놓은 cat과 dog 이미지들을 돌아주는 데이터셋
폴더 구조는 다음과 같다고 해보자
그리고 우선 다음과 같이 코드를 짜 보았다.
import glob
import torch
from torchvision import transforms
from PIL import Image
from torch.utils.data import Dataset, DataLoader
catdogDataset(Dataset):
def __init__(self, path, train=True, transform=None):
self.path = path
if train:
self.cat_path = path + ‘/cat/train’
self.dog_path = path + ‘/dog/train’
else:
self.cat_path = path + ‘/cat/test’
self.dog_path = path + ‘/dog/test’
self.cat_img_list = glob.glob(self.cat_path + ‘/*.png’)
self.dog_img_list = glob.glob(self.dog_path + ‘/*.png’)
self.transform = transform
self.img_list = self.cat_img_list + self.dog_img_list
self._list = [0] * len(self.cat_img_list) + [1] * len(self.dog_img_list)
def __len__(self):
return len(self.img_list)
def __getitem__(self, x):
img_path = self.img_list[x]
label = self._list[x]
img = Image.open(img_path)
if self.transform is not None:
img = self.transform(img)
return img, label
if __name__ == “__main__”:
transform = transforms.Compose(
[
transforms.ToTensor(),
]
)
dataset = catdogDataset(path=’./cat_and_dog’, train=True, transform=transform)
dataloader = DataLoader(dataset=dataset,
batch_size=1,
shuffle=True,
drop_last=False)
for epoch in range(2):
print(f”epoch : {epoch} “)
for batch in dataloader:
img, label = batch
print(img.size(), label)
사실 정말 정말 다양하게 코드를 짤 수 있는데 위의 방식은 제일 무난한 방식인 것 같다. (물론 나는 어쩌다 보니 사용하지 않는다)
우선 하던대로 __init__부터 보면, path 와 train, transform을 입력받을 수 있도록 만들어 놓았다 (당연히 추가하거나 바꿔도 된다). path는 데이터셋의 위치를 알기 위해서고, train이 True냐 False냐에 따라서 train 폴더를 볼지, test 폴더를 볼지 if 문을 통해서 만들어 놓았다. 그 이후에는 glob 이라는 것을 이용해 폴더 내에 있는 파일들을 찾는데, 마지막이 png로 끝나는 애들을 찾는다. 그 외에는 init에서는 별 거 없다. 한 가지는 label을 반환하기 위해서 cat image와 dog image에 대응될 수 있게 cat image 개수만큼 0을, dog image 개수만큼 1을 가지고 있는 _list를 만들어 주었다. getitem에서는 파이토치 텐서를 반환해야만 나중에 데이터로더가 mini batch로 concatenate을 할 수 있기 때문에, ‘cat’, ‘dog’와 같은 string을 쓰면 안 된다.
__len__ 에서는 cat과 dog의 이미지들의 개수를 return 한다.
__getitem__ 에서는 x번째의 이미지를 PIL.Image를 통해 열고 적절한 transform을 해준다. 여기서는 torchvision이 제공하는 transforms을 사용하기로 했고 이들은 PIL Image를 인풋으로 받기 때문에 skimage.imread와 같이 이미지를 numpy array로 반환하는 애를 사용하면 안되고 Image.open(img_path)로 열었다. 또 방금 말했듯이, 파이토치 텐서를 반환해야 하기 때문에 ToTensor()를 통해 PIL Image를 텐서로 바꿔주었다. ToTensor 이전에 transforms.Resize라거나 CenterCrop이나 RandomHorizontalFlip 등 다양한 pre processing 이나 data augmentation을 사용할 수 있다. 마지막으로 이 이미지에 해당하는 label도 함께 return 한다.
위 코드를 돌리면 다음과 같이 나온다.
2048, 2304 짜리 아무 이미지 하나씩을 넣어놓았다. 지금은 batch_size가 1이라서 하나씩 도는 것을 볼 수 있고, label이 0과 1이 나오는 것도 볼 수 있다. 한 가지 상식으로는 Pytorch에서 2D Image를 가지는 mini-batch의 경우 [B, C, W, H] 의 순으로 텐서를 가진다. B는 배치 사이즈고, C는 Channel로 1이면 흑백, 3이면 RGB를 의미한다. W와 H는 이미지의 위아래 길이다. 여기서 알 수 있는 한 가지 딸려오는 상식은 getitem이 return하는 이미지의 위아래 길이는 매번 항상 같아야 한다. 이는 데이터로더가 concatenate을 해서 mini-batch를 만들어야하기 때문인데, 만약 위아래 차원이 다르면 컨캣이 안 되기 때문이다. 때문에 만약 이미지들이 다 크기가 다르다면 1) batch_size를 1로 해서 concat을 안해도 되게 하거나, 2) transform에 Resize나 CenterCrop을 사용해서 이미지들을 같은 크기로 조정해주어야 한다.
2. 이미지들을 __init__ 시점에서 메모리에 모두 올려버리기
이는 3D 데이터를 다루거나 파일의 입출력 시간이 좀 귀찮고 오래 걸리면 필요하다. 위 1번 데이터셋은 __init__ 에서는 이미지들의 경로를 self에 넣어주고, __getitem__에서 매번 path에 해당하는 이미지를 연다. 생각해보면, 우리는 epoch을 백번, 천번, 많게는 십만번도 도는데, 같은 이미지를 매번 그렇게 열어야 하나? 싶으면 이걸 한 번 보면 좋다. (사실 나는 3D data를 다루기 때문에 __init__ 단계에서 skimage.io.imread를 통해 tif를 열어 numpy array로 메모리에 올려놓는 식으로 사용하지만, 1번 데이터셋과 얼추 비슷하게 보여주려고 그냥 PIL Image를 사용했다.
catdogDataset(Dataset):
def __init__(self, path, train=True, transform=None):
self.path = path
if train:
self.cat_path = path + ‘/cat/train’
self.dog_path = path + ‘/dog/train’
else:
self.cat_path = path + ‘/cat/test’
self.dog_path = path + ‘/dog/test’
self.cat_img_list = glob.glob(self.cat_path + ‘/*.png’)
self.dog_img_list = glob.glob(self.dog_path + ‘/*.png’)
self.transform = transform
self.img_list = self.cat_img_list + self.dog_img_list
self.Image_list = [] # 바뀐부분!!!!!!!
for img_path in self.img_list: # 바뀐부분!!!!!!!
self.Image_list.append(Image.open(img_path)) # 바뀐부분!!!!!!!
self._list = [0] * len(self.cat_img_list) + [1] * len(self.dog_img_list)
def __len__(self):
return len(self.img_list)
def __getitem__(self, x):
img = self.Image_list[x] # 바뀐부분!!!!!!!
label = self._list[x]
if self.transform is not None:
img = self.transform(img)
return img, label
위 코드에서 “바뀐부분!!!!!!”이라는 주석만 바꿔놨다. 비교해보면 사실 변한 건 이거다. 원래는 __getitem__에서 Image.open을 하는데, 지금은 __init__에서 Image.open을 그냥 싹 다 해놓고 self에 넣어놓아 (메모리에 다 올려놓고) __getitem__ 에서는 그냥 인덱싱만 한다.
이런 식으로 하면 다음과 같은 일들이 이루어진다.
__init__ 때 조금 시간이 걸린다. 왜냐면 path에 있는 이미지들을 싹 다 읽으니까.
__init__ 이후에 컴퓨터 메모리를 보면 좀 많이 잡아 먹을 수 있다. 왜냐면 path에 있는 이미지들을 싹 다 메모리에 올려놓은 거니까.
대신 __getitem__에서 파일 입출력을 하는 게 아니라 메모리에서 데이터를 가져오는 것이라 __getitem__이 빨라진다.
사실 이건 그냥 CIFAR나 ImageNet 같은 걸 할 때는 별로 추천하지 않는 것이, 이미지 파일 입출력이 그렇게 오래 안 걸린다. 내가 이걸 사용하는 이유는 내가 사용하는 데이터셋이 3-D 이미지 여러 개로 이루어져 있는데, 이 이미지 하나하나가 1기가 정도 되는 큰 이미지들이라 매번 새로 읽어들이면 파일을 읽는 것에 시간을 다 쓴다. 그래서 처음에 __init__ 때 한 번 좀 기다리면서 다 읽어놓고 그 이후에는 메모리 내에서만 데이터를 왔다갔다하려는 것이 목적이라 이렇게 한다.
다음은 그냥 몇 가지 팁들을 적어보았는데 아마 처음 보면 다 이해가 하나도 안 갈 수도 있다. 자세한 예시까지 첨부하면 참 좋겠지만, 너무 힘들다. 혹시 이해가 안 되어 질문을 하면 그 때는 다시 컨디션이 괜찮아서 자세히 설명할 수도 있을 것 같다.
반응형
몇 가지 신기한 혹은 꿀팁들
데이터셋 getitem 에서 numpy array를 return해도 Dataloader를 사용하면 배치를 만드는 과정에서 자동으로 torch tensor로 바꾼다. 0번째 차원을 추가하고 batch_size 개수의 item을 concatenate 하는 것도 당연히 하고!
glob같은 경우 파일 순서가 이상할 수 잇다. 만약 img와 segmentation map img 와 같이 같이 pair 가 되어야하는데 각각이 다른 path에 있고 이름이 막 다른 경우에 glob의 output이 우리가 상식적으로 생각하는 0.png, 1.png, 2.png 이 순서가 아니라서 img 랑 segmentation map 이 다른 순서로 sorting이 되어 있을 수 있다. 이 때 단순히 getitem에서 index 기준으로 그냥 이미지랑 map을 뽑는 식으로 짜면 학습할 때 img와 segmentation map이 페어가 맞지 않을 수도 있다!
getitem에서 그냥 여러 데이터(이미지, label, etc.)를 쉼표로 리턴하는 게 아니라 이 데이터들을 딕셔너리로 리턴할 수도 있을 텐데 (return {‘img’ : self.img_list[x], ‘label’ : self._list[x]}), 이렇게 만들면 데이터로더가 나중에 합칠 때도 이걸 고려해준다! 데이터로더에서 나오는 batch가 {‘img’ : 뭐시기, ‘label’ : 뭐시기} 이렇게 나온다!
torchvision transforms 에서 Resize(128)로 해서 하면 이미지가 128, 128이 되는 것이 아니다. 만약 [128, 256] 짜리 이미지가 있고 Resize(128)로 transform을 만들어서 통과시켜도 [128, 256]이다. torchvision의 Resize가 비율을 유지해서 그렇다. 이는 여러 비율을 가지는 이미지들이 존재하면 위에서 잠깐 말한대로 이미지들의 크기가 달라서 concat이 안되어서 데이터로더에서 에러가 뜬다. 그러니까 Resize와 CenterCrop 같은 걸 함께 사용하자.
글이 아마 약간 이해하기 어렵게 쓴 느낌이 없잖아 있지만, Pytorch에서 커스텀 데이터셋을 사용하려고 한다면 굉장히 도움이 될만한 내용이 많다고 생각한다. (나름 오랜 기간 커스텀 데이터셋을 삽질하면서 내가 공부한 것이 많다.) 짧게 정리해보자면 1) Pytorch의 데이터로더는 별로 건들 게 없고, Dataset을 짜주면 된다. 2) __init__, __len__, __getitem__ 만 잘 짜주면 된다. 인 것 같다. 커스텀 데이터셋은 사실 내가 짠 방식 말고도 정말 수없이 다양하게 짤 수 있다. 내가 위에 예시 코드를 짜면서도 이거말고 다른방식으로 짤까 라는 고민을 정말 다양한 부분에서 했다. 단순히 CIFAR와 같은 널리 알려진 데이터셋만 사용하는 것이 아니라면 꼭 필수적인 것이 커스텀 데이터셋이다. (그리고 이런건 커스텀으로 짜야 멋이 나지) 아마 위 내용들을 알아두면 도움이 많이 될 것이다. 혹시 문장이 이해가 안가거나 궁금한 것이 있다면 댓글을 남겨주시면 최대한 답변을 할 것이다.
저작자표시비영리변경금지
07. 커스텀 데이터셋(Custom Dataset)
이전 섹션을 간단히 검토해 보겠습니다. PyTorch는 데이터 세트 작업을 더 쉽게 만드는 유용한 도구로 torch.utils.data.Dataset 및 torch.utils.data.DataLoader를 제공합니다. 미니 배치 훈련, 데이터 셔플 및 병렬 처리를 단순화합니다. 주요 용도는 Dataset을 정의하고 DataLoader에 전달하는 것입니다.
1. 커스텀 데이터셋(Custom Dataset)
그러나torch.utils.data.Dataset을 상속받아 커스텀 데이터셋을 생성할 수 있는 경우가 있습니다. Torch.utils.data.Dataset은 PyTorch에서 데이터 세트를 제공하는 추상 클래스입니다. Dataset을 상속하고 다음 메서드를 재정의하여 사용자 지정 데이터 집합을 만듭니다.
커스텀 데이터셋을 생성할 때 가장 기본적인 스켈레톤은 다음과 같습니다. 여기에 필요한 3가지 주요 정의가 있습니다.
클래스 CustomDataset(torch.utils.data.Dataset):
def __init __ (자체):
def __len __ (자신):
def __getitem __ (자신, x):
이를 더 자세히 살펴보겠습니다.
클래스 CustomDataset(torch.utils.data.Dataset):
def __init __ (자체):
데이터세트 전처리의 일부
def __len __ (자신):
데이터세트의 길이입니다. 즉, 전체 샘플 수를 쓰는 부분
def __getitem __ (자신, x):
데이터 세트에서 특정 샘플 1개를 가져오는 함수
len(데이터 집합)이 완료되면 데이터 집합의 크기를 반환합니다.
데이터 세트 [i]가 완료되었을 때 i번째 샘플을 얻기 위한 인덱싱을 위한 get_item.
2. 커스텀 데이터셋(Custom Dataset)으로 선형 회귀 구현하기
수입 토치
토치.nn.기능을 F로 가져오기
Torch.utils.data에서 가져오기 데이터 세트
Torch.utils.data에서 DataLoader 가져오기
# 데이터세트 상속
클래스 CustomDataset(데이터 세트):
def __init __ (자체):
self.x_data = [[73, 80, 75],
[93, 88, 93],
[89, 91, 90],
[96, 98, 100],
[73, 66, 70]]
self.y_data = [[152], [185], [180], [196], [142]]
# 총 데이터 수를 반환
def __len __ (자신):
len(self.x_data) 복원
# 인덱스에 매핑된 입출력 데이터를 PyTorch Tensor 형태로 반환
def __getitem __ (자신, x):
x = 토치.FloatTensor(self.x_data[x])
y = 토치.플로트텐서(self.y_data[x])
반환 x, y
데이터 세트 = CustomDataset()
dataloader = DataLoader(데이터 세트, 배치 크기 = 2, 셔플 = True)
모델 = torch.nn.Linear(3,1)
옵티마이저 = torch.optim.SGD(model.parameters(), lr = 1e-5)
nb_epochs = 20
범위의 에포크(nb_epochs + 1):
batch_x의 경우 샘플 열거(데이터 로더):
# 인쇄(batch_x)
# 인쇄(샘플)
x_train, y_train = 샘플
# H(x) 계산
추측 = 모델(x_train)
# 비용 계산
비용 = F.mse_loss(예측, y_train)
# H(x)를 비용으로 계산
optimizer.zero_grad()
비용.뒤로 ()
옵티마이저.스텝()
print(‘Epoch {: 4d}/{} 배치 {}/{} 비용: {: .6f}’. 형식(
epoch, nb_epochs, batch_x+1, len(데이터 로더),
비용 항목 ()
)))
Epoch 0/20 배치 1/3 비용: 29410.156250
Epoch 0/20 배치 2/3 비용: 7150.685059
Epoch 0/20 배치 3/3 비용: 3482.803467
… 제거됨 …
Epoch 20/20 배치 1/3 비용: 0.350531
Epoch 20/20 배치 2/3 비용: 0.653316
Epoch 20/20 배치 3/3 비용: 0.010318
# 임의 입력 선언 [73, 80, 75]
new_var = 토치. 플로트텐서([[73, 80, 75]])
# 입력값 [73, 80, 75]에 대한 예측값 y를 반환하고 pred_y에 저장
pred_y = 모델(new_var)
print(“학습 후 입력이 73, 80, 75일 때의 예측값:”, pred_y)
학습 후 입력이 73, 80, 75일 때의 예측값: tensor ([[151.2319]], grad_fn =)
[Pytorch] Dataset을 만들어보자!
Pytorch용 모델 아키텍처를 구현하기 전에 데이터 세트 객체를 생성하는 방법을 소개하고 싶습니다. 절차는 어렵지 않으나 배치단위 훈련과 효율적인 전처리를 위해 꼭 필요한 과정이다.
큰 틀은 다음과 같다.
Torch.utils.data에서 가져오기 데이터 세트
클래스 CustomDataset(데이터 세트):
def __init __ (자체):
” ‘일부 매개변수 가열’ ”
def __len __ (자신):
” ‘데이터 반환 길이’ ”
def __getitem __ (자신, 인덱스):
” ‘필요에 따라 사전 처리된 데이터를 반환’ ”
먼저 Dataset 모듈을 가져와서 상속받아 Dataset 클래스를 생성해야 합니다. 기본적으로 구현해야 하는 세 가지 기능이 있습니다. __Init__는 클래스가 선언될 때 가장 먼저 실행되는 함수로, 클래스에서 사용할 다양한 변수를 선언하는 곳입니다.
다음으로 __len__은 데이터의 크기를 반환하는 함수이며, 데이터셋의 선언된 변수에 len() 함수를 적용하여 데이터의 크기를 반환한다.
마지막으로 __getitem__은 데이터를 전처리하고 반환하는 부분입니다. 인덱스 변수는 데이터를 인덱싱할 때 사용됩니다.
볼 수 없는, 그것을 시도하고 알아내자!
여기서 사용할 데이터셋은 CIFAR100입니다. 데이터셋마다 불러오는 방법과 전처리하는 방법이 다르기 때문에 데이터셋의 클래스도 그에 맞게 변경되어야 합니다.
클래스 CifarTrainDataset(데이터 세트):
def __init __ (자신, 경로, 변환 = 없음):
f로 열린(경로, ‘rb’):
데이터 = pickle.load (f, 인코딩 = ‘바이트’)
self.change = 변형
self.x = 데이터 [b’데이터 ‘]
self.y = 데이터 [b’fine_labels’]
먼저 클래스와 __init__ 함수를 정의합니다. CIFAR100 데이터셋은 이미지 형식이 아닌 바이트 형식으로 저장되기 때문에 위의 과정을 통해 이미지와 라벨 데이터를 수신해야 한다.
Transform은 당분간 ToTensor만 사용합니다. 이미지 변환 기능은 여러 가지가 있는데 자세한 내용은 다음을 참고하세요. (사실 변환은 아주 기본적인 기능이 하나밖에 없기 때문에 알바멘테이션과 같은 모듈을 사용하는 것을 권장합니다. ..)
https://pytorch.org/vision/stable/transforms.html
def __len __ (자신):
반환 len (self.x)
ds = CifarTrainDataset(“./ datasets/cifar100/train”)
인쇄(len(ds))
50000
다음은 __len__ 함수입니다. 위와 같이 단순히 데이터 len을 반환하면 아래와 같이 데이터셋 변수에 len 함수를 가져와서 데이터 크기를 받게 됩니다.
def __getitem __ (자신, 인덱스):
레이블 = self.y [색인]
r = self.x [인덱스,: 1024] .reshape (32, 32)
g = self.x [인덱스, 1024: 2048] .reshape (32, 32)
b = self.x [색인, 2048:].모양 변경(32, 32)
이미지 = np.dstack ((r, g, b))
if self.transform:
이미지 = self.transform(이미지)
사진, 레이블 복원
마지막으로 __getitem__ 함수입니다. 여기서 데이터 x는 1차원 벡터로 최대 1024 R 채널, 1024-2048 G 채널, 2048-3060 B 채널까지 위의 전처리를 수행한다. Dataloader는 배치 단위로 색인을 생성하므로 색인이라고 가정하고 여기에서 코딩할 수 있습니다.
이제 결과가 나오는지 확인만 하면 끝!!
라벨은 19로 소가 표준 생산량임을 증명합니다! 데이터세트 클래스 생성 완료!
비영리 귀속 변경 금지
파이토치(Pytorch) 6. Custom Dataset
See some more details on the topic pytorch dataset 만들기 here:
PyTorch 기초 – Dataset 만들기 – velog
간단한 Dataset 만들기 딥러닝 모델을 학습하기 전에 필요한 첫 번째 준비물은 데이터라고 할 수 있습니다. 주어진 데이터를 활용해 효과적으로 모델 …
Source: velog.io
Date Published: 5/10/2021
View: 9673
[Pytorch] 진짜 커스텀 데이터셋 만들기, 몇 가지 팁
Pytorch 개발자들이 이미 데이터셋, 데이터로더 클래스를 여러 개 만들어 두었다. 데어터셋의 경우 ImageFolder, DatasetFolder 와 같이 내 폴더 안에 …
Source: honeyjamtech.tistory.com
Date Published: 10/17/2021
View: 1784
07. 커스텀 데이터셋(Custom Dataset)
온라인 책을 제작 공유하는 플랫폼 서비스.
Source: wikidocs.net
Date Published: 12/5/2021
View: 4420
[Pytorch] Dataset을 만들어보자! – 인공지능 스터디룸
Pytorch에 대한 모델 아키텍처 구현을 하기 전에 dataset 객체를 만드는 방법을 먼저 소개하려 한다. 방법은 어렵지 않지만 batch단위의 training과 …
Source: aistudy9314.tistory.com
Date Published: 5/6/2021
View: 5184
[pytorch] Custom dataset, dataloader 만들기 – CV DOODLE
[pytorch] Custom dataset, dataloader 만들기. Moving J 2022. 1. 2. 16:28. * dataset 폴더 구조. minc2500 ├─images │ ├─brick │ │ ├─brick_000000.jpgSource: mvje.tistory.com
Date Published: 12/8/2021
View: 4542
[PyTorch] Dataset과 Dataloder 설명 및 custom … – 휴블로그
[PyTorch] Dataset과 Dataloder 설명 및 custom dataset & dataloader 만들기. 휴석사 2020. 9. 30. 17:32 …Source: sanghyu.tistory.com
Date Published: 4/28/2021
View: 8097
[PyTorch] 4-1. 나만의 이미지 데이터셋 만들기
이미지 데이터 셋을 만들기 위해서는 자신이 원하는 이미지 파일이 있어야합니다. jpg, jpeg, png, bmp 등등의 이미지 형식의 파일들을 별로 폴더를 …
Source: data-panic.tistory.com
Date Published: 12/5/2021
View: 3108
[알쓸쿠잡] PyTorch 사용법1 – 기본&데이터셋
파일에서 사용자 정의 데이터셋 만들기. 사용자 정의 Dataset 클래스는 반드시 3개 함수를 구현해야 한다. : __init__, __ …
Source: kubig-2021-2.tistory.com
Date Published: 6/17/2021
View: 8916
Related searches to pytorch dataset 만들기
Information related to the topic pytorch dataset 만들기
Here are the search results of the thread pytorch dataset 만들기 from Bing. You can read more if you want.
You have just come across an article on the topic pytorch dataset 만들기. If you found this article useful, please share it. Thank you very much.