만일 당신이 고양이와 강아지를 구별한다 해 보자. 당신은 아마 신생아 시절부터 고양이와 강아지를 구분하지는 못했을 것이다. 당신은 어린 시절에, 어른들이 고양이를 고양이라 부르고 강아지를 강아지라 부르는 모습을 보면서 수없이, 고양이와 강아지를 구분할 수 있게 되었을 것이다. 이처럼 인공지능도 고양이와 강아지를 구분하게 하려면, 수없이 그들에게 고양이 사진과 강아지 사진을 보여줘야 할 것이다. 물론 우리가 몇 년에 걸려 한 일은 이 친구는 순식간에 배워야 하니 일반적으로 수만장의 사진이 필요할 것이다. 이를 dataset이라고 한다.
당신이 열역학을 공부한다 해 보자. 열역학을 공부하기 위해서는, 당신은 아마 문제집을 풀고 -> 채점을 하고 -> 틀린 내용을 보강할 것이다. 그리고 중간고사에서 당신이 얼마나 열역학을 잘 이해하는지 테스트할 것이다.
인공지능 모델도 이와 같다. 인공지능 모델도 주어진 데이터로 그들의 정확성을 시험받고, 얼마나 그 정확성을 높이기 위해 모델의 값들을 보정한다. 그러기 위해 쓰이는 데이터셋을 training set이라고 한다. 한 번 모든 training set으로 그 값들을 보정받았으면, 이제 그 보정받은 값들이 얼마나 정확한지 인공지능에게 시험을 치게 하는데, 그것을 test set이라고 한다. 그렇게 한 번 training set으로 값을 보정하고, test set으로 시험을 치는 과정을 한 epoch이라고 한다. 당연히 이 인공지능이란 친구는 여러 epoch을 걸쳐서 태어난다.
좋은 질문이다. 사실 이 녀석은 근본적으로 test set과 크게 다를 것이 없다. 그러나 validation set의 근본적인 차이는 이것이 training에 영향을 끼친다는 것이다. 그 영향을 끼치는 방법은 다양하다. hyperparameter를 맞추거나, validation set으로 테스트한 이후 오히려 training할수록 정확도가 떨어지면 더 이상 training을 하지 않는다거나... 당연히 안 써도 되고, 오히려 validation set만 test set처럼 써도 된다.
그러면 실제로 dataset을 만들어보자.
우리가 할 작업은 바로 이미지 분류(image classification)다. 대학원생들을 잔뜩 괴롭힌 교수님들께서 마침 이 링크에서 데이터셋을 다운로드 받게 해 주셨다. 이 링크로 있는 정보를 다운받아 데이터셋을 만들어보도록 하자. 우리는 여기서 CIFAR10을 사용해볼 예정이다.
위와 같이 데이터셋은 train set은 5개의 data_batch_n 파일로, test set은 하나의 data_batch_n파일로 이루어져 있다. 도대체 이 녀석들이 뭔지 모르겠으니, 다음 링크에 나온 대로 저 파일을 해부하면 뭐가 있는지 체크해 보도록 하자.
import pickle
import time
def unpickle(file):
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding= 'latin1')
return dict
dic = unpickle("/home/devil/cifar-10-batches-py/data_batch_1")
print(dic.keys())
print(dic["batch_label"])
print("labels: ", dic["labels"][:10])
print("data: ", dic["data"][:10])
print("filenames: ", dic["filenames"][:10])
각 내용을 보니 이렇게 되어 있다.
labels: [6, 9, 9, 4, 1, 1, 2, 7, 8, 3]
data: [[ 59 43 50 ... 140 84 72]
[154 126 105 ... 139 142 144]
[255 253 253 ... 83 83 84]
...
[ 28 30 33 ... 100 99 96]
[134 131 128 ... 136 137 138]
[125 110 102 ... 82 84 86]]
filenames: ['leptodactylus_pentadactylus_s_000004.png', 'camion_s_000148.png', 'tipper_truck_s_001250.png', 'american_elk_s_001521.png', 'station_wagon_s_000293.png', 'coupe_s_001735.png', 'cassowary_s_001300.png', 'cow_pony_s_001168.png', 'sea_boat_s_001584.png', 'tabby_s_001355.png']
data에는 무슨 1차원 벡터들이 많이 모여 있는데, 이건 32X32인 사진을 flatten한 것임을 쉽게 눈치챌 수 있다. 또한 labels는 왠지는 모르겠지만 0부터 9까지 값으로 되어 있는데, 이거는 각 물체에 붙은 번호라 할 수 있다. 사진이 0번이면 비행기, 1번이면 차... 이렇게 말이다.
일단 내 마음대로 데이터셋을 만들려면, torch.utils.dataset을 상속받아야 한다.
import torch
from torch.utils.data import Dataset, DataLoader
import os
from PIL import Image
class CustomDataset(Dataset):
def __init__(self, data_dir: str, train: bool):
self.train_dirs = [os.path.join(data_dir, "data_batch_{}".format(i))
for i in range(1, 6)]
self.test_dir = os.path.join(data_dir, "test_batch")
def __len__(self):
return
def __getitem__(self, idx):
return
다음과 같이 말이다. 여기서 파이썬 문법에 정통하다면, __init__
, __len__
, __getitem__
이 뭔지 알 거다. 모르는 사람을 위해 설명하자면, len은 저 Customdataset의 길이고, getitem은 저 dataset의 N번째 인덱스에서 나오는 값이다. 주로 데이터셋을 만들 때 저 세 개는 꼭 상속받아준다.
위를 기반으로 코드를 디자인한 결과는 다음과 같다. 우리는 이제 커스텀 데이터셋을 만들었다. 이제
import os
from PIL import Image
import pickle
import torch
from torch.utils.data import Dataset, DataLoader
import numpy as np
class CIFAR10(Dataset):
def __init__(self, data_dir: str, train: bool):
self.train_dirs = [os.path.join(data_dir, "data_batch_{}".format(i))
for i in range(1, 6)]
self.test_dirs = [os.path.join(data_dir, "test_batch")]
# directory of train set and test set
self.train = train # if the model is training, it's true
self.labels = []
self.datas = []
self.filenames = [] # the list where filenames, datas, labels are stored
self.extract_dataset()
def __len__(self):
return len(self.datas)
def __getitem__(self, idx):
return {"data": self.datas[idx],
"label": self.labels[idx],
"filename": self.filenames[idx]}
def getImg(self, idx):
flattened = np.array(self.datas[idx])
restored_img = flattened.reshape(32,32,3)
return restored_img
#---- getting dictionary file from the file ----#
def unpack(self, dir):
try:
with open(dir, 'rb') as fo:
dict = pickle.load(fo, encoding = "latin1")
return dict
except:
raise ValueError("💀Check the dataset directory again! File doesn't exist.💀")
#---- extracting dataset from file ----#
def extract_dataset(self, dir):
dirs = self.train_dirs if self.train else self.test_dirs
for dir in range(dirs):
data_dict = self.unpack(dir)
self.labels += data_dict["labels"]
self.datas += data_dict["data"]
self.filenames += data_dict["filename"]
assert (len(self.labels) == len(self.datas), "💀Labels and datas must have same number.💀")
그러면 이제 이 코드가 깔쌈하게 짜여졌는지 확인해보자.
if __name__ == "__main__":
dataset = CIFAR10("/home/yeongyoo/cifar-10-batches-py", train=True)
idx2val = ["airplane", "car", "bird", "cat", "deer", "dog", "frog", "horse", "ship", "truck"]
print("dataset_len:", len(dataset))
for i in range(10):
data = dataset[i]
plt.subplot(5, 2, i+1)
plt.imshow(dataset.getImg(i))
plt.title(idx2val[data['label']])
plt.show()
이렇게 써보면 다음과 같이...
오 완벽하게 적용이 되었다!