2022년 7월 23일 토요일

pytorch 파일 기반(file based) DataLoader

시작하기전

앞서서 Pytorch 에서 Data를 읽어 오기 위한 DataLoader에 대해서 알아보았습니다. 이번글을 읽기전에 기본적으로 알아야 하는 내용입니다.

https://swlock.blogspot.com/2022/07/pytorch-dataset-dataloader.html

지난 posting의 dataloader는 반쪽짜리 입니다. dataloader는 결국 한번에 소모하는 RAM소비를 줄이기 위한 방법인데 data전체를 memory에 올리고 있는 상황이 되어 버립니다.

그래서 여기에서는 file 기반으로 변경해서 필요한 부분만 메모리에 올리도록 수정하였습니다.


numpy로 데이터 생성

이 부분은 numpy의 savetxt 를 이용합니다.

import numpy as np

####################################### 데이터 준비
np.random.seed(42)
# np.random.rand(m,n) n*m matrix 로 0~1 랜덤 생성
x = np.random.rand(100, 3)
'''
[[0.37454012 0.95071431 0.73199394]
 [0.59865848 0.15601864 0.15599452]
 [0.05808361 0.86617615 0.60111501]
 ...
'''
x_one_d = x[:,0:1] * x[:,1:2] * x[:,2:3]
'''
[[2.60648878e-01]  # <= [0.37454012 * 0.95071431 * 0.73199394]
 [1.45701819e-02]
 [3.02424805e-02]
 ...
'''

# y는 원하는 값 목표값인 label로 표현합니다.
y = 1 + 2 * x_one_d + .1 * np.random.randn(100, 1)
'''
[[1.52585494]
 [0.96398033]
 [1.27487937]
 ...
'''
# 저장전 확인
print(x.shape,type(x),x[0])
print(y.shape,type(y),y[0])

# 저장
np.savetxt("x_data.csv", x, delimiter=',')
np.savetxt("y_data.csv", y, delimiter=',')

# 저장된것 확인
x_data = np.loadtxt("x_data.csv", delimiter=',', skiprows=0, max_rows=1)
print(x_data.shape,type(x_data),x_data)
if x_data.shape==():
    x_data = np.array([x_data]) 
print(x_data.shape,type(x_data),x_data)
y_data = np.loadtxt("y_data.csv", delimiter=',', skiprows=0, max_rows=1)
print(y_data.shape,type(y_data),y_data)
if y_data.shape==():
    y_data = np.array([y_data]) 
print(y_data.shape,type(y_data),y_data)

실행 결과

(100, 3) <class 'numpy.ndarray'> [0.37454012 0.95071431 0.73199394]
(100, 1) <class 'numpy.ndarray'> [1.52585494]
(3,) <class 'numpy.ndarray'> [0.37454012 0.95071431 0.73199394]
(3,) <class 'numpy.ndarray'> [0.37454012 0.95071431 0.73199394]
() <class 'numpy.ndarray'> 1.5258549401766552
(1,) <class 'numpy.ndarray'> [1.52585494]


x_data.csv 생성된 파일 정보

3.745401188473624909e-01,9.507143064099161656e-01,7.319939418114050911e-01
5.986584841970366000e-01,1.560186404424365181e-01,1.559945203362026467e-01
5.808361216819946105e-02,8.661761457749351800e-01,6.011150117432088047e-01
7.080725777960454881e-01,2.058449429580244683e-02,9.699098521619943236e-01
8.324426408004217404e-01,2.123391106782761550e-01,1.818249672071006184e-01
1.834045098534338170e-01,3.042422429595377231e-01,5.247564316322378408e-01
4.319450186421157634e-01,2.912291401980419137e-01,6.118528947223794701e-01
1.394938606520418345e-01,2.921446485352181544e-01,3.663618432936917024e-01
4.560699842170359286e-01,7.851759613930135995e-01,1.996737821583597361e-01
5.142344384136116053e-01,5.924145688620424677e-01,4.645041271999772459e-02
6.075448519014383653e-01,1.705241236872915289e-01,6.505159298527951606e-02
9.488855372533332444e-01,9.656320330745593594e-01,8.083973481164611341e-01
3.046137691733706854e-01,9.767211400638386998e-02,6.842330265121568944e-01
....


y_data.csv 생성된 파일 정보

1.525854940176655239e+00
9.639803290492371390e-01
1.274879370024478487e+00
1.091665387286355093e+00
8.617645510296900735e-01
1.077207618958690771e+00
1.087757394417082413e+00
1.115103571175657615e+00
1.063752367405901955e+00
1.016827660818809154e+00
1.063977555024557597e+00
2.568002900244755082e+00
....


File DataLoader

전체 dataset의 크기를 알아야 합니다. 파일의 line수를 구하는 것과 같은데 잘못하면 파일의 전체 메모리를 올리게 됩니다. 이 부분은 아래와 같은 코드를 이용하면 일부 RAM을 시용하면서도 전체 라인 수를 구할 수 있습니다.

파일의 전체 라인 수 구하기

        with open(filename, 'r') as fp:
            for count, line in enumerate(fp):
                pass
        total_len = count + 1


전체 소스

나머지는 그렇게 어려운 부분은 없습니다.

train_dataset, val_dataset = random_split(dataset, [80, 20]) 부분이 보이는데 80,20은 전체 dataset에서 분해하기 위한 수량입니다. 입력 데이터가 100개라서 이와 같이 표현한 것이며 수량 합계가 다르다면 적절히 수정하도록 합니다.

import torch
import numpy as np
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
from torch.utils.data.dataset import random_split


class CustomDataset(Dataset):
    def __init__(self, x_tensor_filename, y_tensor_filename):
        self.x_fn = x_tensor_filename
        self.y_fn = y_tensor_filename
        with open(y_tensor_filename, 'r') as fp:
            for count, line in enumerate(fp):
                pass
        self.total_len = count + 1
    def __getitem__(self, index):
        """
        주어진 인덱스 index 에 해당하는 샘플을 데이터셋에서 불러오고 반환합니다.
        """
        x_data = np.loadtxt(self.x_fn, delimiter=',', skiprows=index, max_rows=1)
        # [0.37454012 0.95071431 0.73199394]
        if x_data.shape==():
            x_data = np.array([x_data]) 
        y_data = np.loadtxt(self.y_fn, delimiter=',', skiprows=index, max_rows=1)
        # 1.5258549401766552
        if y_data.shape==():
            y_data = np.array([y_data]) 
        # [1.5258549401766552]
        return (x_data, y_data)
    def __len__(self):
        """
        데이터셋의 샘플 개수를 반환합니다.
        """
        return self.total_len
        
dataset = CustomDataset("x_data.csv", "y_data.csv")

train_dataset, val_dataset = random_split(dataset, [80, 20])

train_loader = DataLoader(dataset=train_dataset, batch_size=9)
val_loader   = DataLoader(dataset=val_dataset, batch_size=9) 

n_epochs = 10

device = 'cuda' if torch.cuda.is_available() else 'cpu'

for epoch in range(n_epochs):
    print(f"epoch ##{epoch}")
    batch_no = 0
    for x_batch, y_batch in train_loader:
        print(f"train batch #{batch_no}, epoch #{epoch}")
        x_batch = x_batch.to(device)
        y_batch = y_batch.to(device)
        print(x_batch,"\n",y_batch)
        batch_no += 1
    
    batch_no = 0    
    for x_val, y_val in val_loader:
        print(f"val batch #{batch_no}, epoch #{epoch}")
        x_val = x_val.to(device)
        y_val = y_val.to(device)
        print(x_val,"\n",y_val)
        batch_no += 1


'''
batch 의 값이 전체 dataset의 배수가 아니며 마지막 batch로 들어오는 크기는 줄어들 수 있음
epoch ##0
train batch #0, epoch #0
tensor([[0.3492, 0.7260, 0.8971],
        [0.8074, 0.8961, 0.3180],
        [0.3745, 0.9507, 0.7320],
        [0.1271, 0.5222, 0.7700],
        [0.2848, 0.0369, 0.6096],
        [0.9132, 0.5113, 0.5015],
        [0.4895, 0.9857, 0.2421],
        [0.0408, 0.5909, 0.6776],
        [0.0359, 0.4656, 0.5426]], device='cuda:0', dtype=torch.float64) 
 tensor([[1.4856],
        [1.5765],
        [1.5259],
        [1.1010],
        [1.0242],
        [1.6776],
        [1.1098],
        [1.0607],
        [1.0973]], device='cuda:0', dtype=torch.float64)
train batch #1, epoch #0

......

'''


파일 방식 구현과 기존 코드의 비교

기존 결과물과 비교해보면 차이가 없다는 것을 알 수 있습니다.


기존 방식 링크

댓글 없음:

댓글 쓰기