[PyTorch] 04. Dataset, DataLoader
Updated:
import torch
from torch.utils.data import Dataset, DataLoader
1. 정형 데이터(Structured Data)와 비정형 데이터(Unstructured Data)
data를 분류하는 기준으로 여러가지가 있지만 크게 정형 데이터와 비정형 데이터로 나눌 수 있다.
정형 데이터 (Structured Data)
쉽게 말해 표를 통해 만들 수 있는 데이터라고 생각하면 된다. 우리는 엑셀이나 데이터베이스로 주어진 데이터를 관리할 때가 많다. 이렇게 엑셀이나 데이터베이스 등 표를 통해 다룰 수 있는 데이터를 정형 데이터라고 생각하면 된다.
장점
- 머신러닝 모델 학습시 feature를 비교적 쉽게 뽑아낼 수 있다는 장점을 지닌다. 표의 각 column이 feature가 되기 때문이다.
- 이해하기 쉽다. 이 데이터가 어떠한 값을 나타내는 지 별도의 지식이 필요하지 않은 경우가 있다.
- 가공하기 쉽다. 각종 데이터를 표를 통해서 다룰 수 있기 때문에 전처리가 쉽고 feature engineering 가능하다.
- 데이터 베이스, 엑셀 등으로 관리하기 편하다.
단점
- 유연하지 않다. 표이기 때문에 표가 만들어진 목적의 용도로만 사용할 수 있다.
- 표를 저장할 저장소의 용량에 제한이 있으면 많은 데이터를 담을 수가 없다.
비정형 데이터 (Unstructed Data)
이미지, 음성, 텍스트 등 쉽게 말해 표를 통해 다룰 수 없는 데이터라고 할 수 있다. 표를 통해 다룰 수 있는 데이터는 공통적으로 지닌 범주가 있다. 이를 feature로 뽑아내 column으로 만들어 표로 정리할 수 있다. 비정형 데이터는 각 데이터가 공통적으로 지닌 범주를 찾기 힘들기 때문에 feature를 뽑아내기 어렵다. 따라서 표로 정리하기가 쉽지 않다.
장점
- 가공되지 않은 형식이기 때문에 일상에서 발생하는 데이터 대부분이 비정형 데이터라고 할 수 있다.
- 일상 속에서 쉽게 발견될 수 있는 형태이기 때문에 모으기 쉽다.
단점
- 형식이 없거나 가공이 되지 않았기 때문에 데이터 분석이 필요할 수 있다.
- 별도로 처리할 수 있는 도구들이 필요하다.
- 어떻게 처리하느냐에 따라 데이터를 다루는 모델의 성능이 차이가 많이 난다. (이것은 정형 데이터도 마찬가지이다.)
신경망은 이미지, 음성, 텍스트 등 비정형 데이터를 다루는 경우가 많다. 따라서 별도로 이러한 데이터를 보관하고 정리하고 정리된 데이터들을 학습에 사용할 수 있게 불러올 수 있는 도구들이 필요하다. PyTorch에서는 torch.data.utils라는 패키지를 통해 Dataset과 DataLoader라는 도구를 제공해준다.
참고: structured vs unstructured data
2. Dataset
Dataset은 데이터를 보관하는 용도로 만들어진 class이다. Dataset을 통해 가지고 있는 데이터를 보관하고 배포할 수 있다. PyTorch에서 제공하는 Dataset은 map-style 형식의 dataset이 있고 iterable-style 형식의 dataset이 있다.
2.1. Dataset 종류
Pytorch에서 제공해주는 Dataset은 abstract class이기 때문에 상속을 받아 구현해서 사용해야한다. 앞서 언급했듯이 Dataset은 map-style 형식과 iterable-style 형식이 존재한다. 각 형식에 따라 구현 방법이 다르다. 주로 mini-batch 등을 위해 map-style 형식의 Dataset이 많이 구현하므로 map-style 방식의 Dataset을 직접 구현해보겠다.
map-style 형식
__gettime__()
과__len__()
을 구현해야한다.__gettime__()
:객체[index/key]
를 통해 값을 반환하기 위해 구현해야하는 메소드, 객체를 index/key로 접근하기 위해 구현해야하는 메소드- a = [1,2,3], list는
__gettime__()
이 구현되어 있기 때문에 a[0]으로 접근가능하다
- a = [1,2,3], list는
__len__()
:len(객체)
를 통해 값을 반환하기 위해 구현해야하는 메소드, data의 전체 갯수 반환하기 위해 구현해야하는 메소드- a = [1,2,3], list는
__len__()
이 구현되어 있기 때문에 len(a)를 통해 전체 갯수 반환이 가능하다.
- a = [1,2,3], list는
itreable-style 형식
__iter__()
를 구현해야한다.__iter__()
: 데이터를 iterator로 만들어 반환하기 위해 구현해야하는 메소드
참고: __getitem__()
, __len__()
, __iter__()
, iterator 관련 포스트
2.2 map-style dataset 구현
앞서 언급했듯이 map-style 형식의 dataset은 __gettime__()
과 __len__()
을 구현해야보았다.
class MyDataset(Dataset):
# 데이터셋 초기화
def __init__(self):
# 데이터셋 길이 반환
def __len__(self):
# 데이터셋에서 데이터를 반환
def __getitem__(self,idx):
$y=ax+b$ 학습을 위한 간단한 dataset을 구현해보자
class MyDataset(Dataset):
# 데이터셋 초기화
def __init__(self):
# 신경망 학습 데이터는 (샘플의 수, 샘플의 feature 수) shape을 지닌 2D-Tensor로 구현해주어야한다.
self.data = torch.arange(10).float().view(-1,1)
self.target = torch.arange(1,20,2).float().view(-1,1)
# 데이터셋 길이 반환
def __len__(self):
return len(self.data)
# 데이터셋에서 데이터를 반환
# 학습 데이터와 타겟 데이터 둘 다 반환해야함
def __getitem__(self,idx):
return self.data[idx], self.target[idx]
3. DataLoader
Dataset에서 Data를 Load 해오는 역할을 하는 class. Dataset에서 직접 data를 가져오면 되지 않을까 생각할 수도 있지만 신경망마다 같은 Dataset이더라도 한 번에 몇 개의 data를 가져와 학습하는 지가 다르기 때문에 쓰인다. 특히 mini-batch 학습을 하는 모델의 경우 batch 숫자를 몇 개로 정하느냐가 다르기 때문에 Dataset을 사용한다면 필수적으로 같이 구현해주어야하는 class이다.
torch.utils.data.DataLoader
Parameter
- dataset (Dataset): load하려는 dataset
- batch_size (int, optional) : batch 크기
- shuffle (bool, optional) : 매 epoch마다 dataset을 섞을지 결정
- sampler (Sampler or Iterable, optional): dataset에서 데이터를 가져오는 방법을 정의한 것. 만약 sampler를 사용하면 shuffle=False가 되어야한다. dataset의 index를 다루는 방법
- batch_sampler (Sampler or Iterable, optional): batch 단위로 작동하는 sampler
- num_workers (int, optional) – data를 load할 때 사용할 subprocess의 수
- collate_fn (callable, optional) – sample을 batch 단위로 묶기 위해 사용하는 것. collate_fn을 사용하면 data와 target이 하나씩 들어오는 것이 아닌 batch 단위로 한 번에 들어온다.
- pin_memory (bool, optional) – Tensor를 CUDA pin memory에 할당.
- drop_last (bool, optional) – 마지막 batch에서 data 갯수가 정해진 batch_size 보다 적을 때 버릴지 말지 선택하는 것.
- timeout (numeric, optional) – data를 불러오는 제한 시간
- worker_init_fn (callable, optional) – 어떤 종류의 worker를 사용할 지 결정
4. Dataset, DataLoader 예제
이전 포스트에서 사용한 LinearModel class를 위한 Dataset과 DataLoader를 만들고 학습을 진행해보겠다.
import torch.nn as nn
class LinearModel(nn.Module):
def __init__(self):
super(LinearModel, self).__init__()
self.weight = nn.Parameter(torch.rand(1))
self.bias = nn.Parameter(torch.rand(1))
def forward(self,x):
return x * self.weight + self.bias
class MyDataset(Dataset):
# 데이터셋 초기화
def __init__(self):
# 신경망 학습 데이터는 (샘플의 수, 샘플의 feature 수) shape을 지닌 2D-Tensor로 구현해주어야한다.
self.data = torch.arange(10).float().view(-1,1)
self.target = torch.arange(1,20,2).float().view(-1,1)
# 데이터셋 길이 반환
def __len__(self):
return len(self.data)
# 데이터셋에서 데이터를 반환
# 학습 데이터와 타겟 데이터 둘 다 반환해야함
def __getitem__(self,idx):
return self.data[idx], self.target[idx]
linear_model = LinearModel()
dataset = MyDataset()
batch_size가 5인 Dataloader와 optimizer로는 SGD를 사용하였다. 손실 함수로는 MSE를 적용하였다.
dataloader = DataLoader(dataset,batch_size=5,shuffle=True)
import torch.optim as optim
optimizer = optim.SGD(linear_model.parameters(),lr=0.01)
criterion = nn.MSELoss()
EPOCH = 10
for epoch in range(EPOCH):
for train_x, train_y in dataloader:
# 예측 수행
pred = linear_model(train_x)
# loss 계산
loss = criterion(pred, train_y)
# parameter grad 0으로 초기화
optimizer.zero_grad()
# gradient 계산
loss.backward()
# 계산된 gradient를 이용하여 parameter 업데이트
optimizer.step()
print(f"EPOCH {epoch} - LOSS {loss}")
EPOCH 0 - LOSS 186.09518432617188
EPOCH 0 - LOSS 0.031408119946718216
EPOCH 1 - LOSS 0.01191028207540512
EPOCH 1 - LOSS 0.02536321058869362
EPOCH 2 - LOSS 0.011766353622078896
EPOCH 2 - LOSS 0.023312849923968315
EPOCH 3 - LOSS 0.016867931932210922
EPOCH 3 - LOSS 0.01830008253455162
EPOCH 4 - LOSS 0.021971695125102997
EPOCH 4 - LOSS 0.012137438170611858
EPOCH 5 - LOSS 0.017795637249946594
EPOCH 5 - LOSS 0.01844112202525139
EPOCH 6 - LOSS 0.017936503514647484
EPOCH 6 - LOSS 0.014872526749968529
EPOCH 7 - LOSS 0.012134464457631111
EPOCH 7 - LOSS 0.019676119089126587
EPOCH 8 - LOSS 0.015200303867459297
EPOCH 8 - LOSS 0.01675037480890751
EPOCH 9 - LOSS 0.014820991083979607
EPOCH 9 - LOSS 0.01564902998507023
총 sample 수가 10개이고 batch size가 5이기 때문에 매 epoch마다 2개의 출력값이 발생하였다.
전체 데이터를 통해 구한 예측값과 실제값을 비교해보자.
x,y = dataset.data, dataset.target
print(f"예측값: {linear_model(x).flatten()}")
print(f"실제값: {y.flatten()}")
예측값: tensor([ 0.7748, 2.8091, 4.8435, 6.8778, 8.9122, 10.9465, 12.9808, 15.0152,
17.0495, 19.0839], grad_fn=<ReshapeAliasBackward0>)
실제값: tensor([ 1., 3., 5., 7., 9., 11., 13., 15., 17., 19.])
실제 20을 넣었을 때 예측값과 실제 원하는 값을 비교해보았다.
x = torch.tensor([[20.0]])
pred = linear_model(x)
print(f"예측값: {pred}")
print(f"실제값: 41")
print(f"loss: {criterion(pred, torch.tensor([[41.0]]))}")
예측값: tensor([[41.4616]], grad_fn=<AddBackward0>)
실제값: 41
loss: 0.2130545973777771
Leave a comment