PyTorch 를 업무에 활용하면서 다양한 종류의 데이터셋을 활용한 프로젝트를 많이 경험하고 있지만, 텐서플로우나 케라스에 비해 PyTorch 는 사용하는 사람이 많이 없어 의지할 구석이 그리 많지 않은 듯 하다. 몇몇 분들과 얘기를 하다보면 그 중에서도 꽤나 많은 비중을 차지하는 이슈가 바로 DataLoader 를 작성하는 부분에서 나오는 것 같다. 정형 데이터 같은 경우에는 scikit-learn 과 같은 라이브러리로 데이터셋을 잘 정제한 뒤 DataLoader 클래스에 넣어주기만 하면 끝이지만, 이미지 데이터에 관해서는 좀 번거로운 과정을 거처야 하는데, 혼자서 하기에는 막막한 부분도 없지 않다.

이번 포스트에서는 PyTorch 에서 이미지 형태의 데이터를 효율적으로 읽어오는 매우 기본적인 몇가지 방안에 대한 프로세스를 소개하고, 초심자들도 쉽게 따라 해 볼 수 있도록 상세한 코드까지 소개하겠다.

Dataset & DataLoader 작성법 (Unlabeled)

프로젝트를 하다 보면 학습에 활용하고 싶은 데이터셋이 서버의 어딘가에 잔뜩 쌓여있거나, 다른 경로에서 관리중인 이미지들도 같이 사용하여 학습을 하고싶은 경우도 종종 발생하기 마련이다. 이러한 상황에서 데이터들을 비지도 학습에 기반 하여 훈련 루프에 무사히 넣기 까지 과정을 생각해 보자.

순서 1. 해당 폴더 안의 데이터 파일 경로를 리스트에 담아두는 과정이 필요하다.

위의 함수는 어디까지나 예시이다. 여기서는 서로 다른 두가지 경로에 위치한 데이터들을 모두 가져오기 위한 상황을 가정 하였으며, for 문이나 확장자가 섞여있는 경우도 각자 상황에 맞게 응용하여 사용하는 것을 전제로 한다.

순서 2. 다음은 이미지 전처리에 대한 고민을 할 차례이다.

PyTorch 의 경우, 이미지 전처리에 관련된 부분은 torchvision 에서 제공하는 transforms 를 사용하면 편리한데, 가장 간단하게는 다음과 같이 구현할 수 있고, 경우에 따라 원하는 augmentation 과정을 얼마든지 추가할 수 있다.

전처리는 Compose 클래스를 통해 통합적으로 이루어 졌는데, 그 안에서 사용된 ToTensor 는 PIL 형태의 이미지나 ndarray 를 PyTorch 가 이해할 수 있는 tensor 자료형으로 바꾸어 주는 역할을 한다. 이처럼 전처리란, 엄밀히 말하자면 데이터셋을 적절한 자료구조로 바꾸는 부분까지도 포함해야 한다.

순서 3. Dataset 을 상속받아 나만의 데이터셋 인스턴스를 생성해주는 클래스를 구현한다.

Dataset 은 torch.utils.data 로부터 상속받는다.

‘__len__’ 에서는 학습 데이터의 갯수를 리턴해주고, ‘ __getitem__’ 에서는 앞서 만든 리스트의 인덱스값을 참조해 해당 이미지를 연 다음 tensor 자료형으로 바꾸어 이미지 전처리를 실행하는 구조이다.

이 방식은 주로 모델을 학습하는 경우에 한하여 사용되는데, 고정된(fixed) 데이터셋의 인덱스를 통해 해당 데이터를 읽어오기 때문에 map-style datasets 타입으로 분류한다. 이 외에도 iterable-style 이 존재하는데, 이 방식은 실시간성 데이터를 로딩하기 위한 타입이기 때문에 본문에서는 스킵하도록 한다.

순서 4. 마지막으로, DataLoader 에 나만의 데이터셋을 넣어 보자.

DataLoader 는 사용하고자 하는 모델을 초기화 하고 훈련 루프가 시작되기 전에 위치하는 것이 일반적이다. 데이터 정규화를 위한 mean 과 std 값, 그리고 batch_size, shuffle 과 같은 옵션은 상황에 맞게 configuration 하면 되겠다.

추가로, 무사히 DataLoader 에 사용하고자 하는 데이터셋을 담았으면, size 연산을 이용하여 tensor의 형태를 확인해 보기 바란다. 정상적으로 구현이 되었다면, 본 예시에서는 batch_size가 64이므로 자료형은 [64, channel, width, height] 와 같은 형태로 출력 될 것이다. 또한, DataLoader 에는 이 외에도 많은 옵션들이 존재하는데, 추후 포스트를 작성하며 유용한 옵션들에 대해 자세히 살펴 볼 예정이다.

Dataset & DataLoader 작성 (Labeled)

그렇다면, 지도 학습을 위한 데이터셋의 경우는 어떤 점을 다르게 구현해야 하는지 알아보기로 한다. 당연하게도 데이터셋은 앞서 본 경우와 달리 체계적으로 관리, 다시 말해 라벨과 관련된 정보가 확보 되어 있어야 할 것이다. 여기서는 지도 학습으로 대표되는 문제들 중, 이미지의 분류 모델을 학습하는 경우를 예로 들어 살펴 볼 것이다.

방법 1 . ImageFolder 사용하기

지도 학습을 위해 torchvision 에서 제공되는 ImageFolder 를 활용하는 방법이 있는데, 생각보다 매우 간단하기 때문에 이 방법을 먼저 살펴보기로 한다. 예를 들어, STL-10 데이터셋을 사용하여 이미지의 클래스를 분류하는 모델을 만드는 경우를 생각해 보자. STL-10 데이터셋은 보통 바이너리 형식으로 제공되는 것이 일반적이지만, 편의를 위해 jpeg 확장자로 변환하여 클래스 별로 폴더를 구분하여 저장해 두었다고 가정해 보자.

STL-10 datasets (https://cs.stanford.edu/~acoates/stl10/)

ImageFolder 를 사용할 준비는 끝이 났다. (….??!!)

이 방법을 사용하게 되면 분류에 사용 되는 라벨이 서브폴더의 이름으로 매겨지기 때문에 원하는 만큼 폴더를 만들어 놓은 뒤, 그 안에 나만의 이미지를 넣어두기만 하면 된다는 점이 메리트로 작용 할 것이다.

실제로 train_imgs.classes 와 train_imgs.class_to_idx 를 출력 해 보면 다음 처럼 클래스명과 클래스의 인덱스까지도 반환해 주는 것을 확인할 수 있다.

방법 2. “__getitem__” 에 라벨 관련 정보 추가하기

두번째 방법은 비지도 학습의 경우와 마찬가지로 map-style dataset 을 작성하고 로딩하는 방식이다. 중요한 차이점은 ‘__getitem__’ 메서드에 라벨과 관련된 정보를 생성하도록 코드를 넣어주어야 한다는 것이다.

위 예시는, 반환값 label 이 이미지 하나하나의 파일명이 잘 관리가 되어있어서 맨 첫 글자 하나만으로 라벨링이 가능한 상황을 가정한 것이다. 바꿔 말하면, 데이터셋이 지도학습을 하기에 적절하지 않은 형태로 관리가 되어있다면 파일명으로 부터 라벨값을 추출하지 않는 방식을 쥐어 짜내야 한다는 뜻이고, 상황에 따라 새로 라벨을 달아줄 바에야 ImageFolder 방식이 더 효율적일지도 모른다는 것이다.

What’s next?

전체 코드는 레포지토리에 올려 두었지만, 한번쯤은 간단한 데이터셋을 준비하여 제시된 순서대로 직접 짜보는 것이 분명 응용력을 키우는데 도움이 될 것이다. 다음 포스트에서는 transforms, 즉 augmentation 방식이 복잡한 경우에 관하여 위에서 구현한 내용을 업그레이드 하고, Dataset, DataLoader 클래스까지 모두 포함한 나만의 데이터셋 wrapper 를 구현한 예시를 살펴 볼 예정이다.

[PyTorch] 나만의 데이터로 DataLoader 작성하기 — Part 1
PyTorch 를 업무에 활용하면서 다양한 종류의 데이터셋을 활용한 프로젝트를 많이 경험하고 있지만, 텐서플로우나 케라스에 비해 PyTorch 는 사용하는 사람이 많이 없어 의지할 구석이 그리 많지 않은 듯 하다. 몇몇 분들과 얘기를 하다보면 그 중에서도 꽤나 많은 비중을 차지하는 이슈가 바로 DataLoader 를 작성하는 부분에서 나오는 것 같다…